From 4eec81cc08c69fb65167396ba1d3f27615a71c21 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Mon, 13 Nov 2023 15:30:17 +0100 Subject: [PATCH] Add test TCP client --- CMakeLists.txt | 6 +- api/oc_collection.c | 110 +- api/oc_rep_encode_internal.h | 5 +- api/oc_server_api.c | 2 - api/oc_tcp.c | 57 +- api/oc_tcp_internal.h | 27 +- api/oc_udp.c | 2 +- api/unittest/collectiontest.cpp | 105 +- api/unittest/discovery/discovery.cpp | 576 ++++++ api/unittest/discovery/discovery.h | 164 ++ .../discovery/discoveryobservetest.cpp | 468 +++++ api/unittest/discovery/discoverytest.cpp | 745 +++++++ api/unittest/discoverytest.cpp | 1707 ----------------- api/unittest/maintest.cpp | 2 +- api/unittest/ocapitest.cpp | 4 +- api/unittest/resourcetest.cpp | 8 +- api/unittest/tcptest.cpp | 243 ++- api/unittest/udptest.cpp | 69 +- include/oc_ri.h | 2 +- messaging/coap/coap.c | 88 +- messaging/coap/coap_internal.h | 29 +- messaging/coap/oscore.c | 53 +- messaging/coap/oscore_internal.h | 24 +- .../unittest/observenotificationstest.cpp | 296 +++ messaging/coap/unittest/tcptest.cpp | 179 ++ port/android/Makefile | 30 +- port/android/ipadapter.c | 29 +- port/android/tcpadapter.c | 123 +- port/common/oc_tcp_socket.c | 279 +++ port/common/oc_tcp_socket_internal.h | 95 + port/common/posix/oc_fcntl.c | 59 + port/common/posix/oc_fcntl_internal.h | 94 + port/common/posix/oc_socket.c | 57 + port/common/posix/oc_socket_internal.h | 49 + port/esp32/adapter/src/ipadapter.c | 28 +- port/esp32/adapter/src/tcpadapter.c | 120 +- port/esp32/main/CMakeLists.txt | 3 + port/linux/Makefile | 72 +- port/linux/ip.c | 1 + port/linux/ipadapter.c | 62 +- port/linux/ipadapter.h | 26 - port/linux/netsocket.c | 2 +- port/linux/socklistener.c | 1 + port/linux/tcpadapter.c | 5 +- port/linux/tcpsession.c | 160 +- port/unittest/fcntltest.cpp | 106 + port/windows/ipadapter.c | 29 +- port/windows/oc_fcntl.c | 36 + port/windows/tcpadapter.c | 119 +- port/windows/vs2015/IoTivity-lite.vcxproj | 7 +- .../vs2015/IoTivity-lite.vcxproj.filters | 21 +- security/oc_oscore_engine.c | 3 +- security/oc_tls.c | 11 +- security/unittest/oscore_test.cpp | 35 + tests/gtest/Collection.cpp | 16 + tests/gtest/Collection.h | 5 +- tests/gtest/Device.cpp | 9 +- tests/gtest/RepPool.cpp | 14 +- tests/gtest/RepPool.h | 1 + tests/gtest/Resource.cpp | 22 + tests/gtest/Resource.h | 10 + tests/gtest/coap/Message.cpp | 150 ++ tests/gtest/coap/Message.h | 84 + tests/gtest/coap/TCPClient.cpp | 341 ++++ tests/gtest/coap/TCPClient.h | 76 + 65 files changed, 4750 insertions(+), 2611 deletions(-) create mode 100644 api/unittest/discovery/discovery.cpp create mode 100644 api/unittest/discovery/discovery.h create mode 100644 api/unittest/discovery/discoveryobservetest.cpp create mode 100644 api/unittest/discovery/discoverytest.cpp delete mode 100644 api/unittest/discoverytest.cpp create mode 100644 messaging/coap/unittest/observenotificationstest.cpp create mode 100644 messaging/coap/unittest/tcptest.cpp create mode 100644 port/common/oc_tcp_socket.c create mode 100644 port/common/oc_tcp_socket_internal.h create mode 100644 port/common/posix/oc_fcntl.c create mode 100644 port/common/posix/oc_fcntl_internal.h create mode 100644 port/common/posix/oc_socket.c create mode 100644 port/common/posix/oc_socket_internal.h create mode 100644 port/unittest/fcntltest.cpp create mode 100644 port/windows/oc_fcntl.c create mode 100644 tests/gtest/coap/Message.cpp create mode 100644 tests/gtest/coap/Message.h create mode 100644 tests/gtest/coap/TCPClient.cpp create mode 100644 tests/gtest/coap/TCPClient.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed3c055e8a..bd40f6d5cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -471,7 +471,7 @@ file(GLOB COMMON_SRC ${PROJECT_SOURCE_DIR}/util/*.c ) -if(UNIX) +if(UNIX OR WIN32) file(GLOB COMMON_POSIX_SRC ${PROJECT_SOURCE_DIR}/port/common/posix/*.c ) @@ -985,10 +985,10 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) list(APPEND OC_UNITTESTS ${OC_PACKAGE_ADD_TEST_TARGET}) endmacro() - file(GLOB COMMONTEST_SRC tests/gtest/*.cpp tests/gtest/tls/*.cpp) + file(GLOB COMMONTEST_SRC tests/gtest/*.cpp tests/gtest/coap/*.cpp tests/gtest/tls/*.cpp) # Unit tests - file(GLOB APITEST_SRC api/unittest/*.cpp api/unittest/encoder/*.cpp api/client/unittest/*.cpp) + file(GLOB APITEST_SRC api/unittest/*.cpp api/unittest/discovery/*.cpp api/unittest/encoder/*.cpp api/client/unittest/*.cpp) set(apitest_files) if (OC_INTROSPECTION_ENABLED AND OC_IDD_API_ENABLED) set(apitest_files ${PROJECT_SOURCE_DIR}/api/unittest/introspectiontest_IDD.cbor) diff --git a/api/oc_collection.c b/api/oc_collection.c index 608100789d..e8b9d42f87 100644 --- a/api/oc_collection.c +++ b/api/oc_collection.c @@ -664,16 +664,17 @@ collection_encode_mandatory_rts(const oc_collection_t *collection) } static bool -collection_encode_links(const oc_collection_t *collection, - const oc_request_t *request) +collection_encode_links(CborEncoder *container, const oc_request_t *request) { - oc_rep_set_array(root, links); - for (const oc_link_t *link = (oc_link_t *)oc_list_head(collection->links); + const oc_collection_t *collection = + (const oc_collection_t *)request->resource; + for (const oc_link_t *link = + (const oc_link_t *)oc_list_head(collection->links); link != NULL; link = link->next) { if (!oc_filter_resource_by_rt(link->resource, request)) { continue; } - oc_rep_object_array_start_item(links); + oc_rep_begin_object(container, links); oc_rep_set_text_string_v1(links, href, oc_string(link->resource->uri), oc_string_len(link->resource->uri)); oc_rep_set_string_array(links, rt, link->resource->types); @@ -743,9 +744,8 @@ collection_encode_links(const oc_collection_t *collection, } oc_rep_close_array(links, eps); - oc_rep_object_array_end_item(links); + oc_rep_end_object(container, links); } - oc_rep_close_array(root, links); return g_err == CborNoError; } @@ -784,7 +784,9 @@ oc_handle_collection_baseline_request(oc_method_t method, oc_request_t *request) collection_encode_mandatory_rts(collection); /* links */ - collection_encode_links(collection, request); + oc_rep_open_array(root, links); + collection_encode_links(oc_rep_array(links), request); + oc_rep_close_array(root, links); /* custom properties */ if (collection->res.get_properties.cb.get_props != NULL) { @@ -805,95 +807,16 @@ oc_handle_collection_baseline_request(oc_method_t method, oc_request_t *request) } static void -oc_handle_collection_linked_list_request(oc_request_t *request) +oc_handle_collection_linked_list_request(const oc_request_t *request) { - const oc_collection_t *collection = (oc_collection_t *)request->resource; - oc_link_t *link = (oc_link_t *)oc_list_head(collection->links); oc_rep_start_links_array(); - while (link != NULL) { - if (oc_filter_resource_by_rt(link->resource, request)) { - oc_rep_object_array_start_item(links); - oc_rep_set_text_string_v1(links, href, oc_string(link->resource->uri), - oc_string_len(link->resource->uri)); - oc_rep_set_string_array(links, rt, link->resource->types); - oc_core_encode_interfaces_mask(oc_rep_object(links), link->interfaces, - false); - oc_rep_set_string_array(links, rel, link->rel); - oc_rep_set_int(links, ins, link->ins); - oc_link_params_t *p = (oc_link_params_t *)oc_list_head(link->params); - while (p) { - oc_rep_set_key_v1(oc_rep_object(links), oc_string(p->key), - oc_string_len(p->key)); - oc_rep_set_value_text_string_v1(links, oc_string(p->value), - oc_string_len(p->value)); - p = p->next; - } - oc_rep_set_object(links, p); - oc_rep_set_uint( - p, bm, - (uint8_t)(link->resource->properties & ~(OC_PERIODIC | OC_SECURE))); - oc_rep_close_object(links, p); - - // tag-pos-desc - if (link->resource->tag_pos_desc > 0) { - const char *desc = - oc_enum_pos_desc_to_str(link->resource->tag_pos_desc); - if (desc) { - // clang-format off - oc_rep_set_text_string(links, tag-pos-desc, desc); - // clang-format on - } - } - - // tag-func-desc - if (link->resource->tag_func_desc > 0) { - const char *func = oc_enum_to_str(link->resource->tag_func_desc); - if (func) { - // clang-format off - oc_rep_set_text_string(links, tag-func-desc, func); - // clang-format on - } - } - - // tag-pos-rel - const double *pos = link->resource->tag_pos_rel; - if (pos[0] != 0 || pos[1] != 0 || pos[2] != 0) { - oc_rep_set_key(oc_rep_object(links), "tag-pos-rel"); - oc_rep_start_array(oc_rep_object(links), tag_pos_rel); - oc_rep_add_double(tag_pos_rel, pos[0]); - oc_rep_add_double(tag_pos_rel, pos[1]); - oc_rep_add_double(tag_pos_rel, pos[2]); - oc_rep_end_array(oc_rep_object(links), tag_pos_rel); - } - - // eps - oc_rep_set_array(links, eps); - oc_endpoint_t *eps = - oc_connectivity_get_endpoints(link->resource->device); - for (; eps != NULL; eps = eps->next) { - if (oc_filter_out_ep_for_resource(eps, link->resource, request->origin, - link->resource->device, false)) { - continue; - } - oc_rep_object_array_start_item(eps); - oc_string64_t ep; - if (oc_endpoint_to_string64(eps, &ep)) { - oc_rep_set_text_string_v1(eps, ep, oc_string(ep), oc_string_len(ep)); - } - oc_rep_object_array_end_item(eps); - } - oc_rep_close_array(links, eps); - - oc_rep_object_array_end_item(links); - } - link = link->next; - } + collection_encode_links(oc_rep_array(links), request); oc_rep_end_links_array(); } static bool collection_invoke_handler(const oc_collection_t *collection, - oc_resource_t *resource, oc_request_t *request) + const oc_resource_t *resource, oc_request_t *request) { oc_interface_mask_t iface = resource->default_interface; if (resource == &collection->res) { @@ -935,7 +858,6 @@ collection_batch_request_process_links(const oc_collection_t *collection, rest_request.request_payload = payload; } - CborEncoder prev_link; for (oc_link_t *link = (oc_link_t *)oc_list_head(collection->links); link != NULL; link = link->next) { if (link->resource == NULL || @@ -951,6 +873,7 @@ collection_batch_request_process_links(const oc_collection_t *collection, oc_string_len(*href)) != 0)) { continue; } + CborEncoder prev_link; memcpy(&prev_link, &links_array, sizeof(CborEncoder)); oc_rep_object_array_start_item(links); rest_request.query = NULL; @@ -991,14 +914,11 @@ collection_batch_request_process_links(const oc_collection_t *collection, &rest_request)) { ecode = oc_status_code_unsafe(OC_STATUS_METHOD_NOT_ALLOWED); memcpy(&links_array, &prev_link, sizeof(CborEncoder)); - continue; + continue; // NOLINT(bugprone-terminating-continue) } } } - if ((method == OC_PUT || method == OC_POST) && - response_buffer.code < oc_status_code_unsafe(OC_STATUS_BAD_REQUEST)) { - } if (response_buffer.code < oc_status_code_unsafe(OC_STATUS_BAD_REQUEST)) { pcode = response_buffer.code; } else { diff --git a/api/oc_rep_encode_internal.h b/api/oc_rep_encode_internal.h index 5cb9ec42a1..f65f8d79bd 100644 --- a/api/oc_rep_encode_internal.h +++ b/api/oc_rep_encode_internal.h @@ -207,8 +207,9 @@ bool oc_rep_encoder_get_content_format(oc_content_format_t *format) OC_NONNULL(); /** @brief Write raw data to encoder */ -int oc_rep_encoder_write_raw(oc_rep_encoder_t *encoder, const uint8_t *data, - size_t len) OC_NONNULL(1); +CborError oc_rep_encoder_write_raw(oc_rep_encoder_t *encoder, + const uint8_t *data, size_t len) + OC_NONNULL(1); /** @brief Write null representation to encoder */ CborError oc_rep_encoder_write_null(oc_rep_encoder_t *encoder, diff --git a/api/oc_server_api.c b/api/oc_server_api.c index f5485ddc16..b03dfaef7d 100644 --- a/api/oc_server_api.c +++ b/api/oc_server_api.c @@ -185,8 +185,6 @@ oc_send_response_with_callback(oc_request_t *request, oc_status_t response_code, content_format = APPLICATION_CBOR; } #endif /* OC_SPEC_VER_OIC */ - // method == OC_GET and response_code == OC_STATUS_OK nepouzit response_length - // ale poslat payload length skutocny if (!oc_send_response_internal(request, response_code, content_format, response_length(), trigger_cb)) { OC_ERR("could not send response: invalid response code"); diff --git a/api/oc_tcp.c b/api/oc_tcp.c index 30a6191c8a..2b2dea746e 100644 --- a/api/oc_tcp.c +++ b/api/oc_tcp.c @@ -87,19 +87,18 @@ oc_tcp_on_connect_event_free(oc_tcp_on_connect_event_t *event) #endif /* OC_HAS_FEATURE_TCP_ASYNC_CONNECT */ bool -oc_tcp_is_valid_header(const oc_message_t *message) +oc_tcp_is_valid_header(const uint8_t *data, size_t data_size, bool is_tls) { - assert(message != NULL); #ifdef OC_SECURITY - if ((message->endpoint.flags & SECURED) != 0) { - if (message->length < 3) { - OC_ERR("TLS header too short: %lu", (long unsigned)message->length); + if (is_tls) { + if (data_size < 3) { + OC_ERR("TLS header too short: %zu", data_size); return false; } // Parse the header fields - uint8_t type = message->data[0]; - uint8_t major_version = message->data[1]; - uint8_t minor_version = message->data[2]; + uint8_t type = data[0]; + uint8_t major_version = data[1]; + uint8_t minor_version = data[2]; OC_DBG("TLS header: record type: %d, major %d, minor %d", type, major_version, minor_version); // Validate the header fields @@ -134,9 +133,15 @@ oc_tcp_is_valid_header(const oc_message_t *message) } return true; } +#else /* !OC_SECURITY */ + (void)is_tls; #endif /* OC_SECURITY */ - int token_len = (COAP_HEADER_TOKEN_LEN_MASK & message->data[0]) >> - COAP_HEADER_TOKEN_LEN_POSITION; + if (data_size < 1) { + OC_ERR("TCP header too short: %zu", data_size); + return false; + } + int token_len = + (COAP_HEADER_TOKEN_LEN_MASK & data[0]) >> COAP_HEADER_TOKEN_LEN_POSITION; if (token_len > COAP_TOKEN_LEN) { OC_ERR("invalid token length: %d", token_len); // Invalid token length @@ -155,7 +160,7 @@ oc_tcp_is_valid_message(oc_message_t *message) return true; } #ifdef OC_OSCORE - if (oscore_is_oscore_message(message) >= 0) { + if (oscore_is_oscore_message(message)) { // it is oscore message return true; } @@ -172,4 +177,34 @@ oc_tcp_is_valid_message(oc_message_t *message) return true; } +long +oc_tcp_get_total_length_from_message_header(const oc_message_t *message) +{ + return oc_tcp_get_total_length_from_header( + message->data, message->length, (message->endpoint.flags & SECURED) != 0); +} + +long +oc_tcp_get_total_length_from_header(const uint8_t *data, size_t data_size, + bool is_tls) +{ + if (!oc_tcp_is_valid_header(data, data_size, is_tls)) { + OC_ERR("invalid header"); + return -1; + } + +#ifdef OC_SECURITY +#define OC_TLS_HEADER_SIZE (5) + if (is_tls) { + if (data_size < OC_TLS_HEADER_SIZE) { + OC_ERR("TLS header too short: %zu", data_size); + return -1; + } + //[3][4] bytes in tls header are tls payload length + return OC_TLS_HEADER_SIZE + ((data[3] << 8) | data[4]); + } +#endif /* OC_SECURITY */ + return coap_tcp_get_packet_size(data, data_size); +} + #endif /* OC_TCP */ diff --git a/api/oc_tcp_internal.h b/api/oc_tcp_internal.h index 3182bcffc8..26c05b812b 100644 --- a/api/oc_tcp_internal.h +++ b/api/oc_tcp_internal.h @@ -23,6 +23,7 @@ #ifdef OC_TCP +#include "messaging/coap/constants.h" #include "port/oc_connectivity.h" #include "oc_endpoint.h" @@ -30,7 +31,11 @@ extern "C" { #endif +#define OC_TCP_DEFAULT_RECEIVE_SIZE \ + (COAP_TCP_DEFAULT_HEADER_LEN + COAP_TCP_MAX_EXTENDED_LENGTH_LEN) + #ifdef OC_HAS_FEATURE_TCP_ASYNC_CONNECT + typedef struct oc_tcp_on_connect_event_s { struct oc_tcp_on_connect_data_s *next; @@ -49,15 +54,31 @@ void oc_tcp_on_connect_event_free(oc_tcp_on_connect_event_t *event); #endif /* OC_HAS_FEATURE_TCP_ASYNC_CONNECT */ -/** @brief Check if the message is a valid CoAP/TLS header */ -bool oc_tcp_is_valid_header(const oc_message_t *message); +/** @brief Check if data is a valid CoAP/TLS header */ +bool oc_tcp_is_valid_header(const uint8_t *data, size_t data_size, bool is_tls); /** @brief Check if the message is a valid for CoAP TCP */ -bool oc_tcp_is_valid_message(oc_message_t *message); +bool oc_tcp_is_valid_message(oc_message_t *message) OC_NONNULL(); + +/** + * @brief Read total length from TCP or TLS header + * + * @param data the data + * @param data_size size of the data + * @param is_tls true if the data is TLS + * @return long + */ +long oc_tcp_get_total_length_from_header(const uint8_t *data, size_t data_size, + bool is_tls); + +/** Convenience wrapper for oc_tcp_get_total_length_from_header */ +long oc_tcp_get_total_length_from_message_header(const oc_message_t *message) + OC_NONNULL(); #ifdef __cplusplus } #endif #endif /* OC_TCP */ + #endif /* OC_TCP_INTERNAL_H */ diff --git a/api/oc_udp.c b/api/oc_udp.c index 2c72a437a7..fe4bcfd54e 100644 --- a/api/oc_udp.c +++ b/api/oc_udp.c @@ -81,7 +81,7 @@ oc_udp_is_valid_message(oc_message_t *message) return true; } #ifdef OC_OSCORE - if (oscore_is_oscore_message(message) >= 0) { + if (oscore_is_oscore_message(message)) { // it is oscore message return true; } diff --git a/api/unittest/collectiontest.cpp b/api/unittest/collectiontest.cpp index 2feb7805a3..5d8e990bd4 100644 --- a/api/unittest/collectiontest.cpp +++ b/api/unittest/collectiontest.cpp @@ -838,6 +838,41 @@ TEST_F(TestCollectionsWithServer, GetETagAfterLinkAddOrRemove) #if !defined(OC_SECURITY) || defined(OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM) +static void +checkLinks(oc::Collection::Links &links) +{ + ASSERT_EQ(2, links.size()); + const oc::LinkData &switchLink = links[switch1URI.data()]; + EXPECT_STREQ(switch1URI.data(), switchLink.href.c_str()); + ASSERT_EQ(1, switchLink.rts.size()); + EXPECT_STREQ(switchRT.data(), switchLink.rts[0].c_str()); + ASSERT_EQ(1, switchLink.rels.size()); + EXPECT_STREQ("hosts", switchLink.rels[0].c_str()); + EXPECT_NE(0, switchLink.ins); + ASSERT_EQ(2, switchLink.ifs.size()); + EXPECT_NE( + switchLink.ifs.end(), + std::find(switchLink.ifs.begin(), switchLink.ifs.end(), OC_IF_BASELINE)); + EXPECT_NE(switchLink.ifs.end(), + std::find(switchLink.ifs.begin(), switchLink.ifs.end(), OC_IF_R)); + ASSERT_EQ(2, switchLink.params.size()); + EXPECT_STREQ("tag", switchLink.params[0].key.c_str()); + EXPECT_STREQ("test", switchLink.params[0].value.c_str()); + EXPECT_STREQ("hidden", switchLink.params[1].key.c_str()); + EXPECT_STREQ("true", switchLink.params[1].value.c_str()); + EXPECT_EQ(OC_DISCOVERABLE | OC_OBSERVABLE, switchLink.bm); + EXPECT_FALSE(switchLink.tag_pos_desc.empty()); + EXPECT_FALSE(switchLink.tag_func_desc.empty()); + EXPECT_EQ(3, switchLink.tag_pos_rel.size()); + EXPECT_EQ(switch1Pos[0], switchLink.tag_pos_rel[0]); + EXPECT_EQ(switch1Pos[1], switchLink.tag_pos_rel[1]); + EXPECT_EQ(switch1Pos[2], switchLink.tag_pos_rel[2]); + EXPECT_FALSE(switchLink.eps.empty()); + + const oc::LinkData &colLink = links[col2URI.data()]; + EXPECT_STREQ(col2URI.data(), colLink.href.c_str()); +} + TEST_F(TestCollectionsWithServer, GetRequest_Baseline) { makeTestResources(); @@ -857,7 +892,7 @@ TEST_F(TestCollectionsWithServer, GetRequest_Baseline) #ifdef OC_HAS_FEATURE_ETAG assertCollectionETag(data->etag, col1URI, data->endpoint->device); #endif /* OC_HAS_FEATURE_ETAG */ - OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload).data()); + OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); auto cData = oc::Collection::ParsePayload(data->payload); ASSERT_TRUE(cData.has_value()); *static_cast(data->user_data) = @@ -881,46 +916,40 @@ TEST_F(TestCollectionsWithServer, GetRequest_Baseline) ASSERT_EQ(1, data.rts_m.size()); EXPECT_STREQ(switchRT.data(), data.rts_m[0].c_str()); - ASSERT_EQ(2, data.links.size()); - - const oc::LinkData &switchLink = data.links[switch1URI.data()]; - EXPECT_STREQ(switch1URI.data(), switchLink.href.c_str()); - ASSERT_EQ(1, switchLink.rts.size()); - EXPECT_STREQ(switchRT.data(), switchLink.rts[0].c_str()); - ASSERT_EQ(1, switchLink.rels.size()); - EXPECT_STREQ("hosts", switchLink.rels[0].c_str()); - EXPECT_NE(0, switchLink.ins); - ASSERT_EQ(2, switchLink.ifs.size()); - EXPECT_NE( - switchLink.ifs.end(), - std::find(switchLink.ifs.begin(), switchLink.ifs.end(), OC_IF_BASELINE)); - EXPECT_NE(switchLink.ifs.end(), - std::find(switchLink.ifs.begin(), switchLink.ifs.end(), OC_IF_R)); - ASSERT_EQ(2, switchLink.params.size()); - EXPECT_STREQ("tag", switchLink.params[0].key.c_str()); - EXPECT_STREQ("test", switchLink.params[0].value.c_str()); - EXPECT_STREQ("hidden", switchLink.params[1].key.c_str()); - EXPECT_STREQ("true", switchLink.params[1].value.c_str()); - EXPECT_EQ(OC_DISCOVERABLE | OC_OBSERVABLE, switchLink.bm); - EXPECT_FALSE(switchLink.tag_pos_desc.empty()); - EXPECT_FALSE(switchLink.tag_func_desc.empty()); - EXPECT_EQ(3, switchLink.tag_pos_rel.size()); - EXPECT_EQ(switch1Pos[0], switchLink.tag_pos_rel[0]); - EXPECT_EQ(switch1Pos[1], switchLink.tag_pos_rel[1]); - EXPECT_EQ(switch1Pos[2], switchLink.tag_pos_rel[2]); - EXPECT_FALSE(switchLink.eps.empty()); - - const oc::LinkData &colLink = data.links[col2URI.data()]; - EXPECT_STREQ(col2URI.data(), colLink.href.c_str()); + checkLinks(data.links); } TEST_F(TestCollectionsWithServer, GetRequest_LinkedList) { - /* TODO: - EXPECT_TRUE( - oc_do_get("/col", ep, "if=" OC_IF_LL_STR, get_handler, HIGH_QOS, - nullptr)); - */ + makeTestResources(); + + auto col1 = + oc_get_collection_by_uri(col1URI.data(), col1URI.length(), kDeviceID); + ASSERT_NE(nullptr, col1); + + // get insecure connection to the testing device + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto get_handler = [](oc_client_response_t *data) { + oc::TestDevice::Terminate(); + ASSERT_EQ(OC_STATUS_OK, data->code); + OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); + auto cData = oc::Collection::ParseLinksPayload(data->payload); + ASSERT_TRUE(cData.has_value()); + *static_cast(data->user_data) = + std::move(cData.value()); + }; + + auto timeout = 1s; + oc::Collection::Links links{}; + ASSERT_TRUE(oc_do_get_with_timeout(oc_string(col1->res.uri), &ep, + "if=" OC_IF_LL_STR, timeout.count(), + get_handler, LOW_QOS, &links)); + oc::TestDevice::PoolEventsMsV1(timeout, true); + + checkLinks(links); } TEST_F(TestCollectionsWithServer, GetRequest_Batch) @@ -944,7 +973,7 @@ TEST_F(TestCollectionsWithServer, GetRequest_Batch) auto get_handler = [](oc_client_response_t *data) { oc::TestDevice::Terminate(); - OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload).data()); + OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); auto *bd = static_cast(data->user_data); bd->code = data->code; #ifndef OC_SECURITY diff --git a/api/unittest/discovery/discovery.cpp b/api/unittest/discovery/discovery.cpp new file mode 100644 index 0000000000..469b6914c7 --- /dev/null +++ b/api/unittest/discovery/discovery.cpp @@ -0,0 +1,576 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "discovery.h" + +#include "api/oc_discovery_internal.h" +#include "api/oc_resource_internal.h" +#include "messaging/coap/observe_internal.h" +#include "oc_api.h" +#include "oc_buffer_settings.h" +#include "oc_core_res.h" +#include "tests/gtest/Device.h" +#include "tests/gtest/Resource.h" +#include "tests/gtest/Utility.h" + +#ifdef OC_COLLECTIONS +#include "tests/gtest/Collection.h" +#endif /* OC_COLLECTIONS */ + +#include + +namespace oc::discovery { + +LinkData +ParseLink(const oc_rep_t *link) +{ + LinkData linkData{}; + + char *str; + size_t str_len; + // rel: string + if (oc_rep_get_string(link, "rel", &str, &str_len)) { + linkData.rel = std::string(str, str_len); + } + + // anchor: string + if (oc_rep_get_string(link, "anchor", &str, &str_len)) { + linkData.anchor = std::string(str, str_len); + } + + // href: string + if (oc_rep_get_string(link, "href", &str, &str_len)) { + linkData.href = std::string(str, str_len); + } + + // rt: array of strings + oc_string_array_t str_array; + size_t str_array_len; + if (oc_rep_get_string_array(link, "rt", &str_array, &str_array_len)) { + for (size_t i = 0; i < str_array_len; ++i) { + linkData.resourceTypes.emplace_back( + oc_string_array_get_item(str_array, i)); + } + } + + // if: array of strings + if (oc_rep_get_string_array(link, "if", &str_array, &str_array_len)) { + for (size_t i = 0; i < str_array_len; ++i) { + std::string iface_str = oc_string_array_get_item(str_array, i); + oc_interface_mask_t iface = + oc_ri_get_interface_mask(iface_str.c_str(), iface_str.length()); + if (iface == 0) { + continue; + } + linkData.interfaces.emplace_back(iface); + } + } + + // p: {"bm": int} + if (oc_rep_t * obj; oc_rep_get_object(link, "p", &obj)) { + if (int64_t properties; oc_rep_get_int(obj, "bm", &properties)) { + linkData.properties = static_cast(properties); + } + } + + // tag-pos-desc: string + if (oc_rep_get_string(link, "tag-pos-desc", &str, &str_len)) { + linkData.tagPosDesc = std::string(str, str_len); + } + + // tag-func-desc: string + if (oc_rep_get_string(link, "tag-func-desc", &str, &str_len)) { + linkData.tagFuncDesc = std::string(str, str_len); + } + + // tag-locn: string + if (oc_rep_get_string(link, "tag-locn", &str, &str_len)) { + linkData.tagLocation = std::string(str, str_len); + } + + // tag-pos-rel: double[3] + double *pos_rel; + if (size_t pos_rel_size; + oc_rep_get_double_array(link, "tag-pos-rel", &pos_rel, &pos_rel_size)) { + for (size_t i = 0; i < pos_rel_size; ++i) { + linkData.tagPosRel.emplace_back(pos_rel[i]); + } + } + + return linkData; +} + +LinkDataMap +ParseLinks(const oc_rep_t *rep) +{ + LinkDataMap links{}; + for (; rep != nullptr; rep = rep->next) { + auto link = ParseLink(rep->value.object); + links[link.href] = link; + } + return links; +} + +BaselineData +ParseBaseline(const oc_rep_t *rep) +{ + const oc_rep_t *obj = rep->value.object; + BaselineData data{}; + if (auto bl_opt = ParseBaselineData(obj)) { + data.baseline = *bl_opt; + } + + char *str; + size_t str_len; + // sduuid: string + if (oc_rep_get_string(obj, OCF_RES_PROP_SDUUID, &str, &str_len)) { + data.sduuid = std::string(str, str_len); + } + + // sdname: string + if (oc_rep_get_string(obj, OCF_RES_PROP_SDNAME, &str, &str_len)) { + data.sdname = std::string(str, str_len); + } + + // links + if (oc_rep_t *links = nullptr; + oc_rep_get_object_array(obj, "links", &links)) { + data.links = ParseLinks(links); + } + return data; +} + +#ifdef OC_RES_BATCH_SUPPORT + +BatchData +ParseBatch(const oc_rep_t *payload) +{ + auto extractUUIDAndURI = + [](std::string_view href) -> std::pair { + // skip past "ocf:// prefix" + std::string_view input = href.substr(6); + size_t uriStart = input.find('/'); + + if (uriStart == std::string_view::npos) { + return std::make_pair("", ""); + } + // Extract the UUID and the URI as separate substrings + std::string_view uuid = input.substr(0, uriStart - 1); + std::string_view uri = input.substr(uriStart); + return std::make_pair(std::string(uuid), std::string(uri)); + }; + + BatchData data{}; + for (const oc_rep_t *rep = payload; rep != nullptr; rep = rep->next) { + const oc_rep_t *obj = rep->value.object; + BatchItem bi{}; + char *str; + size_t str_len; + // href: string + if (oc_rep_get_string(obj, "href", &str, &str_len)) { + std::string_view href(str, str_len); + auto [uuid, uri] = extractUUIDAndURI(href); + bi.deviceUUID = uuid; + bi.href = uri; + } + +#ifdef OC_HAS_FEATURE_ETAG + // etag: byte string + if (oc_rep_get_byte_string(obj, "etag", &str, &str_len)) { + bi.etag.resize(str_len); + std::copy(&str[0], &str[str_len], std::begin(bi.etag)); + } +#endif /* OC_HAS_FEATURE_ETAG */ + + if (!bi.href.empty()) { + data[bi.href] = bi; + } + } + + return data; +} + +#endif /* OC_RES_BATCH_SUPPORT */ + +#ifdef OC_HAS_FEATURE_ETAG + +void +AssertETag(oc_coap_etag_t etag, const oc_endpoint_t *endpoint, size_t device, + bool is_batch) +{ +#ifdef OC_RES_BATCH_SUPPORT + if (is_batch) { + oc::AssertETag(etag, oc_discovery_get_batch_etag(endpoint, device)); + return; + } +#else /* !OC_RES_BATCH_SUPPORT */ + (void)is_batch; +#endif /* OC_RES_BATCH_SUPPORT */ + const oc_resource_t *discovery = + oc_core_get_resource_by_index(OCF_RES, device); + oc::AssertResourceETag(etag, discovery); +} + +#ifdef OC_RES_BATCH_SUPPORT + +void +AssertBatchETag(oc_coap_etag_t etag, size_t device, const BatchData &bd) +{ + const oc_resource_t *discovery = + oc_core_get_resource_by_index(OCF_RES, device); + ASSERT_NE(nullptr, discovery); + uint64_t max_etag = discovery->etag; + for (const auto &[_, value] : bd) { + ASSERT_EQ(sizeof(uint64_t), value.etag.size()); + uint64_t etag_value = 0; + memcpy(&etag_value, value.etag.data(), value.etag.size()); + if (etag_value > max_etag) { + max_etag = etag_value; + } + } + oc::AssertETag(etag, max_etag); +} + +#endif /* OC_RES_BATCH_SUPPORT */ + +#endif /* OC_HAS_FEATURE_ETAG */ + +} // namespace oc::discovery + +namespace { + +const int g_latency{ oc_core_get_latency() }; + +#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) +const long g_max_app_data_size{ oc_get_max_app_data_size() }; +#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ + +} + +void +TestDiscoveryWithServer::SetUpTestCase() +{ +#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) + oc_set_max_app_data_size(16384); +#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ + + // all endpoints should have lat attribute if latency is !=0 + oc_core_set_latency(42); + + ASSERT_TRUE(oc::TestDevice::StartServer()); + +#ifdef OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM + ASSERT_TRUE(oc::SetAccessInRFOTM(OCF_CON, kDeviceID, false, + OC_PERM_RETRIEVE | OC_PERM_UPDATE)); +#endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ + +#ifdef OC_DYNAMIC_ALLOCATION + addDynamicResources(); + +#ifdef OC_COLLECTIONS + addColletions(); +#endif /* OC_COLLECTIONS */ +#endif /* OC_DYNAMIC_ALLOCATION */ +}; + +void +TestDiscoveryWithServer::TearDownTestCase() +{ + oc::TestDevice::StopServer(); + + // restore defaults + oc_core_set_latency(g_latency); +#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) + oc_set_max_app_data_size(g_max_app_data_size); +#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ +} + +void +TestDiscoveryWithServer::TearDown() +{ + coap_observe_counter_reset(); +} + +std::unordered_map + TestDiscoveryWithServer::dynamicResources{}; + +void +TestDiscoveryWithServer::onGetDynamicResource(oc_request_t *request, + oc_interface_mask_t, + void *user_data) +{ + const auto *data = static_cast(user_data); + oc_rep_start_root_object(); + oc_rep_set_int(root, power, data->power); + oc_rep_end_root_object(); + oc_send_response(request, OC_STATUS_OK); +} + +void +TestDiscoveryWithServer::onGetEmptyDynamicResource(oc_request_t *request, + oc_interface_mask_t, void *) +{ + oc_rep_start_root_object(); + oc_rep_end_root_object(); + oc_send_response(request, OC_STATUS_OK); +} + +void +TestDiscoveryWithServer::addDynamicResources() +{ + oc::DynamicResourceHandler handlers1{}; + dynamicResources[std::string(kDynamicURI1)] = { 42 }; + handlers1.onGet = onGetDynamicResource; + handlers1.onGetData = &dynamicResources[std::string(kDynamicURI1)]; + + oc::DynamicResourceHandler handlers2{}; + dynamicResources[std::string(kDynamicURI2)] = { 1337 }; + handlers2.onGet = onGetDynamicResource; + handlers2.onGetData = &dynamicResources[std::string(kDynamicURI2)]; + + oc::DynamicResourceHandler handlers3{}; + handlers3.onGet = onGetEmptyDynamicResource; + + std::vector dynResources = { + oc::makeDynamicResourceToAdd("Dynamic Resource 1", + std::string(kDynamicURI1), + { "oic.d.discoverable", "oic.d.test" }, + { OC_IF_BASELINE, OC_IF_R }, handlers1), + oc::makeDynamicResourceToAdd("Dynamic Resource 2", + std::string(kDynamicURI2), + { "oic.d.undiscoverable", "oic.d.test" }, + { OC_IF_BASELINE, OC_IF_R }, handlers2, 0), + oc::makeDynamicResourceToAdd( + "Dynamic Resource 3", std::string(kDynamicURI3), + { "oic.d.observable", "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, + handlers3, OC_DISCOVERABLE | OC_OBSERVABLE), + }; + for (const auto &dr : dynResources) { + oc_resource_t *res = oc::TestDevice::AddDynamicResource(dr, kDeviceID); + ASSERT_NE(nullptr, res); + + oc_resource_tag_pos_desc(res, OC_POS_TOP); + oc_resource_tag_func_desc(res, OC_ENUM_ACTIVE); + oc_resource_tag_locn(res, OCF_LOCN_RECEIPTIONROOM); + oc_resource_tag_pos_rel(res, 1, 33, 7); + +#ifdef OC_OSCORE + // add secure multicast endpoints to list of endpoints + oc_resource_set_secure_mcast(res, true); +#endif /* OC_OSCORE */ + } +} + +#ifdef OC_COLLECTIONS + +void +TestDiscoveryWithServer::addColletions() +{ + constexpr std::string_view powerSwitchRT = "oic.d.power"; + + auto col = oc::NewCollection("col", kCollectionURI, kDeviceID, "oic.wk.col"); + ASSERT_NE(nullptr, col); + oc_resource_set_discoverable(&col->res, true); + oc_collection_add_supported_rt(&col->res, powerSwitchRT.data()); + oc_collection_add_mandatory_rt(&col->res, powerSwitchRT.data()); + ASSERT_TRUE(oc_add_collection_v1(&col->res)); + + oc::DynamicResourceHandler handlers1{}; + dynamicResources[std::string(kColDynamicURI1)] = { 404 }; + handlers1.onGet = onGetDynamicResource; + handlers1.onGetData = &dynamicResources[std::string(kColDynamicURI1)]; + + auto dr1 = oc::makeDynamicResourceToAdd( + "Collection Resource 1", std::string(kColDynamicURI1), + { std::string(powerSwitchRT), "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, + handlers1); + oc_resource_t *res1 = oc::TestDevice::AddDynamicResource(dr1, kDeviceID); + ASSERT_NE(nullptr, res1); + oc_link_t *link1 = oc_new_link(res1); + ASSERT_NE(link1, nullptr); + oc_collection_add_link(&col->res, link1); + + oc::DynamicResourceHandler handlers2{}; + dynamicResources[std::string(kColDynamicURI2)] = { 1 }; + handlers2.onGet = onGetDynamicResource; + handlers2.onGetData = &dynamicResources[std::string(kColDynamicURI2)]; + + auto dr2 = oc::makeDynamicResourceToAdd( + "Collection Resource 2", std::string(kColDynamicURI2), + { std::string(powerSwitchRT), "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, + handlers2, 0); + oc_resource_t *res2 = oc::TestDevice::AddDynamicResource(dr2, kDeviceID); + ASSERT_NE(nullptr, res2); + oc_link_t *link2 = oc_new_link(res2); + ASSERT_NE(link2, nullptr); + oc_collection_add_link(&col->res, link2); + + oc::DynamicResourceHandler handlers3{}; + handlers3.onGet = onGetEmptyDynamicResource; + + auto dr3 = oc::makeDynamicResourceToAdd( + "Collection Resource 3", std::string(kColDynamicURI3), + { std::string(powerSwitchRT), "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, + handlers3, OC_DISCOVERABLE | OC_OBSERVABLE); + oc_resource_t *res3 = oc::TestDevice::AddDynamicResource(dr3, kDeviceID); + ASSERT_NE(nullptr, res3); + oc_link_t *link3 = oc_new_link(res3); + ASSERT_NE(link3, nullptr); + oc_collection_add_link(&col->res, link3); + + col.release(); +} +#endif /* OC_COLLECTIONS */ + +void +TestDiscoveryWithServer::matchResourceLink(const oc_resource_t *resource, + const oc::discovery::LinkData &link) +{ + // href + EXPECT_STREQ(link.href.c_str(), oc_string(resource->uri)); + + // resource types + auto resourceTypes = oc::GetVector(resource->types); + EXPECT_EQ(link.resourceTypes.size(), resourceTypes.size()); + for (const auto &rt : link.resourceTypes) { + EXPECT_NE(std::end(resourceTypes), + std::find(std::begin(resourceTypes), std::end(resourceTypes), rt)) + << "resource type: " << rt << " not found"; + } + + // interfaces + unsigned iface_mask = 0; + for (const auto &iface : link.interfaces) { + iface_mask |= iface; + } + EXPECT_EQ(iface_mask, resource->interfaces); + + // properties + EXPECT_EQ(link.properties, resource->properties & OCF_RES_POLICY_PROPERTIES); +} + +void +TestDiscoveryWithServer::verifyLinks(const oc::discovery::LinkDataMap &links) +{ +#ifdef OC_SERVER + auto verifyUndiscoverable = [&links](std::string_view uri) { + oc_resource_t *res = + oc_ri_get_app_resource_by_uri(uri.data(), uri.length(), kDeviceID); + ASSERT_NE(nullptr, res); + ASSERT_EQ(0, res->properties & OC_DISCOVERABLE); + EXPECT_EQ(std::end(links), links.find(std::string(uri))); + }; + + auto verifyDiscoverable = [&links](std::string_view uri) { + oc_resource_t *res = + oc_ri_get_app_resource_by_uri(uri.data(), uri.length(), kDeviceID); + ASSERT_NE(nullptr, res); + ASSERT_NE(0, res->properties & OC_DISCOVERABLE); + const auto &linkData = links.find(std::string(uri)); + ASSERT_NE(std::end(links), linkData); + matchResourceLink(res, linkData->second); + }; + + verifyDiscoverable(kDynamicURI1); + verifyUndiscoverable(kDynamicURI2); + verifyDiscoverable(kDynamicURI3); + +#ifdef OC_COLLECTIONS + oc_collection_t *col = oc_get_collection_by_uri( + kCollectionURI.data(), kCollectionURI.length(), kDeviceID); + ASSERT_NE(nullptr, col); + const auto &colLink = links.find(std::string(kCollectionURI)); + ASSERT_NE(std::end(links), colLink); + matchResourceLink(&col->res, colLink->second); + + for (const oc_link_t *link = oc_collection_get_links(&col->res); + link != nullptr; link = link->next) { + const auto &linkData = links.find(oc_string(link->resource->uri)); + if ((link->resource->properties & OC_DISCOVERABLE) == 0) { + EXPECT_EQ(std::end(links), links.find(std::string(kDynamicURI2))); + continue; + } + ASSERT_NE(std::end(links), linkData); + matchResourceLink(link->resource, linkData->second); + } +#endif /* OC_COLLECTIONS */ +#endif /* OC_SERVER */ +} + +#ifdef OC_RES_BATCH_SUPPORT + +TestDiscoveryWithServer::batch_resources_t +TestDiscoveryWithServer::getBatchResources(const oc_endpoint_t *endpoint) +{ + batch_resources_t batch{}; + batch.endpoint = endpoint; + + oc_resources_iterate( + kDeviceID, true, true, true, true, + [](oc_resource_t *resource, void *data) { + if (auto *br = static_cast(data); + oc_discovery_resource_is_in_batch_response(resource, br->endpoint, + true)) { + br->resources.emplace_back(resource); + } + return true; + }, + &batch); + return batch; +} + +#ifndef OC_SECURITY + +static void +verifyBatchPayloadResource(const oc::discovery::BatchData &dbd, + const oc_resource_t *resource) +{ + ASSERT_NE(nullptr, resource); + const auto &it = dbd.find(std::string(oc_string(resource->uri))); + ASSERT_NE(std::end(dbd), it) + << "resource: " << oc_string(resource->uri) << " not found"; +#ifdef OC_HAS_FEATURE_ETAG + oc_coap_etag_t etag{}; + std::copy(it->second.etag.begin(), it->second.etag.end(), etag.value); + etag.length = static_cast(it->second.etag.size()); + oc::AssertResourceETag(etag, resource); +#endif /* OC_HAS_FEATURE_ETAG */ +} + +void +TestDiscoveryWithServer::verifyBatchPayload( + const oc::discovery::BatchData &dbd, + const std::vector &expected) +{ + ASSERT_EQ(expected.size(), dbd.size()); + for (const auto *resource : expected) { + verifyBatchPayloadResource(dbd, resource); + } +} + +void +TestDiscoveryWithServer::verifyBatchPayload(const oc::discovery::BatchData &dbd, + const oc_endpoint_t *endpoint) +{ + auto br = getBatchResources(endpoint); + verifyBatchPayload(dbd, br.resources); +} + +#endif /* !OC_SECURITY */ + +#endif /* OC_RES_BATCH_SUPPORT */ diff --git a/api/unittest/discovery/discovery.h b/api/unittest/discovery/discovery.h new file mode 100644 index 0000000000..d1b22f25da --- /dev/null +++ b/api/unittest/discovery/discovery.h @@ -0,0 +1,164 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#pragma once + +#include "oc_endpoint.h" +#include "oc_rep.h" +#include "oc_ri.h" +#include "tests/gtest/Resource.h" +#include "util/oc_features.h" + +#ifdef OC_HAS_FEATURE_ETAG +#include "messaging/coap/oc_coap.h" +#endif /* OC_HAS_FEATURE_ETAG */ + +#include +#include +#include +#include +#include + +namespace oc::discovery { + +struct LinkData +{ + std::string rel; + std::string anchor; + std::string href; + std::vector resourceTypes; + std::vector interfaces; + oc_resource_properties_t properties; + std::string tagPosDesc; + std::string tagFuncDesc; + std::string tagLocation; + std::vector tagPosRel; +}; + +using LinkDataMap = std::unordered_map; + +struct BaselineData +{ + oc::BaselineData baseline; + std::string sduuid; + std::string sdname; + LinkDataMap links; +}; + +LinkData ParseLink(const oc_rep_t *link); + +LinkDataMap ParseLinks(const oc_rep_t *rep); + +BaselineData ParseBaseline(const oc_rep_t *rep); + +#ifdef OC_RES_BATCH_SUPPORT + +struct BatchItem +{ + std::string deviceUUID; + std::string href; +#ifdef OC_HAS_FEATURE_ETAG + std::vector etag; +#endif /* OC_HAS_FEATURE_ETAG */ +}; + +using BatchData = std::unordered_map; + +BatchData ParseBatch(const oc_rep_t *payload); + +#endif /* OC_RES_BATCH_SUPPORT */ + +#ifdef OC_HAS_FEATURE_ETAG + +void AssertETag(oc_coap_etag_t etag, const oc_endpoint_t *endpoint, + size_t device, bool is_batch = false); + +#ifdef OC_RES_BATCH_SUPPORT + +void AssertBatchETag(oc_coap_etag_t etag, size_t device, const BatchData &bd); + +#endif /* OC_RES_BATCH_SUPPORT */ + +#endif /* OC_HAS_FEATURE_ETAG */ + +} // namespace oc::discovery + +struct DynamicResourceData +{ + int power; +}; + +class TestDiscoveryWithServer : public ::testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + void TearDown() override; + + static void onGetDynamicResource(oc_request_t *request, + oc_interface_mask_t interface, + void *user_data); + static void onGetEmptyDynamicResource(oc_request_t *request, + oc_interface_mask_t interface, + void *user_data); + + static void addDynamicResources(); +#ifdef OC_COLLECTIONS + static void addColletions(); +#endif /* OC_COLLECTIONS */ + + static void matchResourceLink(const oc_resource_t *resource, + const oc::discovery::LinkData &link); + static void verifyLinks(const oc::discovery::LinkDataMap &links); + +#ifdef OC_RES_BATCH_SUPPORT + + struct batch_resources_t + { + const oc_endpoint_t *endpoint; + std::vector resources; + }; + + static batch_resources_t getBatchResources(const oc_endpoint_t *endpoint); + +#ifndef OC_SECURITY + static void verifyBatchPayload( + const oc::discovery::BatchData &dbd, + const std::vector &expected); + static void verifyBatchPayload(const oc::discovery::BatchData &dbd, + const oc_endpoint_t *endpoint); +#endif /* !OC_SECURITY */ + +#endif /* OC_RES_BATCH_SUPPORT */ + + static std::unordered_map dynamicResources; +}; + +constexpr size_t kDeviceID{ 0 }; + +constexpr std::string_view kDynamicURI1 = "/dyn/discoverable"; +constexpr std::string_view kDynamicURI2 = "/dyn/undiscoverable"; +constexpr std::string_view kDynamicURI3 = "/dyn/empty"; + +#ifdef OC_COLLECTIONS + +constexpr std::string_view kCollectionURI = "/col"; +constexpr std::string_view kColDynamicURI1 = "/col/discoverable"; +constexpr std::string_view kColDynamicURI2 = "/col/undiscoverable"; +constexpr std::string_view kColDynamicURI3 = "/col/empty"; + +#endif /* OC_COLLECTIONS */ diff --git a/api/unittest/discovery/discoveryobservetest.cpp b/api/unittest/discovery/discoveryobservetest.cpp new file mode 100644 index 0000000000..6e162421d0 --- /dev/null +++ b/api/unittest/discovery/discoveryobservetest.cpp @@ -0,0 +1,468 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_DISCOVERY_RESOURCE_OBSERVABLE + +#include "discovery.h" + +#include "api/oc_con_resource_internal.h" +#include "api/oc_discovery_internal.h" +#include "api/oc_ri_internal.h" +#include "messaging/coap/coap_internal.h" +#include "messaging/coap/observe_internal.h" +#include "oc_api.h" +#include "oc_core_res.h" +#include "port/oc_log_internal.h" +#include "tests/gtest/Device.h" +#include "tests/gtest/Endpoint.h" +#include "tests/gtest/RepPool.h" + +using namespace std::chrono_literals; + +// payloads are too large for static buffers +#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) + +constexpr std::string_view kDynamicURI = "/dyn/observable"; + +// observe with default (LL) interface +TEST_F(TestDiscoveryWithServer, Observe) +{ + ASSERT_TRUE(oc_get_con_res_announced()); + + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + struct observeData + { + oc::discovery::LinkDataMap links; + int observe; + }; + auto onObserve = [](oc_client_response_t *cr) { + oc::TestDevice::Terminate(); + ASSERT_EQ(OC_STATUS_OK, cr->code); + OC_DBG("OBSERVE(%d) payload: %s", cr->observe_option, + oc::RepPool::GetJson(cr->payload, true).data()); + auto *od = static_cast(cr->user_data); + od->observe = cr->observe_option; + if (cr->observe_option == OC_COAP_OPTION_OBSERVE_REGISTER || + cr->observe_option >= OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE) { +#ifdef OC_HAS_FEATURE_ETAG + oc::discovery::AssertETag(cr->etag, cr->endpoint, cr->endpoint->device); +#endif /* OC_HAS_FEATURE_ETAG */ + od->links = oc::discovery::ParseLinks(cr->payload); + } + }; + observeData od{}; + ASSERT_TRUE( + oc_do_observe(OCF_RES_URI, &ep, nullptr, onObserve, HIGH_QOS, &od)); + oc::TestDevice::PoolEventsMsV1(1s); + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, od.observe); + ASSERT_FALSE(od.links.empty()); + verifyLinks(od.links); + od.observe = 0; + od.links.clear(); + + // adding a resource should trigger an observe notification + oc::DynamicResourceHandler handlers{}; + dynamicResources[std::string(kDynamicURI)] = { 2001 }; + handlers.onGet = onGetDynamicResource; + handlers.onGetData = &dynamicResources[std::string(kDynamicURI)]; + oc_resource_t *res = oc::TestDevice::AddDynamicResource( + oc::makeDynamicResourceToAdd("Dynamic Resource Observable", + std::string(kDynamicURI), + { "oic.d.observable", "oic.d.test" }, + { OC_IF_BASELINE, OC_IF_R }, handlers), + kDeviceID); + ASSERT_NE(nullptr, res); + oc_resource_set_observable(res, true); + + int repeats = 0; + while (od.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, od.observe); + ASSERT_FALSE(od.links.empty()); + verifyLinks(od.links); + od.observe = 0; + od.links.clear(); + + // deleting the resource should also trigger an observe notification + ASSERT_TRUE(oc::TestDevice::ClearDynamicResource(res, true)); + repeats = 0; + while (od.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE + 1, od.observe); + ASSERT_FALSE(od.links.empty()); + verifyLinks(od.links); + od.observe = 0; + od.links.clear(); + + ASSERT_TRUE(oc_stop_observe(OCF_RES_URI, &ep)); + while (od.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, od.observe); +} + +// observe with baseline interface +TEST_F(TestDiscoveryWithServer, ObserveBaseline) +{ + ASSERT_TRUE(oc_get_con_res_announced()); + + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + struct observeBaselineData + { + oc::discovery::BaselineData baseline; + int observe; + }; + auto onObserve = [](oc_client_response_t *cr) { + oc::TestDevice::Terminate(); + ASSERT_EQ(OC_STATUS_OK, cr->code); + OC_DBG("OBSERVE(%d) payload: %s", cr->observe_option, + oc::RepPool::GetJson(cr->payload, true).data()); + auto *obd = static_cast(cr->user_data); + obd->observe = cr->observe_option; + if (cr->observe_option == OC_COAP_OPTION_OBSERVE_REGISTER || + cr->observe_option >= OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE) { +#ifdef OC_HAS_FEATURE_ETAG + oc::discovery::AssertETag(cr->etag, cr->endpoint, cr->endpoint->device); +#endif /* OC_HAS_FEATURE_ETAG */ + obd->baseline = oc::discovery::ParseBaseline(cr->payload); + } + }; + observeBaselineData obd{}; + ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_BASELINE_STR, + onObserve, HIGH_QOS, &obd)); + oc::TestDevice::PoolEventsMsV1(1s); + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, obd.observe); + ASSERT_FALSE(obd.baseline.links.empty()); + verifyLinks(obd.baseline.links); + obd.observe = 0; + obd.baseline.links.clear(); + + // adding a resource should trigger an observe notification + oc::DynamicResourceHandler handlers{}; + dynamicResources[std::string(kDynamicURI)] = { 2001 }; + handlers.onGet = onGetDynamicResource; + handlers.onGetData = &dynamicResources[std::string(kDynamicURI)]; + oc_resource_t *res = oc::TestDevice::AddDynamicResource( + oc::makeDynamicResourceToAdd( + "Dynamic Resource Observable", std::string(kDynamicURI), + { "oic.d.observable", "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, + handlers, OC_DISCOVERABLE | OC_OBSERVABLE), + kDeviceID); + ASSERT_NE(nullptr, res); + + int repeats = 0; + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, obd.observe); + ASSERT_FALSE(obd.baseline.links.empty()); + verifyLinks(obd.baseline.links); + obd.observe = 0; + obd.baseline.links.clear(); + + // deleting the resource should also trigger an observe notification + ASSERT_TRUE(oc::TestDevice::ClearDynamicResource(res, true)); + repeats = 0; + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE + 1, obd.observe); + ASSERT_FALSE(obd.baseline.links.empty()); + verifyLinks(obd.baseline.links); + obd.observe = 0; + obd.baseline.links.clear(); + + ASSERT_TRUE(oc_stop_observe(OCF_RES_URI, &ep)); + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, obd.observe); +} + +#ifdef OC_RES_BATCH_SUPPORT + +#ifdef OC_SECURITY + +TEST_F(TestDiscoveryWithServer, ObserveBatch_F) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto onObserve = [](oc_client_response_t *cr) { + oc::TestDevice::Terminate(); + OC_DBG("OBSERVE(%d) payload: %s", cr->observe_option, + oc::RepPool::GetJson(cr->payload, true).data()); + *static_cast(cr->user_data) = true; + // insecure batch interface requests are unsupported + EXPECT_EQ(OC_STATUS_BAD_REQUEST, cr->code); + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, cr->observe_option); + }; + + bool invoked = false; + ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, onObserve, + HIGH_QOS, &invoked)); + oc::TestDevice::PoolEventsMsV1(1s); + EXPECT_TRUE(invoked); + + // no observers should exist + ASSERT_EQ(0, oc_list_length(coap_get_observers())); +} + +TEST_F(TestDiscoveryWithServer, ObserveBatch) +{ + // TODO: add support for using secure endpoints for communication in tests +} + +#else /* !OC_SECURITY */ + +struct observeBatchData +{ + oc::discovery::BatchData batch; + int observe; +}; + +static void +onBatchObserve(oc_client_response_t *cr) +{ + oc::TestDevice::Terminate(); + OC_DBG("OBSERVE(%d) payload: %s", cr->observe_option, + oc::RepPool::GetJson(cr->payload, true).data()); + ASSERT_EQ(OC_STATUS_OK, cr->code); + auto *obd = static_cast(cr->user_data); + obd->observe = cr->observe_option; + obd->batch = oc::discovery::ParseBatch(cr->payload); +#ifdef OC_HAS_FEATURE_ETAG + oc::discovery::AssertETag(cr->etag, cr->endpoint, cr->endpoint->device, true); + if (cr->observe_option == OC_COAP_OPTION_OBSERVE_REGISTER || + cr->observe_option == OC_COAP_OPTION_OBSERVE_NOT_SET) { + // we have a full payload and the response etag should be the highest etag + // contained the payload + oc::discovery::AssertBatchETag(cr->etag, cr->endpoint->device, obd->batch); + } +#endif /* OC_HAS_FEATURE_ETAG */ +} + +static void +updateResourceByPost(std::string_view uri, const oc_endpoint_t *endpoint, + const std::function &payloadFn) +{ + auto post_handler = [](oc_client_response_t *data) { + oc::TestDevice::Terminate(); + EXPECT_EQ(OC_STATUS_CHANGED, data->code); + OC_DBG("POST payload: %s", oc::RepPool::GetJson(data->payload).data()); + *static_cast(data->user_data) = true; + }; + + bool invoked = false; + ASSERT_TRUE(oc_init_post(uri.data(), endpoint, nullptr, post_handler, LOW_QOS, + &invoked)); + payloadFn(); + auto timeout = 1s; + ASSERT_TRUE(oc_do_post_with_timeout(timeout.count())); + oc::TestDevice::PoolEventsMsV1(timeout, true); + ASSERT_TRUE(invoked); +} + +TEST_F(TestDiscoveryWithServer, ObserveBatchWithResourceUpdate) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + observeBatchData obd{}; + ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, onBatchObserve, + HIGH_QOS, &obd)); + oc::TestDevice::PoolEventsMsV1(1s); + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + // all resources should be in the first payload + verifyBatchPayload(obd.batch, &ep); + obd.observe = 0; + obd.batch.clear(); + + oc_device_info_t *info = oc_core_get_device_info(kDeviceID); + ASSERT_NE(nullptr, info); + std::string deviceName = oc_string(info->name); + // updating the name by the /oc/con resource should trigger a batch observe + // notification with /oc/con and /oic/d resources + updateResourceByPost(OC_CON_URI, &ep, [deviceName]() { + oc_rep_start_root_object(); + oc_rep_set_text_string_v1(root, n, deviceName.c_str(), deviceName.length()); + oc_rep_end_root_object(); + }); + + int repeats = 0; + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_LE(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + std::vector expected{}; + auto *device = oc_core_get_resource_by_index(OCF_D, kDeviceID); + ASSERT_NE(nullptr, device); + if ((device->properties & OC_DISCOVERABLE) != 0) { + expected.emplace_back(device); + } + auto *con = oc_core_get_resource_by_index(OCF_CON, kDeviceID); + ASSERT_NE(nullptr, con); + if ((con->properties & OC_DISCOVERABLE) != 0) { + expected.emplace_back(con); + } + verifyBatchPayload(obd.batch, expected); + obd.observe = 0; + obd.batch.clear(); + + ASSERT_TRUE(oc_stop_observe(OCF_RES_URI, &ep)); + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + // response should be a full batch GET payload with observe option not set + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + verifyBatchPayload(obd.batch, &ep); +} + +TEST_F(TestDiscoveryWithServer, ObserveBatchWithResourceAdded) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + observeBatchData obd{}; + ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, onBatchObserve, + HIGH_QOS, &obd)); + oc::TestDevice::PoolEventsMsV1(1s); + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + // all resources should be in the first payload + verifyBatchPayload(obd.batch, &ep); + obd.observe = 0; + obd.batch.clear(); + + // adding a resource should trigger an observe notification + oc::DynamicResourceHandler handlers{}; + dynamicResources[std::string(kDynamicURI)] = { 2001 }; + handlers.onGet = onGetDynamicResource; + handlers.onGetData = &dynamicResources[std::string(kDynamicURI)]; + oc_resource_t *res = oc::TestDevice::AddDynamicResource( + oc::makeDynamicResourceToAdd( + "Dynamic Resource Observable", std::string(kDynamicURI), + { "oic.d.observable", "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, + handlers, OC_DISCOVERABLE | OC_OBSERVABLE), + kDeviceID); + ASSERT_NE(nullptr, res); + + int repeats = 0; + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_LE(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + obd.observe = 0; + obd.batch.clear(); + + ASSERT_TRUE(oc::TestDevice::ClearDynamicResource(res, true)); + repeats = 0; + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE + 1, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + obd.observe = 0; + obd.batch.clear(); + + ASSERT_TRUE(oc_stop_observe(OCF_RES_URI, &ep)); + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + // response should be a full batch GET payload with observe option not set + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + verifyBatchPayload(obd.batch, &ep); +} + +TEST_F(TestDiscoveryWithServer, ObserveBatchWithEmptyChange) +{ + ASSERT_TRUE(oc_get_con_res_announced()); + + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + observeBatchData obd{}; + ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, onBatchObserve, + HIGH_QOS, &obd)); + oc::TestDevice::PoolEventsMsV1(1s); + EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + // all resources should be in the first payload + verifyBatchPayload(obd.batch, &ep); + obd.observe = 0; + obd.batch.clear(); + + auto *res = oc_ri_get_app_resource_by_uri(kDynamicURI3.data(), + kDynamicURI3.size(), kDeviceID); + ASSERT_NE(nullptr, res); + oc_notify_resource_changed(res); + + int repeats = 0; + while (obd.observe == 0 && repeats < 50) { + oc::TestDevice::PoolEventsMsV1(10ms); + ++repeats; + } + EXPECT_LE(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, obd.observe); + ASSERT_FALSE(obd.batch.empty()); + obd.observe = 0; + obd.batch.clear(); +} + +TEST_F(TestDiscoveryWithServer, ObserveBatchWithEmptyPayload) +{ + // TODO: remove resource from payload -> make it undiscoverable in runtime + // and see what happens +} + +#endif /* OC_SECURITY */ + +#endif /* OC_RES_BATCH_SUPPORT */ + +#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ + +#endif /* OC_DISCOVERY_RESOURCE_OBSERVABLE */ diff --git a/api/unittest/discovery/discoverytest.cpp b/api/unittest/discovery/discoverytest.cpp new file mode 100644 index 0000000000..69fc4c9278 --- /dev/null +++ b/api/unittest/discovery/discoverytest.cpp @@ -0,0 +1,745 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "discovery.h" + +#include "api/oc_client_api_internal.h" +#include "api/oc_discovery_internal.h" +#include "api/oc_etag_internal.h" +#include "api/oc_resource_internal.h" +#include "api/oc_ri_internal.h" +#include "messaging/coap/options_internal.h" +#include "messaging/coap/oc_coap.h" +#include "oc_api.h" +#include "oc_base64.h" +#include "oc_core_res.h" +#include "oc_uuid.h" +#include "port/oc_log_internal.h" +#include "tests/gtest/Collection.h" +#include "tests/gtest/Device.h" +#include "tests/gtest/RepPool.h" +#include "tests/gtest/Resource.h" +#include "util/oc_macros_internal.h" + +#ifdef OC_SECURITY +#include "security/oc_security_internal.h" +#include "security/oc_sdi_internal.h" +#endif /* OC_SECURITY */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace { + +class TestDiscovery : public testing::Test {}; + +TEST_F(TestDiscovery, IsDiscoveryURI_F) +{ + EXPECT_FALSE(oc_is_discovery_resource_uri(OC_STRING_VIEW_NULL)); + EXPECT_FALSE(oc_is_discovery_resource_uri(OC_STRING_VIEW(""))); +} + +TEST_F(TestDiscovery, IsDiscoveryURI_P) +{ + std::string uri = OCF_RES_URI; + EXPECT_TRUE( + oc_is_discovery_resource_uri(oc_string_view(uri.c_str(), uri.length()))); + uri = uri.substr(1, uri.length() - 1); + EXPECT_TRUE( + oc_is_discovery_resource_uri(oc_string_view(uri.c_str(), uri.length()))); +} + +} // namespace + +TEST_F(TestDiscoveryWithServer, GetResourceByIndex_F) +{ + EXPECT_EQ(nullptr, + oc_core_get_resource_by_index(OCF_RES, /*device*/ SIZE_MAX)); +} + +TEST_F(TestDiscoveryWithServer, GetResourceByIndex) +{ + EXPECT_NE(nullptr, oc_core_get_resource_by_index(OCF_RES, kDeviceID)); +} + +TEST_F(TestDiscoveryWithServer, GetResourceByURI_F) +{ + EXPECT_EQ(nullptr, oc_core_get_resource_by_uri_v1( + OCF_RES_URI, OC_CHAR_ARRAY_LEN(OCF_RES_URI), + /*device*/ SIZE_MAX)); +} + +TEST_F(TestDiscoveryWithServer, GetResourceByURI) +{ + oc_resource_t *res = oc_core_get_resource_by_uri_v1( + OCF_RES_URI, OC_CHAR_ARRAY_LEN(OCF_RES_URI), kDeviceID); + EXPECT_NE(nullptr, res); + + EXPECT_STREQ(OCF_RES_URI, oc_string(res->uri)); +} + +template +static void +getRequestWithDomainQuery(const std::string &query) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto get_handler = [](oc_client_response_t *data) { + oc::TestDevice::Terminate(); + EXPECT_EQ(CODE, data->code); + OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); + *static_cast(data->user_data) = true; + }; + + bool invoked = false; + auto timeout = 1s; + EXPECT_TRUE(oc_do_get_with_timeout(OCF_RES_URI, &ep, query.c_str(), + timeout.count(), get_handler, HIGH_QOS, + &invoked)); + oc::TestDevice::PoolEventsMsV1(timeout, true); + EXPECT_TRUE(invoked); +} + +#ifdef OC_SECURITY + +TEST_F(TestDiscoveryWithServer, GetRequest_FailPrivateSecurityDomain) +{ + oc_sec_sdi_t *sdi = oc_sec_sdi_get(kDeviceID); + ASSERT_NE(nullptr, sdi); + sdi->priv = true; + + oc_uuid_t uuid{}; + std::array uuid_str{}; + oc_uuid_to_str(&uuid, &uuid_str[0], uuid_str.size()); + std::string query = OCF_RES_QUERY_SDUUID "=" + std::string(uuid_str.data()); + + getRequestWithDomainQuery(query); + + // restore default + oc_sec_sdi_clear(sdi); +} + +TEST_F(TestDiscoveryWithServer, GetRequest_FailInvalidSecurityDomain) +{ + // bad format of uuid + std::string query = OCF_RES_QUERY_SDUUID "=42"; + getRequestWithDomainQuery(query); +} + +TEST_F(TestDiscoveryWithServer, GetRequest_FailWrongSecurityDomain) +{ + oc_sec_sdi_t *sdi = oc_sec_sdi_get(kDeviceID); + ASSERT_NE(nullptr, sdi); + + oc_uuid_t uuid1; + oc_gen_uuid(&uuid1); + memcpy(&sdi->uuid.id, uuid1.id, sizeof(uuid1.id)); + + // non-matching uuid + oc_uuid_t uuid2; + do { + oc_gen_uuid(&uuid2); + } while (memcmp(uuid1.id, uuid2.id, sizeof(uuid2.id)) == 0); + + std::array uuid_str{}; + oc_uuid_to_str(&uuid2, &uuid_str[0], uuid_str.size()); + std::string query = OCF_RES_QUERY_SDUUID "=" + std::string(uuid_str.data()); + + getRequestWithDomainQuery(query); + + // restore default + oc_sec_sdi_clear(sdi); +} + +#endif /* OC_SECURITY */ + +// payloads are too large for static buffers +#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) + +TEST_F(TestDiscoveryWithServer, GetRequestWithSecurityDomain) +{ + oc_uuid_t uuid; + oc_gen_uuid(&uuid); + +#ifdef OC_SECURITY + oc_sec_sdi_t *sdi = oc_sec_sdi_get(kDeviceID); + ASSERT_NE(nullptr, sdi); + memcpy(&sdi->uuid.id, uuid.id, sizeof(uuid.id)); +#endif /* OC_SECURITY */ + + std::array uuid_str{}; + oc_uuid_to_str(&uuid, &uuid_str[0], uuid_str.size()); + std::string query = OCF_RES_QUERY_SDUUID "=" + std::string(uuid_str.data()); + + getRequestWithDomainQuery(query); + +#ifdef OC_SECURITY + // restore defaults + oc_sec_sdi_clear(sdi); +#endif /* OC_SECURITY */ +} + +// default interface - LL +// payload contains array of discoverable resources +// [ +// { +// "rel": "self", // for /oic/res only +// "anchor": "ocf://03ddc383-a500-41f8-75a6-5c3e51d97906", +// "href": "/oc/con", +// "rt": [ +// "oic.wk.con" +// ], +// "if": [ +// "oic.if.rw", +// "oic.if.baseline" +// ], +// "p": { +// "bm": 3 +// }, +// "eps": null +// "tag-pos-desc": +// "tag-func-desc": +// "tag-locn": string +// "tag-pos-rel": double[3] +// }, +// ... +// ] +TEST_F(TestDiscoveryWithServer, GetRequest) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto get_handler = [](oc_client_response_t *data) { + oc::TestDevice::Terminate(); + ASSERT_EQ(OC_STATUS_OK, data->code); +#ifdef OC_HAS_FEATURE_ETAG + oc::discovery::AssertETag(data->etag, data->endpoint, + data->endpoint->device); +#endif /* OC_HAS_FEATURE_ETAG */ + OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); + *static_cast(data->user_data) = + oc::discovery::ParseLinks(data->payload); + }; + + oc::discovery::LinkDataMap links{}; + auto timeout = 1s; + EXPECT_TRUE(oc_do_get_with_timeout(OCF_RES_URI, &ep, nullptr, timeout.count(), + get_handler, HIGH_QOS, &links)); + oc::TestDevice::PoolEventsMsV1(timeout, true); + ASSERT_FALSE(links.empty()); + + verifyLinks(links); +} + +// baseline interface: +// { +// +// #ifdef OC_SECURITY +// if (!sdi.priv) { +// "sduuid": +// "sdname": +// } +// #endif +// +// "links": [ +// , +// , +// ... +// ] +// } +TEST_F(TestDiscoveryWithServer, GetRequestBaseline) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + +#ifdef OC_SECURITY + oc_uuid_t uuid; + oc_gen_uuid(&uuid); + oc_sec_sdi_t *sdi = oc_sec_sdi_get(kDeviceID); + ASSERT_NE(nullptr, sdi); + memcpy(&sdi->uuid.id, uuid.id, sizeof(uuid.id)); + const std::string_view sdname = "Test Security Domain"; + oc_new_string(&sdi->name, sdname.data(), sdname.length()); +#endif /* OC_SECURITY */ + + auto get_handler = [](oc_client_response_t *data) { + oc::TestDevice::Terminate(); + ASSERT_EQ(OC_STATUS_OK, data->code); +#ifdef OC_HAS_FEATURE_ETAG + oc::discovery::AssertETag(data->etag, data->endpoint, + data->endpoint->device); +#endif /* OC_HAS_FEATURE_ETAG */ + OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); + *static_cast(data->user_data) = + oc::discovery::ParseBaseline(data->payload); + }; + + oc::discovery::BaselineData baseline{}; + auto timeout = 1s; + EXPECT_TRUE(oc_do_get_with_timeout(OCF_RES_URI, &ep, "if=" OC_IF_BASELINE_STR, + timeout.count(), get_handler, HIGH_QOS, + &baseline)); + oc::TestDevice::PoolEventsMsV1(timeout, true); + EXPECT_FALSE(baseline.links.empty()); + +#ifdef OC_SECURITY + std::array uuid_str{}; + oc_uuid_to_str(&sdi->uuid, &uuid_str[0], uuid_str.size()); + EXPECT_STREQ(std::string(uuid_str.data()).c_str(), baseline.sduuid.c_str()); + EXPECT_STREQ(oc_string(sdi->name), baseline.sdname.c_str()); +#endif /* OC_SECURITY */ + + verifyLinks(baseline.links); + +#ifdef OC_SECURITY + // restore defaults + oc_sec_sdi_clear(sdi); +#endif /* OC_SECURITY */ +} + +#ifdef OC_RES_BATCH_SUPPORT + +// batch interface +// [ +// { +// "href": "ocf:///", +// "rep": { +// +// } +// }, +// ... +// ] +TEST_F(TestDiscoveryWithServer, GetRequestBatch) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto get_handler = [](oc_client_response_t *data) { + oc::TestDevice::Terminate(); + OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); + auto batch = static_cast(data->user_data); + *batch = oc::discovery::ParseBatch(data->payload); +#ifdef OC_SECURITY + // need secure endpoint to get batch response if OC_SECURITY is enabled + ASSERT_EQ(OC_STATUS_BAD_REQUEST, data->code); +#else /* !OC_SECURITY */ + ASSERT_EQ(OC_STATUS_OK, data->code); +#ifdef OC_HAS_FEATURE_ETAG + oc::discovery::AssertETag(data->etag, data->endpoint, + data->endpoint->device, true); + // the response etag should be the highest etag contained the payload + oc::discovery::AssertBatchETag(data->etag, data->endpoint->device, *batch); +#endif /* OC_HAS_FEATURE_ETAG */ +#endif /* OC_SECURITY */ + }; + + oc::discovery::BatchData data{}; + auto timeout = 1s; + EXPECT_TRUE(oc_do_get_with_timeout(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, + timeout.count(), get_handler, HIGH_QOS, + &data)); + oc::TestDevice::PoolEventsMsV1(timeout, true); + +#ifdef OC_SECURITY + EXPECT_TRUE(data.empty()); +#else /* !OC_SECURITY */ + ASSERT_FALSE(data.empty()); + verifyBatchPayload(data, &ep); +#endif /* OC_SECURITY */ +} + +#ifdef OC_HAS_FEATURE_ETAG_INCREMENTAL_CHANGES + +static std::string +encodeETagToBase64(const oc_coap_etag_t *etag) +{ + std::array etagB64{}; + int b64_len = + oc_base64_encode_v1(OC_BASE64_ENCODING_URL, false, &etag->value[0], + etag->length, &etagB64[0], etagB64.size()); + if (b64_len == -1) { + throw std::string("base64 encode failed"); + } + std::string etagStr{}; + etagStr.insert(etagStr.end(), etagB64.data(), + etagB64.data() + static_cast(b64_len)); + return etagStr; +} + +static std::string +encodeETagToBase64(uint64_t etag) +{ + oc_coap_etag_t etagBytes{}; + memcpy(&etagBytes.value[0], &etag, sizeof(etag)); + etagBytes.length = sizeof(etag); + return encodeETagToBase64(&etagBytes); +} + +static std::string +testBatchIncrementalChangesQuery(const std::vector &etags) +{ + std::string query = "if=" OC_IF_B_STR; + if (etags.empty()) { + return query + "&" OC_ETAG_QUERY_INCREMENTAL_CHANGES_KEY; + } + + size_t count = 0; + for (auto etag : etags) { + if (count == 0) { + query += "&" OC_ETAG_QUERY_INCREMENTAL_CHANGES_KEY "="; + } else { + query += ","; + } + query += encodeETagToBase64(etag); + ++count; + if (count == 6) { + count = 0; + } + } + return query; +} + +static void +testBatchIncrementalChanges( + const oc_endpoint_t *ep, const oc_coap_etag_t *etag0, + const std::string &query, oc_status_t expectedCode = OC_STATUS_OK, + const std::vector &expected = {}) +{ + struct DiscoveryBatchResponse + { + oc_status_t code; + oc::discovery::BatchData bd; + }; + + auto getHandler = [](oc_client_response_t *data) { + oc::TestDevice::Terminate(); + OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); + auto *dbr = static_cast(data->user_data); + dbr->code = data->code; + dbr->bd = oc::discovery::ParseBatch(data->payload); +#ifdef OC_SECURITY + // need secure endpoint to get batch response if OC_SECURITY is enabled + ASSERT_EQ(OC_STATUS_BAD_REQUEST, data->code); +#else /* !OC_SECURITY */ + ASSERT_TRUE(OC_STATUS_OK == data->code || + OC_STATUS_NOT_MODIFIED == data->code); + oc::discovery::AssertETag(data->etag, data->endpoint, + data->endpoint->device, true); +#endif /* OC_SECURITY */ + }; + + DiscoveryBatchResponse dbr{}; + auto timeout = 1s; + auto configureReq = [](coap_packet_t *req, const void *data) { + if (data == nullptr) { + return; + } + const auto *etag = static_cast(data); + coap_options_set_etag(req, etag->value, etag->length); + }; + ASSERT_TRUE(oc_do_request(OC_GET, OCF_RES_URI, ep, query.c_str(), + timeout.count(), getHandler, HIGH_QOS, &dbr, + configureReq, etag0)); + oc::TestDevice::PoolEventsMsV1(timeout, true); + +#ifdef OC_SECURITY + EXPECT_TRUE(dbr.bd.empty()); + (void)expectedCode; + (void)expected; +#else /* !OC_SECURITY */ + ASSERT_EQ(expectedCode, dbr.code); + if (dbr.code == OC_STATUS_OK) { + ASSERT_FALSE(dbr.bd.empty()); + TestDiscoveryWithServer::verifyBatchPayload(dbr.bd, expected); + } + if (dbr.code == OC_STATUS_NOT_MODIFIED) { + EXPECT_TRUE(dbr.bd.empty()); + } +#endif /* OC_SECURITY */ +} + +static void +testBatchIncrementalChanges( + const oc_endpoint_t *ep, uint64_t etag0, const std::string &query, + oc_status_t expectedCode = OC_STATUS_OK, + const std::vector &expected = {}) +{ + if (etag0 == OC_ETAG_UNINITIALIZED) { + testBatchIncrementalChanges(ep, nullptr, query, expectedCode, expected); + return; + } + + oc_coap_etag_t coapETag{}; + memcpy(&coapETag.value[0], &etag0, sizeof(etag0)); + coapETag.length = sizeof(etag0); + testBatchIncrementalChanges(ep, &coapETag, query, expectedCode, expected); +} + +TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_Single) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto br = getBatchResources(&ep); + // sort in descending order + std::sort(br.resources.begin(), br.resources.end(), + [](const oc_resource_t *a, const oc_resource_t *b) { + return a->etag > b->etag; + }); + ASSERT_LT(1, br.resources.size()); + uint64_t etag0 = br.resources[br.resources.size() / 2 - 1]->etag; + ASSERT_NE(OC_ETAG_UNINITIALIZED, etag0); + + std::vector expected{}; + std::for_each(br.resources.begin(), br.resources.end(), + [&expected, etag0](const oc_resource_t *resource) { + if (resource->etag > etag0) { + expected.emplace_back(resource); + } + }); + testBatchIncrementalChanges(&ep, etag0, testBatchIncrementalChangesQuery({}), + OC_STATUS_OK, expected); +} + +// Invalid ETag0 should be ignored +TEST_F(TestDiscoveryWithServer, + GetRequestBatchIncremental_Single_InvalidIncrementalETag0) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + // invalid ETag0: size of bytes not equal to sizeof(uint64_t), it should be + // ignored and should receive all resources + std::vector etag0Bytes{ 'e', 't', 'a', 'g', '0' }; + assert(etag0Bytes.size() <= COAP_ETAG_LEN); + oc_coap_etag_t etag0{}; + memcpy(&etag0.value[0], &etag0Bytes[0], etag0Bytes.size()); + etag0.length = static_cast(etag0Bytes.size()); + + auto br = getBatchResources(&ep); + ASSERT_FALSE(br.resources.empty()); + + testBatchIncrementalChanges(&ep, &etag0, testBatchIncrementalChangesQuery({}), + OC_STATUS_OK, br.resources); +} + +// invalid ETags in the query should be ignored, but if a single valid ETag is +// found then is should be used +TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_InvalidETags) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + // get non-batch resources + batch_resources_t nonBatch{}; + nonBatch.endpoint = &ep; + oc_resources_iterate( + kDeviceID, true, true, true, true, + [](oc_resource_t *resource, void *data) { + if (auto *br = static_cast(data); + !oc_discovery_resource_is_in_batch_response(resource, br->endpoint, + false)) { + br->resources.emplace_back(resource); + } + return true; + }, + &nonBatch); + + std::string query = + "if=" OC_IF_B_STR "&" OC_ETAG_QUERY_INCREMENTAL_CHANGES_KEY "=12345,67890"; + for (size_t i = 0; i < 2 && i < nonBatch.resources.size(); ++i) { + query += ","; + query += encodeETagToBase64(nonBatch.resources[i]->etag); + } + query += ",,leetETag"; + query += "&" OC_ETAG_QUERY_INCREMENTAL_CHANGES_KEY "=,,"; + for (size_t i = 2; i < 4 && i < nonBatch.resources.size(); ++i) { + query += ","; + std::string etagStr = std::to_string(nonBatch.resources[i]->etag); + if (etagStr.length() > COAP_ETAG_LEN) { + etagStr.resize(COAP_ETAG_LEN); + } + oc_coap_etag_t etag{}; + memcpy(&etag.value[0], etagStr.data(), etagStr.length()); + etag.length = static_cast(etagStr.length()); + query += encodeETagToBase64(&etag); + } + + auto br = getBatchResources(&ep); + ASSERT_LT(1, br.resources.size()); + // sort in descending order + std::sort(br.resources.begin(), br.resources.end(), + [](const oc_resource_t *a, const oc_resource_t *b) { + return a->etag > b->etag; + }); + uint64_t valid = br.resources[br.resources.size() / 2 - 1]->etag; + query += "," + encodeETagToBase64(valid); + query += ",end"; + + std::vector expected{}; + std::for_each(br.resources.begin(), br.resources.end(), + [&expected, valid](const oc_resource_t *resource) { + if (resource->etag > valid) { + expected.emplace_back(resource); + } + }); + testBatchIncrementalChanges(&ep, nullptr, query, OC_STATUS_OK, expected); +} + +TEST_F(TestDiscoveryWithServer, + GetRequestBatchIncremental_Single_InvalidIncrementalUpdates) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto br = getBatchResources(&ep); + ASSERT_FALSE(br.resources.empty()); + // sort in descending order + std::sort(br.resources.begin(), br.resources.end(), + [](const oc_resource_t *a, const oc_resource_t *b) { + return a->etag > b->etag; + }); + // no resource should match (> highest etag), so we should get all resources + uint64_t etag0 = br.resources[0]->etag + 1; + testBatchIncrementalChanges( + &ep, etag0, + testBatchIncrementalChangesQuery({ etag0 + 1, etag0 + 2, etag0 + 3 }), + OC_STATUS_OK, br.resources); +} + +// Test with multiple etags in the query, sorted in descending order, so only +// the first should trigger the iteration of resources and subsequent ETag +// candidates should be skipped because they have a lower value +TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_Multiple) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto br = getBatchResources(&ep); + ASSERT_LT(1, br.resources.size()); + // sort in descending order + std::sort(br.resources.begin(), br.resources.end(), + [](const oc_resource_t *a, const oc_resource_t *b) { + return a->etag > b->etag; + }); + // set etag0 so it will not match any resource, which should result in + // the query getting examined + uint64_t etag0 = br.resources[0]->etag + 1; + + uint64_t pivot = br.resources[br.resources.size() / 2 - 1]->etag - 1; + std::vector etags{}; + std::vector expected{}; + std::for_each(br.resources.begin(), br.resources.end(), + [&etags, &expected, pivot](const oc_resource_t *resource) { + if (resource->etag > pivot) { + expected.emplace_back(resource); + } else { + etags.emplace_back(resource->etag); + } + }); + testBatchIncrementalChanges(&ep, etag0, + testBatchIncrementalChangesQuery(etags), + OC_STATUS_OK, expected); +} + +// Test with multiple etags in the query, sorted in ascending order, so all +// candidates should trigger resources iteration and the last value should be +// finally used as the ETag +TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_MultipleAscending) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto br = getBatchResources(&ep); + ASSERT_LT(1, br.resources.size()); + // sort in ascending order + std::sort(br.resources.begin(), br.resources.end(), + [](const oc_resource_t *a, const oc_resource_t *b) { + return a->etag < b->etag; + }); + // set etag0 so it will not match any resource, which should result in + // the query getting examined + uint64_t etag0 = br.resources[0]->etag + 1; + + uint64_t pivot = br.resources[br.resources.size() / 2 - 1]->etag - 1; + std::vector etags{}; + std::vector expected{}; + std::for_each(br.resources.begin(), br.resources.end(), + [&etags, &expected, pivot](const oc_resource_t *resource) { + if (resource->etag > pivot) { + expected.emplace_back(resource); + } else { + etags.emplace_back(resource->etag); + } + }); + testBatchIncrementalChanges(&ep, etag0, + testBatchIncrementalChangesQuery(etags), + OC_STATUS_OK, expected); +} + +/// Test will all batch resources etags in query, one of them is the latest +/// updated thus we should receive a VALID response with empty payload +TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_AllAscending) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto br = getBatchResources(&ep); + // sort in ascending order + std::sort(br.resources.begin(), br.resources.end(), + [](const oc_resource_t *a, const oc_resource_t *b) { + return a->etag < b->etag; + }); + std::vector etags{}; + std::for_each(br.resources.begin(), br.resources.end(), + [&etags](const oc_resource_t *resource) { + etags.emplace_back(resource->etag); + }); + + testBatchIncrementalChanges(&ep, static_cast(OC_ETAG_UNINITIALIZED), + testBatchIncrementalChangesQuery(etags), + OC_STATUS_NOT_MODIFIED); +} + +#endif /* OC_HAS_FEATURE_ETAG_INCREMENTAL_CHANGES */ + +#endif /* OC_RES_BATCH_SUPPORT */ + +#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ diff --git a/api/unittest/discoverytest.cpp b/api/unittest/discoverytest.cpp deleted file mode 100644 index 5bd7520a59..0000000000 --- a/api/unittest/discoverytest.cpp +++ /dev/null @@ -1,1707 +0,0 @@ -/**************************************************************************** - * - * Copyright (c) 2023 plgd.dev s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"), - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the License. - * - ****************************************************************************/ - -#include "api/oc_client_api_internal.h" -#include "api/oc_con_resource_internal.h" -#include "api/oc_discovery_internal.h" -#include "api/oc_etag_internal.h" -#include "api/oc_resource_internal.h" -#include "api/oc_ri_internal.h" -#include "messaging/coap/options_internal.h" -#include "messaging/coap/oc_coap.h" -#include "messaging/coap/observe_internal.h" -#include "oc_api.h" -#include "oc_base64.h" -#include "oc_core_res.h" -#include "oc_uuid.h" -#include "port/oc_log_internal.h" -#include "tests/gtest/Collection.h" -#include "tests/gtest/Device.h" -#include "tests/gtest/RepPool.h" -#include "tests/gtest/Resource.h" -#include "tests/gtest/Utility.h" -#include "util/oc_macros_internal.h" - -#ifdef OC_SECURITY -#include "security/oc_security_internal.h" -#include "security/oc_sdi_internal.h" -#endif /* OC_SECURITY */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -namespace { -constexpr size_t kDeviceID{ 0 }; - -constexpr std::string_view kDynamicURI1 = "/dyn/discoverable"; -constexpr std::string_view kDynamicURI2 = "/dyn/undiscoverable"; -constexpr std::string_view kDynamicURI3 = "/dyn/observable"; - -constexpr std::string_view kCollectionURI = "/col"; -constexpr std::string_view kColDynamicURI1 = "/col/discoverable"; -constexpr std::string_view kColDynamicURI2 = "/col/undiscoverable"; - -const int g_latency{ oc_core_get_latency() }; - -#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) -const long g_max_app_data_size{ oc_get_max_app_data_size() }; -#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ - -class TestDiscovery : public testing::Test {}; - -TEST_F(TestDiscovery, IsDiscoveryURI_F) -{ - EXPECT_FALSE(oc_is_discovery_resource_uri(OC_STRING_VIEW_NULL)); - EXPECT_FALSE(oc_is_discovery_resource_uri(OC_STRING_VIEW(""))); -} - -TEST_F(TestDiscovery, IsDiscoveryURI_P) -{ - std::string uri = OCF_RES_URI; - EXPECT_TRUE( - oc_is_discovery_resource_uri(oc_string_view(uri.c_str(), uri.length()))); - uri = uri.substr(1, uri.length() - 1); - EXPECT_TRUE( - oc_is_discovery_resource_uri(oc_string_view(uri.c_str(), uri.length()))); -} - -struct DiscoveryLinkData -{ - std::string rel; - std::string anchor; - std::string href; - std::vector resourceTypes; - std::vector interfaces; - oc_resource_properties_t properties; - std::string tagPosDesc; - std::string tagFuncDesc; - std::string tagLocation; - std::vector tagPosRel; -}; - -using DiscoveryLinkDataMap = std::unordered_map; - -struct DiscoveryBatchItem -{ - std::string deviceUUID; - std::string href; -#ifdef OC_HAS_FEATURE_ETAG - std::vector etag; -#endif /* OC_HAS_FEATURE_ETAG */ -}; - -using DiscoveryBatchData = std::unordered_map; - -} // namespace - -struct DynamicResourceData -{ - int power; -}; - -class TestDiscoveryWithServer : public ::testing::Test { -public: - static void SetUpTestCase() - { -#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) - oc_set_max_app_data_size(16384); -#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ - - // all endpoints should have lat attribute if latency is !=0 - oc_core_set_latency(42); - - ASSERT_TRUE(oc::TestDevice::StartServer()); - -#ifdef OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM - ASSERT_TRUE(oc::SetAccessInRFOTM(OCF_CON, kDeviceID, false, - OC_PERM_RETRIEVE | OC_PERM_UPDATE)); -#endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ - -#ifdef OC_DYNAMIC_ALLOCATION - addDynamicResources(); - -#ifdef OC_COLLECTIONS - addColletions(); -#endif /* OC_COLLECTIONS */ -#endif /* OC_DYNAMIC_ALLOCATION */ - } - - static void TearDownTestCase() - { - oc::TestDevice::StopServer(); - - // restore defaults - oc_core_set_latency(g_latency); -#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) - oc_set_max_app_data_size(g_max_app_data_size); -#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ - } - - void SetUp() override - { - coap_observe_counter_reset(); - } - -#ifdef OC_DYNAMIC_ALLOCATION - static void onGetDynamicResource(oc_request_t *request, oc_interface_mask_t, - void *user_data) - { - const auto *data = static_cast(user_data); - oc_rep_start_root_object(); - oc_rep_set_int(root, power, data->power); - oc_rep_end_root_object(); - oc_send_response(request, OC_STATUS_OK); - } - - static void addDynamicResources(); - -#ifdef OC_COLLECTIONS - static void addColletions(); -#endif /* OC_COLLECTIONS */ - - static std::unordered_map dynamicResources; -#endif // OC_DYNAMIC_ALLOCATION - -#ifdef OC_HAS_FEATURE_ETAG - static void assertETag(oc_coap_etag_t etag1, uint64_t etag2) - { - ASSERT_EQ(sizeof(etag2), etag1.length); - std::array etag2_buf{}; - memcpy(&etag2_buf[0], &etag2, etag2_buf.size()); - ASSERT_EQ(0, memcmp(&etag1.value[0], &etag2_buf[0], etag1.length)); - } - - static void assertResourceETag(oc_coap_etag_t etag, - const oc_resource_t *resource) - { - ASSERT_NE(nullptr, resource); - assertETag(etag, resource->etag); - } - - static void assertDiscoveryETag(oc_coap_etag_t etag, - const oc_endpoint_t *endpoint, size_t device, - bool is_batch = false) - { -#ifdef OC_RES_BATCH_SUPPORT - if (is_batch) { - assertETag(etag, oc_discovery_get_batch_etag(endpoint, device)); - return; - } -#else /* !OC_RES_BATCH_SUPPORT */ - (void)is_batch; -#endif /* OC_RES_BATCH_SUPPORT */ - const oc_resource_t *discovery = - oc_core_get_resource_by_index(OCF_RES, device); - assertResourceETag(etag, discovery); - } - -#ifdef OC_RES_BATCH_SUPPORT - static void assertBatchETag(oc_coap_etag_t etag, size_t device, - const DiscoveryBatchData &bd) - { - const oc_resource_t *discovery = - oc_core_get_resource_by_index(OCF_RES, device); - ASSERT_NE(nullptr, discovery); - uint64_t max_etag = discovery->etag; - for (const auto &[_, value] : bd) { - ASSERT_EQ(sizeof(uint64_t), value.etag.size()); - uint64_t etag_value = 0; - memcpy(&etag_value, value.etag.data(), value.etag.size()); - if (etag_value > max_etag) { - max_etag = etag_value; - } - } - assertETag(etag, max_etag); - } -#endif /* OC_RES_BATCH_SUPPORT */ - -#endif /* OC_HAS_FEATURE_ETAG */ -}; - -#ifdef OC_DYNAMIC_ALLOCATION - -std::unordered_map - TestDiscoveryWithServer::dynamicResources{}; - -void -TestDiscoveryWithServer::addDynamicResources() -{ - oc::DynamicResourceHandler handlers1{}; - dynamicResources[std::string(kDynamicURI1)] = { 42 }; - handlers1.onGet = onGetDynamicResource; - handlers1.onGetData = &dynamicResources[std::string(kDynamicURI1)]; - - oc::DynamicResourceHandler handlers2{}; - dynamicResources[std::string(kDynamicURI2)] = { 1337 }; - handlers2.onGet = onGetDynamicResource; - handlers2.onGetData = &dynamicResources[std::string(kDynamicURI2)]; - - std::vector dynResources = { - oc::makeDynamicResourceToAdd("Dynamic Resource 1", - std::string(kDynamicURI1), - { "oic.d.discoverable", "oic.d.test" }, - { OC_IF_BASELINE, OC_IF_R }, handlers1), - oc::makeDynamicResourceToAdd("Dynamic Resource 2", - std::string(kDynamicURI2), - { "oic.d.undiscoverable", "oic.d.test" }, - { OC_IF_BASELINE, OC_IF_R }, handlers2, 0), - }; - for (const auto &dr : dynResources) { - oc_resource_t *res = oc::TestDevice::AddDynamicResource(dr, kDeviceID); - ASSERT_NE(nullptr, res); - - oc_resource_tag_pos_desc(res, OC_POS_TOP); - oc_resource_tag_func_desc(res, OC_ENUM_ACTIVE); - oc_resource_tag_locn(res, OCF_LOCN_RECEIPTIONROOM); - oc_resource_tag_pos_rel(res, 1, 33, 7); - -#ifdef OC_OSCORE - // add secure multicast endpoints to list of endpoints - oc_resource_set_secure_mcast(res, true); -#endif /* OC_OSCORE */ - } -} - -#ifdef OC_COLLECTIONS -void -TestDiscoveryWithServer::addColletions() -{ - constexpr std::string_view powerSwitchRT = "oic.d.power"; - - auto col = oc::NewCollection("col", kCollectionURI, kDeviceID, "oic.wk.col"); - ASSERT_NE(nullptr, col); - oc_resource_set_discoverable(&col->res, true); - oc_collection_add_supported_rt(&col->res, powerSwitchRT.data()); - oc_collection_add_mandatory_rt(&col->res, powerSwitchRT.data()); - ASSERT_TRUE(oc_add_collection_v1(&col->res)); - - oc::DynamicResourceHandler handlers1{}; - dynamicResources[std::string(kColDynamicURI1)] = { 404 }; - handlers1.onGet = onGetDynamicResource; - handlers1.onGetData = &dynamicResources[std::string(kColDynamicURI1)]; - - auto dr1 = oc::makeDynamicResourceToAdd( - "Collection Resource 1", std::string(kColDynamicURI1), - { std::string(powerSwitchRT), "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, - handlers1); - oc_resource_t *res1 = oc::TestDevice::AddDynamicResource(dr1, kDeviceID); - ASSERT_NE(nullptr, res1); - oc_link_t *link1 = oc_new_link(res1); - ASSERT_NE(link1, nullptr); - oc_collection_add_link(&col->res, link1); - - oc::DynamicResourceHandler handlers2{}; - dynamicResources[std::string(kColDynamicURI2)] = { 1 }; - handlers2.onGet = onGetDynamicResource; - handlers2.onGetData = &dynamicResources[std::string(kColDynamicURI2)]; - - auto dr2 = oc::makeDynamicResourceToAdd( - "Collection Resource 2", std::string(kColDynamicURI2), - { std::string(powerSwitchRT), "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, - handlers2, 0); - oc_resource_t *res2 = oc::TestDevice::AddDynamicResource(dr2, kDeviceID); - ASSERT_NE(nullptr, res2); - oc_link_t *link2 = oc_new_link(res2); - ASSERT_NE(link2, nullptr); - oc_collection_add_link(&col->res, link2); - - col.release(); -} - -#endif /* OC_COLLECTIONS */ - -#endif // OC_DYNAMIC_ALLOCATION - -TEST_F(TestDiscoveryWithServer, GetResourceByIndex_F) -{ - EXPECT_EQ(nullptr, - oc_core_get_resource_by_index(OCF_RES, /*device*/ SIZE_MAX)); -} - -TEST_F(TestDiscoveryWithServer, GetResourceByIndex) -{ - EXPECT_NE(nullptr, oc_core_get_resource_by_index(OCF_RES, kDeviceID)); -} - -TEST_F(TestDiscoveryWithServer, GetResourceByURI_F) -{ - EXPECT_EQ(nullptr, oc_core_get_resource_by_uri_v1( - OCF_RES_URI, OC_CHAR_ARRAY_LEN(OCF_RES_URI), - /*device*/ SIZE_MAX)); -} - -TEST_F(TestDiscoveryWithServer, GetResourceByURI) -{ - oc_resource_t *res = oc_core_get_resource_by_uri_v1( - OCF_RES_URI, OC_CHAR_ARRAY_LEN(OCF_RES_URI), kDeviceID); - EXPECT_NE(nullptr, res); - - EXPECT_STREQ(OCF_RES_URI, oc_string(res->uri)); -} - -template -static void -getRequestWithDomainQuery(const std::string &query) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto get_handler = [](oc_client_response_t *data) { - oc::TestDevice::Terminate(); - EXPECT_EQ(CODE, data->code); - OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload).data()); - *static_cast(data->user_data) = true; - }; - - bool invoked = false; - auto timeout = 1s; - EXPECT_TRUE(oc_do_get_with_timeout(OCF_RES_URI, &ep, query.c_str(), - timeout.count(), get_handler, HIGH_QOS, - &invoked)); - oc::TestDevice::PoolEventsMsV1(timeout, true); - EXPECT_TRUE(invoked); -} - -#ifdef OC_SECURITY - -TEST_F(TestDiscoveryWithServer, GetRequest_FailPrivateSecurityDomain) -{ - oc_sec_sdi_t *sdi = oc_sec_sdi_get(kDeviceID); - ASSERT_NE(nullptr, sdi); - sdi->priv = true; - - oc_uuid_t uuid{}; - std::array uuid_str{}; - oc_uuid_to_str(&uuid, &uuid_str[0], uuid_str.size()); - std::string query = OCF_RES_QUERY_SDUUID "=" + std::string(uuid_str.data()); - - getRequestWithDomainQuery(query); - - // restore default - oc_sec_sdi_clear(sdi); -} - -TEST_F(TestDiscoveryWithServer, GetRequest_FailInvalidSecurityDomain) -{ - // bad format of uuid - std::string query = OCF_RES_QUERY_SDUUID "=42"; - getRequestWithDomainQuery(query); -} - -TEST_F(TestDiscoveryWithServer, GetRequest_FailWrongSecurityDomain) -{ - oc_sec_sdi_t *sdi = oc_sec_sdi_get(kDeviceID); - ASSERT_NE(nullptr, sdi); - - oc_uuid_t uuid1; - oc_gen_uuid(&uuid1); - memcpy(&sdi->uuid.id, uuid1.id, sizeof(uuid1.id)); - - // non-matching uuid - oc_uuid_t uuid2; - do { - oc_gen_uuid(&uuid2); - } while (memcmp(uuid1.id, uuid2.id, sizeof(uuid2.id)) == 0); - - std::array uuid_str{}; - oc_uuid_to_str(&uuid2, &uuid_str[0], uuid_str.size()); - std::string query = OCF_RES_QUERY_SDUUID "=" + std::string(uuid_str.data()); - - getRequestWithDomainQuery(query); - - // restore default - oc_sec_sdi_clear(sdi); -} - -#endif /* OC_SECURITY */ - -// payloads are too large for static buffers -#if defined(OC_DYNAMIC_ALLOCATION) && !defined(OC_APP_DATA_BUFFER_SIZE) - -static void -matchResourceLink(const oc_resource_t *resource, const DiscoveryLinkData &link) -{ - // href - EXPECT_STREQ(link.href.c_str(), oc_string(resource->uri)); - - // resource types - auto resourceTypes = oc::GetVector(resource->types); - EXPECT_EQ(link.resourceTypes.size(), resourceTypes.size()); - for (const auto &rt : link.resourceTypes) { - EXPECT_NE(std::end(resourceTypes), - std::find(std::begin(resourceTypes), std::end(resourceTypes), rt)) - << "resource type: " << rt << " not found"; - } - - // interfaces - unsigned iface_mask = 0; - for (const auto &iface : link.interfaces) { - iface_mask |= iface; - } - EXPECT_EQ(iface_mask, resource->interfaces); - - // properties - EXPECT_EQ(link.properties, resource->properties & OCF_RES_POLICY_PROPERTIES); -} - -static void -verifyLinks(const DiscoveryLinkDataMap &links) -{ -#ifdef OC_SERVER - auto verifyUndiscoverable = [&links](std::string_view uri) { - oc_resource_t *res = - oc_ri_get_app_resource_by_uri(uri.data(), uri.length(), kDeviceID); - ASSERT_NE(nullptr, res); - ASSERT_EQ(0, res->properties & OC_DISCOVERABLE); - EXPECT_EQ(std::end(links), links.find(std::string(uri))); - }; - - auto verifyDiscoverable = [&links](std::string_view uri) { - oc_resource_t *res = - oc_ri_get_app_resource_by_uri(uri.data(), uri.length(), kDeviceID); - ASSERT_NE(nullptr, res); - ASSERT_NE(0, res->properties & OC_DISCOVERABLE); - const auto &linkData = links.find(std::string(uri)); - ASSERT_NE(std::end(links), linkData); - matchResourceLink(res, linkData->second); - }; - - verifyDiscoverable(kDynamicURI1); - verifyUndiscoverable(kDynamicURI2); - -#ifdef OC_COLLECTIONS - oc_collection_t *col = oc_get_collection_by_uri( - kCollectionURI.data(), kCollectionURI.length(), kDeviceID); - ASSERT_NE(nullptr, col); - const auto &colLink = links.find(std::string(kCollectionURI)); - ASSERT_NE(std::end(links), colLink); - matchResourceLink(&col->res, colLink->second); - - for (const oc_link_t *link = oc_collection_get_links(&col->res); - link != nullptr; link = link->next) { - const auto &linkData = links.find(oc_string(link->resource->uri)); - if ((link->resource->properties & OC_DISCOVERABLE) == 0) { - EXPECT_EQ(std::end(links), links.find(std::string(kDynamicURI2))); - continue; - } - ASSERT_NE(std::end(links), linkData); - matchResourceLink(link->resource, linkData->second); - } -#endif /* OC_COLLECTIONS */ -#endif /* OC_SERVER */ -} - -TEST_F(TestDiscoveryWithServer, GetRequestWithSecurityDomain) -{ - oc_uuid_t uuid; - oc_gen_uuid(&uuid); - -#ifdef OC_SECURITY - oc_sec_sdi_t *sdi = oc_sec_sdi_get(kDeviceID); - ASSERT_NE(nullptr, sdi); - memcpy(&sdi->uuid.id, uuid.id, sizeof(uuid.id)); -#endif /* OC_SECURITY */ - - std::array uuid_str{}; - oc_uuid_to_str(&uuid, &uuid_str[0], uuid_str.size()); - std::string query = OCF_RES_QUERY_SDUUID "=" + std::string(uuid_str.data()); - - getRequestWithDomainQuery(query); - -#ifdef OC_SECURITY - // restore defaults - oc_sec_sdi_clear(sdi); -#endif /* OC_SECURITY */ -} - -static DiscoveryLinkData -parseLink(const oc_rep_t *link) -{ - DiscoveryLinkData linkData{}; - - char *str; - size_t str_len; - // rel: string - if (oc_rep_get_string(link, "rel", &str, &str_len)) { - linkData.rel = std::string(str, str_len); - } - - // anchor: string - if (oc_rep_get_string(link, "anchor", &str, &str_len)) { - linkData.anchor = std::string(str, str_len); - } - - // href: string - if (oc_rep_get_string(link, "href", &str, &str_len)) { - linkData.href = std::string(str, str_len); - } - - // rt: array of strings - oc_string_array_t str_array; - size_t str_array_len; - if (oc_rep_get_string_array(link, "rt", &str_array, &str_array_len)) { - for (size_t i = 0; i < str_array_len; ++i) { - linkData.resourceTypes.emplace_back( - oc_string_array_get_item(str_array, i)); - } - } - - // if: array of strings - if (oc_rep_get_string_array(link, "if", &str_array, &str_array_len)) { - for (size_t i = 0; i < str_array_len; ++i) { - std::string iface_str = oc_string_array_get_item(str_array, i); - oc_interface_mask_t iface = - oc_ri_get_interface_mask(iface_str.c_str(), iface_str.length()); - if (iface == 0) { - continue; - } - linkData.interfaces.emplace_back(iface); - } - } - - // p: {"bm": int} - if (oc_rep_t * obj; oc_rep_get_object(link, "p", &obj)) { - if (int64_t properties; oc_rep_get_int(obj, "bm", &properties)) { - linkData.properties = static_cast(properties); - } - } - - // tag-pos-desc: string - if (oc_rep_get_string(link, "tag-pos-desc", &str, &str_len)) { - linkData.tagPosDesc = std::string(str, str_len); - } - - // tag-func-desc: string - if (oc_rep_get_string(link, "tag-func-desc", &str, &str_len)) { - linkData.tagFuncDesc = std::string(str, str_len); - } - - // tag-locn: string - if (oc_rep_get_string(link, "tag-locn", &str, &str_len)) { - linkData.tagLocation = std::string(str, str_len); - } - - // tag-pos-rel: double[3] - double *pos_rel; - if (size_t pos_rel_size; - oc_rep_get_double_array(link, "tag-pos-rel", &pos_rel, &pos_rel_size)) { - for (size_t i = 0; i < pos_rel_size; ++i) { - linkData.tagPosRel.emplace_back(pos_rel[i]); - } - } - - return linkData; -} - -static DiscoveryLinkDataMap -parseLinks(const oc_rep_t *rep) -{ - DiscoveryLinkDataMap links{}; - for (; rep != nullptr; rep = rep->next) { - auto link = parseLink(rep->value.object); - links[link.href] = link; - } - return links; -} - -// default interface - LL -// payload contains array of discoverable resources -// [ -// { -// "rel": "self", // for /oic/res only -// "anchor": "ocf://03ddc383-a500-41f8-75a6-5c3e51d97906", -// "href": "/oc/con", -// "rt": [ -// "oic.wk.con" -// ], -// "if": [ -// "oic.if.rw", -// "oic.if.baseline" -// ], -// "p": { -// "bm": 3 -// }, -// "eps": null -// "tag-pos-desc": -// "tag-func-desc": -// "tag-locn": string -// "tag-pos-rel": double[3] -// }, -// ... -// ] -TEST_F(TestDiscoveryWithServer, GetRequest) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto get_handler = [](oc_client_response_t *data) { - oc::TestDevice::Terminate(); - ASSERT_EQ(OC_STATUS_OK, data->code); -#ifdef OC_HAS_FEATURE_ETAG - assertDiscoveryETag(data->etag, data->endpoint, data->endpoint->device); -#endif /* OC_HAS_FEATURE_ETAG */ - OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload).data()); - *static_cast(data->user_data) = - parseLinks(data->payload); - }; - - DiscoveryLinkDataMap links{}; - auto timeout = 1s; - EXPECT_TRUE(oc_do_get_with_timeout(OCF_RES_URI, &ep, nullptr, timeout.count(), - get_handler, HIGH_QOS, &links)); - oc::TestDevice::PoolEventsMsV1(timeout, true); - ASSERT_FALSE(links.empty()); - - verifyLinks(links); -} - -struct DiscoveryBaselineData -{ - oc::BaselineData baseline; - std::string sduuid; - std::string sdname; - DiscoveryLinkDataMap links; -}; - -static DiscoveryBaselineData -parseBaselinePayload(const oc_rep_t *payload) -{ - const oc_rep_t *rep = payload->value.object; - DiscoveryBaselineData data{}; - if (auto bl_opt = oc::ParseBaselineData(rep)) { - data.baseline = *bl_opt; - } - - char *str; - size_t str_len; - // sduuid: string - if (oc_rep_get_string(rep, OCF_RES_PROP_SDUUID, &str, &str_len)) { - data.sduuid = std::string(str, str_len); - } - - // sdname: string - if (oc_rep_get_string(rep, OCF_RES_PROP_SDNAME, &str, &str_len)) { - data.sdname = std::string(str, str_len); - } - - // links - if (oc_rep_t *obj = nullptr; oc_rep_get_object_array(rep, "links", &obj)) { - data.links = parseLinks(obj); - } - return data; -} - -// baseline interface: -// { -// -// #ifdef OC_SECURITY -// if (!sdi.priv) { -// "sduuid": -// "sdname": -// } -// #endif -// -// "links": [ -// , -// , -// ... -// ] -// } -TEST_F(TestDiscoveryWithServer, GetRequestBaseline) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - -#ifdef OC_SECURITY - oc_uuid_t uuid; - oc_gen_uuid(&uuid); - oc_sec_sdi_t *sdi = oc_sec_sdi_get(kDeviceID); - ASSERT_NE(nullptr, sdi); - memcpy(&sdi->uuid.id, uuid.id, sizeof(uuid.id)); - const std::string_view sdname = "Test Security Domain"; - oc_new_string(&sdi->name, sdname.data(), sdname.length()); -#endif /* OC_SECURITY */ - - auto get_handler = [](oc_client_response_t *data) { - oc::TestDevice::Terminate(); - ASSERT_EQ(OC_STATUS_OK, data->code); -#ifdef OC_HAS_FEATURE_ETAG - assertDiscoveryETag(data->etag, data->endpoint, data->endpoint->device); -#endif /* OC_HAS_FEATURE_ETAG */ - OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload).data()); - auto *baseline = static_cast(data->user_data); - *baseline = parseBaselinePayload(data->payload); - }; - - DiscoveryBaselineData baseline{}; - auto timeout = 1s; - EXPECT_TRUE(oc_do_get_with_timeout(OCF_RES_URI, &ep, "if=" OC_IF_BASELINE_STR, - timeout.count(), get_handler, HIGH_QOS, - &baseline)); - oc::TestDevice::PoolEventsMsV1(timeout, true); - EXPECT_FALSE(baseline.links.empty()); - -#ifdef OC_SECURITY - std::array uuid_str{}; - oc_uuid_to_str(&sdi->uuid, &uuid_str[0], uuid_str.size()); - EXPECT_STREQ(std::string(uuid_str.data()).c_str(), baseline.sduuid.c_str()); - EXPECT_STREQ(oc_string(sdi->name), baseline.sdname.c_str()); -#endif /* OC_SECURITY */ - - verifyLinks(baseline.links); - -#ifdef OC_SECURITY - // restore defaults - oc_sec_sdi_clear(sdi); -#endif /* OC_SECURITY */ -} - -#ifdef OC_RES_BATCH_SUPPORT - -struct batch_resources_t -{ - const oc_endpoint_t *endpoint; - std::vector resources; -}; - -static batch_resources_t -getBatchResources(const oc_endpoint_t *endpoint) -{ - batch_resources_t batch{}; - batch.endpoint = endpoint; - - oc_resources_iterate( - kDeviceID, true, true, true, true, - [](oc_resource_t *resource, void *data) { - if (auto *br = static_cast(data); - oc_discovery_resource_is_in_batch_response(resource, br->endpoint, - true)) { - br->resources.emplace_back(resource); - } - return true; - }, - &batch); - return batch; -} - -#ifndef OC_SECURITY - -static void -verifyBatchPayloadResource(const DiscoveryBatchData &dbd, - const oc_resource_t *resource) -{ - ASSERT_NE(nullptr, resource); - const auto &it = dbd.find(std::string(oc_string(resource->uri))); - ASSERT_NE(std::end(dbd), it) - << "resource: " << oc_string(resource->uri) << " not found"; -#ifdef OC_HAS_FEATURE_ETAG - oc_coap_etag_t etag{}; - std::copy(it->second.etag.begin(), it->second.etag.end(), etag.value); - etag.length = static_cast(it->second.etag.size()); - TestDiscoveryWithServer::assertResourceETag(etag, resource); -#endif /* OC_HAS_FEATURE_ETAG */ -} - -static void -verifyBatchPayload(const DiscoveryBatchData &dbd, - const std::vector &expected) -{ - ASSERT_EQ(expected.size(), dbd.size()); - for (const auto *resource : expected) { - verifyBatchPayloadResource(dbd, resource); - } -} - -static void -verifyBatchPayload(const DiscoveryBatchData &dbd, const oc_endpoint_t *endpoint) -{ - auto br = getBatchResources(endpoint); - verifyBatchPayload(dbd, br.resources); -} - -#endif /* !OC_SECURITY */ - -static DiscoveryBatchData -parseBatchPayload(const oc_rep_t *payload) -{ - auto extractUUIDAndURI = - [](std::string_view href) -> std::pair { - // skip past "ocf:// prefix" - std::string_view input = href.substr(6); - size_t uriStart = input.find('/'); - - if (uriStart == std::string_view::npos) { - return std::make_pair("", ""); - } - // Extract the UUID and the URI as separate substrings - std::string_view uuid = input.substr(0, uriStart - 1); - std::string_view uri = input.substr(uriStart); - return std::make_pair(std::string(uuid), std::string(uri)); - }; - - DiscoveryBatchData data{}; - for (const oc_rep_t *rep = payload; rep != nullptr; rep = rep->next) { - const oc_rep_t *obj = rep->value.object; - DiscoveryBatchItem bi{}; - char *str; - size_t str_len; - // href: string - if (oc_rep_get_string(obj, "href", &str, &str_len)) { - std::string_view href(str, str_len); - auto [uuid, uri] = extractUUIDAndURI(href); - bi.deviceUUID = uuid; - bi.href = uri; - } - -#ifdef OC_HAS_FEATURE_ETAG - // etag: byte string - if (oc_rep_get_byte_string(obj, "etag", &str, &str_len)) { - bi.etag.resize(str_len); - std::copy(&str[0], &str[str_len], std::begin(bi.etag)); - } -#endif /* OC_HAS_FEATURE_ETAG */ - - if (!bi.href.empty()) { - data[bi.href] = bi; - } - } - - return data; -} - -// batch interface -// [ -// { -// "href": "ocf:///", -// "rep": { -// -// } -// }, -// ... -// ] -TEST_F(TestDiscoveryWithServer, GetRequestBatch) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto get_handler = [](oc_client_response_t *data) { - oc::TestDevice::Terminate(); - OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload).data()); - auto *batch = static_cast(data->user_data); - *batch = parseBatchPayload(data->payload); -#ifdef OC_SECURITY - // need secure endpoint to get batch response if OC_SECURITY is enabled - ASSERT_EQ(OC_STATUS_BAD_REQUEST, data->code); -#else /* !OC_SECURITY */ - ASSERT_EQ(OC_STATUS_OK, data->code); -#ifdef OC_HAS_FEATURE_ETAG - assertDiscoveryETag(data->etag, data->endpoint, data->endpoint->device, - true); - // the response etag should be the highest etag contained the payload - assertBatchETag(data->etag, data->endpoint->device, *batch); -#endif /* OC_HAS_FEATURE_ETAG */ -#endif /* OC_SECURITY */ - }; - - DiscoveryBatchData data{}; - auto timeout = 1s; - EXPECT_TRUE(oc_do_get_with_timeout(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, - timeout.count(), get_handler, HIGH_QOS, - &data)); - oc::TestDevice::PoolEventsMsV1(timeout, true); - -#ifdef OC_SECURITY - EXPECT_TRUE(data.empty()); -#else /* !OC_SECURITY */ - ASSERT_FALSE(data.empty()); - verifyBatchPayload(data, &ep); -#endif /* OC_SECURITY */ -} - -#ifdef OC_HAS_FEATURE_ETAG_INCREMENTAL_CHANGES - -static std::string -encodeETagToBase64(const oc_coap_etag_t *etag) -{ - std::array etagB64{}; - int b64_len = - oc_base64_encode_v1(OC_BASE64_ENCODING_URL, false, &etag->value[0], - etag->length, &etagB64[0], etagB64.size()); - if (b64_len == -1) { - throw std::string("base64 encode failed"); - } - std::string etagStr{}; - etagStr.insert(etagStr.end(), etagB64.data(), - etagB64.data() + static_cast(b64_len)); - return etagStr; -} - -static std::string -encodeETagToBase64(uint64_t etag) -{ - oc_coap_etag_t etagBytes{}; - memcpy(&etagBytes.value[0], &etag, sizeof(etag)); - etagBytes.length = sizeof(etag); - return encodeETagToBase64(&etagBytes); -} - -static std::string -testBatchIncrementalChangesQuery(const std::vector &etags) -{ - std::string query = "if=" OC_IF_B_STR; - if (etags.empty()) { - return query + "&" OC_ETAG_QUERY_INCREMENTAL_CHANGES_KEY; - } - - size_t count = 0; - for (auto etag : etags) { - if (count == 0) { - query += "&" OC_ETAG_QUERY_INCREMENTAL_CHANGES_KEY "="; - } else { - query += ","; - } - query += encodeETagToBase64(etag); - ++count; - if (count == 6) { - count = 0; - } - } - return query; -} - -static void -testBatchIncrementalChanges( - const oc_endpoint_t *ep, const oc_coap_etag_t *etag0, - const std::string &query, oc_status_t expectedCode = OC_STATUS_OK, - const std::vector &expected = {}) -{ - struct DiscoveryBatchResponse - { - oc_status_t code; - DiscoveryBatchData bd; - }; - - auto getHandler = [](oc_client_response_t *data) { - oc::TestDevice::Terminate(); - OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload).data()); - auto *dbr = static_cast(data->user_data); - dbr->code = data->code; - dbr->bd = parseBatchPayload(data->payload); -#ifdef OC_SECURITY - // need secure endpoint to get batch response if OC_SECURITY is enabled - ASSERT_EQ(OC_STATUS_BAD_REQUEST, data->code); -#else /* !OC_SECURITY */ - ASSERT_TRUE(OC_STATUS_OK == data->code || - OC_STATUS_NOT_MODIFIED == data->code); - TestDiscoveryWithServer::assertDiscoveryETag(data->etag, data->endpoint, - data->endpoint->device, true); -#endif /* OC_SECURITY */ - }; - - DiscoveryBatchResponse dbr{}; - auto timeout = 1s; - auto configureReq = [](coap_packet_t *req, const void *data) { - if (data == nullptr) { - return; - } - const auto *etag = static_cast(data); - coap_options_set_etag(req, etag->value, etag->length); - }; - ASSERT_TRUE(oc_do_request(OC_GET, OCF_RES_URI, ep, query.c_str(), - timeout.count(), getHandler, HIGH_QOS, &dbr, - configureReq, etag0)); - oc::TestDevice::PoolEventsMsV1(timeout, true); - -#ifdef OC_SECURITY - EXPECT_TRUE(dbr.bd.empty()); - (void)expectedCode; - (void)expected; -#else /* !OC_SECURITY */ - ASSERT_EQ(expectedCode, dbr.code); - if (dbr.code == OC_STATUS_OK) { - ASSERT_FALSE(dbr.bd.empty()); - verifyBatchPayload(dbr.bd, expected); - } - if (dbr.code == OC_STATUS_NOT_MODIFIED) { - EXPECT_TRUE(dbr.bd.empty()); - } -#endif /* OC_SECURITY */ -} - -static void -testBatchIncrementalChanges( - const oc_endpoint_t *ep, uint64_t etag0, const std::string &query, - oc_status_t expectedCode = OC_STATUS_OK, - const std::vector &expected = {}) -{ - if (etag0 == OC_ETAG_UNINITIALIZED) { - testBatchIncrementalChanges(ep, nullptr, query, expectedCode, expected); - return; - } - - oc_coap_etag_t coapETag{}; - memcpy(&coapETag.value[0], &etag0, sizeof(etag0)); - coapETag.length = sizeof(etag0); - testBatchIncrementalChanges(ep, &coapETag, query, expectedCode, expected); -} - -TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_Single) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto br = getBatchResources(&ep); - // sort in descending order - std::sort(br.resources.begin(), br.resources.end(), - [](const oc_resource_t *a, const oc_resource_t *b) { - return a->etag > b->etag; - }); - ASSERT_LT(1, br.resources.size()); - uint64_t etag0 = br.resources[br.resources.size() / 2 - 1]->etag; - ASSERT_NE(OC_ETAG_UNINITIALIZED, etag0); - - std::vector expected{}; - std::for_each(br.resources.begin(), br.resources.end(), - [&expected, etag0](const oc_resource_t *resource) { - if (resource->etag > etag0) { - expected.emplace_back(resource); - } - }); - testBatchIncrementalChanges(&ep, etag0, testBatchIncrementalChangesQuery({}), - OC_STATUS_OK, expected); -} - -// Invalid ETag0 should be ignored -TEST_F(TestDiscoveryWithServer, - GetRequestBatchIncremental_Single_InvalidIncrementalETag0) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - // invalid ETag0: size of bytes not equal to sizeof(uint64_t), it should be - // ignored and should receive all resources - std::vector etag0Bytes{ 'e', 't', 'a', 'g', '0' }; - assert(etag0Bytes.size() <= COAP_ETAG_LEN); - oc_coap_etag_t etag0{}; - memcpy(&etag0.value[0], &etag0Bytes[0], etag0Bytes.size()); - etag0.length = static_cast(etag0Bytes.size()); - - auto br = getBatchResources(&ep); - ASSERT_FALSE(br.resources.empty()); - - testBatchIncrementalChanges(&ep, &etag0, testBatchIncrementalChangesQuery({}), - OC_STATUS_OK, br.resources); -} - -// invalid ETags in the query should be ignored, but if a single valid ETag is -// found then is should be used -TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_InvalidETags) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - // get non-batch resources - batch_resources_t nonBatch{}; - nonBatch.endpoint = &ep; - oc_resources_iterate( - kDeviceID, true, true, true, true, - [](oc_resource_t *resource, void *data) { - if (auto *br = static_cast(data); - !oc_discovery_resource_is_in_batch_response(resource, br->endpoint, - false)) { - br->resources.emplace_back(resource); - } - return true; - }, - &nonBatch); - - std::string query = - "if=" OC_IF_B_STR "&" OC_ETAG_QUERY_INCREMENTAL_CHANGES_KEY "=12345,67890"; - for (size_t i = 0; i < 2 && i < nonBatch.resources.size(); ++i) { - query += ","; - query += encodeETagToBase64(nonBatch.resources[i]->etag); - } - query += ",,leetETag"; - query += "&" OC_ETAG_QUERY_INCREMENTAL_CHANGES_KEY "=,,"; - for (size_t i = 2; i < 4 && i < nonBatch.resources.size(); ++i) { - query += ","; - std::string etagStr = std::to_string(nonBatch.resources[i]->etag); - if (etagStr.length() > COAP_ETAG_LEN) { - etagStr.resize(COAP_ETAG_LEN); - } - oc_coap_etag_t etag{}; - memcpy(&etag.value[0], etagStr.data(), etagStr.length()); - etag.length = static_cast(etagStr.length()); - query += encodeETagToBase64(&etag); - } - - auto br = getBatchResources(&ep); - ASSERT_LT(1, br.resources.size()); - // sort in descending order - std::sort(br.resources.begin(), br.resources.end(), - [](const oc_resource_t *a, const oc_resource_t *b) { - return a->etag > b->etag; - }); - uint64_t valid = br.resources[br.resources.size() / 2 - 1]->etag; - query += "," + encodeETagToBase64(valid); - query += ",end"; - - std::vector expected{}; - std::for_each(br.resources.begin(), br.resources.end(), - [&expected, valid](const oc_resource_t *resource) { - if (resource->etag > valid) { - expected.emplace_back(resource); - } - }); - testBatchIncrementalChanges(&ep, nullptr, query, OC_STATUS_OK, expected); -} - -TEST_F(TestDiscoveryWithServer, - GetRequestBatchIncremental_Single_InvalidIncrementalUpdates) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto br = getBatchResources(&ep); - ASSERT_FALSE(br.resources.empty()); - // sort in descending order - std::sort(br.resources.begin(), br.resources.end(), - [](const oc_resource_t *a, const oc_resource_t *b) { - return a->etag > b->etag; - }); - // no resource should match (> highest etag), so we should get all resources - uint64_t etag0 = br.resources[0]->etag + 1; - testBatchIncrementalChanges( - &ep, etag0, - testBatchIncrementalChangesQuery({ etag0 + 1, etag0 + 2, etag0 + 3 }), - OC_STATUS_OK, br.resources); -} - -// Test with multiple etags in the query, sorted in descending order, so only -// the first should trigger the iteration of resources and subsequent ETag -// candidates should be skipped because they have a lower value -TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_Multiple) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto br = getBatchResources(&ep); - ASSERT_LT(1, br.resources.size()); - // sort in descending order - std::sort(br.resources.begin(), br.resources.end(), - [](const oc_resource_t *a, const oc_resource_t *b) { - return a->etag > b->etag; - }); - // set etag0 so it will not match any resource, which should result in - // the query getting examined - uint64_t etag0 = br.resources[0]->etag + 1; - - uint64_t pivot = br.resources[br.resources.size() / 2 - 1]->etag - 1; - std::vector etags{}; - std::vector expected{}; - std::for_each(br.resources.begin(), br.resources.end(), - [&etags, &expected, pivot](const oc_resource_t *resource) { - if (resource->etag > pivot) { - expected.emplace_back(resource); - } else { - etags.emplace_back(resource->etag); - } - }); - testBatchIncrementalChanges(&ep, etag0, - testBatchIncrementalChangesQuery(etags), - OC_STATUS_OK, expected); -} - -// Test with multiple etags in the query, sorted in ascending order, so all -// candidates should trigger resources iteration and the last value should be -// finally used as the ETag -TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_MultipleAscending) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto br = getBatchResources(&ep); - ASSERT_LT(1, br.resources.size()); - // sort in ascending order - std::sort(br.resources.begin(), br.resources.end(), - [](const oc_resource_t *a, const oc_resource_t *b) { - return a->etag < b->etag; - }); - // set etag0 so it will not match any resource, which should result in - // the query getting examined - uint64_t etag0 = br.resources[0]->etag + 1; - - uint64_t pivot = br.resources[br.resources.size() / 2 - 1]->etag - 1; - std::vector etags{}; - std::vector expected{}; - std::for_each(br.resources.begin(), br.resources.end(), - [&etags, &expected, pivot](const oc_resource_t *resource) { - if (resource->etag > pivot) { - expected.emplace_back(resource); - } else { - etags.emplace_back(resource->etag); - } - }); - testBatchIncrementalChanges(&ep, etag0, - testBatchIncrementalChangesQuery(etags), - OC_STATUS_OK, expected); -} - -/// Test will all batch resources etags in query, one of them is the latest -/// updated thus we should receive a VALID response with empty payload -TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_AllAscending) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto br = getBatchResources(&ep); - // sort in ascending order - std::sort(br.resources.begin(), br.resources.end(), - [](const oc_resource_t *a, const oc_resource_t *b) { - return a->etag < b->etag; - }); - std::vector etags{}; - std::for_each(br.resources.begin(), br.resources.end(), - [&etags](const oc_resource_t *resource) { - etags.emplace_back(resource->etag); - }); - - testBatchIncrementalChanges(&ep, static_cast(OC_ETAG_UNINITIALIZED), - testBatchIncrementalChangesQuery(etags), - OC_STATUS_NOT_MODIFIED); -} - -#endif /* OC_HAS_FEATURE_ETAG_INCREMENTAL_CHANGES */ - -#endif /* OC_RES_BATCH_SUPPORT */ - -#ifdef OC_DISCOVERY_RESOURCE_OBSERVABLE - -// observe with default (LL) interface -TEST_F(TestDiscoveryWithServer, Observe) -{ - ASSERT_TRUE(oc_get_con_res_announced()); - - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - struct observeData - { - DiscoveryLinkDataMap links; - int observe; - }; - auto onObserve = [](oc_client_response_t *cr) { - oc::TestDevice::Terminate(); - ASSERT_EQ(OC_STATUS_OK, cr->code); - OC_DBG("OBSERVE(%d) payload: %s", cr->observe_option, - oc::RepPool::GetJson(cr->payload, true).data()); - auto *od = static_cast(cr->user_data); - od->observe = cr->observe_option; - if (cr->observe_option == OC_COAP_OPTION_OBSERVE_REGISTER || - cr->observe_option >= OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE) { -#ifdef OC_HAS_FEATURE_ETAG - assertDiscoveryETag(cr->etag, cr->endpoint, cr->endpoint->device); -#endif /* OC_HAS_FEATURE_ETAG */ - od->links = parseLinks(cr->payload); - } - }; - observeData od{}; - ASSERT_TRUE( - oc_do_observe(OCF_RES_URI, &ep, nullptr, onObserve, HIGH_QOS, &od)); - oc::TestDevice::PoolEventsMsV1(1s); - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, od.observe); - ASSERT_FALSE(od.links.empty()); - verifyLinks(od.links); - od.observe = 0; - od.links.clear(); - - // adding a resource should trigger an observe notification - oc::DynamicResourceHandler handlers{}; - dynamicResources[std::string(kDynamicURI3)] = { 2001 }; - handlers.onGet = onGetDynamicResource; - handlers.onGetData = &dynamicResources[std::string(kDynamicURI3)]; - oc_resource_t *res = oc::TestDevice::AddDynamicResource( - oc::makeDynamicResourceToAdd("Dynamic Resource 3", - std::string(kDynamicURI3), - { "oic.d.observable", "oic.d.test" }, - { OC_IF_BASELINE, OC_IF_R }, handlers), - kDeviceID); - ASSERT_NE(nullptr, res); - oc_resource_set_observable(res, true); - - int repeats = 0; - while (od.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, od.observe); - ASSERT_FALSE(od.links.empty()); - verifyLinks(od.links); - od.observe = 0; - od.links.clear(); - - // deleting the resource should also trigger an observe notification - ASSERT_TRUE(oc::TestDevice::ClearDynamicResource(res, true)); - repeats = 0; - while (od.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE + 1, od.observe); - ASSERT_FALSE(od.links.empty()); - verifyLinks(od.links); - od.observe = 0; - od.links.clear(); - - ASSERT_TRUE(oc_stop_observe(OCF_RES_URI, &ep)); - while (od.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, od.observe); -} - -// observe with baseline interface -TEST_F(TestDiscoveryWithServer, ObserveBaseline) -{ - ASSERT_TRUE(oc_get_con_res_announced()); - - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - struct observeBaselineData - { - DiscoveryBaselineData baseline; - int observe; - }; - auto onObserve = [](oc_client_response_t *cr) { - oc::TestDevice::Terminate(); - ASSERT_EQ(OC_STATUS_OK, cr->code); - OC_DBG("OBSERVE(%d) payload: %s", cr->observe_option, - oc::RepPool::GetJson(cr->payload, true).data()); - auto *obd = static_cast(cr->user_data); - obd->observe = cr->observe_option; - if (cr->observe_option == OC_COAP_OPTION_OBSERVE_REGISTER || - cr->observe_option >= OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE) { -#ifdef OC_HAS_FEATURE_ETAG - assertDiscoveryETag(cr->etag, cr->endpoint, cr->endpoint->device); -#endif /* OC_HAS_FEATURE_ETAG */ - obd->baseline = parseBaselinePayload(cr->payload); - } - }; - observeBaselineData obd{}; - ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_BASELINE_STR, - onObserve, HIGH_QOS, &obd)); - oc::TestDevice::PoolEventsMsV1(1s); - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, obd.observe); - ASSERT_FALSE(obd.baseline.links.empty()); - verifyLinks(obd.baseline.links); - obd.observe = 0; - obd.baseline.links.clear(); - - // adding a resource should trigger an observe notification - oc::DynamicResourceHandler handlers{}; - dynamicResources[std::string(kDynamicURI3)] = { 2001 }; - handlers.onGet = onGetDynamicResource; - handlers.onGetData = &dynamicResources[std::string(kDynamicURI3)]; - oc_resource_t *res = oc::TestDevice::AddDynamicResource( - oc::makeDynamicResourceToAdd( - "Dynamic Resource 3", std::string(kDynamicURI3), - { "oic.d.observable", "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, - handlers, OC_DISCOVERABLE | OC_OBSERVABLE), - kDeviceID); - ASSERT_NE(nullptr, res); - - int repeats = 0; - while (obd.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, obd.observe); - ASSERT_FALSE(obd.baseline.links.empty()); - verifyLinks(obd.baseline.links); - obd.observe = 0; - obd.baseline.links.clear(); - - // deleting the resource should also trigger an observe notification - ASSERT_TRUE(oc::TestDevice::ClearDynamicResource(res, true)); - repeats = 0; - while (obd.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE + 1, obd.observe); - ASSERT_FALSE(obd.baseline.links.empty()); - verifyLinks(obd.baseline.links); - obd.observe = 0; - obd.baseline.links.clear(); - - ASSERT_TRUE(oc_stop_observe(OCF_RES_URI, &ep)); - while (obd.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, obd.observe); -} - -#ifdef OC_RES_BATCH_SUPPORT - -#ifdef OC_SECURITY - -TEST_F(TestDiscoveryWithServer, ObserveBatch_F) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - auto onObserve = [](oc_client_response_t *cr) { - oc::TestDevice::Terminate(); - OC_DBG("OBSERVE(%d) payload: %s", cr->observe_option, - oc::RepPool::GetJson(cr->payload, true).data()); - *static_cast(cr->user_data) = true; - // insecure batch interface requests are unsupported - EXPECT_EQ(OC_STATUS_BAD_REQUEST, cr->code); - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, cr->observe_option); - }; - - bool invoked = false; - ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, onObserve, - HIGH_QOS, &invoked)); - oc::TestDevice::PoolEventsMsV1(1s); - EXPECT_TRUE(invoked); - - // no observers should exist - ASSERT_EQ(0, oc_list_length(coap_get_observers())); -} - -// TEST_F(TestDiscoveryWithServer, ObserveBatch) -// { -// TODO: add support for using secure endpoints for communication in tests -// } - -#else /* !OC_SECURITY */ - -struct observeBatchData -{ - DiscoveryBatchData batch; - int observe; -}; - -static void -onBatchObserve(oc_client_response_t *cr) -{ - oc::TestDevice::Terminate(); - OC_DBG("OBSERVE(%d) payload: %s", cr->observe_option, - oc::RepPool::GetJson(cr->payload, true).data()); - ASSERT_EQ(OC_STATUS_OK, cr->code); - auto *obd = static_cast(cr->user_data); - obd->observe = cr->observe_option; - obd->batch = parseBatchPayload(cr->payload); -#ifdef OC_HAS_FEATURE_ETAG - TestDiscoveryWithServer::assertDiscoveryETag(cr->etag, cr->endpoint, - cr->endpoint->device, true); - if (cr->observe_option == OC_COAP_OPTION_OBSERVE_REGISTER || - cr->observe_option == OC_COAP_OPTION_OBSERVE_NOT_SET) { - // we have a full payload and the response etag should be the highest etag - // contained the payload - TestDiscoveryWithServer::assertBatchETag(cr->etag, cr->endpoint->device, - obd->batch); - } -#endif /* OC_HAS_FEATURE_ETAG */ -} - -static void -updateResourceByPost(std::string_view uri, const oc_endpoint_t *endpoint, - const std::function &payloadFn) -{ - auto post_handler = [](oc_client_response_t *data) { - oc::TestDevice::Terminate(); - EXPECT_EQ(OC_STATUS_CHANGED, data->code); - OC_DBG("POST payload: %s", oc::RepPool::GetJson(data->payload).data()); - *static_cast(data->user_data) = true; - }; - - bool invoked = false; - ASSERT_TRUE(oc_init_post(uri.data(), endpoint, nullptr, post_handler, LOW_QOS, - &invoked)); - payloadFn(); - auto timeout = 1s; - ASSERT_TRUE(oc_do_post_with_timeout(timeout.count())); - oc::TestDevice::PoolEventsMsV1(timeout, true); - ASSERT_TRUE(invoked); -} - -TEST_F(TestDiscoveryWithServer, ObserveBatchWithResourceUpdate) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - observeBatchData obd{}; - ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, onBatchObserve, - HIGH_QOS, &obd)); - oc::TestDevice::PoolEventsMsV1(1s); - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, obd.observe); - ASSERT_FALSE(obd.batch.empty()); - // all resources should be in the first payload - verifyBatchPayload(obd.batch, &ep); - obd.observe = 0; - obd.batch.clear(); - - oc_device_info_t *info = oc_core_get_device_info(kDeviceID); - ASSERT_NE(nullptr, info); - std::string deviceName = oc_string(info->name); - // updating the name by the /oc/con resource should trigger a batch observe - // notification with /oc/con and /oic/d resources - updateResourceByPost(OC_CON_URI, &ep, [deviceName]() { - oc_rep_start_root_object(); - oc_rep_set_text_string_v1(root, n, deviceName.c_str(), deviceName.length()); - oc_rep_end_root_object(); - }); - - int repeats = 0; - while (obd.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_LE(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, obd.observe); - ASSERT_FALSE(obd.batch.empty()); - std::vector expected{}; - auto *device = oc_core_get_resource_by_index(OCF_D, kDeviceID); - ASSERT_NE(nullptr, device); - if ((device->properties & OC_DISCOVERABLE) != 0) { - expected.emplace_back(device); - } - auto *con = oc_core_get_resource_by_index(OCF_CON, kDeviceID); - ASSERT_NE(nullptr, con); - if ((con->properties & OC_DISCOVERABLE) != 0) { - expected.emplace_back(con); - } - verifyBatchPayload(obd.batch, expected); - obd.observe = 0; - obd.batch.clear(); - - ASSERT_TRUE(oc_stop_observe(OCF_RES_URI, &ep)); - while (obd.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - // response should be a full batch GET payload with observe option not set - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, obd.observe); - ASSERT_FALSE(obd.batch.empty()); - verifyBatchPayload(obd.batch, &ep); -} - -TEST_F(TestDiscoveryWithServer, ObserveBatchWithResourceAdded) -{ - auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); - ASSERT_TRUE(epOpt.has_value()); - auto ep = std::move(*epOpt); - - observeBatchData obd{}; - ASSERT_TRUE(oc_do_observe(OCF_RES_URI, &ep, "if=" OC_IF_B_STR, onBatchObserve, - HIGH_QOS, &obd)); - oc::TestDevice::PoolEventsMsV1(1s); - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, obd.observe); - ASSERT_FALSE(obd.batch.empty()); - // all resources should be in the first payload - verifyBatchPayload(obd.batch, &ep); - obd.observe = 0; - obd.batch.clear(); - - // adding a resource should trigger an observe notification - oc::DynamicResourceHandler handlers{}; - dynamicResources[std::string(kDynamicURI3)] = { 2001 }; - handlers.onGet = onGetDynamicResource; - handlers.onGetData = &dynamicResources[std::string(kDynamicURI3)]; - oc_resource_t *res = oc::TestDevice::AddDynamicResource( - oc::makeDynamicResourceToAdd( - "Dynamic Resource 3", std::string(kDynamicURI3), - { "oic.d.observable", "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, - handlers, OC_DISCOVERABLE | OC_OBSERVABLE), - kDeviceID); - ASSERT_NE(nullptr, res); - - int repeats = 0; - while (obd.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_LE(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE, obd.observe); - ASSERT_FALSE(obd.batch.empty()); - obd.observe = 0; - obd.batch.clear(); - - ASSERT_TRUE(oc::TestDevice::ClearDynamicResource(res, true)); - repeats = 0; - while (obd.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_SEQUENCE_START_VALUE + 1, obd.observe); - ASSERT_FALSE(obd.batch.empty()); - obd.observe = 0; - obd.batch.clear(); - - ASSERT_TRUE(oc_stop_observe(OCF_RES_URI, &ep)); - while (obd.observe == 0 && repeats < 50) { - oc::TestDevice::PoolEventsMsV1(10ms); - ++repeats; - } - // response should be a full batch GET payload with observe option not set - EXPECT_EQ(OC_COAP_OPTION_OBSERVE_NOT_SET, obd.observe); - ASSERT_FALSE(obd.batch.empty()); - verifyBatchPayload(obd.batch, &ep); -} - -TEST_F(TestDiscoveryWithServer, ObserveBatchWithEmptyPayload) -{ - // TODO: remove resource from payload -> make it undiscoverable in runtime and - // see what happens -} - -#endif /* OC_SECURITY */ - -#endif /* OC_RES_BATCH_SUPPORT */ - -#endif /* OC_DISCOVERY_RESOURCE_OBSERVABLE */ - -#endif /* OC_DYNAMIC_ALLOCATION && !OC_APP_DATA_BUFFER_SIZE */ diff --git a/api/unittest/maintest.cpp b/api/unittest/maintest.cpp index ead6d2fcff..af1fedd6d5 100644 --- a/api/unittest/maintest.cpp +++ b/api/unittest/maintest.cpp @@ -83,7 +83,7 @@ class TestMain : public testing::Test { #ifndef _WIN32 pthread_cond_destroy(&cv); pthread_mutex_destroy(&mutex); -#endif /* _WIN32 */ +#endif /* !_WIN32 */ } void SetUp() override diff --git a/api/unittest/ocapitest.cpp b/api/unittest/ocapitest.cpp index 0403524097..643c677a16 100644 --- a/api/unittest/ocapitest.cpp +++ b/api/unittest/ocapitest.cpp @@ -195,7 +195,7 @@ class ApiHelper { #ifdef _WIN32 InitializeCriticalSection(&s_mutex); InitializeConditionVariable(&s_cv); -#else +#else /* !_WIN32 */ if (pthread_mutex_init(&s_mutex, nullptr) != 0) { throw std::string("cannot initialize mutex"); } @@ -218,7 +218,7 @@ class ApiHelper { #ifndef _WIN32 pthread_cond_destroy(&s_cv); pthread_mutex_destroy(&s_mutex); -#endif /* _WIN32 */ +#endif /* !_WIN32 */ } static void lock() diff --git a/api/unittest/resourcetest.cpp b/api/unittest/resourcetest.cpp index d35d5ad99f..d8debb0fec 100644 --- a/api/unittest/resourcetest.cpp +++ b/api/unittest/resourcetest.cpp @@ -135,28 +135,28 @@ TEST_F(TestResource, GetMethodHandler) { oc_resource_t res{}; auto getCB = [](oc_request_t *, oc_interface_mask_t, void *) { - // no-op; + // no-op }; char getData; res.get_handler.cb = getCB; res.get_handler.user_data = &getData; auto postCB = [](oc_request_t *, oc_interface_mask_t, void *) { - // no-op; + // no-op }; char postData; res.post_handler.cb = postCB; res.post_handler.user_data = &postData; auto putCB = [](oc_request_t *, oc_interface_mask_t, void *) { - // no-op; + // no-op }; char putData; res.put_handler.cb = putCB; res.put_handler.user_data = &putData; auto deleteCB = [](oc_request_t *, oc_interface_mask_t, void *) { - // no-op; + // no-op }; char deleteData; res.delete_handler.cb = deleteCB; diff --git a/api/unittest/tcptest.cpp b/api/unittest/tcptest.cpp index cdeb3b7ad2..2713d525a6 100644 --- a/api/unittest/tcptest.cpp +++ b/api/unittest/tcptest.cpp @@ -22,15 +22,23 @@ #include "oc_endpoint.h" #include "oc_buffer.h" #include "api/oc_tcp_internal.h" +#include "messaging/coap/coap_internal.h" #include "port/oc_allocator_internal.h" +#include "tests/gtest/Endpoint.h" +#include "util/oc_features.h" -#ifdef _WIN32 -#include -#endif /* _WIN32 */ +#ifdef OC_OSCORE +#include "messaging/coap/oscore_internal.h" +#endif /* OC_OSCORE */ #include #include #include +#include + +#ifdef _WIN32 +#include +#endif /* _WIN32 */ #ifdef OC_SECURITY #include @@ -38,7 +46,7 @@ class TCPMessage : public testing::Test { public: - void SetUp() override + static void SetUpTestCase() { #ifdef _WIN32 WSADATA wsaData; @@ -49,7 +57,7 @@ class TCPMessage : public testing::Test { #endif /* !OC_DYNAMIC_ALLOCATION */ } - void TearDown() override + static void TearDownTestCase() { #ifndef OC_DYNAMIC_ALLOCATION oc_allocator_mutex_destroy(); @@ -59,38 +67,231 @@ class TCPMessage : public testing::Test { #endif /* _WIN32 */ } - static void ValidateMessage(bool exp, bool secure, - const std::vector &data) + static void ValidateHeader(bool exp, bool secure, const uint8_t *data, + size_t data_size) + { + EXPECT_EQ(exp, oc_tcp_is_valid_header(data, data_size, secure)); + } + + static void ValidateHeader(bool exp, bool secure, + const std::vector &data) + { + EXPECT_EQ(exp, oc_tcp_is_valid_header(&data[0], data.size(), secure)); + } + + static void ValidateMessage(bool exp, bool secure, const uint8_t *data, + size_t data_size) { oc_message_t *msg = oc_allocate_message(); - msg->endpoint.flags = secure ? SECURED : (transport_flags)0; - memcpy(msg->data, &data[0], data.size()); - msg->length = data.size(); - EXPECT_EQ(exp, oc_tcp_is_valid_header(msg)); + ASSERT_NE(nullptr, msg); + int flags = IPV6 | TCP; + flags |= secure ? SECURED : 0; + msg->endpoint.flags = static_cast(flags); + if (data_size > 0) { + memcpy(msg->data, data, data_size); + } + msg->length = data_size; + EXPECT_EQ(exp, oc_tcp_is_valid_message(msg)); oc_message_unref(msg); } + + static void ValidateMessage(bool exp, bool secure, + const std::vector &data) + { + ValidateMessage(exp, secure, &data[0], data.size()); + } + + static void ValidateMessage(bool exp, bool secure, bool oscore, + coap_packet_t &packet) + { + std::array buffer{}; + size_t buffer_len = coap_oscore_serialize_message( + &packet, &buffer[0], buffer.size(), true, true, oscore); + ASSERT_LT(0, buffer_len); + ValidateMessage(exp, secure, &buffer[0], buffer.size()); + } + + static void ValidateHeaderLength(long exp, bool secure, const uint8_t *data, + size_t data_size) + { + EXPECT_EQ(exp, + oc_tcp_get_total_length_from_header(data, data_size, secure)); + } + + static void ValidateHeaderLength(long exp, bool secure, + const std::vector &data) + { + ValidateHeaderLength(exp, secure, &data[0], data.size()); + } + + static void ValidateHeaderLength(long exp, bool secure, oc_message_t *msg) + { + int flags = IPV6 | TCP; + flags |= secure ? SECURED : 0; + msg->endpoint.flags = static_cast(flags); + EXPECT_EQ(exp, oc_tcp_get_total_length_from_message_header(msg)); + } }; +#ifdef OC_HAS_FEATURE_TCP_ASYNC_CONNECT + +TEST_F(TCPMessage, CreateConnectEvent) +{ + auto ep = oc::endpoint::FromString("coap+tcp://[::1]:42"); + on_tcp_connect_t on_connect = [](const oc_endpoint_t *, int, void *) { + // no-op + }; + int data = 42; + + oc_tcp_on_connect_event_t *event = oc_tcp_on_connect_event_create( + &ep, OC_TCP_SOCKET_STATE_CONNECTED, on_connect, &data); + ASSERT_NE(nullptr, event); + EXPECT_EQ(OC_TCP_SOCKET_STATE_CONNECTED, event->state); + EXPECT_EQ(0, memcmp(&ep, &event->endpoint, sizeof(oc_endpoint_t))); + EXPECT_EQ(on_connect, event->fn); + EXPECT_EQ(&data, event->fn_data); + + oc_tcp_on_connect_event_free(event); +} + +#ifndef OC_DYNAMIC_ALLOCATION + +TEST_F(TCPMessage, CreateConnectEvent_FailAllocation) +{ + auto ep = oc::endpoint::FromString("coap+tcp://[::1]:42"); + + std::vector events{}; + for (int i = 0; i < OC_MAX_TCP_PEERS; ++i) { + oc_tcp_on_connect_event_t *event = oc_tcp_on_connect_event_create( + &ep, OC_TCP_SOCKET_STATE_CONNECTED, nullptr, nullptr); + ASSERT_NE(nullptr, event); + events.push_back(event); + } + + oc_tcp_on_connect_event_t *event = oc_tcp_on_connect_event_create( + &ep, OC_TCP_SOCKET_STATE_CONNECTED, nullptr, nullptr); + EXPECT_EQ(nullptr, event); + + for (auto e : events) { + oc_tcp_on_connect_event_free(e); + } +} + +#endif /* !OC_DYNAMIC_ALLOCATION */ + +TEST_F(TCPMessage, FreeNullConnectEvent) +{ + oc_tcp_on_connect_event_free(nullptr); +} + +#endif /* OC_HAS_FEATURE_TCP_ASYNC_CONNECT */ + TEST_F(TCPMessage, ValidateHeader) { - ValidateMessage(true, false, { 1, 2, 3, 4 }); - ValidateMessage(false, false, { 0xff, 2, 3, 4 }); + ValidateHeader(false, false, nullptr, 0); + ValidateHeader(true, false, { 1, 2, 3, 4 }); + ValidateHeader(false, false, { 0xff, 2, 3, 4 }); + #ifdef OC_SECURITY - ValidateMessage(true, true, - { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_3 }); - ValidateMessage( + ValidateHeader(false, true, nullptr, 0); + ValidateHeader(true, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3, 1 }); + ValidateHeader(true, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3, 2 }); + ValidateHeader(true, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3, + MBEDTLS_SSL_MINOR_VERSION_3 }); + ValidateHeader(true, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3, + MBEDTLS_SSL_MINOR_VERSION_4 }); + ValidateHeader( false, true, { MBEDTLS_SSL_MSG_HANDSHAKE, 0xff, MBEDTLS_SSL_MINOR_VERSION_3 }); - ValidateMessage( + ValidateHeader( false, true, { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3, 0xff }); - ValidateMessage(false, true, - { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3 }); - ValidateMessage( + ValidateHeader(false, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3 }); + ValidateHeader( false, true, { 0xff, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3 }); #endif /* OC_SECURITY */ } +TEST_F(TCPMessage, ValidateMessage) +{ +#ifdef OC_SECURITY + coap_packet_t tlsPacket = {}; + ValidateMessage(true, true, false, tlsPacket); +#ifdef OC_OSCORE + coap_packet_t oscorePacket = {}; + coap_tcp_init_message(&oscorePacket, COAP_GET); + coap_set_header_oscore(&oscorePacket, nullptr, 0, nullptr, 0, nullptr, 0); + ValidateMessage(true, false, true, oscorePacket); +#endif /* OC_OSCORE */ +#endif /* OC_SECURITY */ + + ValidateMessage(false, false, { 1, 2, 3, 4 }); + + coap_packet_t packet = {}; + coap_tcp_init_message(&packet, COAP_GET); + ValidateMessage(true, false, false, packet); +} + +TEST_F(TCPMessage, GetTotalLength) +{ + ValidateHeaderLength(-1, false, { 0xff, 2, 3, 4 }); + + coap_packet_t packet = {}; + coap_tcp_init_message(&packet, COAP_GET); + std::array buffer{}; + size_t buffer_len = + coap_serialize_message(&packet, buffer.data(), buffer.size()); + ASSERT_LT(0, buffer_len); + ValidateHeaderLength(2, false, &buffer[0], buffer_len); + +#ifdef OC_SECURITY + ValidateHeaderLength(-1, true, { 1 }); + + mbedtls_ssl_config conf; + mbedtls_ssl_config_init(&conf); + ASSERT_EQ(0, mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)); + mbedtls_ssl_conf_min_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, + MBEDTLS_SSL_MINOR_VERSION_3); + + mbedtls_ssl_context ssl; + mbedtls_ssl_init(&ssl); + ASSERT_EQ(0, mbedtls_ssl_setup(&ssl, &conf)); + + oc_message_t *message = oc_allocate_message(); + ASSERT_NE(nullptr, message); + mbedtls_ssl_set_bio( + &ssl, message, + [](void *ctx, const unsigned char *buf, size_t len) { + auto *msg = static_cast(ctx); + memcpy(msg->data, buf, len); + msg->length = len; + return static_cast(len); + }, + nullptr, nullptr); + + ssl.major_ver = MBEDTLS_SSL_MAJOR_VERSION_3; + ssl.minor_ver = MBEDTLS_SSL_MINOR_VERSION_3; + ssl.state = MBEDTLS_SSL_HANDSHAKE_OVER; + std::vector data{ 0x01, 0x02, 0x03, 0x04 }; + ASSERT_EQ(data.size(), mbedtls_ssl_write(&ssl, &data[0], data.size())); + ValidateHeaderLength(/*OC_TLS_HEADER_SIZE*/ 5 + data.size(), true, message); + + oc_message_unref(message); + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + + ValidateHeaderLength(-1, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, MBEDTLS_SSL_MAJOR_VERSION_3, + MBEDTLS_SSL_MINOR_VERSION_3 }); +#endif /* OC_SECURITY */ +} + #endif /* OC_TCP */ \ No newline at end of file diff --git a/api/unittest/udptest.cpp b/api/unittest/udptest.cpp index c1e5876c9d..a432dd5a7d 100644 --- a/api/unittest/udptest.cpp +++ b/api/unittest/udptest.cpp @@ -23,6 +23,11 @@ #include "port/oc_allocator_internal.h" #include "port/oc_log_internal.h" +#if defined(OC_SECURITY) && defined(OC_OSCORE) +#include "messaging/coap/coap_internal.h" +#include "messaging/coap/oscore_internal.h" +#endif /* OC_SECURITY && OC_OSCORE */ + #include #include #include @@ -37,7 +42,7 @@ class UDPMessage : public testing::Test { public: - void SetUp() override + static void SetUpTestCase() { #ifdef _WIN32 WSADATA wsaData; @@ -48,7 +53,7 @@ class UDPMessage : public testing::Test { #endif /* !OC_DYNAMIC_ALLOCATION */ } - void TearDown() override + static void TearDownTestCase() { #ifndef OC_DYNAMIC_ALLOCATION oc_allocator_mutex_destroy(); @@ -58,23 +63,58 @@ class UDPMessage : public testing::Test { #endif /* _WIN32 */ } - static void ValidateMessage(bool exp, bool secure, - const std::vector &data) + static void ValidateMessage(bool exp, bool secure, const uint8_t *data, + size_t data_size) { oc_message_t *msg = oc_allocate_message(); - msg->endpoint.flags = secure ? SECURED : (transport_flags)0; - memcpy(msg->data, &data[0], data.size()); - msg->length = data.size(); + ASSERT_NE(nullptr, msg); + int flags = IPV6; + flags |= secure ? SECURED : 0; + msg->endpoint.flags = static_cast(flags); + if (data_size > 0) { + memcpy(msg->data, data, data_size); + } + msg->length = data_size; EXPECT_EQ(exp, oc_udp_is_valid_message(msg)); oc_message_unref(msg); } + + static void ValidateMessage(bool exp, bool secure, + const std::vector &data) + { + ValidateMessage(exp, secure, &data[0], data.size()); + } + +#if defined(OC_SECURITY) && defined(OC_OSCORE) + static void ValidateMessage(bool exp, bool secure, bool oscore, + coap_packet_t &packet) + { + std::array buffer{}; + size_t buffer_len = coap_oscore_serialize_message( + &packet, &buffer[0], buffer.size(), true, true, oscore); + ASSERT_LT(0, buffer_len); + ValidateMessage(exp, secure, &buffer[0], buffer.size()); + } +#endif /* OC_SECURITY && OC_OSCORE */ }; TEST_F(UDPMessage, ValidateHeader) { + ValidateMessage(false, false, nullptr, 0); ValidateMessage(true, false, { 1 << COAP_HEADER_VERSION_POSITION, 2, 3, 4 }); ValidateMessage(false, false, { 0xff, 2, 3, 4 }); #ifdef OC_SECURITY + OC_DBG("ValidateMessage(true, true, {MBEDTLS_SSL_MSG_HANDSHAKE, " + "255-MBEDTLS_SSL_MAJOR_VERSION_3+2, 255-1+1});"); + ValidateMessage(true, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, + 255 - MBEDTLS_SSL_MAJOR_VERSION_3 + 2, 255 - 1 + 1 }); + OC_DBG( + "ValidateMessage(true, true, {MBEDTLS_SSL_MSG_HANDSHAKE, " + "255-MBEDTLS_SSL_MAJOR_VERSION_3+2, 255-MBEDTLS_SSL_MINOR_VERSION_3+1});"); + ValidateMessage(true, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, + 255 - MBEDTLS_SSL_MAJOR_VERSION_3 + 2, 255 - 2 + 1 }); OC_DBG( "ValidateMessage(true, true, {MBEDTLS_SSL_MSG_HANDSHAKE, " "255-MBEDTLS_SSL_MAJOR_VERSION_3+2, 255-MBEDTLS_SSL_MINOR_VERSION_3+1});"); @@ -82,6 +122,13 @@ TEST_F(UDPMessage, ValidateHeader) { MBEDTLS_SSL_MSG_HANDSHAKE, 255 - MBEDTLS_SSL_MAJOR_VERSION_3 + 2, 255 - MBEDTLS_SSL_MINOR_VERSION_3 + 1 }); + OC_DBG( + "ValidateMessage(true, true, {MBEDTLS_SSL_MSG_HANDSHAKE, " + "255-MBEDTLS_SSL_MAJOR_VERSION_3+2, 255-MBEDTLS_SSL_MINOR_VERSION_4+1});"); + ValidateMessage(true, true, + { MBEDTLS_SSL_MSG_HANDSHAKE, + 255 - MBEDTLS_SSL_MAJOR_VERSION_3 + 2, + 255 - MBEDTLS_SSL_MINOR_VERSION_4 + 1 }); OC_DBG("ValidateMessage(false, true, {MBEDTLS_SSL_MSG_HANDSHAKE, 0xff, " "255-MBEDTLS_SSL_MINOR_VERSION_3+1});"); ValidateMessage( @@ -104,5 +151,13 @@ TEST_F(UDPMessage, ValidateHeader) ValidateMessage(true, true, { 0xff, 255 - MBEDTLS_SSL_MAJOR_VERSION_3 + 2, 255 - MBEDTLS_SSL_MINOR_VERSION_3 + 1 }); + +#ifdef OC_OSCORE + OC_DBG("ValidateMessage(true, false, true, oscorePacket);"); + coap_packet_t oscorePacket = {}; + coap_udp_init_message(&oscorePacket, COAP_TYPE_NON, COAP_GET, 0); + coap_set_header_oscore(&oscorePacket, nullptr, 0, nullptr, 0, nullptr, 0); + ValidateMessage(true, false, true, oscorePacket); +#endif /* OC_OSCORE */ #endif /* OC_SECURITY */ } \ No newline at end of file diff --git a/include/oc_ri.h b/include/oc_ri.h index 535f72ca39..7a6d44373b 100644 --- a/include/oc_ri.h +++ b/include/oc_ri.h @@ -336,7 +336,7 @@ struct oc_resource_s oc_request_handler_t put_handler; ///< callback for PUT oc_request_handler_t post_handler; ///< callback for POST oc_request_handler_t delete_handler; ///< callback for DELETE -#if defined(OC_COLLECTIONS) +#ifdef OC_COLLECTIONS oc_properties_cb_t get_properties; ///< callback for get properties oc_properties_cb_t set_properties; ///< callback for set properties #endif diff --git a/messaging/coap/coap.c b/messaging/coap/coap.c index bd5cf9f863..fbaf251bac 100644 --- a/messaging/coap/coap.c +++ b/messaging/coap/coap.c @@ -1095,13 +1095,17 @@ coap_oscore_parse_options(coap_packet_t *packet, const uint8_t *data, return last_error; } -/*---------------------------------------------------------------------------*/ + #ifdef OC_TCP -static void -coap_tcp_set_header_fields(coap_packet_t *packet, + +void +coap_tcp_set_header_length(coap_packet_t *packet, uint8_t num_extended_length_bytes, uint8_t len, size_t extended_len) { + assert(packet != NULL); + assert(num_extended_length_bytes <= 4); + packet->buffer[0] = 0x00; packet->buffer[0] |= COAP_TCP_HEADER_LEN_MASK & len << COAP_TCP_HEADER_LEN_POSITION; @@ -1112,6 +1116,15 @@ coap_tcp_set_header_fields(coap_packet_t *packet, packet->buffer[i] = (uint8_t)(extended_len >> (8 * (num_extended_length_bytes - i))); } +} + +static void +coap_tcp_set_header_fields(coap_packet_t *packet, + uint8_t num_extended_length_bytes, uint8_t len, + size_t extended_len) +{ + coap_tcp_set_header_length(packet, num_extended_length_bytes, len, + extended_len); packet->buffer[1 + num_extended_length_bytes] = packet->code; } @@ -1167,37 +1180,47 @@ coap_tcp_compute_message_length(const coap_packet_t *packet, COAP_DBG("-COAP_TCP header len field : %u Extended length : %zd ", *len, *extended_len); } -/*---------------------------------------------------------------------------*/ -void -coap_tcp_parse_message_length(const uint8_t *data, size_t *message_length, + +bool +coap_tcp_parse_message_length(const uint8_t *data, size_t data_size, + size_t *message_length, uint8_t *num_extended_length_bytes) { + assert(data != NULL); + assert(data_size > 0); uint8_t tcp_len = (COAP_TCP_HEADER_LEN_MASK & data[0]) >> COAP_TCP_HEADER_LEN_POSITION; - *message_length = 0; + size_t length = 0; + uint8_t extended_bytes = 0; if (tcp_len < COAP_TCP_EXTENDED_LENGTH_1) { - *message_length = tcp_len; + length = tcp_len; } else { - uint8_t i = 1; - *num_extended_length_bytes = - (uint8_t)(1 << (tcp_len - COAP_TCP_EXTENDED_LENGTH_1)); - for (i = 1; i <= *num_extended_length_bytes; i++) { - *message_length |= ((uint32_t)(0x000000FF & data[i]) - << (8 * (*num_extended_length_bytes - i))); + extended_bytes = (uint8_t)(1 << (tcp_len - COAP_TCP_EXTENDED_LENGTH_1)); + if (data_size < ((size_t)extended_bytes + 1)) { + COAP_ERR("invalid TCP data"); + return false; + } + for (uint8_t i = 1; i <= extended_bytes; ++i) { + length |= + ((uint32_t)(0x000000FF & data[i]) << (8 * (extended_bytes - i))); } if (COAP_TCP_EXTENDED_LENGTH_1 == tcp_len) { - *message_length += COAP_TCP_EXTENDED_LENGTH_1_DEFAULT_LEN; + length += COAP_TCP_EXTENDED_LENGTH_1_DEFAULT_LEN; } else if (COAP_TCP_EXTENDED_LENGTH_2 == tcp_len) { - *message_length += COAP_TCP_EXTENDED_LENGTH_2_DEFAULT_LEN; - } else if (COAP_TCP_EXTENDED_LENGTH_3 == tcp_len) { - *message_length += COAP_TCP_EXTENDED_LENGTH_3_DEFAULT_LEN; + length += COAP_TCP_EXTENDED_LENGTH_2_DEFAULT_LEN; + } else { + assert(COAP_TCP_EXTENDED_LENGTH_3 == tcp_len); + length += COAP_TCP_EXTENDED_LENGTH_3_DEFAULT_LEN; } } - COAP_DBG("message_length : %zd, num_extended_length_bytes : %u", - *message_length, *num_extended_length_bytes); + COAP_DBG("message_length : %zd, num_extended_length_bytes : %u", length, + extended_bytes); + *message_length = length; + *num_extended_length_bytes = extended_bytes; + return true; } #endif /* OC_TCP */ @@ -1483,22 +1506,23 @@ coap_udp_parse_message(coap_packet_t *packet, uint8_t *data, size_t data_len, } #ifdef OC_TCP -size_t -coap_tcp_get_packet_size(const uint8_t *data) + +long +coap_tcp_get_packet_size(const uint8_t *data, size_t data_size) { - size_t total_length = 0; size_t message_length = 0; - uint8_t num_extended_length_bytes = 0; - coap_tcp_parse_message_length(data, &message_length, - &num_extended_length_bytes); + if (!coap_tcp_parse_message_length(data, data_size, &message_length, + &num_extended_length_bytes)) { + return -1; + } uint8_t token_len = (COAP_HEADER_TOKEN_LEN_MASK & data[0]) >> COAP_HEADER_TOKEN_LEN_POSITION; - total_length = COAP_TCP_DEFAULT_HEADER_LEN + num_extended_length_bytes + - token_len + message_length; + size_t total_length = COAP_TCP_DEFAULT_HEADER_LEN + + num_extended_length_bytes + token_len + message_length; - return total_length; + return (long)total_length; } coap_status_t @@ -1520,8 +1544,10 @@ coap_tcp_parse_message(coap_packet_t *packet, uint8_t *data, size_t data_len, /* parse header fields */ size_t message_length = 0; uint8_t num_extended_length_bytes = 0; - coap_tcp_parse_message_length(data, &message_length, - &num_extended_length_bytes); + if (!coap_tcp_parse_message_length(data, data_len, &message_length, + &num_extended_length_bytes)) { + return BAD_REQUEST_4_00; + } packet->type = COAP_TYPE_NON; packet->mid = 0; diff --git a/messaging/coap/coap_internal.h b/messaging/coap/coap_internal.h index 3cf1c6f69e..8ab23391a2 100644 --- a/messaging/coap/coap_internal.h +++ b/messaging/coap/coap_internal.h @@ -286,9 +286,20 @@ size_t coap_set_option_header(unsigned int delta, size_t length, uint8_t *buffer); #ifdef OC_TCP + +/** @brief Initialize a TCP message */ void coap_tcp_init_message(coap_packet_t *packet, uint8_t code) OC_NONNULL(); -size_t coap_tcp_get_packet_size(const uint8_t *data) OC_NONNULL(); +/** @brief Encode the packet size into the header */ +void coap_tcp_set_header_length(coap_packet_t *packet, + uint8_t num_extended_length_bytes, uint8_t len, + size_t extended_len) OC_NONNULL(); + +/** @brief Examine data as if it contains a valid CoAP TCP message header and + * extract the total message size + */ +long coap_tcp_get_packet_size(const uint8_t *data, size_t data_size) + OC_NONNULL(); /** * @brief Parse TCP CoAP message @@ -303,9 +314,23 @@ coap_status_t coap_tcp_parse_message(coap_packet_t *packet, uint8_t *data, size_t data_len, bool validate) OC_NONNULL(); -void coap_tcp_parse_message_length(const uint8_t *data, size_t *message_length, +/** + * @brief Parse TCP CoAP message length + * + * @param data raw message data (cannot be NULL) + * @param data_size length of raw message data + * @param[out] message_length message length (cannot be NULL) + * @param[out] num_extended_length_bytes number of extended length bytes (cannot + * be NULL) + * + * @return true if parsing was successful + * @return false otherwise + */ +bool coap_tcp_parse_message_length(const uint8_t *data, size_t data_size, + size_t *message_length, uint8_t *num_extended_length_bytes) OC_NONNULL(); + #endif /* OC_TCP */ #ifdef __cplusplus diff --git a/messaging/coap/oscore.c b/messaging/coap/oscore.c index caee118b04..76ca6b7601 100644 --- a/messaging/coap/oscore.c +++ b/messaging/coap/oscore.c @@ -157,37 +157,43 @@ oscore_get_outer_code(const coap_packet_t *packet) return oc_status_code_unsafe(OC_STATUS_CHANGED); } -int -coap_get_header_oscore(coap_packet_t *packet, uint8_t **piv, uint8_t *piv_len, - uint8_t **kid, uint8_t *kid_len, uint8_t **kid_ctx, - uint8_t *kid_ctx_len) +bool +coap_get_header_oscore(const coap_packet_t *packet, const uint8_t **piv, + uint8_t *piv_len, const uint8_t **kid, uint8_t *kid_len, + const uint8_t **kid_ctx, uint8_t *kid_ctx_len) { if (!IS_OPTION(packet, COAP_OPTION_OSCORE)) { - return 0; + return false; } /* Partial IV */ - if (piv) { + if (piv != NULL) { *piv = packet->piv; + } + if (piv_len != NULL) { *piv_len = packet->piv_len; } /* kid */ - if (kid) { + if (kid != NULL) { *kid = packet->kid; + } + if (kid_len != NULL) { *kid_len = packet->kid_len; } /* kid context */ - if (kid_ctx) { + if (kid_ctx != NULL) { *kid_ctx = packet->kid_ctx; + } + if (kid_ctx_len != NULL) { *kid_ctx_len = packet->kid_ctx_len; } - return 1; + return true; } -int +void coap_set_header_oscore(coap_packet_t *packet, const uint8_t *piv, uint8_t piv_len, const uint8_t *kid, uint8_t kid_len, const uint8_t *kid_ctx, uint8_t kid_ctx_len) @@ -217,8 +223,6 @@ coap_set_header_oscore(coap_packet_t *packet, const uint8_t *piv, } SET_OPTION(packet, COAP_OPTION_OSCORE); - - return 1; } int @@ -426,19 +430,21 @@ oscore_parse_inner_message(uint8_t *data, size_t data_len, return COAP_NO_ERROR; } -int +bool oscore_is_oscore_message(const oc_message_t *msg) { const uint8_t *current_option = NULL; /* Determine exact location of the CoAP options in the packet buffer */ #ifdef OC_TCP - if (msg->endpoint.flags & TCP) { + if ((msg->endpoint.flags & TCP) != 0) { /* Calculate CoAP_TCP header length */ size_t message_length = 0; uint8_t num_extended_length_bytes = 0; - coap_tcp_parse_message_length(msg->data, &message_length, - &num_extended_length_bytes); + if (!coap_tcp_parse_message_length(msg->data, msg->length, &message_length, + &num_extended_length_bytes)) { + return false; + } current_option = msg->data + COAP_TCP_DEFAULT_HEADER_LEN + num_extended_length_bytes; @@ -491,18 +497,15 @@ oscore_is_oscore_message(const oc_message_t *msg) option_number += option_delta; - switch (option_number) { - case COAP_OPTION_OSCORE: + if (option_number == COAP_OPTION_OSCORE) { /* Found the OSCORE option, return success */ - return 0; - default: - break; + return true; } current_option += option_length; } - return -1; + return false; } coap_status_t @@ -520,8 +523,10 @@ oscore_parse_outer_message(oc_message_t *msg, coap_packet_t *packet) /* parse header fields */ size_t message_length = 0; uint8_t num_extended_length_bytes = 0; - coap_tcp_parse_message_length(msg->data, &message_length, - &num_extended_length_bytes); + if (!coap_tcp_parse_message_length(msg->data, msg->length, &message_length, + &num_extended_length_bytes)) { + return BAD_REQUEST_4_00; + } packet->type = COAP_TYPE_NON; packet->mid = 0; diff --git a/messaging/coap/oscore_internal.h b/messaging/coap/oscore_internal.h index b08256cc01..c922e04449 100644 --- a/messaging/coap/oscore_internal.h +++ b/messaging/coap/oscore_internal.h @@ -25,28 +25,38 @@ #include "port/oc_connectivity.h" #include "util/oc_compiler.h" +#include + #ifdef __cplusplus extern "C" { #endif +/** Set OSCORE option */ +void coap_set_header_oscore(coap_packet_t *packet, const uint8_t *piv, + uint8_t piv_len, const uint8_t *kid, + uint8_t kid_len, const uint8_t *kid_ctx, + uint8_t kid_ctx_len) OC_NONNULL(1); + +/** Get OSCORE option */ +bool coap_get_header_oscore(const coap_packet_t *packet, const uint8_t **piv, + uint8_t *piv_len, const uint8_t **kid, + uint8_t *kid_len, const uint8_t **kid_ctx, + uint8_t *kid_ctx_len) OC_NONNULL(1); + +/** Check if message is an OSCORE message */ +bool oscore_is_oscore_message(const oc_message_t *msg) OC_NONNULL(); + void oscore_send_error(const coap_packet_t *packet, uint8_t code, const oc_endpoint_t *endpoint); int oscore_read_piv(const uint8_t *piv, uint8_t piv_len, uint64_t *ssn); int oscore_store_piv(uint64_t ssn, uint8_t *piv, uint8_t *piv_len); uint32_t oscore_get_outer_code(const coap_packet_t *packet); -int oscore_is_oscore_message(const oc_message_t *msg); int coap_parse_oscore_option(coap_packet_t *packet, const uint8_t *current_option, size_t option_length); size_t coap_serialize_oscore_option(unsigned int *current_number, const coap_packet_t *packet, uint8_t *buffer); -int coap_get_header_oscore(coap_packet_t *packet, uint8_t **piv, - uint8_t *piv_len, uint8_t **kid, uint8_t *kid_len, - uint8_t **kid_ctx, uint8_t *kid_ctx_len); -int coap_set_header_oscore(coap_packet_t *packet, const uint8_t *piv, - uint8_t piv_len, const uint8_t *kid, uint8_t kid_len, - const uint8_t *kid_ctx, uint8_t kid_ctx_len); coap_status_t oscore_parse_inner_message(uint8_t *data, size_t data_len, coap_packet_t *packet); coap_status_t oscore_parse_outer_message(oc_message_t *msg, diff --git a/messaging/coap/unittest/observenotificationstest.cpp b/messaging/coap/unittest/observenotificationstest.cpp new file mode 100644 index 0000000000..9f32f5b54f --- /dev/null +++ b/messaging/coap/unittest/observenotificationstest.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_TCP + +#include "api/oc_core_res_internal.h" +#include "api/oc_discovery_internal.h" +#include "api/oc_message_internal.h" +#include "api/oc_ri_internal.h" +#include "messaging/coap/observe_internal.h" +#include "messaging/coap/options_internal.h" +#include "messaging/coap/transactions_internal.h" +#include "oc_api.h" +#include "oc_buffer.h" +#include "oc_core_res.h" +#include "port/oc_log_internal.h" +#include "tests/gtest/Device.h" +#include "tests/gtest/coap/Message.h" +#include "tests/gtest/coap/TCPClient.h" +#include "tests/gtest/RepPool.h" +#include "tests/gtest/Resource.h" + +#ifdef OC_SECURITY +#include "security/oc_security_internal.h" +#endif // OC_SECURITY + +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; +using namespace oc::coap; + +static constexpr size_t kDeviceID{ 0 }; + +constexpr std::string_view kDynamicURI1 = "/dyn/empty"; +constexpr std::string_view kDynamicURI2 = "/dyn/nonempty"; + +class TestObservationWithServer : public testing::Test { +public: + static void SetUpTestCase() + { + ASSERT_TRUE(oc::TestDevice::StartServer()); + + addDynamicResources(); + } + + static void TearDownTestCase() { oc::TestDevice::StopServer(); } + + void TearDown() override + { +#if defined(OC_RES_BATCH_SUPPORT) && defined(OC_DISCOVERY_RESOURCE_OBSERVABLE) + coap_free_all_discovery_batch_observers(); +#endif /* OC_RES_BATCH_SUPPORT && OC_DISCOVERY_RESOURCE_OBSERVABLE */ + coap_free_all_observers(); + coap_observe_counter_reset(); + } + + static void onGetEmptyResource(oc_request_t *request, oc_interface_mask_t, + void *) + { + oc_rep_start_root_object(); + oc_rep_end_root_object(); + oc_send_response(request, OC_STATUS_OK); + } + + static void onGetResource(oc_request_t *request, oc_interface_mask_t, void *) + { + oc_rep_start_root_object(); + oc_rep_set_boolean(root, content, true); + oc_rep_end_root_object(); + oc_send_response(request, OC_STATUS_OK); + } + + static void addDynamicResources(); +}; + +void +TestObservationWithServer::addDynamicResources() +{ +#ifndef OC_DYNAMIC_ALLOCATION + static_assert(OC_MAX_APP_RESOURCES > 2, "OC_MAX_APP_RESOURCES > 2"); +#endif // OC_DYNAMIC_ALLOCATION + + oc::DynamicResourceHandler handlers1{}; + handlers1.onGet = onGetEmptyResource; + auto dynResource1 = oc::makeDynamicResourceToAdd( + "Dynamic Resource 1", std::string(kDynamicURI1), + { "oic.d.dynamic", "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, handlers1, + OC_OBSERVABLE); + oc_resource_t *res1 = + oc::TestDevice::AddDynamicResource(dynResource1, kDeviceID); + ASSERT_NE(nullptr, res1); +#ifdef OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM + ASSERT_TRUE(oc::SetAccessInRFOTM(res1, true, OC_PERM_RETRIEVE)); +#endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ + + oc::DynamicResourceHandler handlers2{}; + handlers2.onGet = onGetResource; + auto dynResource2 = oc::makeDynamicResourceToAdd( + "Dynamic Resource 2", std::string(kDynamicURI2), + { "oic.d.dynamic", "oic.d.test" }, { OC_IF_BASELINE, OC_IF_R }, handlers2, + OC_OBSERVABLE); + oc_resource_t *res2 = + oc::TestDevice::AddDynamicResource(dynResource2, kDeviceID); + ASSERT_NE(nullptr, res2); +#ifdef OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM + ASSERT_TRUE(oc::SetAccessInRFOTM(res1, true, OC_PERM_RETRIEVE)); +#endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ +} + +static coap_packet_t +getPacket(oc_message_t *msg) +{ + coap_packet_t packet; + EXPECT_EQ(COAP_NO_ERROR, + coap_tcp_parse_message(&packet, msg->data, msg->length, false)); + return packet; +} + +static void +printObservation(const coap_packet_t &packet, int32_t observe) +{ + (void)packet; + (void)observe; +#if OC_DBG_IS_ENABLED + const uint8_t *payload = nullptr; + size_t payload_len = coap_get_payload(&packet, &payload); + + oc::RepPool pool{}; + oc::oc_rep_unique_ptr rep{ nullptr, nullptr }; + if (payload_len > 0) { + rep = pool.ParsePayload(payload, payload_len); + } + OC_DBG("OBSERVE(%d) payload: %s", observe, + oc::RepPool::GetJson(rep.get(), true).data()); +#endif // OC_DBG_IS_ENABLED +} + +TEST_F(TestObservationWithServer, ObserveDeviceName) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID, TCP, SECURED); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + +#ifdef OC_SECURITY + oc_sec_self_own(kDeviceID); +#endif // OC_SECURITY + + message::SyncQueue messages{}; + TCPClient client([&messages](message::oc_message_unique_ptr &&msg) { + messages.Push(std::move(msg)); + oc::TestDevice::Terminate(); + }); + message::token_t token{ 0x01, 0x03, 0x03, 0x07, 0x04, 0x02 }; + std::thread workerThread([&ep, &client, &token]() { + ASSERT_TRUE(client.Connect(&ep)); + + auto message = message::tcp::RegisterObserve(token, OCF_D_URI, "", &ep); + ASSERT_NE(nullptr, message.get()); + ASSERT_TRUE(client.Send(message->data, message->length)); + + client.Run(); + }); + + bool is_observable = + (oc_core_get_resource_by_index(OCF_D, kDeviceID)->properties & + OC_OBSERVABLE) != 0; + + // wait for response to observe registration + auto msg = message::WaitForMessage(messages, 1s); + ASSERT_NE(nullptr, msg.get()); + auto packet = getPacket(msg.get()); + ASSERT_EQ(CONTENT_2_05, packet.code); + ASSERT_EQ(token.size(), packet.token_len); + EXPECT_EQ(0, memcmp(&token[0], packet.token, token.size())); + int32_t observe; + ASSERT_EQ(is_observable, coap_options_get_observe(&packet, &observe)); + printObservation(packet, observe); + + if (is_observable) { + ASSERT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, observe); + + std::string deviceName{ "new test name" }; + oc_core_device_set_name(kDeviceID, deviceName.c_str(), deviceName.length()); + oc_notify_resource_changed(oc_core_get_resource_by_index(OCF_D, kDeviceID)); + // wait for first notification + msg = message::WaitForMessage(messages, 1s); + ASSERT_NE(nullptr, msg.get()); + auto observation = getPacket(msg.get()); + ASSERT_EQ(CONTENT_2_05, observation.code); + ASSERT_EQ(token.size(), packet.token_len); + EXPECT_EQ(0, memcmp(&token[0], packet.token, token.size())); + coap_options_get_observe(&observation, &observe); + ASSERT_EQ(2, observe); + printObservation(observation, observe); + } + + client.Terminate(); + workerThread.join(); + +#ifdef OC_SECURITY + oc_sec_self_disown(kDeviceID); +#endif // OC_SECURITY +} + +#if !defined(OC_SECURITY) || defined(OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM) + +TEST_F(TestObservationWithServer, ObserveEmptyResource) +{ + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID, TCP, SECURED); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + message::SyncQueue messages{}; + TCPClient client([&messages](message::oc_message_unique_ptr &&msg) { + messages.Push(std::move(msg)); + oc::TestDevice::Terminate(); + }); + message::token_t token{ 0x01, 0x03, 0x03, 0x07, 0x04, 0x02 }; + std::thread workerThread([&ep, &client, &token]() { + ASSERT_TRUE(client.Connect(&ep)); + + auto message = + message::tcp::RegisterObserve(token, std::string(kDynamicURI1), "", &ep); + ASSERT_NE(nullptr, message.get()); + ASSERT_TRUE(client.Send(message->data, message->length)); + + client.Run(); + }); + + // wait for response to observe registration + auto msg = message::WaitForMessage(messages, 1s); + ASSERT_NE(nullptr, msg.get()); + auto packet = getPacket(msg.get()); + ASSERT_EQ(CONTENT_2_05, packet.code); + ASSERT_EQ(token.size(), packet.token_len); + EXPECT_EQ(0, memcmp(&token[0], packet.token, token.size())); + int32_t observe; + ASSERT_TRUE(coap_options_get_observe(&packet, &observe)); + ASSERT_EQ(OC_COAP_OPTION_OBSERVE_REGISTER, observe); + printObservation(packet, observe); + +#ifdef OC_SECURITY + // must be in RFNOP to send obsevations + oc_sec_self_own(kDeviceID); +#endif // OC_SECURITY + + auto *res = oc_ri_get_app_resource_by_uri(kDynamicURI1.data(), + kDynamicURI1.size(), kDeviceID); + ASSERT_NE(nullptr, res); + oc_notify_resource_changed(res); + // wait for first notification + msg = message::WaitForMessage(messages, 1s); + ASSERT_NE(nullptr, msg.get()); + auto observation = getPacket(msg.get()); + ASSERT_EQ(CONTENT_2_05, observation.code); + ASSERT_EQ(token.size(), packet.token_len); + EXPECT_EQ(0, memcmp(&token[0], packet.token, token.size())); + coap_options_get_observe(&observation, &observe); + ASSERT_EQ(2, observe); + printObservation(observation, observe); + const uint8_t *payload = nullptr; + size_t payload_len = coap_get_payload(&packet, &payload); + ASSERT_EQ(0, payload_len); + + client.Terminate(); + workerThread.join(); + +#ifdef OC_SECURITY + oc_sec_self_disown(kDeviceID); +#endif // OC_SECURITY +} + +#endif // !OC_SECURITY || OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM + +#endif // OC_TCP diff --git a/messaging/coap/unittest/tcptest.cpp b/messaging/coap/unittest/tcptest.cpp new file mode 100644 index 0000000000..30ed4fa8ad --- /dev/null +++ b/messaging/coap/unittest/tcptest.cpp @@ -0,0 +1,179 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_TCP + +#include "messaging/coap/coap_internal.h" +#include "messaging/coap/constants.h" +#include "messaging/coap/options_internal.h" + +#include +#include + +TEST(CoapTCP, ParseLength_0) +{ + coap_packet_t packet{}; + coap_tcp_init_message(&packet, COAP_GET); + std::array buffer{}; + packet.buffer = &buffer[0]; + + // case 0: header size 0-12 + auto uri = std::string(4, 'a'); + coap_options_set_uri_path(&packet, uri.c_str(), uri.length()); + auto hdr = coap_calculate_header_size(&packet, true, true, false, 0); + coap_tcp_set_header_length(&packet, hdr.num_extended_length_bytes, hdr.length, + hdr.extended_length); + + size_t message_length; + uint8_t num_extended_length_bytes; + ASSERT_TRUE(coap_tcp_parse_message_length( + &buffer[0], buffer.size(), &message_length, &num_extended_length_bytes)); + EXPECT_EQ(message_length, hdr.length + hdr.extended_length); + EXPECT_EQ(num_extended_length_bytes, hdr.num_extended_length_bytes); +} + +TEST(CoapTCP, ParseLength_1) +{ + coap_packet_t packet{}; + coap_tcp_init_message(&packet, COAP_GET); + std::array buffer{}; + packet.buffer = &buffer[0]; + + // case 1: header size 13-268 + auto uri = std::string(13, 'a'); + coap_options_set_uri_path(&packet, uri.c_str(), uri.length()); + auto hdr = coap_calculate_header_size(&packet, true, true, false, 0); + coap_tcp_set_header_length(&packet, hdr.num_extended_length_bytes, hdr.length, + hdr.extended_length); + + size_t message_length; + uint8_t num_extended_length_bytes; + ASSERT_TRUE(coap_tcp_parse_message_length( + &buffer[0], buffer.size(), &message_length, &num_extended_length_bytes)); + EXPECT_EQ(message_length, + COAP_TCP_EXTENDED_LENGTH_1_DEFAULT_LEN + hdr.extended_length); + EXPECT_EQ(num_extended_length_bytes, hdr.num_extended_length_bytes); +} + +TEST(CoapTCP, ParseLength_2) +{ + coap_packet_t packet{}; + coap_tcp_init_message(&packet, COAP_GET); + std::array buffer{}; + packet.buffer = &buffer[0]; + + // case 2: header size 269-65804 + auto uri = std::string(269, 'a'); + coap_options_set_uri_path(&packet, uri.c_str(), uri.length()); + auto hdr = coap_calculate_header_size(&packet, true, true, false, 0); + coap_tcp_set_header_length(&packet, hdr.num_extended_length_bytes, hdr.length, + hdr.extended_length); + + size_t message_length; + uint8_t num_extended_length_bytes; + ASSERT_TRUE(coap_tcp_parse_message_length( + &buffer[0], buffer.size(), &message_length, &num_extended_length_bytes)); + EXPECT_EQ(message_length, + COAP_TCP_EXTENDED_LENGTH_2_DEFAULT_LEN + hdr.extended_length); + EXPECT_EQ(num_extended_length_bytes, hdr.num_extended_length_bytes); +} + +TEST(CoapTCP, ParseLength_3) +{ + coap_packet_t packet{}; + coap_tcp_init_message(&packet, COAP_GET); + std::array buffer{}; + packet.buffer = &buffer[0]; + + // case 3: header size 65805- + auto uri = std::string(65805, 'a'); + coap_options_set_uri_path(&packet, uri.c_str(), uri.length()); + auto hdr = coap_calculate_header_size(&packet, true, true, false, 0); + coap_tcp_set_header_length(&packet, hdr.num_extended_length_bytes, hdr.length, + hdr.extended_length); + + size_t message_length; + uint8_t num_extended_length_bytes; + ASSERT_TRUE(coap_tcp_parse_message_length( + &buffer[0], buffer.size(), &message_length, &num_extended_length_bytes)); + EXPECT_EQ(message_length, + COAP_TCP_EXTENDED_LENGTH_3_DEFAULT_LEN + hdr.extended_length); + EXPECT_EQ(num_extended_length_bytes, hdr.num_extended_length_bytes); +} + +TEST(CoapTCP, ParseLength_Fail) +{ + coap_packet_t packet{}; + coap_tcp_init_message(&packet, COAP_GET); + std::array buffer{}; + packet.buffer = &buffer[0]; + + // case 2: header size 269-65804 + auto uri = std::string(269, 'a'); + coap_options_set_uri_path(&packet, uri.c_str(), uri.length()); + auto hdr = coap_calculate_header_size(&packet, true, true, false, 0); + coap_tcp_set_header_length(&packet, hdr.num_extended_length_bytes, hdr.length, + hdr.extended_length); + + std::array corrupted{}; + // take the first byte with the length, but don't take the extended bytes + corrupted[0] = packet.buffer[0]; + size_t message_length; + uint8_t num_extended_length_bytes; + EXPECT_FALSE(coap_tcp_parse_message_length(&corrupted[0], corrupted.size(), + &message_length, + &num_extended_length_bytes)); +} + +TEST(CoapTCP, GetPacketSize) +{ + coap_packet_t packet{}; + coap_tcp_init_message(&packet, COAP_GET); + std::array buffer{}; + packet.buffer = &buffer[0]; + auto hdr = coap_calculate_header_size(&packet, true, true, false, 0); + coap_tcp_set_header_length(&packet, hdr.num_extended_length_bytes, hdr.length, + hdr.extended_length); + + EXPECT_EQ(COAP_TCP_DEFAULT_HEADER_LEN, + coap_tcp_get_packet_size(&buffer[0], buffer.size())); +} + +TEST(CoapTCP, GetPacketSize_Fail) +{ + coap_packet_t packet{}; + coap_tcp_init_message(&packet, COAP_GET); + std::array buffer{}; + packet.buffer = &buffer[0]; + + // case 1: header size 13-268 + auto uri = std::string(13, 'a'); + coap_options_set_uri_path(&packet, uri.c_str(), uri.length()); + auto hdr = coap_calculate_header_size(&packet, true, true, false, 0); + coap_tcp_set_header_length(&packet, hdr.num_extended_length_bytes, hdr.length, + hdr.extended_length); + + std::array corrupted{}; + // take the first byte with the length, but don't take the extended bytes + corrupted[0] = packet.buffer[0]; + EXPECT_EQ(-1, coap_tcp_get_packet_size(&corrupted[0], corrupted.size())); +} + +#endif /* OC_TCP */ diff --git a/port/android/Makefile b/port/android/Makefile index a24a9bc6a6..7e178ed07a 100644 --- a/port/android/Makefile +++ b/port/android/Makefile @@ -144,7 +144,6 @@ endif CTIMESTAMP+=../../api/c-timestamp/timestamp_compare.c ../../api/c-timestamp/timestamp_format.c ../../api/c-timestamp/timestamp_valid.c ../../api/c-timestamp/timestamp_parse.c -SRC_PORT_COMMON:=$(wildcard ../../port/common/*.c) $(wildcard ../../port/common/posix/*.c) ifneq ($(MEMORY_TRACE),1) SRC_UTIL:=$(filter-out %_mem_trace.c,$(wildcard ../../util/*.c)) else @@ -153,7 +152,7 @@ endif ifeq ($(JSON_ENCODER),1) SRC_UTIL+=$(wildcard ../../util/jsmn/*.c) endif -SRC_COMMON:=${CBOR} ${CTIMESTAMP} ${SRC_PORT_COMMON} ${SRC_UTIL} +SRC_COMMON:=${CBOR} ${CTIMESTAMP} ${SRC_UTIL} SRC_API:=$(wildcard ../../api/*.c) ifneq ($(INTROSPECTION),1) SRC_API:=$(filter-out %oc_introspection.c,${SRC_API}) @@ -165,6 +164,7 @@ ifneq ($(JSON_ENCODER),1) SRC_API:=$(filter-out %oc_rep_decode_json.c %oc_rep_encode_json.c,${SRC_API}) endif SRC:=${SRC_API} $(wildcard ../../messaging/coap/*.c ../../port/android/*.c) +SRC_PORT_COMMON:=$(wildcard ../../port/common/*.c ../../port/common/posix/*.c) SRC_CLIENT:=$(wildcard ../../api/client/*.c) SRC_CLOUD:=$(wildcard ../../api/cloud/*.c) @@ -196,6 +196,7 @@ WARNING_FLAGS=-Wall -Wextra -Werror -Wno-error=deprecated-declarations -pedantic CFLAGS_CLOUD=-I../../api/cloud -DOC_CLIENT -DOC_SERVER CFLAGS?=-fPIC -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -ffreestanding -Os -fno-stack-protector -ffunction-sections -fdata-sections -fno-strict-overflow -I./ -I../../include/ -I../../deps/tinycbor/src -I../../ -std=gnu99 $(WARNING_FLAGS) -DLONG_BIT=64 -D__ANDROID_MIN_SDK_VERSION__=${ANDROID_API} OBJ_COMMON=$(addprefix ${OBJDIR}/,$(notdir $(SRC_COMMON:.c=.o))) +OBJ_PORT_COMMON=$(addprefix obj/port/,$(notdir $(SRC_PORT_COMMON:.c=.o))) OBJ_CLIENT=$(addprefix ${OBJDIR}/client/,$(notdir $(SRC:.c=.o) $(SRC_CLIENT:.c=.o))) OBJ_SERVER=$(addprefix ${OBJDIR}/server/,$(filter-out oc_obt.o oc_obt_otm_justworks.o oc_obt_otm_randompin.o oc_obt_otm_cert.o oc_obt_certs.o,$(notdir $(SRC:.c=.o)))) ifeq ($(CLOUD),1) @@ -205,7 +206,6 @@ OBJ_CLOUD = endif OBJ_CLIENT_SERVER=$(addprefix ${OBJDIR}/client_server/,$(notdir $(SRC:.c=.o) $(SRC_CLIENT:.c=.o))) VPATH=../../api/:../../api/client/:../../api/cloud/:../../api/c-timestamp:../../messaging/coap/: -VPATH+=../../port/common/:../../port/common/posix/: VPATH+=../../util/:../../util/jsmn/: VPATH+=../../deps/tinycbor/src/:../../deps/mbedtls/library: LIBS?= -lm -llog @@ -321,12 +321,20 @@ all: $(CONSTRAINED_LIBS) $(NATIVE_SAMPLES) $(SWIG) .PHONY: clean -${SRC} ${SRC_COMMON} ${SRC_CLIENT}: $(MBEDTLS_PATCH_FILE) +${SRC} ${SRC_COMMON} ${SRC_PORT_COMMON} ${SRC_CLIENT}: $(MBEDTLS_PATCH_FILE) ${OBJDIR}/%.o: %.c @mkdir -p ${@D} ${CC} -c -o $@ $< ${CFLAGS} +obj/port/%.o: ../../port/common/%.c + @mkdir -p ${@D} + ${CC} -c -o $@ $< ${CFLAGS} + +obj/port/%.o: ../../port/common/posix/%.c + @mkdir -p ${@D} + ${CC} -c -o $@ $< ${CFLAGS} + ${OBJDIR}/server/%.o: %.c @mkdir -p ${@D} ${CC} -c -o $@ $< ${CFLAGS} -DOC_SERVER @@ -343,14 +351,14 @@ ${OBJDIR}/cloud/%.o: %.c @mkdir -p ${@D} ${CC} -c -o $@ $< ${CFLAGS} ${CFLAGS_CLOUD} -libiotivity-lite-server.a: $(OBJ_COMMON) $(OBJ_SERVER) - $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_SERVER) +libiotivity-lite-server.a: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_SERVER) + $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_SERVER) -libiotivity-lite-client.a: $(OBJ_COMMON) $(OBJ_CLIENT) - $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_CLIENT) +libiotivity-lite-client.a: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT) + $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT) -libiotivity-lite-client-server.a: $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) - $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) +libiotivity-lite-client-server.a: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) + $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) server: libiotivity-lite-server.a ${CC} -o $@ ../../apps/server_linux.c libiotivity-lite-server.a -DOC_SERVER ${CFLAGS} ${LIBS} @@ -406,7 +414,7 @@ server_multithread_linux: libiotivity-lite-server.a client_multithread_linux: libiotivity-lite-client.a ${CC} -o $@ ../../apps/client_multithread_linux.c libiotivity-lite-client.a -DOC_CLIENT ${CFLAGS} ${LIBS} -swig: $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) +swig: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) ${MAKE} -C ${SWIG_DIR} ifneq ($(SECURE),0) diff --git a/port/android/ipadapter.c b/port/android/ipadapter.c index 24bdb61766..b5c9e64105 100644 --- a/port/android/ipadapter.c +++ b/port/android/ipadapter.c @@ -29,6 +29,7 @@ #include "oc_core_res.h" #include "oc_endpoint.h" #include "oc_network_monitor.h" +#include "port/common/posix/oc_socket_internal.h" #include "port/oc_assert.h" #include "port/oc_connectivity.h" #include "port/oc_connectivity_internal.h" @@ -1051,40 +1052,20 @@ oc_send_buffer(oc_message_t *message) OC_LOGipaddr(message->endpoint); OC_DBG("%s", ""); - struct sockaddr_storage receiver; - memset(&receiver, 0, sizeof(struct sockaddr_storage)); -#ifdef OC_IPV4 - if (message->endpoint.flags & IPV4) { - struct sockaddr_in *r = (struct sockaddr_in *)&receiver; - memcpy(&r->sin_addr.s_addr, message->endpoint.addr.ipv4.address, - sizeof(r->sin_addr.s_addr)); - r->sin_family = AF_INET; - r->sin_port = htons(message->endpoint.addr.ipv4.port); - } else { -#else - { -#endif - struct sockaddr_in6 *r = (struct sockaddr_in6 *)&receiver; - memcpy(r->sin6_addr.s6_addr, message->endpoint.addr.ipv6.address, - sizeof(r->sin6_addr.s6_addr)); - r->sin6_family = AF_INET6; - r->sin6_port = htons(message->endpoint.addr.ipv6.port); - r->sin6_scope_id = message->endpoint.addr.ipv6.scope; - } - int send_sock = -1; - ip_context_t *dev = get_ip_context_for_device(message->endpoint.device); - - if (!dev) { + if (dev == NULL) { return -1; } + struct sockaddr_storage receiver = oc_socket_get_address(&message->endpoint); + #ifdef OC_TCP if (message->endpoint.flags & TCP) { return oc_tcp_send_buffer(dev, message, &receiver); } #endif /* OC_TCP */ + int send_sock = -1; #ifdef OC_SECURITY if (message->endpoint.flags & SECURED) { #ifdef OC_IPV4 diff --git a/port/android/tcpadapter.c b/port/android/tcpadapter.c index f6d6a517f6..7d31fdc918 100644 --- a/port/android/tcpadapter.c +++ b/port/android/tcpadapter.c @@ -25,6 +25,8 @@ #include "messaging/coap/coap_internal.h" #include "oc_endpoint.h" #include "oc_session_events.h" +#include "port/common/oc_tcp_socket_internal.h" +#include "port/common/posix/oc_fcntl_internal.h" #include "port/oc_assert.h" #include "port/oc_log_internal.h" #include "tcpadapter.h" @@ -56,11 +58,6 @@ #define OC_TCP_LISTEN_BACKLOG 3 -#define TLS_HEADER_SIZE 5 - -#define DEFAULT_RECEIVE_SIZE \ - (COAP_TCP_DEFAULT_HEADER_LEN + COAP_TCP_MAX_EXTENDED_LENGTH_LEN) - #define LIMIT_RETRY_CONNECT 5 #define TCP_CONNECT_TIMEOUT 5 @@ -114,6 +111,7 @@ static long get_interface_index(int sock) { struct sockaddr_storage addr; + memset(&addr, 0, sizeof(addr)); socklen_t socklen = sizeof(addr); if (getsockname(sock, (struct sockaddr *)&addr, &socklen) == -1) { OC_ERR("failed obtaining socket information %d", errno); @@ -325,21 +323,6 @@ get_ready_to_read_session(fd_set *setfds) return session; } -static size_t -get_total_length_from_header(oc_message_t *message, oc_endpoint_t *endpoint) -{ - size_t total_length = 0; - if (endpoint->flags & SECURED) { - //[3][4] bytes in tls header are tls payload length - total_length = - TLS_HEADER_SIZE + (size_t)((message->data[3] << 8) | message->data[4]); - } else { - total_length = coap_tcp_get_packet_size(message->data); - } - - return total_length; -} - adapter_receive_state_t oc_tcp_receive_message(ip_context_t *dev, fd_set *fds, oc_message_t *message) { @@ -409,7 +392,7 @@ oc_tcp_receive_message(ip_context_t *dev, fd_set *fds, oc_message_t *message) // receive message. size_t total_length = 0; - size_t want_read = DEFAULT_RECEIVE_SIZE; + size_t want_read = OC_TCP_DEFAULT_RECEIVE_SIZE; message->length = 0; do { int count = @@ -439,12 +422,16 @@ oc_tcp_receive_message(ip_context_t *dev, fd_set *fds, oc_message_t *message) message->encrypted = 1; } #endif /* OC_SECURITY */ - if (!oc_tcp_is_valid_header(message)) { - OC_ERR("invalid header"); + + long length_from_header = + oc_tcp_get_total_length_from_message_header(message); + if (length_from_header < 0) { + OC_ERR("invalid message size in header"); free_tcp_session(session); ret_with_code(ADAPTER_STATUS_ERROR); } - total_length = get_total_length_from_header(message, &session->endpoint); + + total_length = (size_t)length_from_header; // check to avoid buffer overflow if (total_length > oc_message_buffer_size()) { OC_ERR( @@ -497,73 +484,6 @@ get_session_socket(const oc_endpoint_t *endpoint) return sock; } -static int -connect_nonb(int sockfd, const struct sockaddr *r, int r_len, int nsec) -{ - int error; - socklen_t len; - fd_set rset, wset; - struct timeval tval; - - int flags = fcntl(sockfd, F_GETFL, 0); - if (flags < 0) { - OC_ERR("failed to get file descriptor flags (error=%d)", (int)errno); - return -1; - } - - if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { - OC_ERR("failed to add O_NONBLOCK to file descriptor flags (error=%d)", - (int)errno); - return -1; - } - - int n; - if ((n = connect(sockfd, (struct sockaddr *)r, r_len)) < 0) { - if (errno != EINPROGRESS) { - OC_ERR("failed to connect to address (error=%d)", (int)errno); - return -1; - } - } - - /* Do whatever we want while the connect is taking place. */ - if (n == 0) { - goto done; /* connect completed immediately */ - } - - FD_ZERO(&rset); - FD_SET(sockfd, &rset); - wset = rset; - tval.tv_sec = nsec; - tval.tv_usec = 0; - - if ((n = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) { - /* timeout */ - return -1; - } - - if (!FD_ISSET(sockfd, &rset) && !FD_ISSET(sockfd, &wset)) { - OC_ERR("select error: sockfd not set"); - return -1; - } - len = sizeof(error); - if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { - OC_ERR("get socket options error: %d", (int)errno); - return -1; /* Solaris pending error */ - } - if (error != 0) { - OC_ERR("socket error: %d", error); - return -1; - } - -done: - if (fcntl(sockfd, F_SETFL, flags) < 0) { - OC_ERR("failed restore original file descriptor flags (error=%d)", - (int)errno); - return -1; - } - return 0; -} - static int initiate_new_session(ip_context_t *dev, oc_endpoint_t *endpoint, const struct sockaddr_storage *receiver) @@ -572,26 +492,11 @@ initiate_new_session(ip_context_t *dev, oc_endpoint_t *endpoint, uint8_t retry_cnt = 0; while (retry_cnt < LIMIT_RETRY_CONNECT) { - if (endpoint->flags & IPV6) { - sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); -#ifdef OC_IPV4 - } else if (endpoint->flags & IPV4) { - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); -#endif - } - - if (sock < 0) { - OC_ERR("could not create socket for new TCP session"); - return -1; - } - - socklen_t receiver_size = sizeof(*receiver); - if (connect_nonb(sock, (struct sockaddr *)receiver, receiver_size, - TCP_CONNECT_TIMEOUT) == 0) { + sock = + oc_tcp_socket_connect_and_wait(endpoint, receiver, TCP_CONNECT_TIMEOUT); + if (sock >= 0) { break; } - - close(sock); retry_cnt++; OC_DBG("connect failed, retry(%d)", retry_cnt); } diff --git a/port/common/oc_tcp_socket.c b/port/common/oc_tcp_socket.c new file mode 100644 index 0000000000..c1f7b64893 --- /dev/null +++ b/port/common/oc_tcp_socket.c @@ -0,0 +1,279 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_TCP + +#include "messaging/coap/coap_internal.h" +#include "port/common/oc_tcp_socket_internal.h" +#include "port/common/posix/oc_fcntl_internal.h" +#include "port/common/posix/oc_socket_internal.h" +#include "port/oc_connectivity.h" +#include "port/oc_log_internal.h" + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include + +#else /* !_WIN32 */ + +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#ifdef _WIN32 +#define OC_ADDRLEN_T int +#else /* !_WIN32 */ +#define OC_ADDRLEN_T socklen_t +#endif /* _WIN32 */ + +#ifdef _WIN32 + +static SOCKET +tcp_create_socket(const oc_endpoint_t *endpoint) +{ + SOCKET sock = INVALID_SOCKET; + if ((endpoint->flags & IPV6) != 0) { + sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); +#ifdef OC_IPV4 + } else if ((endpoint->flags & IPV4) != 0) { + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif + } + + if (sock == INVALID_SOCKET) { + OC_ERR("could not create socket for new TCP session %d", WSAGetLastError()); + return INVALID_SOCKET; + } + return sock; +} + +static int +tcp_try_connect_nonblocking(SOCKET sock, const struct sockaddr *r, int r_len) +{ + if (!oc_fcntl_set_nonblocking(sock)) { + OC_ERR("cannot set non-blocking socket(%llu)", sock); + return -1; + } + + while (true) { + int n = connect(sock, r, r_len); + if (n == 0) { + return OC_TCP_SOCKET_STATE_CONNECTED; + } + + int error = WSAGetLastError(); + if (error == WSAEINTR) { + continue; + } + if (error == WSAEWOULDBLOCK || error == WSAEALREADY) { + return OC_TCP_SOCKET_STATE_CONNECTING; + } + + OC_ERR("connect to socket(%llu) failed with error: %d", sock, error); + return -1; + } +} + +#else /* !_WIN32 */ + +static int +tcp_create_socket(const oc_endpoint_t *endpoint) +{ + int sock = -1; + if ((endpoint->flags & IPV6) != 0) { + sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); +#ifdef OC_IPV4 + } else if ((endpoint->flags & IPV4) != 0) { + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif + } + + if (sock < 0) { + OC_ERR("could not create socket for TCP session"); + return OC_INVALID_SOCKET; + } + return sock; +} + +static int +tcp_try_connect_nonblocking(int sockfd, const struct sockaddr *r, + socklen_t r_len) +{ + if (!oc_fcntl_set_nonblocking(sockfd)) { + OC_ERR("cannot set non-blocking socket(%d)", sockfd); + return -1; + } + + while (true) { + int n = connect(sockfd, r, r_len); + if (n == 0) { + return OC_TCP_SOCKET_STATE_CONNECTED; + } + if (errno == EINPROGRESS || errno == EALREADY) { + return OC_TCP_SOCKET_STATE_CONNECTING; + } + if (errno == EINTR || errno == EAGAIN) { + continue; + } + OC_ERR("connect to socket(%d) failed with error: %d", sockfd, (int)errno); + return -1; + } +} + +#endif /* _WIN32 */ + +oc_tcp_socket_t +oc_tcp_socket_connect(const oc_endpoint_t *endpoint, + const struct sockaddr_storage *receiver) +{ + oc_tcp_socket_t cs = { + .fd = OC_INVALID_SOCKET, + .state = -1, + }; + OC_SOCKET_T sock = tcp_create_socket(endpoint); + if (sock == OC_INVALID_SOCKET) { + return cs; + } + + struct sockaddr_storage rc; + if (receiver == NULL) { + rc = oc_socket_get_address(endpoint); + receiver = &rc; + } + + OC_ADDRLEN_T size = sizeof(*receiver); + int ret = + tcp_try_connect_nonblocking(sock, (const struct sockaddr *)receiver, size); + if (ret < 0) { + OC_CLOSE_SOCKET(sock); + return cs; + } + cs.fd = sock; + cs.state = ret; + return cs; +} + +#ifndef OC_HAS_FEATURE_TCP_ASYNC_CONNECT + +static bool +tcp_socket_wait_for_connection(oc_tcp_socket_t *socket, int timeout_s) +{ + fd_set wset; + FD_ZERO(&wset); + FD_SET(socket->fd, &wset); + struct timeval tval = { + .tv_sec = timeout_s, + .tv_usec = 0, + }; + int n = + select(socket->fd + 1, NULL, &wset, NULL, timeout_s != 0 ? &tval : NULL); + if (n == 0) { +#ifdef _WIN32 + WSASetLastError(WSAETIMEDOUT); +#else /* !_WIN32 */ + errno = ETIMEDOUT; +#endif /* _WIN32 */ + return false; + } + +#ifdef _WIN32 + if (n == SOCKET_ERROR) { + OC_ERR("select error: %d", WSAGetLastError()); + return false; + } +#else /* !_WIN32 */ + if (n < 0) { + OC_ERR("select error: %d", (int)errno); + return false; + } +#endif /* _WIN32 */ + + if (!FD_ISSET(socket->fd, &wset)) { + OC_ERR("select error: sockfd not set"); + return false; + } + +#ifndef _WIN32 + int error = 0; + socklen_t len = sizeof(error); + if (getsockopt(socket->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + OC_ERR("get socket options error: %d", (int)errno); + return false; /* Solaris pending error */ + } + if (error != 0) { + OC_ERR("socket error: %d", error); + return false; + } +#endif /* !_WIN32 */ + + socket->state = OC_TCP_SOCKET_STATE_CONNECTED; + return true; +} + +#if OC_ERR_IS_ENABLED + +static int +tcp_last_error(void) +{ +#ifdef _WIN32 + return WSAGetLastError(); +#else /* !_WIN32 */ + return errno; +#endif /* _WIN32 */ +} + +#endif /* OC_ERR_IS_ENABLED */ + +OC_SOCKET_T +oc_tcp_socket_connect_and_wait(const oc_endpoint_t *endpoint, + const struct sockaddr_storage *receiver, + int timeout_s) +{ + oc_tcp_socket_t ts = oc_tcp_socket_connect(endpoint, receiver); + if (ts.state == -1) { + return OC_INVALID_SOCKET; + } + + if (ts.state == OC_TCP_SOCKET_STATE_CONNECTED) { + goto done; + } + + if (!tcp_socket_wait_for_connection(&ts, timeout_s)) { + OC_ERR("failed to connect to address (error=%d)", tcp_last_error()); + OC_CLOSE_SOCKET(ts.fd); + return OC_INVALID_SOCKET; + } + +done: + if (!oc_fcntl_set_blocking(ts.fd)) { + OC_ERR("cannot set blocking socket (error=%d)", tcp_last_error()); + OC_CLOSE_SOCKET(ts.fd); + return OC_INVALID_SOCKET; + } + return ts.fd; +} + +#endif /* !OC_HAS_FEATURE_TCP_ASYNC_CONNECT */ + +#endif /* OC_TCP */ diff --git a/port/common/oc_tcp_socket_internal.h b/port/common/oc_tcp_socket_internal.h new file mode 100644 index 0000000000..723188ab93 --- /dev/null +++ b/port/common/oc_tcp_socket_internal.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#ifndef PORT_COMMON_OC_TCP_SOCKET_H +#define PORT_COMMON_OC_TCP_SOCKET_H + +#ifdef OC_TCP + +#include "oc_endpoint.h" +#include "util/oc_features.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else /* !_WIN32 */ +#include +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#define OC_SOCKET_T SOCKET +#define OC_CLOSE_SOCKET closesocket +#define OC_INVALID_SOCKET (INVALID_SOCKET) +#else /* !_WIN32 */ +#define OC_SOCKET_T int +#define OC_CLOSE_SOCKET close +#define OC_INVALID_SOCKET (-1) +#endif /* _WIN32 */ + +typedef struct +{ + OC_SOCKET_T fd; + int state; +} oc_tcp_socket_t; + +/** + * @brief Initialize a non-blocking TCP socket and attempt to connect to the + * given endpoint. + * + * @param endpoint endpoint of the socket + * @param receiver receiver of the socket (if NULL, the address from the + * endpoint will be used) + * @return oc_tcp_socket_t value with -1 in state on error + * @return oc_tcp_socket_t with a valid file descriptor and in + * OC_TCP_SOCKET_STATE_CONNECTED or OC_TCP_SOCKET_STATE_CONNECTING state on + * success + */ +oc_tcp_socket_t oc_tcp_socket_connect(const oc_endpoint_t *endpoint, + const struct sockaddr_storage *receiver) + OC_NONNULL(1); + +#ifndef OC_HAS_FEATURE_TCP_ASYNC_CONNECT + +/** + * @brief Initialize a non-blocking TCP socket and attempt to connect to the + * given endpoint. This function will block until the socket is connected or the + * timeout expires. + * + * @param endpoint endpoint of the socket + * @param receiver receiver of the socket + * @param timeout_s timeout in seconds + * @return OC_SOCKET_T on success + * @return OC_INVALID_SOCKET on error + */ +OC_SOCKET_T oc_tcp_socket_connect_and_wait( + const oc_endpoint_t *endpoint, const struct sockaddr_storage *receiver, + int timeout_s) OC_NONNULL(1); + +#endif /* !OC_HAS_FEATURE_TCP_ASYNC_CONNECT */ + +#ifdef __cplusplus +} +#endif + +#endif /* OC_TCP */ + +#endif /* PORT_COMMON_OC_TCP_SOCKET_H */ diff --git a/port/common/posix/oc_fcntl.c b/port/common/posix/oc_fcntl.c new file mode 100644 index 0000000000..9003fcd4b2 --- /dev/null +++ b/port/common/posix/oc_fcntl.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "port/common/posix/oc_fcntl_internal.h" + +#ifndef _WIN32 + +#include + +int +oc_fcntl_set_flags(int fd, unsigned to_add, unsigned to_remove) +{ + int old_flags = fcntl(fd, F_GETFL, 0); + if (old_flags < 0) { + return -1; + } + + int flags = old_flags; + flags &= (int)~to_remove; + flags |= (int)to_add; + + if (flags == old_flags) { + return flags; + } + + if (fcntl(fd, F_SETFL, flags) < 0) { + return -1; + } + return flags; +} + +bool +oc_fcntl_set_blocking(int fd) +{ + return oc_fcntl_set_flags(fd, 0, O_NONBLOCK) != -1; +} + +bool +oc_fcntl_set_nonblocking(int fd) +{ + return oc_fcntl_set_flags(fd, O_NONBLOCK, 0) != -1; +} + +#endif /* !_WIN32 */ diff --git a/port/common/posix/oc_fcntl_internal.h b/port/common/posix/oc_fcntl_internal.h new file mode 100644 index 0000000000..804c353060 --- /dev/null +++ b/port/common/posix/oc_fcntl_internal.h @@ -0,0 +1,94 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#ifndef PORT_POSIX_OC_FCNTL_H +#define PORT_POSIX_OC_FCNTL_H + +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + +/** + * @brief Set socket descriptor to blocking mode. + * + * @param fd file descriptor + * @return true on success + * @return false on failure + */ +bool oc_fcntl_set_blocking(SOCKET sock); + +/** + * @brief Set socket descriptor to non-blocking mode. + * + * @param fd file descriptor + * @return true on success + * @return false on failure + */ +bool oc_fcntl_set_nonblocking(SOCKET sock); + +#else /* !_WIN32 */ + +/** + * @brief Add or remove file descriptor flags. + * + * Functions gets the current file descriptor flags, removes the to_remove + * flags, then adds the to_add flags and updates the file descriptor flags with + * this new value. + * + * @param fd file descriptor + * @param to_add flags to be added + * @param to_remove flags to be removed + * @return >=0 on success, the current file descriptor flags + * @return -1 on failure + */ +int oc_fcntl_set_flags(int fd, unsigned to_add, unsigned to_remove); + +/** + * @brief Set file descriptor to blocking mode. + * + * @param fd file descriptor + * @return true on success + * @return false on failure + */ +bool oc_fcntl_set_blocking(int fd); + +/** + * @brief Set file descriptor to non-blocking mode. + * + * @param fd file descriptor + * @return true on success + * @return false on failure + */ +bool oc_fcntl_set_nonblocking(int fd); + +#endif /* _WIN32 */ + +#ifdef __cplusplus +} +#endif + +#endif /* PORT_POSIX_OC_FCNTL_H */ diff --git a/port/common/posix/oc_socket.c b/port/common/posix/oc_socket.c new file mode 100644 index 0000000000..e7476f4051 --- /dev/null +++ b/port/common/posix/oc_socket.c @@ -0,0 +1,57 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "port/common/posix/oc_socket_internal.h" + +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else /* !_WIN32 */ +#include +#endif /* _WIN32 */ + +struct sockaddr_storage +oc_socket_get_address(const oc_endpoint_t *endpoint) +{ + assert(endpoint != NULL); + + struct sockaddr_storage addr; + memset(&addr, 0, sizeof(addr)); + +#ifdef OC_IPV4 + if ((endpoint->flags & IPV4) != 0) { + struct sockaddr_in *r = (struct sockaddr_in *)&addr; + memcpy(&r->sin_addr.s_addr, endpoint->addr.ipv4.address, + sizeof(r->sin_addr.s_addr)); + r->sin_family = AF_INET; + r->sin_port = htons(endpoint->addr.ipv4.port); + return addr; + } +#endif /* OC_IPV4 */ + struct sockaddr_in6 *r = (struct sockaddr_in6 *)&addr; + memcpy(r->sin6_addr.s6_addr, endpoint->addr.ipv6.address, + sizeof(r->sin6_addr.s6_addr)); + r->sin6_family = AF_INET6; + r->sin6_port = htons(endpoint->addr.ipv6.port); + r->sin6_scope_id = endpoint->addr.ipv6.scope; + return addr; +} diff --git a/port/common/posix/oc_socket_internal.h b/port/common/posix/oc_socket_internal.h new file mode 100644 index 0000000000..7f669bd3f9 --- /dev/null +++ b/port/common/posix/oc_socket_internal.h @@ -0,0 +1,49 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#ifndef PORT_POSIX_OC_SOCKET_H +#define PORT_POSIX_OC_SOCKET_H + +#include "oc_endpoint.h" +#include "util/oc_compiler.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else /* !_WIN32 */ +#include +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Extract socket address from the endpoint. + * + * @param endpoint endpoint with address (cannot be NULL) + * @return IPv4 or IPv6 address extracted from the endpoint (cannot be NULL) + */ +struct sockaddr_storage oc_socket_get_address(const oc_endpoint_t *endpoint) + OC_NONNULL(); + +#ifdef __cplusplus +} +#endif + +#endif /* PORT_POSIX_OC_SOCKET_H */ diff --git a/port/esp32/adapter/src/ipadapter.c b/port/esp32/adapter/src/ipadapter.c index 507ffacf80..8c88b10301 100644 --- a/port/esp32/adapter/src/ipadapter.c +++ b/port/esp32/adapter/src/ipadapter.c @@ -21,6 +21,7 @@ #endif #include "api/oc_endpoint_internal.h" #include "api/oc_network_events_internal.h" +#include "port/common/posix/oc_socket_internal.h" #include "port/oc_assert.h" #include "port/oc_connectivity.h" #include "port/oc_connectivity_internal.h" @@ -986,29 +987,12 @@ oc_send_buffer(oc_message_t *message) OC_LOGipaddr(message->endpoint); OC_DBG("%s", ""); - struct sockaddr_storage receiver; - memset(&receiver, 0, sizeof(struct sockaddr_storage)); -#ifdef OC_IPV4 - if (message->endpoint.flags & IPV4) { - struct sockaddr_in *r = (struct sockaddr_in *)&receiver; - memcpy(&r->sin_addr.s_addr, message->endpoint.addr.ipv4.address, - sizeof(r->sin_addr.s_addr)); - r->sin_family = AF_INET; - r->sin_port = htons(message->endpoint.addr.ipv4.port); - } else { -#else - { -#endif - struct sockaddr_in6 *r = (struct sockaddr_in6 *)&receiver; - memcpy(r->sin6_addr.s6_addr, message->endpoint.addr.ipv6.address, - sizeof(r->sin6_addr.s6_addr)); - r->sin6_family = AF_INET6; - r->sin6_port = htons(message->endpoint.addr.ipv6.port); - r->sin6_scope_id = message->endpoint.addr.ipv6.scope; + ip_context_t *dev = get_ip_context_for_device(message->endpoint.device); + if (dev == NULL) { + return -1; } - int send_sock = -1; - ip_context_t *dev = get_ip_context_for_device(message->endpoint.device); + struct sockaddr_storage receiver = oc_socket_get_address(&message->endpoint); #ifdef OC_TCP if (message->endpoint.flags & TCP) { @@ -1016,6 +1000,8 @@ oc_send_buffer(oc_message_t *message) } #endif /* OC_TCP */ + int send_sock = -1; + #ifdef OC_SECURITY if (message->endpoint.flags & SECURED) { #ifdef OC_IPV4 diff --git a/port/esp32/adapter/src/tcpadapter.c b/port/esp32/adapter/src/tcpadapter.c index a3d26199d3..72816d236b 100644 --- a/port/esp32/adapter/src/tcpadapter.c +++ b/port/esp32/adapter/src/tcpadapter.c @@ -25,6 +25,8 @@ #include "messaging/coap/coap_internal.h" #include "oc_endpoint.h" #include "oc_session_events.h" +#include "port/common/oc_tcp_socket_internal.h" +#include "port/common/posix/oc_fcntl_internal.h" #include "port/oc_assert.h" #include "port/oc_log_internal.h" #include "tcpadapter.h" @@ -45,11 +47,6 @@ #define OC_TCP_LISTEN_BACKLOG 3 -#define TLS_HEADER_SIZE 5 - -#define DEFAULT_RECEIVE_SIZE \ - (COAP_TCP_DEFAULT_HEADER_LEN + COAP_TCP_MAX_EXTENDED_LENGTH_LEN) - #define LIMIT_RETRY_CONNECT 5 #define TCP_CONNECT_TIMEOUT 5 @@ -296,21 +293,6 @@ get_ready_to_read_session(fd_set *setfds) return session; } -static size_t -get_total_length_from_header(oc_message_t *message, oc_endpoint_t *endpoint) -{ - size_t total_length = 0; - if (endpoint->flags & SECURED) { - //[3][4] bytes in tls header are tls payload length - total_length = - TLS_HEADER_SIZE + (size_t)((message->data[3] << 8) | message->data[4]); - } else { - total_length = coap_tcp_get_packet_size(message->data); - } - - return total_length; -} - adapter_receive_state_t oc_tcp_receive_message(ip_context_t *dev, fd_set *fds, oc_message_t *message) { @@ -382,7 +364,7 @@ oc_tcp_receive_message(ip_context_t *dev, fd_set *fds, oc_message_t *message) // receive message. size_t total_length = 0; - size_t want_read = DEFAULT_RECEIVE_SIZE; + size_t want_read = OC_TCP_DEFAULT_RECEIVE_SIZE; message->length = 0; do { int count = @@ -412,12 +394,16 @@ oc_tcp_receive_message(ip_context_t *dev, fd_set *fds, oc_message_t *message) message->encrypted = 1; } #endif /* OC_SECURITY */ - if (!oc_tcp_is_valid_header(message)) { - OC_ERR("invalid header"); + + long length_from_header = + oc_tcp_get_total_length_from_message_header(message); + if (length_from_header < 0) { + OC_ERR("invalid message size in header"); free_tcp_session(session); ret_with_code(ADAPTER_STATUS_ERROR); } - total_length = get_total_length_from_header(message, &session->endpoint); + + total_length = (size_t)length_from_header; // check to avoid buffer overflow if (total_length > oc_message_buffer_size()) { OC_ERR( @@ -470,68 +456,6 @@ get_session_socket(const oc_endpoint_t *endpoint) return sock; } -static int -connect_nonb(int sockfd, const struct sockaddr *r, int r_len, int nsec) -{ - int flags, n, error; - socklen_t len; - fd_set wset; - struct timeval tval; - - flags = fcntl(sockfd, F_GETFL, 0); - if (flags < 0) { - return -1; - } - - error = fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - if (error < 0) { - return -1; - } - - error = 0; - if ((n = connect(sockfd, (struct sockaddr *)r, r_len)) < 0) { - if (errno != EINPROGRESS) - return -1; - } - - /* Do whatever we want while the connect is taking place. */ - if (n == 0) { - goto done; /* connect completed immediately */ - } - - FD_ZERO(&wset); - FD_SET(sockfd, &wset); - tval.tv_sec = nsec; - tval.tv_usec = 0; - - if (select(sockfd + 1, NULL, &wset, NULL, nsec ? &tval : NULL) == 0) { - /* timeout */ - errno = ETIMEDOUT; - return -1; - } - - if (FD_ISSET(sockfd, &wset)) { - len = sizeof(error); - if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) - return -1; /* Solaris pending error */ - } else { - OC_DBG("select error: sockfd not set"); - return -1; - } - -done: - if (error < 0) { - errno = error; - return -1; - } else { - error = fcntl(sockfd, F_SETFL, flags); /* restore file status flags */ - if (error < 0) { - return -1; - } - } - return 0; -} - static int initiate_new_session(ip_context_t *dev, oc_endpoint_t *endpoint, const struct sockaddr_storage *receiver) @@ -540,29 +464,13 @@ initiate_new_session(ip_context_t *dev, oc_endpoint_t *endpoint, uint8_t retry_cnt = 0; while (retry_cnt < LIMIT_RETRY_CONNECT) { - if (endpoint->flags & IPV6) { - sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); -#ifdef OC_IPV4 - } else if (endpoint->flags & IPV4) { - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); -#endif - } - - if (sock < 0) { - OC_ERR("could not create socket for new TCP session"); - return -1; - } - - socklen_t receiver_size = sizeof(*receiver); - int ret = 0; - if ((ret = connect_nonb(sock, (struct sockaddr *)receiver, receiver_size, - TCP_CONNECT_TIMEOUT)) == 0) { + sock = + oc_tcp_socket_connect_and_wait(endpoint, receiver, TCP_CONNECT_TIMEOUT); + if (sock >= 0) { break; } - - close(sock); retry_cnt++; - OC_DBG("connect fail with %d. retry(%d)", ret, retry_cnt); + OC_DBG("connect failed, retry(%d)", retry_cnt); } if (retry_cnt >= LIMIT_RETRY_CONNECT) { diff --git a/port/esp32/main/CMakeLists.txt b/port/esp32/main/CMakeLists.txt index 1480ed3357..7376402e29 100644 --- a/port/esp32/main/CMakeLists.txt +++ b/port/esp32/main/CMakeLists.txt @@ -71,7 +71,10 @@ set(sources ${CMAKE_CURRENT_SOURCE_DIR}/../../../messaging/coap/separate.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../messaging/coap/transactions.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../port/common/oc_ip.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../port/common/oc_tcp_socket.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../port/common/posix/oc_allocator.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../port/common/posix/oc_fcntl.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../port/common/posix/oc_socket.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../util/oc_buffer.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../util/oc_etimer.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../util/oc_list.c diff --git a/port/linux/Makefile b/port/linux/Makefile index 36f8e00c0f..d40d81b5a9 100644 --- a/port/linux/Makefile +++ b/port/linux/Makefile @@ -49,18 +49,24 @@ SECURITY_HEADERS = -I$(ROOT_DIR)/security \ -I$(MBEDTLS_DIR)/include MESSAGING_HEADERS = -I$(ROOT_DIR)/messaging/coap COMMON_TEST_DIR := $(ROOT_DIR)/tests/gtest +COMMON_TEST_COAP_DIR := $(COMMON_TEST_DIR)/coap COMMON_TEST_TLS_DIR := $(COMMON_TEST_DIR)/tls COMMON_TEST_OBJ_DIR := $(COMMON_TEST_DIR)/obj -COMMON_TEST_TLS_OBJ_DIR := $(COMMON_TEST_DIR)/tls/obj -COMMON_TEST_SRC_FILES := $(wildcard $(COMMON_TEST_DIR)/*.cpp $(COMMON_TEST_TLS_DIR)/*.cpp) +COMMON_TEST_COAP_OBJ_DIR := $(COMMON_TEST_COAP_DIR)/obj +COMMON_TEST_TLS_OBJ_DIR := $(COMMON_TEST_TLS_DIR)/obj +COMMON_TEST_SRC_FILES := $(wildcard $(COMMON_TEST_DIR)/*.cpp $(COMMON_TEST_COAP_DIR)/*.cpp $(COMMON_TEST_TLS_DIR)/*.cpp) COMMON_TEST_OBJ_FILES := $(patsubst $(COMMON_TEST_DIR)/%.cpp,$(COMMON_TEST_OBJ_DIR)/%.o,$(COMMON_TEST_SRC_FILES)) -COMMON_TEST_OBJ_FILES := $(patsubst $(COMMON_TEST_TLS_DIR)/%.cpp,$(COMMON_TEST_OBJ_DIR)/%.o, $(COMMON_TEST_OBJ_FILES)) +COMMON_TEST_OBJ_FILES := $(patsubst $(COMMON_TEST_COAP_DIR)/%.cpp,$(COMMON_TEST_COAP_OBJ_DIR)/%.o,$(COMMON_TEST_OBJ_FILES)) +COMMON_TEST_OBJ_FILES := $(patsubst $(COMMON_TEST_TLS_DIR)/%.cpp,$(COMMON_TEST_OBJ_DIR)/%.o,$(COMMON_TEST_OBJ_FILES)) API_TEST_DIR = $(ROOT_DIR)/api/unittest -API_TEST_ENCODER_DIR = $(ROOT_DIR)/api/unittest/encoder +API_TEST_DISCOVERY_DIR = $(API_TEST_DIR)/discovery +API_TEST_ENCODER_DIR = $(API_TEST_DIR)/encoder API_TEST_OBJ_DIR = $(API_TEST_DIR)/obj -API_TEST_ENCODER_OBJ_DIR = $(API_TEST_DIR)/encoder/obj -API_TEST_SRC_FILES := $(wildcard $(API_TEST_DIR)/*.cpp $(API_TEST_ENCODER_DIR)/*.cpp) +API_TEST_DISCOVERY_OBJ_DIR = $(API_TEST_DISCOVERY_DIR)/obj +API_TEST_ENCODER_OBJ_DIR = $(API_TEST_ENCODER_DIR)/obj +API_TEST_SRC_FILES := $(wildcard $(API_TEST_DIR)/*.cpp $(API_TEST_DISCOVERY_DIR)/*.cpp $(API_TEST_ENCODER_DIR)/*.cpp) API_TEST_OBJ_FILES := $(patsubst $(API_TEST_DIR)/%.cpp,$(API_TEST_OBJ_DIR)/%.o,$(API_TEST_SRC_FILES)) +API_TEST_OBJ_FILES := $(patsubst $(API_TEST_DISCOVERY_DIR)/%.cpp,$(API_TEST_ENCODER_OBJ_DIR)/%.o,$(API_TEST_OBJ_FILES)) API_TEST_OBJ_FILES := $(patsubst $(API_TEST_ENCODER_DIR)/%.cpp,$(API_TEST_ENCODER_OBJ_DIR)/%.o,$(API_TEST_OBJ_FILES)) SECURITY_TEST_DIR = $(ROOT_DIR)/security/unittest SECURITY_TEST_OBJ_DIR = $(SECURITY_TEST_DIR)/obj @@ -119,7 +125,6 @@ endif CTIMESTAMP+=../../api/c-timestamp/timestamp_compare.c ../../api/c-timestamp/timestamp_format.c ../../api/c-timestamp/timestamp_valid.c ../../api/c-timestamp/timestamp_parse.c -SRC_PORT_COMMON:=$(wildcard ../../port/common/*.c) $(wildcard ../../port/common/posix/*.c) ifneq ($(MEMORY_TRACE),1) SRC_UTIL:=$(filter-out %_mem_trace.c,$(wildcard ../../util/*.c)) else @@ -128,7 +133,7 @@ endif ifeq ($(JSON_ENCODER),1) SRC_UTIL+=$(wildcard ../../util/jsmn/*.c) endif -SRC_COMMON:=${CBOR} ${CTIMESTAMP} ${SRC_PORT_COMMON} ${SRC_UTIL} +SRC_COMMON:=${CBOR} ${CTIMESTAMP} ${SRC_UTIL} SRC_API:=$(wildcard ../../api/*.c) ifneq ($(INTROSPECTION),1) SRC_API:=$(filter-out %oc_introspection.c,${SRC_API}) @@ -140,6 +145,7 @@ ifneq ($(JSON_ENCODER),1) SRC_API:=$(filter-out %oc_rep_decode_json.c %oc_rep_encode_json.c,${SRC_API}) endif SRC:=${SRC_API} $(wildcard ../../messaging/coap/*.c ../../port/linux/*.c) +SRC_PORT_COMMON:=$(wildcard ../../port/common/*.c ../../port/common/posix/*.c) SRC_CLIENT:=$(wildcard ../../api/client/*.c) SRC_CLOUD:=$(wildcard ../../api/cloud/*.c) @@ -168,6 +174,7 @@ CFLAGS_CLOUD=-I../../api/cloud -DOC_CLIENT -DOC_SERVER CFLAGS=-fPIC -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -ffreestanding -Os -fno-stack-protector -ffunction-sections -fdata-sections -fno-reorder-functions -fno-defer-pop -fno-strict-overflow -I./ -I../../include/ -I../../ -I../../deps/tinycbor/src -I../../api -std=gnu99 $(WARNING_FLAGS) #-Wl,-Map,client.map CXXFLAGS+=-fPIC -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -ffreestanding -Os -fno-stack-protector -ffunction-sections -fdata-sections -fno-reorder-functions -fno-defer-pop -fno-strict-overflow -I./ -I../../include/ -I../../ -I../../deps/tinycbor/src $(WARNING_FLAGS) #-Wl,-Map,client.map OBJ_COMMON=$(addprefix obj/,$(notdir $(SRC_COMMON:.c=.o))) +OBJ_PORT_COMMON=$(addprefix obj/port/,$(notdir $(SRC_PORT_COMMON:.c=.o))) OBJ_CLIENT=$(addprefix obj/client/,$(notdir $(SRC:.c=.o) $(SRC_CLIENT:.c=.o))) OBJ_SERVER=$(addprefix obj/server/,$(filter-out oc_obt.o oc_obt_otm_justworks.o oc_obt_otm_randompin.o oc_obt_otm_cert.o oc_obt_otm_streamlined_onboarding.o oc_obt_certs.o,$(notdir $(SRC:.c=.o)))) OBJ_CLOUD=$(addprefix obj/cloud/,$(notdir $(SRC_CLOUD:.c=.o))) @@ -178,7 +185,6 @@ OBJ_PYTHON=$(addprefix obj/python/,$(notdir $(SRC_PYTHON:.c=.o))) # $(info SRC_PYTHON is $(SRC_PYTHON)) VPATH=../../api/:../../api/client/:../../api/cloud/:../../api/c-timestamp:../../messaging/coap/: -VPATH+=../../port/common/:../../port/common/posix/: VPATH+=../../python/: VPATH+=../../util/:../../util/jsmn/: VPATH+=../../deps/tinycbor/src/:../../deps/mbedtls/library: @@ -369,6 +375,10 @@ $(COMMON_TEST_OBJ_DIR)/%.o: $(COMMON_TEST_DIR)/%.cpp @mkdir -p ${@D} $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) -I$(ROOT_DIR)/deps/tinycbor/src -c $< -o $@ +$(COMMON_TEST_COAP_OBJ_DIR)/%.o: $(COMMON_TEST_COAP_DIR)/%.cpp + @mkdir -p ${@D} + $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) -I$(ROOT_DIR)/deps/tinycbor/src -c $< -o $@ + $(COMMON_TEST_TLS_OBJ_DIR)/%.o: $(COMMON_TEST_TLS_DIR)/%.cpp @mkdir -p ${@D} $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) -I$(ROOT_DIR)/deps/tinycbor/src -c $< -o $@ @@ -377,6 +387,10 @@ $(API_TEST_OBJ_DIR)/%.o: $(API_TEST_DIR)/%.cpp @mkdir -p ${@D} $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) -c $< -o $@ +$(API_TEST_DISCOVERY_OBJ_DIR)/%.o: $(API_TEST_DISCOVERY_DIR)/%.cpp + @mkdir -p ${@D} + $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) -c $< -o $@ + $(API_TEST_ENCODER_OBJ_DIR)/%.o: $(API_TEST_ENCODER_DIR)/%.cpp @mkdir -p ${@D} $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) -c $< -o $@ @@ -423,12 +437,20 @@ copy_idd_files: @cp ../../apps/server_rules_IDD.cbor . @cp ../../apps/cloud_proxy_IDD.cbor . -${SRC} ${SRC_COMMON} ${SRC_CLIENT}: $(MBEDTLS_PATCH_FILE) +${SRC} ${SRC_COMMON} ${SRC_PORT_COMMON} ${SRC_CLIENT}: $(MBEDTLS_PATCH_FILE) obj/%.o: %.c @mkdir -p ${@D} ${CC} -c -o $@ $< ${CFLAGS} +obj/port/%.o: ../../port/common/%.c + @mkdir -p ${@D} + ${CC} -c -o $@ $< ${CFLAGS} + +obj/port/%.o: ../../port/common/posix/%.c + @mkdir -p ${@D} + ${CC} -c -o $@ $< ${CFLAGS} + obj/server/%.o: %.c @mkdir -p ${@D} ${CC} -c -o $@ $< ${CFLAGS} -DOC_SERVER @@ -455,27 +477,27 @@ obj/client/oc_introspection.o: ../../include/server_introspection.dat.h obj/client_server/oc_introspection.o: ../../include/server_introspection.dat.h endif -libiotivity-lite-server.a: $(OBJ_COMMON) $(OBJ_SERVER) - $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_SERVER) +libiotivity-lite-server.a: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_SERVER) + $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_SERVER) -libiotivity-lite-server.so: $(OBJ_COMMON) $(OBJ_SERVER) - $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_SERVER) $(LIBS) +libiotivity-lite-server.so: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_SERVER) + $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_SERVER) $(LIBS) -libiotivity-lite-client.a: $(OBJ_COMMON) $(OBJ_CLIENT) - $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_CLIENT) +libiotivity-lite-client.a: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT) + $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT) -libiotivity-lite-client.so: $(OBJ_COMMON) $(OBJ_CLIENT) - $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_CLIENT) $(LIBS) +libiotivity-lite-client.so: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT) + $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT) $(LIBS) -libiotivity-lite-client-server.a: $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) - $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) +libiotivity-lite-client-server.a: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) + $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) -libiotivity-lite-client-python.so: $(OBJ_COMMON) $(OBJ_CLIENT) $(OBJ_PYTHON) - $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_CLIENT) $(OBJ_PYTHON) $(LIBS) +libiotivity-lite-client-python.so: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT) $(OBJ_PYTHON) + $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT) $(OBJ_PYTHON) $(LIBS) cp $@ ../../python -libiotivity-lite-client-server.so: $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) - $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) $(LIBS) +libiotivity-lite-client-server.so: $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) + $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) $(LIBS) server: libiotivity-lite-server.a $(ROOT_DIR)/apps/server_linux.c @mkdir -p $@_creds @@ -658,7 +680,7 @@ iotivity-lite-client-server.pc: iotivity-lite-client-server.pc.in -e 's,@extra_cflags@,-I$${includedir}/iotivity-lite/tinycbor $(EXTRA_CFLAGS),' \ -e 's,@extra_libs@,$(EXTRA_LIBS),' -swig: $(CONSTRAINED_LIBS) $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) +swig: $(CONSTRAINED_LIBS) $(OBJ_COMMON) $(OBJ_PORT_COMMON) $(OBJ_CLIENT_SERVER) $(OBJ_CLOUD) ${MAKE} -C ${SWIG_DIR} ifneq ($(SECURE),0) diff --git a/port/linux/ip.c b/port/linux/ip.c index cac4a4130d..3984c253e0 100644 --- a/port/linux/ip.c +++ b/port/linux/ip.c @@ -134,6 +134,7 @@ oc_ip_recv_msg(int sock, uint8_t *recv_buf, long recv_buf_size, oc_endpoint_t *endpoint, bool multicast) { struct sockaddr_storage client; + memset(&client, 0, sizeof(client)); struct iovec iovec[1]; struct msghdr msg; char msg_control[CMSG_LEN(sizeof(struct sockaddr_storage))]; diff --git a/port/linux/ipadapter.c b/port/linux/ipadapter.c index a3101e6d05..4dcdc7d230 100644 --- a/port/linux/ipadapter.c +++ b/port/linux/ipadapter.c @@ -27,6 +27,8 @@ #include "oc_core_res.h" #include "oc_endpoint.h" #include "oc_network_monitor.h" +#include "port/common/posix/oc_fcntl_internal.h" +#include "port/common/posix/oc_socket_internal.h" #include "port/oc_assert.h" #include "port/oc_clock.h" #include "port/oc_connectivity.h" @@ -997,32 +999,6 @@ network_event_thread(void *data) return NULL; } -bool -oc_get_socket_address(const oc_endpoint_t *endpoint, - struct sockaddr_storage *addr) -{ - if (endpoint == NULL || addr == NULL) { - return false; - } -#ifdef OC_IPV4 - if ((endpoint->flags & IPV4) != 0) { - struct sockaddr_in *r = (struct sockaddr_in *)addr; - memcpy(&r->sin_addr.s_addr, endpoint->addr.ipv4.address, - sizeof(r->sin_addr.s_addr)); - r->sin_family = AF_INET; - r->sin_port = htons(endpoint->addr.ipv4.port); - return true; - } -#endif /* OC_IPV4 */ - struct sockaddr_in6 *r = (struct sockaddr_in6 *)addr; - memcpy(r->sin6_addr.s6_addr, endpoint->addr.ipv6.address, - sizeof(r->sin6_addr.s6_addr)); - r->sin6_family = AF_INET6; - r->sin6_port = htons(endpoint->addr.ipv6.port); - r->sin6_scope_id = endpoint->addr.ipv6.scope; - return true; -} - static int oc_send_buffer_internal(oc_message_t *message, bool create, bool queue) { @@ -1030,18 +1006,13 @@ oc_send_buffer_internal(oc_message_t *message, bool create, bool queue) OC_LOGipaddr(message->endpoint); OC_DBG("%s", ""); - struct sockaddr_storage receiver; - memset(&receiver, 0, sizeof(struct sockaddr_storage)); - if (!oc_get_socket_address(&message->endpoint, &receiver)) { - OC_ERR("cannot retrieve socket address"); - return -1; - } - ip_context_t *dev = oc_get_ip_context_for_device(message->endpoint.device); if (dev == NULL) { return -1; } + struct sockaddr_storage receiver = oc_socket_get_address(&message->endpoint); + #ifdef OC_TCP if ((message->endpoint.flags & TCP) != 0) { if (create) { @@ -1445,7 +1416,7 @@ initialize_ip_context(ip_context_t *dev, size_t device, OC_ERR("shutdown pipe: %d", errno); return false; } - if (oc_set_fd_flags(dev->shutdown_pipe[0], O_NONBLOCK, 0) < 0) { + if (!oc_fcntl_set_nonblocking(dev->shutdown_pipe[0])) { OC_ERR("Could not set non-block shutdown_pipe[0]"); return false; } @@ -1620,26 +1591,3 @@ oc_connectivity_end_session(const oc_endpoint_t *endpoint) } } #endif /* OC_TCP */ - -int -oc_set_fd_flags(int sockfd, int to_add, int to_remove) -{ - int old_flags = fcntl(sockfd, F_GETFL, 0); - if (old_flags < 0) { - return -1; - } - - int flags = old_flags; - flags &= ~to_remove; - flags |= to_add; - - if (flags == old_flags) { - return flags; - } - - if (fcntl(sockfd, F_SETFL, flags) < 0) { - return -1; - } - - return flags; -} diff --git a/port/linux/ipadapter.h b/port/linux/ipadapter.h index 0e028b2845..a16b0a06f2 100644 --- a/port/linux/ipadapter.h +++ b/port/linux/ipadapter.h @@ -27,32 +27,6 @@ extern "C" { #endif -/** - * @brief Add or remove file descriptor flags. - * - * Functions gets the current file descriptor flags, removes the to_remove - * flags, then adds the to_add flags and updates the file descriptor flags with - * this new value. - * - * @param sockfd file descriptor - * @param to_add flags to be added - * @param to_remove flags to be removed - * @return >0 on success, the current file descriptor flags - * @return -1 on failure - */ -int oc_set_fd_flags(int sockfd, int to_add, int to_remove); - -/** - * @brief Extract socket address from the endpoint. - * - * @param[in] endpoint endpoint with address - * @param[out] addr ipv4 or ipv6 address extracted from the endpoint - * @return true on success - * @return false on failure - */ -bool oc_get_socket_address(const oc_endpoint_t *endpoint, - struct sockaddr_storage *addr); - /** * @brief Get ip context for device. */ diff --git a/port/linux/netsocket.c b/port/linux/netsocket.c index 481b4000a4..07058fec0a 100644 --- a/port/linux/netsocket.c +++ b/port/linux/netsocket.c @@ -299,7 +299,7 @@ static int netsocket_create_ipv4(uint16_t port, bool multicast) { struct sockaddr_storage sockaddr; - memset(&sockaddr, 0, sizeof(struct sockaddr_storage)); + memset(&sockaddr, 0, sizeof(sockaddr)); struct sockaddr_in *sa = (struct sockaddr_in *)&sockaddr; sa->sin_family = AF_INET; sa->sin_addr.s_addr = INADDR_ANY; diff --git a/port/linux/socklistener.c b/port/linux/socklistener.c index 2881e8c52a..7681de7c04 100644 --- a/port/linux/socklistener.c +++ b/port/linux/socklistener.c @@ -53,6 +53,7 @@ oc_sock_listener_get_port(const oc_sock_listener_t *server) return -1; } struct sockaddr_storage sockaddr; + memset(&sockaddr, 0, sizeof(sockaddr)); socklen_t socklen = sizeof(sockaddr); if (getsockname(server->sock, (struct sockaddr *)&sockaddr, &socklen) == -1) { OC_ERR("obtaining socket information %d", errno); diff --git a/port/linux/tcpadapter.c b/port/linux/tcpadapter.c index 59c4e37f37..1d9f83bb40 100644 --- a/port/linux/tcpadapter.c +++ b/port/linux/tcpadapter.c @@ -20,6 +20,7 @@ #include "ipadapter.h" #include "ipcontext.h" #include "tcpsession.h" +#include "port/common/posix/oc_fcntl_internal.h" #include "port/oc_assert.h" #include "port/oc_log_internal.h" #include @@ -204,11 +205,11 @@ tcp_connectivity_init(ip_context_t *dev, oc_connectivity_ports_t ports) OC_ERR("Could not initialize connection pipe"); return false; } - if (oc_set_fd_flags(dev->tcp.connect_pipe[0], O_NONBLOCK, 0) < 0) { + if (!oc_fcntl_set_nonblocking(dev->tcp.connect_pipe[0])) { OC_ERR("Could not set non-blocking connect_pipe[0]"); return false; } - if (oc_set_fd_flags(dev->tcp.connect_pipe[1], O_NONBLOCK, 0) < 0) { + if (!oc_fcntl_set_nonblocking(dev->tcp.connect_pipe[1])) { OC_ERR("Could not set non-blocking connect_pipe[1]"); return false; } diff --git a/port/linux/tcpsession.c b/port/linux/tcpsession.c index 0acef49a2d..980fd99303 100644 --- a/port/linux/tcpsession.c +++ b/port/linux/tcpsession.c @@ -18,6 +18,10 @@ #define __USE_GNU +#include "util/oc_features.h" + +#ifdef OC_TCP + #include "api/oc_endpoint_internal.h" #include "api/oc_message_internal.h" #include "api/oc_network_events_internal.h" @@ -29,12 +33,15 @@ #include "oc_buffer.h" #include "oc_endpoint.h" #include "oc_session_events.h" +#include "port/common/oc_tcp_socket_internal.h" +#include "port/common/posix/oc_fcntl_internal.h" +#include "port/common/posix/oc_socket_internal.h" #include "port/oc_assert.h" #include "port/oc_connectivity_internal.h" #include "tcpsession.h" -#include "util/oc_features.h" #include "util/oc_macros_internal.h" #include "util/oc_memb.h" + #include #include #include @@ -44,13 +51,6 @@ #include #include -#ifdef OC_TCP - -#define TLS_HEADER_SIZE 5 - -#define DEFAULT_RECEIVE_SIZE \ - (COAP_TCP_DEFAULT_HEADER_LEN + COAP_TCP_MAX_EXTENDED_LENGTH_LEN) - typedef struct tcp_session_t { struct tcp_session_t *next; @@ -159,6 +159,7 @@ static long get_interface_index(int sock) { struct sockaddr_storage addr; + memset(&addr, 0, sizeof(addr)); socklen_t socklen = sizeof(addr); if (getsockname(sock, (struct sockaddr *)&addr, &socklen) == -1) { OC_ERR("failed obtaining socket information %d", errno); @@ -251,6 +252,7 @@ accept_new_session_locked(ip_context_t *dev, int fd, fd_set *setfds, oc_endpoint_t *endpoint) { struct sockaddr_storage receive_from; + memset(&receive_from, 0, sizeof(receive_from)); socklen_t receive_len = sizeof(receive_from); int new_socket = accept(fd, (struct sockaddr *)&receive_from, &receive_len); @@ -321,18 +323,6 @@ free_session_for_device_locked(size_t device) } } -static size_t -get_total_length_from_header(const oc_message_t *message, - const oc_endpoint_t *endpoint) -{ - if ((endpoint->flags & SECURED) != 0) { - //[3][4] bytes in tls header are tls payload length - return TLS_HEADER_SIZE + - (size_t)((message->data[3] << 8) | message->data[4]); - } - return coap_tcp_get_packet_size(message->data); -} - static adapter_receive_state_t tcp_receive_server_message_locked(ip_context_t *dev, fd_set *fds, oc_message_t *message) @@ -405,7 +395,7 @@ tcp_session_receive_message_locked(tcp_session_t *session, oc_message_t *message) { size_t total_length = 0; - size_t want_read = DEFAULT_RECEIVE_SIZE; + size_t want_read = OC_TCP_DEFAULT_RECEIVE_SIZE; message->length = 0; do { ssize_t count = @@ -437,12 +427,16 @@ tcp_session_receive_message_locked(tcp_session_t *session, message->encrypted = 1; } #endif /* OC_SECURITY */ - if (!oc_tcp_is_valid_header(message)) { - OC_ERR("invalid header"); + + long length_from_header = + oc_tcp_get_total_length_from_message_header(message); + if (length_from_header < 0) { + OC_ERR("invalid message size in header"); free_session_locked(session, true); return ADAPTER_STATUS_ERROR; } - total_length = get_total_length_from_header(message, &session->endpoint); + + total_length = (size_t)length_from_header; // check to avoid buffer overflow if (total_length > oc_message_buffer_size()) { OC_ERR( @@ -531,89 +525,6 @@ find_session_by_endpoint_locked(const oc_endpoint_t *endpoint) #ifdef OC_HAS_FEATURE_TCP_ASYNC_CONNECT -static int -try_connect_nonblocking(int sockfd, const struct sockaddr *r, socklen_t r_len) -{ - if (oc_set_fd_flags(sockfd, O_NONBLOCK, 0) < 0) { - OC_ERR("cannot set non-blocking socket(%d)", sockfd); - return -1; - } - - while (true) { - int n = connect(sockfd, r, r_len); - if (n == 0) { - return OC_TCP_SOCKET_STATE_CONNECTED; - } else if (n < 0) { - if (errno == EINPROGRESS || errno == EALREADY) { - return OC_TCP_SOCKET_STATE_CONNECTING; - } - if (errno == EINTR || errno == EAGAIN) { - continue; - } - OC_ERR("connect to socked(%d) failed with error: %d", sockfd, (int)errno); - return -1; - } - } -} - -typedef struct -{ - tcp_session_t *session; - tcp_waiting_session_t *waiting_session; - bool created; -} tcp_connect_result_t; - -typedef struct -{ - int socket; - int state; -} tcp_connected_socket_t; - -static tcp_connected_socket_t -tcp_create_connected_socket(const oc_endpoint_t *endpoint, - const struct sockaddr_storage *receiver) -{ - tcp_connected_socket_t cs = { - .socket = -1, - .state = -1, - }; - int sock = -1; - if ((endpoint->flags & IPV6) != 0) { - sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); -#ifdef OC_IPV4 - } else if ((endpoint->flags & IPV4) != 0) { - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); -#endif - } - - if (sock < 0) { - OC_ERR("could not create socket for TCP session"); - return cs; - } - - struct sockaddr_storage rc; - if (receiver == NULL) { - memset(&rc, 0, sizeof(struct sockaddr_storage)); - if (!oc_get_socket_address(endpoint, &rc)) { - close(sock); - OC_ERR("cannot retrieve socket address"); - return cs; - } - receiver = &rc; - } - - socklen_t size = sizeof(*receiver); - int ret = - try_connect_nonblocking(sock, (const struct sockaddr *)receiver, size); - if (ret < 0) { - close(sock); - return cs; - } - cs.socket = sock; - cs.state = ret; - return cs; -} - static tcp_session_t * tcp_create_session_locked(int sock, ip_context_t *dev, oc_endpoint_t *endpoint, bool signal) @@ -707,6 +618,13 @@ tcp_create_waiting_session_locked(int sock, ip_context_t *dev, return ws; } +typedef struct +{ + tcp_session_t *session; + tcp_waiting_session_t *waiting_session; + bool created; +} tcp_connect_result_t; + static tcp_connect_result_t tcp_connect_locked(ip_context_t *dev, oc_endpoint_t *endpoint, const struct sockaddr_storage *receiver, @@ -732,25 +650,25 @@ tcp_connect_locked(ip_context_t *dev, oc_endpoint_t *endpoint, return res; } - tcp_connected_socket_t cs = tcp_create_connected_socket(endpoint, receiver); + oc_tcp_socket_t cs = oc_tcp_socket_connect(endpoint, receiver); if (cs.state == OC_TCP_SOCKET_STATE_CONNECTED) { OC_DBG("successfully initiated TCP connection"); - s = tcp_create_session_locked(cs.socket, dev, endpoint, true); + s = tcp_create_session_locked(cs.fd, dev, endpoint, true); if (s != NULL) { res.created = true; return res; } } else if (cs.state == OC_TCP_SOCKET_STATE_CONNECTING) { - ws = tcp_create_waiting_session_locked(cs.socket, dev, endpoint, - on_tcp_connect, on_tcp_connect_data); + ws = tcp_create_waiting_session_locked(cs.fd, dev, endpoint, on_tcp_connect, + on_tcp_connect_data); if (ws != NULL) { res.waiting_session = ws; res.created = true; return res; } } - if (cs.socket >= 0) { - close(cs.socket); + if (cs.fd >= 0) { + close(cs.fd); } OC_ERR("cannot create session"); return res; @@ -1155,7 +1073,7 @@ tcp_try_connect_waiting_session_locked(tcp_waiting_session_t *ws, int *err) return false; } - if (oc_set_fd_flags(ws->sock, 0, O_NONBLOCK) < 0) { + if (!oc_fcntl_set_blocking(ws->sock)) { OC_ERR("cannot set blocking socket(%d)", ws->sock); return false; } @@ -1232,11 +1150,11 @@ tcp_retry_waiting_session_locked(tcp_waiting_session_t *ws, ws->sock = -1; } - tcp_connected_socket_t cs = tcp_create_connected_socket(&ws->endpoint, NULL); + oc_tcp_socket_t cs = oc_tcp_socket_connect(&ws->endpoint, NULL); if (cs.state == OC_TCP_SOCKET_STATE_CONNECTED) { OC_DBG("successfully initiated TCP connection"); tcp_session_t *s = - tcp_create_session_locked(cs.socket, ws->dev, &ws->endpoint, false); + tcp_create_session_locked(cs.fd, ws->dev, &ws->endpoint, false); if (s == NULL) { OC_ERR("cannot allocate ongoing TCP connection"); return -1; @@ -1252,7 +1170,7 @@ tcp_retry_waiting_session_locked(tcp_waiting_session_t *ws, ++ws->retry.count; ws->retry.start = now_mt; ws->retry.force = 0; - ws->sock = cs.socket; + ws->sock = cs.fd; tcp_waiting_session_set_socked_locked(&ws->dev->tcp, ws->sock); return OC_TCP_SOCKET_STATE_CONNECTING; } @@ -1351,12 +1269,7 @@ oc_tcp_connect_to_endpoint(ip_context_t *dev, oc_endpoint_t *endpoint, on_tcp_connect_t on_tcp_connect, void *on_tcp_connect_data) { - struct sockaddr_storage receiver; - memset(&receiver, 0, sizeof(struct sockaddr_storage)); - if (!oc_get_socket_address(endpoint, &receiver)) { - OC_ERR("cannot retrieve socket address"); - return OC_TCP_SOCKET_ERROR; - } + struct sockaddr_storage receiver = oc_socket_get_address(endpoint); pthread_mutex_lock(&g_mutex); tcp_connect_result_t res = tcp_connect_locked( dev, endpoint, &receiver, on_tcp_connect, on_tcp_connect_data); @@ -1388,4 +1301,5 @@ oc_tcp_connect(oc_endpoint_t *endpoint, on_tcp_connect_t on_tcp_connect, } #endif /* OC_HAS_FEATURE_TCP_ASYNC_CONNECT */ + #endif /* OC_TCP */ diff --git a/port/unittest/fcntltest.cpp b/port/unittest/fcntltest.cpp new file mode 100644 index 0000000000..9736c59e91 --- /dev/null +++ b/port/unittest/fcntltest.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "port/common/posix/oc_fcntl_internal.h" +#include "tests/gtest/Endpoint.h" + +#ifdef OC_TCP +#include "port/common/oc_tcp_socket_internal.h" +#endif /* OC_TCP */ + +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif /* _WIN32 */ + +class TestFcntl : public testing::Test { +public: + static void SetUpTestCase() + { +#ifdef _WIN32 + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif /* _WIN32 */ + } + + static void TearDownTestCase() + { +#ifdef _WIN32 + WSACleanup(); +#endif /* _WIN32 */ + } +}; + +#ifdef OC_TCP + +TEST_F(TestFcntl, SetBlocking) +{ + EXPECT_FALSE(oc_fcntl_set_blocking(OC_INVALID_SOCKET)); + + auto ep = oc::endpoint::FromString("coap://[::1]:80"); + oc_tcp_socket_t tcp = oc_tcp_socket_connect(&ep, nullptr); + ASSERT_NE(-1, tcp.state); + ASSERT_NE(OC_INVALID_SOCKET, tcp.fd); + + EXPECT_TRUE(oc_fcntl_set_blocking(tcp.fd)); + + OC_CLOSE_SOCKET(tcp.fd); +} + +TEST_F(TestFcntl, SetNonBlocking) +{ + EXPECT_FALSE(oc_fcntl_set_nonblocking(OC_INVALID_SOCKET)); + + auto ep = oc::endpoint::FromString("coap://[::1]:80"); + oc_tcp_socket_t tcp = oc_tcp_socket_connect(&ep, nullptr); + ASSERT_NE(-1, tcp.state); + ASSERT_NE(OC_INVALID_SOCKET, tcp.fd); + + EXPECT_TRUE(oc_fcntl_set_nonblocking(tcp.fd)); + + OC_CLOSE_SOCKET(tcp.fd); +} + +#endif /* OC_TCP */ + +#ifndef _WIN32 + +TEST_F(TestFcntl, SetFlags) +{ + // invalid fd + EXPECT_EQ(-1, oc_fcntl_set_flags(-1, 0, 0)); + + std::string file = "./flags"; + FILE *fp = fopen(file.c_str(), "w"); + ASSERT_NE(nullptr, fp); + int fd = fileno(fp); + ASSERT_NE(-1, fd); + + int flags = oc_fcntl_set_flags(fd, O_APPEND, 0); + ASSERT_NE(-1, flags); + EXPECT_EQ(false, oc_fcntl_set_flags(fd, 0, flags)); + + ASSERT_EQ(0, remove(file.c_str())); +} + +#endif /* !_WIN32 */ diff --git a/port/windows/ipadapter.c b/port/windows/ipadapter.c index 5dffef4534..e0e3fa5923 100644 --- a/port/windows/ipadapter.c +++ b/port/windows/ipadapter.c @@ -20,6 +20,7 @@ #include "api/oc_network_events_internal.h" #include "api/oc_session_events_internal.h" +#include "port/common/posix/oc_socket_internal.h" #include "port/oc_assert.h" #include "port/oc_connectivity.h" #include "port/oc_connectivity_internal.h" @@ -1126,35 +1127,21 @@ oc_send_buffer(oc_message_t *message) OC_DBG("Outgoing message of size %zd bytes to", message->length); OC_LOGipaddr(message->endpoint); OC_DBG("%s", ""); - struct sockaddr_storage receiver; - memset(&receiver, 0, sizeof(receiver)); -#ifdef OC_IPV4 - if (message->endpoint.flags & IPV4) { - struct sockaddr_in *r = (struct sockaddr_in *)&receiver; - memcpy(&r->sin_addr.s_addr, message->endpoint.addr.ipv4.address, - sizeof(r->sin_addr.s_addr)); - r->sin_family = AF_INET; - r->sin_port = htons(message->endpoint.addr.ipv4.port); - } else { -#else - { -#endif - struct sockaddr_in6 *r = (struct sockaddr_in6 *)&receiver; - memcpy(r->sin6_addr.s6_addr, message->endpoint.addr.ipv6.address, - sizeof(r->sin6_addr.s6_addr)); - r->sin6_family = AF_INET6; - r->sin6_port = htons(message->endpoint.addr.ipv6.port); - r->sin6_scope_id = message->endpoint.addr.ipv6.scope; - } - SOCKET send_sock = INVALID_SOCKET; ip_context_t *dev = get_ip_context_for_device(message->endpoint.device); + if (dev == NULL) { + return -1; + } + + struct sockaddr_storage receiver = oc_socket_get_address(&message->endpoint); + #ifdef OC_TCP if (message->endpoint.flags & TCP) { return oc_tcp_send_buffer(dev, message, &receiver); } #endif /* OC_TCP */ + SOCKET send_sock = INVALID_SOCKET; #ifdef OC_SECURITY if (message->endpoint.flags & SECURED) { #ifdef OC_IPV4 diff --git a/port/windows/oc_fcntl.c b/port/windows/oc_fcntl.c new file mode 100644 index 0000000000..900df80d83 --- /dev/null +++ b/port/windows/oc_fcntl.c @@ -0,0 +1,36 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "port/common/posix/oc_fcntl_internal.h" + +#define WIN32_LEAN_AND_MEAN +#include + +bool +oc_fcntl_set_blocking(SOCKET sock) +{ + unsigned long mode = 0; + return ioctlsocket(sock, FIONBIO, &mode) == NO_ERROR; +} + +bool +oc_fcntl_set_nonblocking(SOCKET sock) +{ + unsigned long mode = 1; + return ioctlsocket(sock, FIONBIO, &mode) == NO_ERROR; +} diff --git a/port/windows/tcpadapter.c b/port/windows/tcpadapter.c index 672dce8078..ca186306eb 100644 --- a/port/windows/tcpadapter.c +++ b/port/windows/tcpadapter.c @@ -21,6 +21,8 @@ #include "api/oc_network_events_internal.h" #include "api/oc_session_events_internal.h" #include "api/oc_tcp_internal.h" +#include "port/common/oc_tcp_socket_internal.h" +#include "port/common/posix/oc_fcntl_internal.h" #include "port/oc_assert.h" #include "port/oc_log_internal.h" #include "util/oc_memb.h" @@ -31,6 +33,7 @@ #include "oc_endpoint.h" #include "oc_session_events.h" #include "tcpadapter.h" + #include #include #include @@ -39,11 +42,6 @@ #define OC_TCP_LISTEN_BACKLOG 3 -#define TLS_HEADER_SIZE 5 - -#define DEFAULT_RECEIVE_SIZE \ - (COAP_TCP_DEFAULT_HEADER_LEN + COAP_TCP_MAX_EXTENDED_LENGTH_LEN) - #define LIMIT_RETRY_CONNECT 5 #define TCP_CONNECT_TIMEOUT 5 @@ -127,6 +125,7 @@ static long get_interface_index(SOCKET sock) { struct sockaddr_storage addr; + memset(&addr, 0, sizeof(addr)); if (get_assigned_tcp_port(sock, &addr) == SOCKET_ERROR) { return SOCKET_ERROR; } @@ -204,17 +203,6 @@ free_tcp_session_async_locked(tcp_session_t *session) OC_DBG("free TCP session async"); } -static int -set_socket_block_mode(SOCKET sockfd, u_long nonblock) -{ - int error = ioctlsocket(sockfd, FIONBIO, &nonblock); - if (error == SOCKET_ERROR) { - OC_ERR("set socket as blocking(%lu) %d", nonblock, WSAGetLastError()); - return SOCKET_ERROR; - } - return 0; -} - static int add_new_session_locked(SOCKET sock, ip_context_t *dev, oc_endpoint_t *endpoint, tcp_csm_state_t state) @@ -325,21 +313,6 @@ find_session_by_endpoint_locked(const oc_endpoint_t *endpoint) return session; } -static size_t -get_total_length_from_header(oc_message_t *message, oc_endpoint_t *endpoint) -{ - size_t total_length = 0; - if (endpoint->flags & SECURED) { - //[3][4] bytes in tls header are tls payload length - total_length = - TLS_HEADER_SIZE + (size_t)((message->data[3] << 8) | message->data[4]); - } else { - total_length = coap_tcp_get_packet_size(message->data); - } - - return total_length; -} - void oc_tcp_end_session(const oc_endpoint_t *endpoint) { @@ -364,54 +337,6 @@ get_session_socket_locked(oc_endpoint_t *endpoint) return sock; } -static int -connect_nonb(SOCKET sockfd, const struct sockaddr *r, int r_len, int nsec) -{ - if (set_socket_block_mode(sockfd, 1) == SOCKET_ERROR) { - return SOCKET_ERROR; - } - - int n = connect(sockfd, (struct sockaddr *)r, r_len); - if (n == SOCKET_ERROR) { - if (WSAGetLastError() != WSAEWOULDBLOCK) { - OC_ERR("connect %d", WSAGetLastError()); - return SOCKET_ERROR; - } - } - - /* Do whatever we want while the connect is taking place. */ - if (n == 0) { - return 0; /* connect completed immediately */ - } - - fd_set wset; - FD_ZERO(&wset); - FD_SET(sockfd, &wset); - struct timeval tval; - tval.tv_sec = nsec; - tval.tv_usec = 0; - - if ((n = select((int)sockfd + 1, NULL, &wset, NULL, nsec ? &tval : NULL)) == - 0) { - /* timeout */ - WSASetLastError(WSAETIMEDOUT); - OC_ERR("connect %d", WSAGetLastError()); - return SOCKET_ERROR; - } - if (n == SOCKET_ERROR) { - return SOCKET_ERROR; - } - - if (FD_ISSET(sockfd, &wset)) { - if (set_socket_block_mode(sockfd, 0) == SOCKET_ERROR) { - return SOCKET_ERROR; - } - return 0; - } - OC_DBG("select error: sockfd not set"); - return SOCKET_ERROR; -} - static SOCKET initiate_new_session_locked(ip_context_t *dev, oc_endpoint_t *endpoint, const struct sockaddr_storage *receiver) @@ -420,30 +345,14 @@ initiate_new_session_locked(ip_context_t *dev, oc_endpoint_t *endpoint, uint8_t retry_cnt = 0; while (retry_cnt < LIMIT_RETRY_CONNECT) { - if (endpoint->flags & IPV6) { - sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); -#ifdef OC_IPV4 - } else if (endpoint->flags & IPV4) { - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); -#endif - } - - if (sock == INVALID_SOCKET) { - OC_ERR("could not create socket for new TCP session %d", - WSAGetLastError()); - return sock; - } - - socklen_t receiver_size = sizeof(*receiver); - int ret = 0; - if ((ret = connect_nonb(sock, (struct sockaddr *)receiver, receiver_size, - TCP_CONNECT_TIMEOUT)) == 0) { + sock = + oc_tcp_socket_connect_and_wait(endpoint, receiver, TCP_CONNECT_TIMEOUT); + if (sock != INVALID_SOCKET) { break; } - closesocket(sock); retry_cnt++; - OC_DBG("connect fail with %d. retry(%d)", ret, retry_cnt); + OC_DBG("connect failed, retry(%d)", retry_cnt); } if (retry_cnt >= LIMIT_RETRY_CONNECT) { @@ -517,7 +426,7 @@ static int recv_message_with_tcp_session(tcp_session_t *session, oc_message_t *message) { size_t total_length = 0; - size_t want_read = DEFAULT_RECEIVE_SIZE; + size_t want_read = OC_TCP_DEFAULT_RECEIVE_SIZE; message->length = 0; do { int count = recv(session->sock, (char *)message->data + message->length, @@ -547,12 +456,16 @@ recv_message_with_tcp_session(tcp_session_t *session, oc_message_t *message) message->encrypted = 1; } #endif /* OC_SECURITY */ - if (!oc_tcp_is_valid_header(message)) { - OC_ERR("invalid header"); + + long length_from_header = + oc_tcp_get_total_length_from_message_header(message); + if (length_from_header < 0) { + OC_ERR("invalid message size in header"); free_tcp_session(session); return ADAPTER_STATUS_ERROR; } - total_length = get_total_length_from_header(message, &session->endpoint); + + total_length = (size_t)length_from_header; // check to avoid buffer overflow if (total_length > oc_message_buffer_size()) { OC_ERR( diff --git a/port/windows/vs2015/IoTivity-lite.vcxproj b/port/windows/vs2015/IoTivity-lite.vcxproj index 82d8d81a22..8882edd07f 100644 --- a/port/windows/vs2015/IoTivity-lite.vcxproj +++ b/port/windows/vs2015/IoTivity-lite.vcxproj @@ -441,6 +441,10 @@ + + + + CompileAsC CompileAsC @@ -474,12 +478,13 @@ - + + diff --git a/port/windows/vs2015/IoTivity-lite.vcxproj.filters b/port/windows/vs2015/IoTivity-lite.vcxproj.filters index ad671ff833..f8ab4bf0fc 100644 --- a/port/windows/vs2015/IoTivity-lite.vcxproj.filters +++ b/port/windows/vs2015/IoTivity-lite.vcxproj.filters @@ -293,9 +293,6 @@ Core - - Port - Port @@ -305,6 +302,12 @@ Port + + Port + + + Port + Port @@ -476,6 +479,18 @@ Port + + Port + + + Port + + + Port + + + Port + mbedTLS diff --git a/security/oc_oscore_engine.c b/security/oc_oscore_engine.c index a3d31d03a1..a368d0554b 100644 --- a/security/oc_oscore_engine.c +++ b/security/oc_oscore_engine.c @@ -332,8 +332,7 @@ oc_oscore_recv_message(oc_message_t *message) * Dispatch oc_message_t to the CoAP layer */ - if (oscore_is_oscore_message(message) >= 0 && - !oscore_parse_message(message)) { + if (oscore_is_oscore_message(message) && !oscore_parse_message(message)) { goto oscore_recv_error; } OC_DBG("#################################"); diff --git a/security/oc_tls.c b/security/oc_tls.c index 874d3c3ada..4cc1444f01 100644 --- a/security/oc_tls.c +++ b/security/oc_tls.c @@ -23,6 +23,7 @@ #include "api/oc_message_internal.h" #include "api/oc_network_events_internal.h" #include "api/oc_session_events_internal.h" +#include "api/oc_tcp_internal.h" #include "messaging/coap/engine_internal.h" #include "messaging/coap/observe_internal.h" #include "oc_api.h" @@ -2749,8 +2750,6 @@ assert_all_roles_internal(oc_client_response_t *data) #endif /* OC_PKI && OC_CLIENT */ #ifdef OC_TCP -#define DEFAULT_RECEIVE_SIZE \ - (COAP_TCP_DEFAULT_HEADER_LEN + COAP_TCP_MAX_EXTENDED_LENGTH_LEN) static void tls_read_application_data_tcp(oc_tls_peer_t *peer) @@ -2767,11 +2766,13 @@ tls_read_application_data_tcp(oc_tls_peer_t *peer) if (peer->processed_recv_message) { size_t want_read = 0; size_t total_length = 0; - if (peer->processed_recv_message->length < DEFAULT_RECEIVE_SIZE) { - want_read = DEFAULT_RECEIVE_SIZE - peer->processed_recv_message->length; + if (peer->processed_recv_message->length < OC_TCP_DEFAULT_RECEIVE_SIZE) { + want_read = + OC_TCP_DEFAULT_RECEIVE_SIZE - peer->processed_recv_message->length; } else { total_length = - coap_tcp_get_packet_size(peer->processed_recv_message->data); + coap_tcp_get_packet_size(peer->processed_recv_message->data, + peer->processed_recv_message->length); if (total_length > (size_t)OC_PDU_SIZE) { OC_ERR("oc_tls_tcp: total receive length(%ld) is bigger than max pdu " "size(%ld)", diff --git a/security/unittest/oscore_test.cpp b/security/unittest/oscore_test.cpp index fcce917f80..584a64b19e 100644 --- a/security/unittest/oscore_test.cpp +++ b/security/unittest/oscore_test.cpp @@ -52,6 +52,41 @@ class TestOSCORE : public testing::Test { } }; +TEST_F(TestOSCORE, GetHeader_F) +{ + coap_packet_t packet{}; + EXPECT_FALSE(coap_get_header_oscore(&packet, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr)); +} + +TEST_F(TestOSCORE, GetHeader_P) +{ + coap_packet_t packet{}; + std::array piv_in = { 0x01 }; + std::array kid_in = { 0x02, 0x03 }; + std::array kid_ctx_in = { 0x04, 0x05, 0x06 }; + coap_set_header_oscore(&packet, &piv_in[0], piv_in.size(), &kid_in[0], + kid_in.size(), &kid_ctx_in[0], kid_ctx_in.size()); + + EXPECT_TRUE(coap_get_header_oscore(&packet, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr)); + + const uint8_t *piv = nullptr; + uint8_t piv_len = 0; + const uint8_t *kid = nullptr; + uint8_t kid_len = 0; + const uint8_t *kid_ctx = nullptr; + uint8_t kid_ctx_len = 0; + ASSERT_TRUE(coap_get_header_oscore(&packet, &piv, &piv_len, &kid, &kid_len, + &kid_ctx, &kid_ctx_len)); + EXPECT_EQ(piv_len, piv_in.size()); + EXPECT_EQ(0, memcmp(piv, &piv_in[0], piv_len)); + EXPECT_EQ(kid_len, kid_in.size()); + EXPECT_EQ(0, memcmp(kid, &kid_in[0], kid_len)); + EXPECT_EQ(kid_ctx_len, kid_ctx_in.size()); + EXPECT_EQ(0, memcmp(kid_ctx, &kid_ctx_in[0], kid_ctx_len)); +} + /* Test cases from RFC 8613 */ /* C.1. Test Vector 1: Key Derivation with Master Salt */ diff --git a/tests/gtest/Collection.cpp b/tests/gtest/Collection.cpp index a90a22415d..55aa00c8d6 100644 --- a/tests/gtest/Collection.cpp +++ b/tests/gtest/Collection.cpp @@ -26,6 +26,22 @@ namespace oc { +std::optional +Collection::ParseLinksPayload(const oc_rep_t *rep) +{ + Collection::Links links; + for (auto link = rep; link != nullptr; link = link->next) { + auto linkData = Link::ParsePayload(link->value.object); + if (linkData) { + links[linkData->href] = *linkData; + } + } + if (links.empty()) { + return std::nullopt; + } + return links; +} + std::optional Collection::ParsePayload(const oc_rep_t *rep) { diff --git a/tests/gtest/Collection.h b/tests/gtest/Collection.h index b76fa5a4c2..1341e27549 100644 --- a/tests/gtest/Collection.h +++ b/tests/gtest/Collection.h @@ -39,12 +39,14 @@ using oc_collection_unique_ptr = class Collection { public: + using Links = std::unordered_map; + struct Data { std::optional baseline; std::vector rts; std::vector rts_m; - std::unordered_map links; + Links links; std::unordered_map properties; }; @@ -59,6 +61,7 @@ class Collection { using BatchData = std::unordered_map; static std::optional ParsePayload(const oc_rep_t *rep); + static std::optional ParseLinksPayload(const oc_rep_t *rep); static BatchData ParseBatchPayload(const oc_rep_t *rep); }; diff --git a/tests/gtest/Device.cpp b/tests/gtest/Device.cpp index e3b18b1e88..36a20c5793 100644 --- a/tests/gtest/Device.cpp +++ b/tests/gtest/Device.cpp @@ -130,7 +130,7 @@ Device::~Device() #ifndef _WIN32 pthread_cond_destroy(&cv_); pthread_mutex_destroy(&mutex_); -#endif /* _WIN32 */ +#endif /* !_WIN32 */ } void @@ -198,9 +198,6 @@ Device::Terminate() { OC_ATOMIC_STORE8(terminate_, 1); SignalEventLoop(); -#ifdef OC_REQUEST_HISTORY - oc_request_history_init(); -#endif /* OC_REQUEST_HISTORY */ } void @@ -236,6 +233,10 @@ Device::PoolEventsMs(uint64_t mseconds, bool addDelay) Unlock(); } +#ifdef OC_REQUEST_HISTORY + oc_request_history_init(); +#endif /* OC_REQUEST_HISTORY */ + oc_remove_delayed_callback(this, Device::QuitEvent); } diff --git a/tests/gtest/RepPool.cpp b/tests/gtest/RepPool.cpp index 7f605a2d54..beb98d341c 100644 --- a/tests/gtest/RepPool.cpp +++ b/tests/gtest/RepPool.cpp @@ -57,17 +57,23 @@ RepPool::Clear() } oc_rep_unique_ptr -RepPool::ParsePayload() +RepPool::ParsePayload(const uint8_t *payload, size_t payload_len) { - const uint8_t *payload = oc_rep_get_encoder_buf(); - int payload_len = oc_rep_get_encoded_payload_size(); - EXPECT_NE(payload_len, -1); oc_rep_set_pool(&rep_objects_); oc_rep_t *rep = nullptr; EXPECT_EQ(CborNoError, oc_parse_rep(payload, payload_len, &rep)); return oc_rep_unique_ptr(rep, &oc_free_rep); } +oc_rep_unique_ptr +RepPool::ParsePayload() +{ + const uint8_t *payload = oc_rep_get_encoder_buf(); + int payload_len = oc_rep_get_encoded_payload_size(); + EXPECT_NE(payload_len, -1); + return ParsePayload(payload, static_cast(payload_len)); +} + std::vector RepPool::GetJson(const oc_rep_t *rep, bool pretty_print) { diff --git a/tests/gtest/RepPool.h b/tests/gtest/RepPool.h index f916a19020..4ef17a773b 100644 --- a/tests/gtest/RepPool.h +++ b/tests/gtest/RepPool.h @@ -40,6 +40,7 @@ class RepPool { oc_memb *GetRepObjectsPool() { return &rep_objects_; } + oc_rep_unique_ptr ParsePayload(const uint8_t *payload, size_t payload_len); /* convert global CborEncoder to oc_rep_t */ oc_rep_unique_ptr ParsePayload(); diff --git a/tests/gtest/Resource.cpp b/tests/gtest/Resource.cpp index e7c8a10e0f..0414dec300 100644 --- a/tests/gtest/Resource.cpp +++ b/tests/gtest/Resource.cpp @@ -28,6 +28,8 @@ #include "api/oc_collection_internal.h" #endif /* OC_COLLECTIONS */ +#include + namespace oc { void @@ -189,4 +191,24 @@ SetAccessInRFOTM(oc_core_resource_t index, size_t device, bool make_public, #endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ +#ifdef OC_HAS_FEATURE_ETAG + +void +AssertETag(oc_coap_etag_t etag1, uint64_t etag2) +{ + ASSERT_EQ(sizeof(etag2), etag1.length); + std::array etag2_buf{}; + memcpy(&etag2_buf[0], &etag2, etag2_buf.size()); + ASSERT_EQ(0, memcmp(&etag1.value[0], &etag2_buf[0], etag1.length)); +} + +void +AssertResourceETag(oc_coap_etag_t etag, const oc_resource_t *resource) +{ + ASSERT_NE(nullptr, resource); + AssertETag(etag, resource->etag); +} + +#endif /* OC_HAS_FEATURE_ETAG */ + } // namespace oc diff --git a/tests/gtest/Resource.h b/tests/gtest/Resource.h index 12cef5aa2c..73bea4f3a9 100644 --- a/tests/gtest/Resource.h +++ b/tests/gtest/Resource.h @@ -21,6 +21,10 @@ #include "oc_ri.h" #include "util/oc_features.h" +#ifdef OC_HAS_FEATURE_ETAG +#include "messaging/coap/oc_coap.h" +#endif /* OC_HAS_FEATURE_ETAG */ + #include #include #include @@ -59,4 +63,10 @@ bool SetAccessInRFOTM(oc_core_resource_t index, size_t device, bool make_public, #endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ +#ifdef OC_HAS_FEATURE_ETAG +void AssertETag(oc_coap_etag_t etag1, uint64_t etag2); + +void AssertResourceETag(oc_coap_etag_t etag, const oc_resource_t *resource); +#endif /* OC_HAS_FEATURE_ETAG */ + } // namespace oc diff --git a/tests/gtest/coap/Message.cpp b/tests/gtest/coap/Message.cpp new file mode 100644 index 0000000000..0e7a59474c --- /dev/null +++ b/tests/gtest/coap/Message.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "api/oc_helpers_internal.h" +#include "api/oc_message_internal.h" +#include "Message.h" +#include "messaging/coap/coap_internal.h" +#include "messaging/coap/constants.h" +#include "messaging/coap/options_internal.h" +#include "tests/gtest/Device.h" +#include "util/oc_memb.h" + +#ifdef OC_TCP +#include "messaging/coap/signal_internal.h" +#endif // OC_TCP + +#include + +namespace oc::coap::message { + +constexpr size_t kPoolSize = 8; +OC_MEMB(test_messages, oc_message_t, kPoolSize); + +bool +SyncQueue::IsEmpty() const +{ + std::lock_guard lock(mutex_); + return queue_.empty(); +} + +void +SyncQueue::Push(oc_message_unique_ptr &&message) +{ + std::lock_guard lock(mutex_); + queue_.emplace_back(std::move(message)); +} + +oc_message_unique_ptr +SyncQueue::Pop() +{ + std::lock_guard lock(mutex_); + if (queue_.empty()) { + return oc_message_unique_ptr(nullptr, nullptr); + } + auto message = std::move(queue_.front()); + queue_.pop_front(); + return message; +} + +oc_message_unique_ptr +WaitForMessage(SyncQueue &queue, + std::function runFor, + std::chrono::milliseconds timeout) +{ + while (true) { + auto msg = queue.Pop(); + if (!msg) { + runFor(timeout); + msg = queue.Pop(); + } + + if (!msg) { + return message::oc_message_unique_ptr(nullptr, nullptr); + } + +#ifdef OC_TCP + coap_packet_t packet; + if (coap_tcp_parse_message(&packet, msg->data, msg->length, false) != + COAP_NO_ERROR) { + return message::oc_message_unique_ptr(nullptr, nullptr); + } + if (coap_check_signal_message(packet.code)) { + continue; + } +#endif // OC_TCP + return msg; + } +} + +oc_message_unique_ptr +WaitForMessage(SyncQueue &queue, std::chrono::milliseconds timeout) +{ + return WaitForMessage( + queue, + [](std::chrono::milliseconds t) { oc::TestDevice::PoolEventsMsV1(t); }, + timeout); +} + +oc_message_unique_ptr +AllocateMessage() +{ + oc_message_t *message = oc_allocate_message_from_pool(&test_messages); + if (message == nullptr) { + return oc_message_unique_ptr(nullptr, oc_message_unref); + } + return oc_message_unique_ptr(message, oc_message_unref); +} + +#ifdef OC_TCP + +namespace tcp { + +oc_message_unique_ptr +RegisterObserve(token_t token, const std::string &uri, const std::string &query, + const oc_endpoint_t *endpoint) +{ + coap_packet_t pkt; + coap_tcp_init_message(&pkt, COAP_GET); + + if (!token.empty()) { + coap_set_token(&pkt, &token[0], token.size()); + } + coap_options_set_accept(&pkt, APPLICATION_VND_OCF_CBOR); + coap_options_set_uri_path(&pkt, uri.c_str(), uri.length()); + if (!query.empty()) { + coap_options_set_uri_query(&pkt, query.c_str(), query.length()); + } + coap_options_set_observe(&pkt, OC_COAP_OPTION_OBSERVE_REGISTER); + + auto message = AllocateMessage(); + if (message.get() == nullptr) { + return message; + } + memcpy(&message->endpoint, endpoint, sizeof(oc_endpoint_t)); + message->length = + coap_serialize_message(&pkt, message->data, oc_message_buffer_size()); + + return message; +} + +} // namespace tcp + +#endif // OC_TCP + +} // namespace oc::coap::message diff --git a/tests/gtest/coap/Message.h b/tests/gtest/coap/Message.h new file mode 100644 index 0000000000..a448a81cc2 --- /dev/null +++ b/tests/gtest/coap/Message.h @@ -0,0 +1,84 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#pragma once + +#include "oc_endpoint.h" +#include "port/oc_connectivity.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace oc::coap::message { + +using oc_message_unique_ptr = + std::unique_ptr; + +using token_t = std::vector; + +class SyncQueue { +public: + SyncQueue() = default; + ~SyncQueue() = default; + + SyncQueue(const SyncQueue &) = delete; + SyncQueue &operator=(const SyncQueue &) = delete; + SyncQueue(SyncQueue &&) = delete; + SyncQueue &operator=(SyncQueue &&) = delete; + + void Push(oc_message_unique_ptr &&message); + oc_message_unique_ptr Pop(); + bool IsEmpty() const; + +private: + mutable std::mutex mutex_; + std::deque queue_; +}; + +/** Wait for the synchronized queue to receive a message until a timeout */ +oc_message_unique_ptr WaitForMessage( + SyncQueue &queue, std::function runFor, + std::chrono::milliseconds timeout); + +/** Use oc::TestDevice to poll and wait the synchronized queue to receive a + * message. */ +oc_message_unique_ptr WaitForMessage(SyncQueue &queue, + std::chrono::milliseconds timeout); + +/** Allocate message from test pool */ +oc_message_unique_ptr AllocateMessage(); + +#ifdef OC_TCP + +namespace tcp { + +/** Create observation registration message */ +oc_message_unique_ptr RegisterObserve(token_t token, const std::string &uri, + const std::string &query, + const oc_endpoint_t *endpoint); + +} // namespace tcp + +#endif // OC_TCP + +} // namespace oc::coap::message diff --git a/tests/gtest/coap/TCPClient.cpp b/tests/gtest/coap/TCPClient.cpp new file mode 100644 index 0000000000..17dbc196ea --- /dev/null +++ b/tests/gtest/coap/TCPClient.cpp @@ -0,0 +1,341 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "util/oc_features.h" + +#if defined(OC_TCP) + +#include "api/oc_message_internal.h" +#include "api/oc_tcp_internal.h" +#include "port/common/oc_tcp_socket_internal.h" +#include "port/common/posix/oc_fcntl_internal.h" +#include "port/common/posix/oc_socket_internal.h" +#include "TCPClient.h" +#include "util/oc_memb.h" + +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else /* !_WIN32 */ +#include +#include +#include +#include +#endif /* _WIN32 */ + +using namespace std::chrono_literals; + +namespace oc::coap { + +namespace { + +bool +net_init() +{ +#ifdef _WIN32 + static bool wsa_init_done = false; + WSADATA wsaData; + if (!wsa_init_done) { + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + return false; + + wsa_init_done = true; + } +#endif // _WIN32 + return true; +} + +#ifdef OC_HAS_FEATURE_TCP_ASYNC_CONNECT + +bool +wait_for_connection(oc_tcp_socket_t *socket, int timeout_s) +{ + fd_set wset; + FD_ZERO(&wset); + FD_SET(socket->fd, &wset); + timeval tval{}; + tval.tv_sec = timeout_s; + tval.tv_usec = 0; + int n; + if ((n = select(socket->fd + 1, NULL, &wset, NULL, + timeout_s != 0 ? &tval : NULL)) == 0) { +#ifdef _WIN32 + WSASetLastError(WSAETIMEDOUT); +#else /* !_WIN32 */ + errno = ETIMEDOUT; +#endif /* _WIN32 */ + return false; + } + +#ifdef _WIN32 + if (n == SOCKET_ERROR) { + OC_ERR("select error: %d", WSAGetLastError()); + return false; + } +#else /* !_WIN32 */ + if (n < 0) { + OC_ERR("select error: %d", errno); + return false; + } +#endif /* _WIN32 */ + + if (!FD_ISSET(socket->fd, &wset)) { + OC_ERR("select error: sockfd not set"); + return false; + } + +#ifndef _WIN32 + int error = 0; + socklen_t len = sizeof(error); + if (getsockopt(socket->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + OC_ERR("get socket options error: %d", errno); + return false; /* Solaris pending error */ + } + if (error != 0) { + OC_ERR("socket error: %d", error); + return false; + } +#endif /* !_WIN32 */ + + socket->state = OC_TCP_SOCKET_STATE_CONNECTED; + return true; +} + +#endif // OC_HAS_FEATURE_TCP_ASYNC_CONNECT + +} // namespace + +TCPClient::TCPClient(OnRead onRead) + : onRead_(onRead) + , socket_(OC_INVALID_SOCKET) + , terminated_(false) +{ +} + +TCPClient::~TCPClient() +{ + Close(); +} + +OC_SOCKET_T +TCPClient::ConnectAndWait(const oc_endpoint_t *endpoint) +{ + sockaddr_storage receiver = oc_socket_get_address(endpoint); +#ifdef OC_HAS_FEATURE_TCP_ASYNC_CONNECT + oc_tcp_socket_t ts = oc_tcp_socket_connect(endpoint, &receiver); + if (ts.state == -1) { + return OC_INVALID_SOCKET; + } + + if (ts.state == OC_TCP_SOCKET_STATE_CONNECTED) { + goto done; + } + + if (!wait_for_connection(&ts, 5)) { + OC_CLOSE_SOCKET(ts.fd); + return OC_INVALID_SOCKET; + } + +done: + if (!oc_fcntl_set_blocking(ts.fd)) { + OC_CLOSE_SOCKET(ts.fd); + return OC_INVALID_SOCKET; + } + return ts.fd; +#else /* !OC_HAS_FEATURE_TCP_ASYNC_CONNECT */ + return oc_tcp_socket_connect_and_wait(endpoint, &receiver, 5); +#endif /* OC_HAS_FEATURE_TCP_ASYNC_CONNECT */ +} + +bool +TCPClient::Connect(const oc_endpoint_t *endpoint) +{ + if (!net_init()) { + return false; + } + auto sock = ConnectAndWait(endpoint); + if (sock == OC_INVALID_SOCKET) { + return false; + } + endpoint_ = *endpoint; + endpoint_.next = nullptr; + socket_ = sock; + return true; +} + +void +TCPClient::Close() +{ + if (socket_ != OC_INVALID_SOCKET) { + OC_CLOSE_SOCKET(socket_); + socket_ = OC_INVALID_SOCKET; + } +} + +static bool +wasInterrupted() +{ +#ifdef _WIN32 + return WSAGetLastError() == WSAEINTR; +#else /* !_WIN32 */ + return errno == EINTR; +#endif /* _WIN32 */ +} + +long +TCPClient::Send(const uint8_t *data, size_t size) +{ + if (socket_ == OC_INVALID_SOCKET) { + throw std::string("socket is not connected"); + } + + size_t bytes_sent = 0; + do { +#if defined(__linux__) || defined(__ANDROID__) || defined(ESP_PLATFORM) + int flags = MSG_NOSIGNAL; +#else /* !__linux__ && !__ANDROID__ && !ESP_PLATFORM */ + int flags = 0; +#endif /* __linux__ || __ANDROID__ || ESP_PLATFORM */ + // cast to char* to make Windows happy + ssize_t send_len = + send(socket_, (const char *)data + bytes_sent, size - bytes_sent, flags); + if (send_len < 0) { + if (wasInterrupted()) { + continue; + } + if (bytes_sent == 0) { + return -1; + } + return static_cast(bytes_sent); + } + bytes_sent += send_len; + } while (bytes_sent < size); + + assert(bytes_sent <= LONG_MAX); + return static_cast(bytes_sent); +} + +message::oc_message_unique_ptr +TCPClient::ReceiveMessage() +{ + auto msg = message::AllocateMessage(); + if (msg.get() == nullptr) { + return msg; + } + + long size = Receive(msg->data, oc_message_buffer_size()); + if (size < 0) { + msg.reset(); + return msg; + } + msg->length = static_cast(size); + return msg; +} + +long +TCPClient::Receive(uint8_t *buffer, size_t size) +{ + size_t total_length = 0; + size_t want_read = + COAP_TCP_DEFAULT_HEADER_LEN + COAP_TCP_MAX_EXTENDED_LENGTH_LEN; + if (size < want_read) { + return -1; + } + + size_t written = 0; + do { + // cast to char* to make Windows happy + ssize_t count = recv(socket_, (char *)buffer + written, want_read, 0); + if (count < 0) { + if (wasInterrupted()) { + continue; + } + return -1; + } + if (count == 0) { + return -1; + } + written += (size_t)count; + want_read -= (size_t)count; + + if (total_length == 0) { + long length_from_header = oc_tcp_get_total_length_from_header( + buffer, size, (endpoint_.flags & SECURED) != 0); + if (length_from_header < 0) { + return -1; + } + total_length = static_cast(length_from_header); + want_read = total_length - static_cast(count); + } + } while (total_length > written); + + return static_cast(written); +} + +bool +TCPClient::Poll(std::chrono::milliseconds timeout) +{ + if (socket_ == OC_INVALID_SOCKET) { + throw std::string("socket is not connected"); + } + + fd_set readFDs; + FD_ZERO(&readFDs); + FD_SET(socket_, &readFDs); + + timeval tv{}; + if (timeout.count() > 0) { + tv.tv_sec = timeout.count() / 1000; + tv.tv_usec = (timeout.count() % 1000) * 1000; + } + + int ret; + do { + ret = select(socket_ + 1, &readFDs, nullptr, nullptr, + timeout.count() == 0 ? nullptr : &tv); + } while (ret < 0 && wasInterrupted()); + + return FD_ISSET(socket_, &readFDs); +} + +void +TCPClient::Run() +{ + while (!terminated_) { + if (Poll(100ms)) { + auto message = ReceiveMessage(); + if (!message) { + break; + } + onRead_(std::move(message)); + } + } +} + +void +TCPClient::Terminate() +{ + terminated_ = true; +} + +} // namespace oc::coap + +#endif // OC_TCP diff --git a/tests/gtest/coap/TCPClient.h b/tests/gtest/coap/TCPClient.h new file mode 100644 index 0000000000..028aaed10b --- /dev/null +++ b/tests/gtest/coap/TCPClient.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#pragma once + +#include "util/oc_features.h" + +#ifdef OC_TCP + +#include "messaging/coap/coap_internal.h" +#include "Message.h" +#include "oc_endpoint.h" +#include "port/common/oc_tcp_socket_internal.h" + +#include +#include +#include +#include +#include +#include + +namespace oc::coap { + +using OnRead = std::function; + +class TCPClient { +public: + TCPClient(OnRead onRead); + ~TCPClient(); + + TCPClient(TCPClient &) = delete; + TCPClient &operator=(const TCPClient &) = delete; + TCPClient(TCPClient &&) noexcept = delete; + TCPClient &operator=(TCPClient &&) = delete; + + bool Connect(const oc_endpoint_t *endpoint); + void Close(); + + long Send(const uint8_t *data, size_t size); + + void Run(); + void Terminate(); + +private: + OnRead onRead_; + oc_endpoint_t endpoint_; + OC_SOCKET_T socket_; + std::atomic_bool terminated_; + + OC_SOCKET_T ConnectAndWait(const oc_endpoint_t *endpoint); + + message::oc_message_unique_ptr ReceiveMessage(); + long Receive(uint8_t *buffer, size_t size); + + bool Poll( + std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); +}; + +} // namespace oc::coap + +#endif // OC_TCP