diff --git a/.github/workflows/CLA.yml b/.github/workflows/CLA.yml index a5c6adcf5d..8908887ea7 100644 --- a/.github/workflows/CLA.yml +++ b/.github/workflows/CLA.yml @@ -3,7 +3,7 @@ on: issue_comment: types: [created] pull_request_target: - types: [opened,closed,synchronize] + types: [opened, closed, synchronize] jobs: CLAssistant: @@ -16,15 +16,15 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # the below token should have repo scope and must be manually added by you in the repository's secret - PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} with: - path-to-signatures: 'signatures/version1/cla.json' + path-to-signatures: "signatures/version1/cla.json" #path-to-document: 'https://github.com/cla-assistant/github-action/blob/master/SAPCLA.md' # e.g. a CLA or a DCO document - path-to-document: 'https://github.com/iotivity/iotivity-lite/blob/master/LICENSE.md' # branch should not be protected - branch: 'main' + path-to-document: "https://github.com/iotivity/iotivity-lite/blob/master/LICENSE.md" # branch should not be protected + branch: "main" allowlist: user1,bot* - #below are the optional inputs - If the optional inputs are not given, then default values will be taken + #below are the optional inputs - If the optional inputs are not given, then default values will be taken #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) #remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository) #create-file-commit-message: 'For example: Creating file for storing CLA Signatures' diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 554a517a9f..d09da37945 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -6,16 +6,16 @@ name: Check formatting on: # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "check-formatting" + # This workflow contains a single job called "check-formatting" check-formatting: # The type of runner that the job will run on runs-on: ubuntu-latest @@ -39,4 +39,3 @@ jobs: cmake ../. make format git diff --exit-code --ignore-submodules=all - diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml index de38db7646..7e54f586da 100644 --- a/.github/workflows/cmake-linux.yml +++ b/.github/workflows/cmake-linux.yml @@ -43,6 +43,8 @@ jobs: - args: "-DOC_SECURITY_ENABLED=OFF -DOC_TCP_ENABLED=ON" # secure off, ipv4 on, tcp on - args: "-DOC_SECURITY_ENABLED=OFF -DOC_TCP_ENABLED=ON -DOC_IPV4_ENABLED=ON" + # collection create if on, push notification on, tcp off + - args: "-DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_PUSH_ENABLED=ON" steps: - name: Checkout repository diff --git a/.github/workflows/ctt-bot.yml b/.github/workflows/ctt-bot.yml index a654b1ccf6..8e2e760c62 100644 --- a/.github/workflows/ctt-bot.yml +++ b/.github/workflows/ctt-bot.yml @@ -24,7 +24,7 @@ jobs: issue-number: ${{ github.event.pull_request.number }} body: | :tada: Thank you for your code contribution! To guarantee the change/addition is conformant to the [OCF Specification](https://openconnectivity.org), we would like to ask you to execute OCF Conformance Testing of your change :point_up: **when your work is ready to be reviewed**. - + --- :information_source: To verify your latest change (${{ github.event.pull_request.head.sha }}), label this PR with `OCF Conformance Testing`. diff --git a/.github/workflows/doxygen-publish.yml b/.github/workflows/doxygen-publish.yml index fa2b075ae0..8e945981a8 100644 --- a/.github/workflows/doxygen-publish.yml +++ b/.github/workflows/doxygen-publish.yml @@ -2,9 +2,9 @@ name: CI-doxygen-publish on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] types: [closed] # Allows you to run this workflow manually from the Actions tab diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index 81acc54ba3..c55383af9e 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -2,9 +2,9 @@ name: doxygen documentation on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -25,4 +25,3 @@ jobs: doxygen --version cd ${{ github.workspace }}/iotivity-lite/tools ./build_doc.sh - diff --git a/.github/workflows/sonar-cloud-analysis.yml b/.github/workflows/sonar-cloud-analysis.yml index d92165c209..be1d79b085 100644 --- a/.github/workflows/sonar-cloud-analysis.yml +++ b/.github/workflows/sonar-cloud-analysis.yml @@ -55,7 +55,7 @@ jobs: mkdir build && cd build # sonar-scanner currently cannot handle multi configuration configuration (ie. compilation of the same file with different defines), # so we enable as many features as possible so we get max. amount of code analysis - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=ON -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_WKCORE_ENABLED=ON -DOC_SOFTWARE_UPDATE_ENABLED=ON -DBUILD_TESTING=ON .. + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=ON -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_WKCORE_ENABLED=ON -DOC_SOFTWARE_UPDATE_ENABLED=ON -DOC_PUSH_ENABLED=ON -DBUILD_TESTING=ON .. cd .. # for files defined in multiple cmake targets, sonar-scanner seems to take the configuration from the first compilation of the file, # so we force client-server target to be compiled first so we get analysis of code with both OC_CLIENT and OC_SERVER enabled diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 8e5fc6a953..4649671aab 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -29,5 +29,5 @@ jobs: - name: Build with clang-tidy run: | mkdir linuxbuild && cd linuxbuild - cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DOC_CLANG_TIDY_ENABLED=ON -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_WKCORE_ENABLED=ON -DOC_SOFTWARE_UPDATE_ENABLED=ON -DBUILD_TESTING=OFF .. + cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DOC_CLANG_TIDY_ENABLED=ON -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_WKCORE_ENABLED=ON -DOC_SOFTWARE_UPDATE_ENABLED=ON -DOC_PUSH_ENABLED=ON -DBUILD_TESTING=OFF .. make all diff --git a/.gitignore b/.gitignore index cf0e12fd84..47f827f16e 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,7 @@ service/resource-directory/client/unittest/obj build python/plgd_headers.config + +# eclipse config file +.cproject +.project diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a557d84ca..2e4d207303 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,8 @@ set(OC_WKCORE_ENABLED OFF CACHE BOOL "Enable well-known core resource.") set(OC_OSCORE_ENABLED ON CACHE BOOL "Enable oscore support.") set(OC_IPV4_ENABLED OFF CACHE BOOL "Enable IPv4 support.") set(OC_DNS_LOOKUP_IPV6_ENABLED OFF CACHE BOOL "Enable IPv6 DNS lookup.") +set(OC_PUSH_ENABLED OFF CACHE BOOL "Enable Push Notification.") +set(OC_PUSHDEBUG_ENABLED OFF CACHE BOOL "Enable debug messages for Push Notification.") set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -88,6 +90,14 @@ set(PRIVATE_COMPILE_DEFINITIONS "") set(PUBLIC_COMPILE_DEFINITIONS "") set(MBEDTLS_COMPILE_DEFINITIONS "__OC_PLATFORM" "__OC_RANDOM") +if(OC_PUSH_ENABLED) + list(APPEND PUBLIC_COMPILE_DEFINITIONS "OC_PUSH") + if(OC_PUSHDEBUG_ENABLED) + list(APPEND PUBLIC_COMPILE_DEFINITIONS "OC_PUSHDEBUG") + endif() + set(OC_DYNAMIC_ALLOCATION_ENABLED ON) + set(OC_COLLECTIONS_IF_CREATE_ENABLED ON) +endif() if(OC_DYNAMIC_ALLOCATION_ENABLED) list(APPEND PUBLIC_COMPILE_DEFINITIONS "OC_DYNAMIC_ALLOCATION") list(APPEND MBEDTLS_COMPILE_DEFINITIONS "OC_DYNAMIC_ALLOCATION") diff --git a/api/oc_core_res.c b/api/oc_core_res.c index 60412e875b..93fb7f93c7 100644 --- a/api/oc_core_res.c +++ b/api/oc_core_res.c @@ -14,6 +14,7 @@ // limitations under the License. */ +#include "util/oc_features.h" #include "oc_core_res.h" #include "api/cloud/oc_cloud_internal.h" #include "oc_api.h" @@ -51,6 +52,12 @@ static bool announce_con_res = false; static int res_latency = 0; static OC_ATOMIC_UINT32_T g_device_count = 0; +/*todo4me <2022/9/5> remove later*/ +#ifdef OC_HAS_FEATURE_PUSH +void oc_create_pushconf_resource(size_t device_index); +void oc_create_pushreceiver_resource(size_t device_index); +#endif + /* Although used several times in the OCF spec, "/oic/con" is not accepted by the spec. Use a private prefix instead. Update OC_NAMELEN_CON_RES if changing the value. @@ -422,6 +429,11 @@ oc_core_add_new_device(const char *uri, const char *rt, const char *name, oc_create_cloudconf_resource(device_count); #endif /* OC_CLIENT && OC_SERVER && OC_CLOUD */ +#ifdef OC_HAS_FEATURE_PUSH + oc_create_pushconf_resource(device_count); + oc_create_pushreceiver_resource(device_count); +#endif + oc_device_info[device_count].data = data; if (oc_connectivity_init(device_count) < 0) { diff --git a/api/oc_events.h b/api/oc_events.h index 1c45fcdbdd..9fe6974031 100644 --- a/api/oc_events.h +++ b/api/oc_events.h @@ -52,6 +52,10 @@ typedef enum { SW_UPDATE_UPGRADING, SW_UPDATE_DONE, #endif /* OC_SOFTWARE_UPDATE */ + +#ifdef OC_HAS_FEATURE_PUSH + PUSH_RSC_STATE_CHANGED, +#endif /* OC_HAS_FEATURE_PUSH */ __NUM_OC_EVENT_TYPES__ } oc_events_t; diff --git a/api/oc_helpers.c b/api/oc_helpers.c index 47faacd117..22ec2c8655 100644 --- a/api/oc_helpers.c +++ b/api/oc_helpers.c @@ -105,6 +105,13 @@ _oc_free_string( } } +void +oc_set_string(oc_string_t *ocstring, const char *str, size_t str_len) +{ + oc_free_string(ocstring); + oc_new_string(ocstring, str, str_len); +} + void oc_concat_strings(oc_string_t *concat, const char *str1, const char *str2) { diff --git a/api/oc_main.c b/api/oc_main.c index 873deda754..c91daef7b8 100644 --- a/api/oc_main.c +++ b/api/oc_main.c @@ -70,6 +70,10 @@ static bool *drop_commands; static bool drop_commands[OC_MAX_NUM_DEVICES]; #endif /* !OC_DYNAMIC_ALLOCATION */ +#ifdef OC_HAS_FEATURE_PUSH +void oc_push_free(); +#endif + static bool initialized = false; static const oc_handler_t *app_callbacks; static oc_factory_presets_t factory_presets; @@ -374,6 +378,10 @@ oc_main_shutdown(void) oc_collections_free_rt_factories(); #endif /* OC_COLLECTIONS && OC_SERVER && OC_COLLECTIONS_IF_CREATE */ +#ifdef OC_HAS_FEATURE_PUSH + oc_push_free(); +#endif + oc_ri_shutdown(); #ifdef OC_SECURITY @@ -434,4 +442,4 @@ bool oc_drop_command(size_t device) { return drop_commands[device]; -} \ No newline at end of file +} diff --git a/api/oc_push.c b/api/oc_push.c new file mode 100644 index 0000000000..a00dbf65bc --- /dev/null +++ b/api/oc_push.c @@ -0,0 +1,2699 @@ +/**************************************************************************** + * + * Copyright 2021 ETRI All Rights Reserved. + * + * 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. + * + * Created on: Aug 23, 2021, + * Author: Joo-Chul Kevin Lee (rune@etri.re.kr) + * + * + ****************************************************************************/ + +#include "util/oc_features.h" +#include "oc_push.h" + +#ifdef OC_HAS_FEATURE_PUSH + +#include "oc_api.h" +#include "oc_events.h" +#include "oc_rep.h" +#include "oc_endpoint.h" +#include "oc_ri.h" +#include "oc_core_res.h" +#include "oc_signal_event_loop.h" +#include "util/oc_process.h" +#include "util/oc_list.h" +#include "util/oc_mmem.h" +#include "util/oc_compiler.h" +#include + +#if defined(OC_PUSHDEBUG) || defined(OC_DEBUG) +#define OC_PUSH_DBG(...) OC_LOG("D", __VA_ARGS__) +#define OC_PUSH_WRN(...) OC_LOG("W", __VA_ARGS__) +#define OC_PUSH_ERR(...) OC_LOG("E", __VA_ARGS__) +#else +#define OC_PUSH_DBG(...) +#define OC_PUSH_WRN(...) +#define OC_PUSH_ERR(...) +#endif + +/** + * @brief Push Proxy state + */ +typedef enum { + OC_PP_WFP, ///< Wait For Provisioning + OC_PP_WFU, ///< Wait For Update + OC_PP_WFR, ///< Wait For Response + OC_PP_WFUM, ///< Wait For Update Mitigation + OC_PP_WFRM, ///< Wait For Response Mitigation + OC_PP_ERR, ///< Error + OC_PP_TOUT ///< Timeout +} oc_pp_state_t; + +/** + * @brief structure for handling "rt": ["oic.r.notificationselector", + * "oic.r.pushproxy"] Resource + */ +typedef struct oc_ns +{ + struct oc_ns *next; + oc_resource_t + *resource; ///< used to point ["oic.r.notificationselector", + ///< "oic.r.pushproxy"] Resource managed by iotivity-lite + /* notificaiton selector */ + oc_string_t phref; ///< oic.r.notificationselector:phref (optional) + oc_string_array_t prt; ///< oic.r.notificationselector:prt (optional) + oc_string_array_t pif; ///< oic.r.notificationselector:pif (optional) + /* push proxy */ + oc_string_t pushtarget_di; ///< device id of target (e.g. + ///< ocf://17087f8c-13e3-4849-4258-65af2a47df63) + oc_endpoint_t + pushtarget_ep; ///< endpoint of pushtarget (e.g. coaps://[fe80::b1d6]:1122) + oc_string_t targetpath; ///< path in pushtarget (e.g. /pushedLightSwitch) + oc_string_t pushqif; ///< oic.r.pushproxy:pushqif (deprecated) + oc_string_array_t sourcert; ///< oic.r.pushproxy:sourcert + oc_string_t state; ///< oic.r.pushproxy:state + void *user_data; ///< used to point updated pushable Resource +} oc_ns_t; + +/** + * @brief structure for member of "oic.r.pushreceiver:receivers" object array + */ +typedef struct oc_recv +{ + struct oc_recv *next; + oc_string_t receiveruri; ///< oic.r.pushreceiver:receivers:receiveruri + oc_string_array_t rts; ///< oic.r.pushreceiver:receivers:rts +} oc_recv_t; + +/** + * @brief structure for handling for Push Receiver Resource + */ +typedef struct oc_recvs +{ + struct oc_recvs *next; + oc_resource_t *resource; ///< used to point ["oic.r.pushreceiver"] Resource + ///< managed by iotivity-lite + OC_LIST_STRUCT(receivers); ///< oic.r.pushreceiver:receivers object array +} oc_recvs_t; + +/** + * @brief memory block for storing new collection member of Push Configuration + * Resource + */ +OC_MEMB(g_ns_instance_memb, oc_ns_t, 1); + +/** + * @brief `ns_col_list` keeps real data of all Notification Selector Resources + * (it includes all Resources of all Devices) + * + * each list member is instance of `oc_ns_t` + */ +OC_LIST(g_ns_list); + +/** + * @brief memory block definition for storing new Receiver object array of Push + * Receiver Resource + */ +OC_MEMB(g_recvs_instance_memb, oc_recvs_t, 1); + +/** + * @brief memory block definition for storing new Receiver object of Receiver + * object array + */ +OC_MEMB(g_recv_instance_memb, oc_recv_t, 1); + +/** + * @brief `g_recvs_list` keeps real data of all Receiver object in Push Receiver + * Resource (it includes all Receiver objects of Resource of all Devices) + * + * each list member is instance of `oc_recvs_t` + */ +OC_LIST(g_recvs_list); + +/** + * @brief memory block definition for storing properties representation of + * pushed resource + */ +OC_MEMB(g_rep_instance_memb, oc_rep_t, 1); + +/** + * @brief memory block definition for storing pushed resource representation + * list + */ +OC_MEMB(g_pushd_rsc_rep_instance_memb, oc_pushd_resource_rep_t, 1); + +/** + * @brief `pushed_rsc_list` keeps Resource representation of Pushed Resources + */ +OC_LIST(g_pushd_rsc_rep_list); + +/** + * @brief process which handles push notification + */ +OC_PROCESS(oc_push_process, "Push Notification handler"); + +const char *pp_state_strs[] = { + "waitingforprovisioning", /*OC_PP_WFP*/ + "waitingforupdate", /*OC_PP_WFU*/ + "waitingforresponse", /*OC_PP_WFR*/ + "waitingforupdatemitigation", /*OC_PP_WFUM*/ + "waitingforresponsemitigation", /*OC_PP_WFRM*/ + "error", /*OC_PP_ERR*/ + "timeout" /*OC_PP_TOUT*/ +}; + +/* + * mandatory property of oic.r.pushporxy, oic.r.pushreceivers + */ +enum { + PP_PUSHTARGET = 0x01, + PP_SOURCERT = 0x02, + PR_RECEIVERS = 0x01, + PR_RECEIVERURI = 0x02, + PR_RTS = 0x04 +}; + +/* + * if this callback function is provided by user, it will called whenever new + * push is arrived... + */ +static void (*oc_push_arrived)(oc_pushd_resource_rep_t *) = NULL; + +#define pp_statestr(i) (pp_state_strs[(i)]) + +/** + * @brief update Push Proxy state from state to new_state + * + * @param state oc_string_t + * @param new_state char * + */ +#define pp_update_state(state, new_state) \ + (oc_set_string(&(state), (new_state), strlen((new_state)))) + +/** + * @brief initialize oc_string_t object + */ +#define oc_init_string(str) \ + do { \ + (str).size = 0; \ + (str).ptr = NULL; \ + (str).next = NULL; \ + } while (0) + +/** + * @brief initialize oc_string_array_t object + */ +#define oc_init_string_array(str_array) oc_init_string((str_array)) + +void +oc_set_on_push_arrived(oc_on_push_arrived_t func) +{ + oc_push_arrived = func; +} + +/** + * @brief callback to be called to set existing (or just created by + * `get_ns_instance()`) data structure for `notification selector` + * with received Resource representation + * + * @param resource not used + * @param rep Resource representation structure + * @param data internal structure for storing `notification selector` + * resource (oc_memb struct for ["oic.r.notificationselector", + * "oic.r.pushproxy"] Resource) + * + * @return true:success, false:fail + */ +static bool +set_ns_properties(oc_resource_t *resource, oc_rep_t *rep, void *data) +{ + (void)resource; + bool pushtarget_is_updated = false; + +#define IS_PROPERTY(rep, prop) \ + oc_string_len((rep)->name) == (sizeof(prop) - 1) && \ + memcmp(oc_string((rep)->name), prop, sizeof(prop) - 1) == 0 + + /* + * `data` is set when new Notification Selector Resource is created + * by calling `oc_resource_set_properties_cbs()` in `get_ns_instance()` + */ + oc_ns_t *ns_instance = (oc_ns_t *)data; + while (rep != NULL) { + switch (rep->type) { + case OC_REP_STRING: + /* + * oic.r.notificationselector:phref + * - optional + */ + if (IS_PROPERTY(rep, "phref")) { + if (!strcmp(oc_string(rep->value.string), "")) { + oc_free_string(&ns_instance->phref); + } else { + oc_set_string(&ns_instance->phref, oc_string(rep->value.string), + oc_string_len(rep->value.string)); + } + OC_PUSH_DBG("oic.r.pushproxy:phref (%s)", oc_string(rep->value.string)); + break; + } + /* + * oic.r.pushproxy:pushtarget + * - mandatory + */ + if (IS_PROPERTY(rep, "pushtarget")) { + if (strcmp(oc_string(rep->value.string), "") == 0) { + /* NULL pushtarget ("") is still acceptable... */ + OC_PUSH_DBG("NULL \"pushtarget\" is received, still stay in " + "\"waitforprovisioning\" state..."); + + /* clear endpoint */ + memset(&ns_instance->pushtarget_ep, 0, + sizeof(ns_instance->pushtarget_ep)); + + /* clear target path */ + oc_set_string(&ns_instance->targetpath, "", 0); + + } else { + /* if non-NULL pushtarget.. */ + oc_endpoint_t *new_ep; + oc_string_t new_targetpath; + + new_ep = oc_new_endpoint(); + oc_init_string(new_targetpath); + + OC_PUSH_DBG("oic.r.pushproxy:pushtarget (%s)", + oc_string(rep->value.string)); + + if (oc_string_to_endpoint(&rep->value.string, new_ep, + &new_targetpath) < 0) { + OC_PUSH_ERR("oic.r.pushproxy:pushtarget (%s) parsing failed!", + oc_string(rep->value.string)); + + oc_free_endpoint(new_ep); + oc_free_string(&new_targetpath); + + return false; + } else { + oc_free_string(&ns_instance->targetpath); + + /* update with new values... */ + oc_endpoint_copy(&ns_instance->pushtarget_ep, new_ep); + oc_new_string(&ns_instance->targetpath, oc_string(new_targetpath), + oc_string_len(new_targetpath)); + + OC_PUSH_DBG("oic.r.pushproxy:pushtarget (%s)", + oc_string(rep->value.string)); + + /* return memory */ + oc_free_endpoint(new_ep); + oc_free_string(&new_targetpath); + + if (oc_string_len(ns_instance->targetpath)) { + OC_PUSH_DBG("oic.r.pushproxy:pushtarget parsing is successful! " + "targetpath (\"%s\")", + oc_string(ns_instance->targetpath)); + pushtarget_is_updated = true; + } else { + OC_PUSH_ERR("path part of \"pushtarget\" should not be NULL!!"); + return false; + } + } + } + break; + } + /* + * TODO4ME <2022/04/17> deprecated property, remove later... + * oic.r.pushproxy:pushqif + * - optional + */ + if (IS_PROPERTY(rep, "pushqif")) { + oc_set_string(&ns_instance->pushqif, oc_string(rep->value.string), + oc_string_len(rep->value.string)); + break; + } + /* + * oic.r.pushproxy:state + * - RETRIEVE: mandatory + * - UPDATE: optional + */ + if (IS_PROPERTY(rep, "state")) { + /* state can be modified only if Push Proxy is in "tout" or "err" state + */ + if (strcmp(oc_string(ns_instance->state), pp_statestr(OC_PP_ERR)) && + strcmp(oc_string(ns_instance->state), pp_statestr(OC_PP_TOUT))) { + OC_PUSH_ERR("state can be modified only if Push Proxy is in \"tout\" " + "or \"err\" state"); + return false; + } + + /* "waitingforupdate" is only acceptable value */ + if (strcmp(oc_string(rep->value.string), pp_statestr(OC_PP_WFU))) { + OC_PUSH_ERR( + "only \"waitingforupdate\" is allowed to reset \"state\""); + return false; + } + + OC_PUSH_DBG("state of Push Proxy (\"%s\") is reset (%s => %s)", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->state), + oc_string(rep->value.string)); + pp_update_state(ns_instance->state, oc_string(rep->value.string)); + break; + } + break; + + case OC_REP_STRING_ARRAY: + /* + * oic.r.notificationselector:prt + * - optional + */ + if (IS_PROPERTY(rep, "prt")) { + oc_free_string_array(&ns_instance->prt); + + oc_new_string_array( + &ns_instance->prt, + oc_string_array_get_allocated_size(rep->value.array)); + + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(rep->value.array); + i++) { + OC_PUSH_DBG("oic.r.pushproxy:prt (%s)", + oc_string_array_get_item(rep->value.array, i)); + oc_string_array_add_item( + ns_instance->prt, oc_string_array_get_item(rep->value.array, i)); + } + break; + } + /* + * oic.r.notificationselector:pif + * - optional + */ + if (IS_PROPERTY(rep, "pif")) { + oc_free_string_array(&ns_instance->pif); + + oc_new_string_array( + &ns_instance->pif, + oc_string_array_get_allocated_size(rep->value.array)); + + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(rep->value.array); + i++) { + OC_PUSH_DBG("oic.r.pushproxy:pif (%s)", + oc_string_array_get_item(rep->value.array, i)); + oc_string_array_add_item( + ns_instance->pif, oc_string_array_get_item(rep->value.array, i)); + } + break; + } + /* + * oic.r.pushproxy:sourcert + * - mandatory + */ + if (IS_PROPERTY(rep, "sourcert")) { + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(rep->value.array); + i++) { + if (strcmp(oc_string_array_get_item(rep->value.array, i), + "oic.r.pushpayload")) { + OC_PUSH_ERR("illegal oic.r.pushproxy:sourcert value (%s)!", + oc_string_array_get_item(rep->value.array, i)); + return false; + } + } + + oc_free_string_array(&ns_instance->sourcert); + oc_new_string_array( + &ns_instance->sourcert, + oc_string_array_get_allocated_size(rep->value.array)); + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(rep->value.array); + i++) { + OC_PUSH_DBG("oic.r.pushproxy:sourcert (%s)", + oc_string_array_get_item(rep->value.array, i)); + oc_string_array_add_item( + ns_instance->sourcert, + oc_string_array_get_item(rep->value.array, i)); + } + break; + } + break; + + default: + OC_PUSH_ERR("not supported Property (\"%s\")", oc_string(rep->name)); + break; + } + rep = rep->next; + } + + /* + * re-check condition which lets state move from "err"/"tout" to "wfu" + * - only configurator can change "state" when it is in "err"/"tout" state + */ + if (pushtarget_is_updated && + strcmp(oc_string(ns_instance->state), pp_statestr(OC_PP_ERR)) && + strcmp(oc_string(ns_instance->state), pp_statestr(OC_PP_TOUT)) && + strcmp(oc_string(ns_instance->state), pp_statestr(OC_PP_WFU))) { + OC_PUSH_DBG("state of Push Proxy (\"%s\") is changed (%s => %s)", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->state), pp_statestr(OC_PP_WFU)); + pp_update_state(ns_instance->state, pp_statestr(OC_PP_WFU)); + } else { + OC_PUSH_DBG("pushtarget of Push Proxy (\"%s\") is still NULL, or Push " + "Proxy is already in (\"%s\")", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->state)); + } + + return true; +} + +/** + * @brief callback to be called to fill the contents of `notification + * selector` from existing data structure (`oc_ns_t`) + * + * @param resource + * @param iface_mask interface to be used to send response + * @param data internal structure for storing `notification selector` + * resource (oc_memb struct for ["oic.r.notificationselector", + * "oic.r.pushproxy"] Resource) + */ +static void +get_ns_properties(oc_resource_t *resource, oc_interface_mask_t iface_mask, + void *data) +{ + /* + * `data` is set when new Notification Selector Resource is created + * by calling `oc_resource_set_properties_cbs()` in `get_ns_instance()` + */ + oc_ns_t *ns_instance = (oc_ns_t *)data; + + oc_rep_begin_root_object(); + switch (iface_mask) { + case OC_IF_BASELINE: + oc_process_baseline_interface(resource); + OC_FALLTHROUGH; + case OC_IF_RW: + /* + * phref optional + * - optional + */ + if (oc_string(ns_instance->phref)) { + oc_rep_set_text_string(root, phref, oc_string(ns_instance->phref)); + } + + /* + * prt + * - optional + */ + if (oc_string_array_get_allocated_size(ns_instance->prt)) { + oc_rep_open_array(root, prt); + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(ns_instance->prt); i++) { + oc_rep_add_text_string(prt, + oc_string_array_get_item(ns_instance->prt, i)); + } + oc_rep_close_array(root, prt); + } + + /* + * pif + * - optional + */ + if (oc_string_array_get_allocated_size(ns_instance->pif)) { + oc_rep_open_array(root, pif); + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(ns_instance->pif); i++) { + oc_rep_add_text_string(pif, + oc_string_array_get_item(ns_instance->pif, i)); + } + oc_rep_close_array(root, pif); + } + + /* + * pushtarget + */ + oc_string_t ep; + oc_string_t full_uri; + if (oc_endpoint_to_string(&ns_instance->pushtarget_ep, &ep) < 0) { + /* handle NULL pushtarget... */ +#if 0 + char ipv6addrstr[50], ipv4addrstr[50]; + inet_ntop(AF_INET6, ns_instance->pushtarget_ep.addr.ipv6.address, ipv6addrstr, 50); + inet_ntop(AF_INET, ns_instance->pushtarget_ep.addr.ipv4.address, ipv4addrstr, 50); + + if (!strcmp(ipv6addrstr, "::") && !strcmp(ipv4addrstr, "0.0.0.0")) + { + oc_new_string(&full_uri, "", strlen("")); + } +#endif + /* if pushtarget endpoint is NULL or illegal value.. just return NULL */ + oc_new_string(&full_uri, "", strlen("")); + } else { + if (oc_string_len(ns_instance->targetpath)) + oc_concat_strings(&full_uri, oc_string(ep), + oc_string(ns_instance->targetpath)); + else + oc_new_string(&full_uri, oc_string(ep), oc_string_len(ep)); + + oc_free_string(&ep); + } + + oc_rep_set_text_string(root, pushtarget, oc_string(full_uri)); + oc_free_string(&full_uri); + + /* + * pushqif + */ + oc_rep_set_text_string(root, pushqif, oc_string(ns_instance->pushqif)); + + /* + * sourcert + */ + if (oc_string_array_get_allocated_size(ns_instance->sourcert)) { + oc_rep_open_array(root, sourcert); + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(ns_instance->sourcert); + i++) { + oc_rep_add_text_string( + sourcert, oc_string_array_get_item(ns_instance->sourcert, i)); + } + oc_rep_close_array(root, sourcert); + } + + /* + * state + */ + oc_rep_set_text_string(root, state, oc_string(ns_instance->state)); + + break; + default: + break; + } + oc_rep_end_root_object(); +} + +/** + * @brief callback function used to RETRIEVE `Notification Selector + Push Proxy + * Resource which is autogenerated through `oic.if.crete` interface + * + * @param request request delivered from stack + * @param iface_mask OCF interface delivered from stack + * @param user_data oc_ns_t object + */ +static void +get_ns(oc_request_t *request, oc_interface_mask_t iface_mask, void *user_data) +{ + get_ns_properties(request->resource, iface_mask, user_data); + oc_send_response(request, OC_STATUS_OK); +} + +/** + * @brief callback function used to UPDATE `Notification Selector + Push Proxy + * Resource which is autogenerated through `oic.if.crete` interface + * + * @param request request delivered from stack + * @param iface_mask OCF interface delivered from stack + * @param user_data oc_ns_t object + */ +static void +post_ns(oc_request_t *request, oc_interface_mask_t iface_mask, void *user_data) +{ + (void)iface_mask; + + OC_PUSH_DBG("trying to update notification selector (\"%s\")... ", + oc_string(request->resource->uri)); + + if (set_ns_properties(request->resource, request->request_payload, + user_data)) { + oc_send_response(request, OC_STATUS_CHANGED); + } else { + oc_send_response(request, OC_STATUS_BAD_REQUEST); + } +} + +/** + * @brief callback function used to DELETE `Notification Selector + Push Proxy + * Resource which is autogenerated through `oic.if.crete` interface + * + * @param request request delivered from stack + * @param iface_mask OCF interface delivered from stack + * @param user_data oc_ns_t object + */ +static void +delete_ns(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) +{ + (void)iface_mask; + (void)user_data; + + OC_PUSH_DBG("trying to delete notification selector (\"%s\")... ", + oc_string(request->resource->uri)); + + if (oc_delete_resource(request->resource)) { + oc_send_response(request, OC_STATUS_DELETED); + } else { + oc_send_response(request, OC_STATUS_BAD_REQUEST); + } +} + +/** + * @brief callback callback for getting & creating new `Notification Selector + * + Push Proxy` Resource instance + * + * @param href href delivered from stack + * @param types Resource types delivered from stack + * @param bm operation bitmask deliverd from stack + * @param iface_mask OCF interface delivered froms stack + * @param device device index + * @return oc_resource_t for new `Notification Selector + Push Proxy` + */ +static oc_resource_t * +get_ns_instance(const char *href, oc_string_array_t *types, + oc_resource_properties_t bm, oc_interface_mask_t iface_mask, + size_t device) +{ + oc_ns_t *ns_instance = (oc_ns_t *)oc_memb_alloc(&g_ns_instance_memb); + if (ns_instance == NULL) { + OC_PUSH_ERR("oc_memb_alloc() error!"); + return NULL; + } + + ns_instance->resource = oc_new_resource( + NULL, href, (uint8_t)oc_string_array_get_allocated_size(*types), device); + if (ns_instance->resource == NULL) { + OC_PUSH_ERR("oc_new_resource() error!"); + oc_memb_free(&g_ns_instance_memb, ns_instance); + return NULL; + } + + for (int i = 0; i < (int)oc_string_array_get_allocated_size(*types); i++) { + const char *rt = oc_string_array_get_item(*types, i); + oc_resource_bind_resource_type(ns_instance->resource, rt); + } + oc_resource_bind_resource_interface(ns_instance->resource, iface_mask); + ns_instance->resource->properties = bm; + oc_resource_set_default_interface(ns_instance->resource, OC_IF_RW); + oc_resource_set_request_handler(ns_instance->resource, OC_GET, get_ns, + ns_instance); + oc_resource_set_request_handler(ns_instance->resource, OC_POST, post_ns, + ns_instance); + oc_resource_set_request_handler(ns_instance->resource, OC_DELETE, delete_ns, + ns_instance); + oc_resource_set_properties_cbs(ns_instance->resource, get_ns_properties, + ns_instance, set_ns_properties, ns_instance); + oc_add_resource(ns_instance->resource); + + OC_PUSH_DBG("new link (\"%s\") and corresponding resource for \"%s\" " + "collection is created", + oc_string(ns_instance->resource->uri), PUSHCONFIG_RESOURCE_PATH); + + /* initialize properties */ + oc_init_string(ns_instance->phref); + oc_init_string_array(ns_instance->prt); + oc_init_string_array(ns_instance->pif); + oc_init_string(ns_instance->pushtarget_di); + oc_init_string(ns_instance->targetpath); + oc_init_string_array(ns_instance->sourcert); + oc_new_string(&ns_instance->state, pp_statestr(OC_PP_WFP), + strlen(pp_statestr(OC_PP_WFP))); + ns_instance->user_data = NULL; + + OC_PUSH_DBG("state of Push Proxy (\"%s\") is initialized (%s)", + oc_string(ns_instance->resource->uri), pp_statestr(OC_PP_WFP)); + + /* + * add this new Notification Selector Resource to the list + * which keeps all Notification Selectors of all Devices + */ + oc_list_add(g_ns_list, ns_instance); + return ns_instance->resource; +} + +/** + * @brief callback for freeing existing notification selector + * (this callback is called when target resource pointed by `link` is deleted + * by calling `oc_delete_resource()`) + * + */ +static void +free_ns_instance(oc_resource_t *resource) +{ + OC_PUSH_DBG("delete ns_instance for resource (\"%s\")...", + oc_string(resource->uri)); + + oc_ns_t *ns_instance = (oc_ns_t *)oc_list_head(g_ns_list); + while (ns_instance) { + if (ns_instance->resource == resource) { + /* remove link target resource itself here... */ + oc_delete_resource(resource); + + /* remove oc_ns_t instance from list */ + oc_list_remove(g_ns_list, ns_instance); + + /* free each field of ns_instance */ + oc_free_string(&ns_instance->phref); + oc_free_string_array(&ns_instance->prt); + oc_free_string_array(&ns_instance->pif); + + oc_endpoint_t *ep = ns_instance->pushtarget_ep.next; + oc_endpoint_t *next; + while (ep) { + next = ep->next; + oc_free_endpoint(ep); + ep = next; + } + + oc_free_string(&ns_instance->targetpath); + oc_free_string(&ns_instance->pushqif); + oc_free_string_array(&ns_instance->sourcert); + + oc_free_string(&ns_instance->state); + + oc_memb_free(&g_ns_instance_memb, ns_instance); + return; + } + ns_instance = ns_instance->next; + } +} + +/** + * @brief initialize Push Configuration Resource + * + * @details + * for Origin Server: \n + * - Push Configuration ("oic.r.pushconfiguration") \n + * - Notification Selector + Push Proxy ("oic.r.notificationselector" + + * "oic.r.pushproxy") \n + * + * @param device_index device index + */ +void +oc_create_pushconf_resource(size_t device_index) +{ + /* create Push Configuration Resource */ + oc_resource_t *push_conf = oc_new_collection( + "Push Configuration", PUSHCONFIG_RESOURCE_PATH, 1, device_index); + + if (push_conf) { + oc_resource_bind_resource_type(push_conf, "oic.r.pushconfiguration"); + oc_resource_bind_resource_interface(push_conf, OC_IF_LL | OC_IF_CREATE | + OC_IF_BASELINE); + oc_resource_set_default_interface(push_conf, OC_IF_LL); + oc_resource_set_discoverable(push_conf, true); + + /* set "rts" Property */ + oc_collection_add_supported_rt(push_conf, "oic.r.notificationselector"); + oc_collection_add_supported_rt(push_conf, "oic.r.pushproxy"); + + /* LINK creation, deletion handler */ + oc_collections_add_rt_factory("oic.r.notificationselector", get_ns_instance, + free_ns_instance); + + oc_add_collection(push_conf); + } else { + OC_PUSH_ERR("oc_new_collection() error!"); + } +} + +/** + * @brief build response payload of pushed Resource + */ +static void +_build_rep_payload(CborEncoder *parent, oc_rep_t *rep) +{ + CborEncoder child; + oc_rep_t *obj; + + if (!rep) + return; + + switch (rep->type) { + case OC_REP_NIL: + break; + + case OC_REP_INT: + /* oc_rep_set_int(object, key, value) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + g_err |= oc_rep_encode_int(parent, rep->value.integer); + + break; + + case OC_REP_DOUBLE: + /* oc_rep_set_double(object, key, value) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + g_err |= oc_rep_encode_double(parent, rep->value.double_p); + + break; + + case OC_REP_BOOL: + /* oc_rep_set_boolean(object, key, value) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + g_err |= oc_rep_encode_boolean(parent, rep->value.boolean); + + break; + + case OC_REP_BYTE_STRING_ARRAY: + /* oc_rep_open_array(root, xxxx) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + memset(&child, 0, sizeof(child)); + g_err |= oc_rep_encoder_create_array(parent, &child, CborIndefiniteLength); + + /* oc_rep_add_byte_string(xxxx, str) */ + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(rep->value.array); i++) { + g_err |= oc_rep_encode_byte_string( + &child, (const uint8_t *)oc_string_array_get_item(rep->value.array, i), + oc_string_array_get_item_size(rep->value.array, i)); + } + + /* oc_rep_close_array(root, xxxx) */ + g_err |= oc_rep_encoder_close_container(parent, &child); + break; + + case OC_REP_STRING_ARRAY: + /* oc_rep_open_array(root, xxxx) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + memset(&child, 0, sizeof(child)); + g_err |= oc_rep_encoder_create_array(parent, &child, CborIndefiniteLength); + + /* oc_rep_add_text_string(xxxx, str) */ + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(rep->value.array); i++) { + if ((const char *)oc_string_array_get_item(rep->value.array, i) != NULL) { + g_err |= oc_rep_encode_text_string( + &child, oc_string_array_get_item(rep->value.array, i), + oc_string_array_get_item_size(rep->value.array, i)); + } else { + g_err |= oc_rep_encode_text_string(&child, "", 0); + } + } + + /* oc_rep_close_array(root, xxxx) */ + g_err |= oc_rep_encoder_close_container(parent, &child); + break; + + case OC_REP_BOOL_ARRAY: + /* oc_rep_open_array(root, xxxx) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + memset(&child, 0, sizeof(child)); + g_err |= oc_rep_encoder_create_array(parent, &child, CborIndefiniteLength); + + /* oc_rep_add_boolean(xxxx, value) */ + for (int i = 0; i < (int)rep->value.array.size; i++) { + g_err |= + oc_rep_encode_boolean(&child, ((char *)(rep->value.array.ptr))[i]); + } + + /* oc_rep_close_array(root, xxxx) */ + g_err |= oc_rep_encoder_close_container(parent, &child); + break; + + case OC_REP_DOUBLE_ARRAY: + /* oc_rep_open_array(root, xxxx) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + memset(&child, 0, sizeof(child)); + g_err |= oc_rep_encoder_create_array(parent, &child, CborIndefiniteLength); + + /* oc_rep_add_double(xxxx, value) */ + for (int i = 0; i < (int)rep->value.array.size; i++) { + g_err |= + oc_rep_encode_double(&child, ((double *)(rep->value.array.ptr))[i]); + } + + /* oc_rep_close_array(root, xxxx) */ + g_err |= oc_rep_encoder_close_container(parent, &child); + break; + + case OC_REP_INT_ARRAY: + /* oc_rep_open_array(root, xxxx) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + memset(&child, 0, sizeof(child)); + g_err |= oc_rep_encoder_create_array(parent, &child, CborIndefiniteLength); + + /* oc_rep_add_int(xxxx, value) */ + for (int i = 0; i < (int)rep->value.array.size; i++) { + g_err |= + oc_rep_encode_int(&child, ((int64_t *)(rep->value.array.ptr))[i]); + } + + /* oc_rep_close_array(root, xxxx) */ + g_err |= oc_rep_encoder_close_container(parent, &child); + break; + + case OC_REP_BYTE_STRING: + /* oc_rep_set_byte_string(object, key, value, length) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + g_err |= oc_rep_encode_byte_string( + parent, (const uint8_t *)oc_string(rep->value.string), + oc_string_len(rep->value.string)); + break; + + case OC_REP_STRING: + /* oc_rep_set_text_string(object, key, value) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + + if ((const char *)oc_string(rep->value.string) != NULL) { + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->value.string), + oc_string_len(rep->value.string)); + } else { + g_err |= oc_rep_encode_text_string(parent, "", 0); + } + break; + + case OC_REP_OBJECT: + + /* oc_rep_open_object(parent, key) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + memset(&child, 0, sizeof(child)); + g_err |= oc_rep_encoder_create_map(parent, &child, CborIndefiniteLength); + + _build_rep_payload(&child, rep->value.object); + + /* oc_rep_close_object(parent, key) */ + g_err |= oc_rep_encoder_close_container(parent, &child); + break; + + case OC_REP_OBJECT_ARRAY: + + /* oc_rep_open_array(root, xxxx) */ + g_err |= oc_rep_encode_text_string(parent, oc_string(rep->name), + oc_string_len(rep->name)); + memset(&child, 0, sizeof(child)); + g_err |= oc_rep_encoder_create_array(parent, &child, CborIndefiniteLength); + + /* recurse remaining objects... */ + obj = rep->value.object_array; + while (obj) { + /* oc_rep_object_array_begin_item(key) */ + CborEncoder obj_map; + memset(&obj_map, 0, sizeof(obj_map)); + g_err |= + oc_rep_encoder_create_map(&child, &obj_map, CborIndefiniteLength); + + _build_rep_payload(&obj_map, obj->value.object); + + /* oc_rep_object_array_end_item(key) */ + g_err |= oc_rep_encoder_close_container(&child, &obj_map); + obj = obj->next; + } + + /* oc_rep_close_array(root, xxxx) */ + g_err |= oc_rep_encoder_close_container(parent, &child); + break; + + default: + break; + } + + _build_rep_payload(parent, rep->next); +} + +/** + * @brief find Resource representation for pushed Resource + * + * @param uri uri for pushed Resource + * @param device_index device index which pushed Resource belongs to + * @return `oc_pushd_resource_rep_t` instance + */ +static oc_pushd_resource_rep_t * +_find_pushd_rsc_rep_by_uri(oc_string_t *uri, size_t device_index) +{ + oc_pushd_resource_rep_t *pushd_rsc_rep = + (oc_pushd_resource_rep_t *)(oc_list_head(g_pushd_rsc_rep_list)); + + while (pushd_rsc_rep) { + if (!strcmp(oc_string(pushd_rsc_rep->resource->uri), oc_string(*uri)) && + (pushd_rsc_rep->resource->device == device_index)) { + break; + } else { + pushd_rsc_rep = pushd_rsc_rep->next; + } + } + + return pushd_rsc_rep; +} + +/** + * @brief callback for RETRIEVE of pushed Resource + * + * @param request request delivered from stack + * @param iface_mask OCF interface delivered from stack + * @param user_data not used + */ +static void +get_pushd_rsc(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) +{ + (void)user_data; + + int result = OC_STATUS_OK; + oc_pushd_resource_rep_t *pushd_rsc_rep = _find_pushd_rsc_rep_by_uri( + &request->resource->uri, request->resource->device); + + if (!pushd_rsc_rep) { + OC_PUSH_ERR("something wrong, can't find resource representation for " + "pushed resource (%s)...", + oc_string(request->resource->uri)); + return; + } + + if (pushd_rsc_rep->rep) { + oc_rep_begin_root_object(); + switch (iface_mask) { + case OC_IF_BASELINE: + oc_process_baseline_interface(request->resource); + OC_FALLTHROUGH; + case OC_IF_R: + case OC_IF_RW: + _build_rep_payload(&root_map, pushd_rsc_rep->rep); + break; + default: + break; + } + oc_rep_end_root_object(); + + oc_send_response(request, result); + } else { + OC_PUSH_ERR("resource representation for pushed resource (%s) is found, " + "but no resource representation for it is built yet!", + oc_string(request->resource->uri)); + + oc_send_response(request, OC_STATUS_NOT_FOUND); + } +} + +/** + * @brief check if "rt" of pushed resource is part of "rts" (all value of + * "rt" should be part of "rts") + * + * @param recv_obj receiver object + * @param rep payload representation of pushed Resource + * @return not 0: found, 0: not found + */ +static bool +_check_pushd_rsc_rt(oc_recv_t *recv_obj, oc_rep_t *rep) +{ + bool result = 0; + size_t rt_len; + size_t rts_len; + size_t i; + size_t j; + + if (!recv_obj || !rep) + return result; + + rts_len = oc_string_array_get_allocated_size(recv_obj->rts); + + /* if "rts" is not configured (""), any pushed resource can be accepted... */ + if ((rts_len == 1) && !strcmp(oc_string_array_get_item(recv_obj->rts, 0), "")) + return 1; + + while (rep) { + if ((rep->type == OC_REP_STRING_ARRAY) && + !strcmp(oc_string(rep->name), "rt")) { + rt_len = oc_string_array_get_allocated_size(rep->value.array); + for (i = 0; i < rt_len; i++) { + for (j = 0; j < rts_len; j++) { + if (!strcmp(oc_string_array_get_item(rep->value.array, i), + oc_string_array_get_item(recv_obj->rts, j))) + break; + } + if (j == rts_len) { + break; + } + } + if (i == rt_len) + result = 1; + + break; + } + rep = rep->next; + } + + return result; +} + +/** + * @brief find Resource representation of Push Receiver Resource + * which belongs to `device_index` + * + * @param device_index device index + * @return `oc_recvs_t` instance + */ +static oc_recvs_t * +_find_recvs_by_device(size_t device_index) +{ + oc_recvs_t *recvs_instance = (oc_recvs_t *)oc_list_head(g_recvs_list); + + while (recvs_instance) { + if (recvs_instance->resource->device == device_index) { + break; + } else { + recvs_instance = recvs_instance->next; + } + } + + return recvs_instance; +} + +/** + * @brief build Resource representation for pushed Resource + * + * @details `oc_rep_set_pool()` should be called before calling this func + * + * @param org_rep Resource representation delivered from stack + * @return duplication of `org_rep` + */ +static oc_rep_t * +_create_pushd_rsc_rep(oc_rep_t *org_rep) +{ + if (!org_rep) + return org_rep; + + oc_rep_t *new_rep; + + new_rep = oc_alloc_rep(); + + new_rep->next = _create_pushd_rsc_rep(org_rep->next); + + new_rep->type = org_rep->type; + oc_new_string(&((new_rep)->name), oc_string(org_rep->name), + oc_string_len(org_rep->name)); + + switch (org_rep->type) { + case OC_REP_NIL: + break; + case OC_REP_INT: + new_rep->value.integer = org_rep->value.integer; + break; + case OC_REP_DOUBLE: + new_rep->value.double_p = org_rep->value.double_p; + break; + case OC_REP_BOOL: + new_rep->value.boolean = org_rep->value.boolean; + break; + case OC_REP_BYTE_STRING_ARRAY: + case OC_REP_STRING_ARRAY: + oc_new_string_array( + &(new_rep->value.array), + oc_string_array_get_allocated_size(org_rep->value.array)); + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(org_rep->value.array); + i++) { + oc_string_array_add_item( + new_rep->value.array, + oc_string_array_get_item(org_rep->value.array, i)); + } + break; + case OC_REP_BOOL_ARRAY: + oc_new_bool_array(&(new_rep->value.array), + oc_bool_array_size(org_rep->value.array)); + memcpy(new_rep->value.array.ptr, org_rep->value.array.ptr, + org_rep->value.array.size * sizeof(uint8_t)); + break; + case OC_REP_DOUBLE_ARRAY: + oc_new_double_array(&(new_rep->value.array), + oc_double_array_size(org_rep->value.array)); + memcpy(new_rep->value.array.ptr, org_rep->value.array.ptr, + org_rep->value.array.size * sizeof(double)); + break; + case OC_REP_INT_ARRAY: + oc_new_int_array(&(new_rep->value.array), + oc_int_array_size(org_rep->value.array)); + memcpy(new_rep->value.array.ptr, org_rep->value.array.ptr, + org_rep->value.array.size * sizeof(int64_t)); + break; + case OC_REP_BYTE_STRING: + case OC_REP_STRING: + oc_new_string(&(new_rep->value.string), oc_string(org_rep->value.string), + oc_string_len(org_rep->value.string)); + break; + case OC_REP_OBJECT: + new_rep->value.object = _create_pushd_rsc_rep(org_rep->value.object); + break; + case OC_REP_OBJECT_ARRAY: + new_rep->value.object_array = + _create_pushd_rsc_rep(org_rep->value.object_array); + break; + default: + break; + } + + return new_rep; +} + +/** + * @brief print Resource representation + */ +void +oc_print_pushd_resource(const oc_rep_t *payload) +{ + static int depth = 0; + char prefix_width = 3; + const char *prefix_str = " "; + char depth_prefix[1024]; + const oc_rep_t *rep = payload; + const oc_rep_t *obj; + int i; + + /* check buffer overflow */ + if ((size_t)(prefix_width * (depth + 1) + 1) > sizeof(depth_prefix)) { + return; + } + +#if 0 + depth_prefix[sizeof(depth_prefix) - 1] = '\0'; + depth++; + for (i = 0; i < depth; i++) { + strncpy(depth_prefix + (i * prefix_width), prefix_str, sizeof(depth_prefix)-(i * prefix_width)); + } + if (depth_prefix[sizeof(depth_prefix) - 1] != '\0') { + return; + } +#endif + depth++; + for (i = 0; i < depth; i++) { + strcpy(depth_prefix + (i * prefix_width), prefix_str); + } + + depth_prefix[i * prefix_width] = '\0'; + + if (!rep) { + OC_PUSH_DBG("no data!"); + depth--; + return; + } + + if (depth == 1) + PRINT("\n\n"); + + while (rep != NULL) { + switch (rep->type) { + case OC_REP_BOOL: + PRINT("%s%s: %d\n", depth_prefix, oc_string(rep->name), + rep->value.boolean); + break; + + case OC_REP_BOOL_ARRAY: + PRINT("%s%s: \n%s[\n", depth_prefix, oc_string(rep->name), depth_prefix); + for (i = 0; i < (int)oc_bool_array_size(rep->value.array); i++) { + PRINT("%s%s\"%d\"\n", depth_prefix, prefix_str, + oc_bool_array(rep->value.array)[i]); + } + PRINT("%s]\n", depth_prefix); + break; + + case OC_REP_INT: + PRINT("%s%s: %ld\n", depth_prefix, oc_string(rep->name), + rep->value.integer); + break; + + case OC_REP_INT_ARRAY: + PRINT("%s%s: \n%s[\n", depth_prefix, oc_string(rep->name), depth_prefix); + for (i = 0; i < (int)oc_int_array_size(rep->value.array); i++) { + PRINT("%s%s\"%ld\"\n", depth_prefix, prefix_str, + oc_int_array(rep->value.array)[i]); + } + PRINT("%s]\n", depth_prefix); + break; + + case OC_REP_DOUBLE: + PRINT("%s%s: %f\n", depth_prefix, oc_string(rep->name), + rep->value.double_p); + break; + + case OC_REP_DOUBLE_ARRAY: + PRINT("%s%s: \n%s[\n", depth_prefix, oc_string(rep->name), depth_prefix); + for (i = 0; i < (int)oc_double_array_size(rep->value.array); i++) { + PRINT("%s%s\"%f\"\n", depth_prefix, prefix_str, + oc_double_array(rep->value.array)[i]); + } + PRINT("%s]\n", depth_prefix); + break; + + case OC_REP_STRING: + PRINT("%s%s: \"%s\"\n", depth_prefix, oc_string(rep->name), + oc_string(rep->value.string)); + break; + + case OC_REP_STRING_ARRAY: + PRINT("%s%s: \n%s[\n", depth_prefix, oc_string(rep->name), depth_prefix); + for (i = 0; i < (int)oc_string_array_get_allocated_size(rep->value.array); + i++) { + PRINT("%s%s\"%s\"\n", depth_prefix, prefix_str, + oc_string_array_get_item(rep->value.array, i)); + } + PRINT("%s]\n", depth_prefix); + break; + + case OC_REP_OBJECT: + PRINT("%s%s: \n%s{ \n", depth_prefix, oc_string(rep->name), depth_prefix); + oc_print_pushd_resource(rep->value.object); + PRINT("%s}\n", depth_prefix); + break; + + case OC_REP_OBJECT_ARRAY: + case OC_REP_NIL: + PRINT("%s%s: \n%s[\n", depth_prefix, oc_string(rep->name), depth_prefix); + depth++; + obj = rep->value.object_array; + while (obj) { + PRINT("%s%s{\n", depth_prefix, prefix_str); + oc_print_pushd_resource(obj->value.object); + obj = obj->next; + PRINT("%s%s}", depth_prefix, prefix_str); + if (obj) + PRINT(",\n"); + else + PRINT("\n"); + } + depth--; + PRINT("%s]\n", depth_prefix); + break; + + default: + PRINT("%s%s: unknown type: %d ???\n", depth_prefix, oc_string(rep->name), + rep->type); + break; + } + rep = rep->next; + } + depth--; +} + +/** + * @brief try to find `receiver` object which has `uri` as its `uri` + * Property + * + * @param recvs_instance Resource representation of Push Receiver Resource + * @param uri uri string + * @param uri_len length of uri string + * @return NULL: not found, not NULL: found `receiver` object + */ +static oc_recv_t * +_find_recv_obj_by_uri(oc_recvs_t *recvs_instance, const char *uri, int uri_len) +{ + oc_recv_t *recv = (oc_recv_t *)oc_list_head(recvs_instance->receivers); + + while (recv) { + if (!strncmp(oc_string(recv->receiveruri), uri, uri_len)) { + break; + } else { + recv = recv->next; + } + } + + return recv; +} + +/** + * @brief try to find `receiver` object which has `uri_string` as its `uri` + * Property + */ +#define _find_recv_obj_by_uri2(recvs_instance, uri_string) \ + (_find_recv_obj_by_uri((recvs_instance), oc_string(uri_string), \ + oc_string_len(uri_string))) + +static oc_rep_t * +_rep_list_remove(oc_rep_t **rep_list, oc_rep_t **item) +{ + oc_rep_t *removed_item; + + for (oc_rep_t **l = rep_list; *l != NULL; l = &(*l)->next) { + if (*l == *item) { + *l = (*l)->next; + + removed_item = *item; + *item = (*item)->next; + removed_item->next = NULL; + return removed_item; + } + } + + return NULL; +} + +/** + * @brief callback for UPDATE of pushed Resource + * + * @param request request delivered from stack + * @param iface_mask OCF interface delivered from stack + * @param user_data not used + */ +static void +post_pushd_rsc(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) +{ + (void)iface_mask; + (void)user_data; + + int result = OC_STATUS_CHANGED; + oc_rep_t *rep = request->request_payload; + oc_rep_t *common_property; + oc_pushd_resource_rep_t *pushd_rsc_rep; + oc_recvs_t *recvs_instance; + oc_recv_t *recv_obj; + + recvs_instance = _find_recvs_by_device(request->resource->device); + if (recvs_instance) { + recv_obj = _find_recv_obj_by_uri2(recvs_instance, request->resource->uri); + if (!recv_obj) { + OC_PUSH_ERR("can't find receiver object for (%s)", + oc_string(request->resource->uri)); + return; + } + } else { + OC_PUSH_ERR("can't find push receiver properties for (%s) in device (%ld), " + "the target resource may not be a \"push receiver resource\"", + oc_string(request->resource->uri), request->resource->device); + return; + } + + /* check if rt of pushed resource is part of configured rts */ + if (!_check_pushd_rsc_rt(recv_obj, rep)) { + OC_PUSH_ERR( + "pushed resource type(s) is not in \"rts\" of push recerver object"); + result = OC_STATUS_FORBIDDEN; + } else { + while (rep) { + /* + * <2022/4/20> skip "rt" (array), "if" (array), "n" (optional), "id" + * (optional) common property in the payload ("oic.r.pushpayload") because + * "rt" and "if" are already processed here... + */ + switch (rep->type) { + case OC_REP_STRING_ARRAY: + if (!strcmp(oc_string(rep->name), "rt")) { + /* update rt */ + oc_free_string_array(&request->resource->types); + oc_new_string_array( + &request->resource->types, + oc_string_array_get_allocated_size(rep->value.array)); + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(rep->value.array); + i++) { + oc_string_array_add_item( + request->resource->types, + oc_string_array_get_item(rep->value.array, i)); + } + + /* + * remove rep from list.. + * - remove rep from list and move pointer to the next rep... + * - removed rep is handed over as return value + */ + common_property = _rep_list_remove(&request->request_payload, &rep); + oc_free_rep(common_property); + continue; + + } else if (!strcmp(oc_string(rep->name), "if")) { + /* update if */ + request->resource->interfaces = 0; + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(rep->value.array); + i++) { + request->resource->interfaces |= oc_ri_get_interface_mask( + oc_string_array_get_item(rep->value.array, i), + oc_string_array_get_item_size(rep->value.array, i)); + } + + common_property = _rep_list_remove(&request->request_payload, &rep); + oc_free_rep(common_property); + continue; + } + break; + case OC_REP_STRING: + if (!strcmp(oc_string(rep->name), "n")) { + /* update name */ + oc_set_string(&request->resource->name, oc_string(rep->value.string), + oc_string_len(rep->value.string)); + + common_property = _rep_list_remove(&request->request_payload, &rep); + oc_free_rep(common_property); + continue; + } + break; + + default: + break; + } + rep = rep->next; + } + + /* + * + * store received "oic.r.pushpayload" resource contents + * + */ + pushd_rsc_rep = _find_pushd_rsc_rep_by_uri(&request->resource->uri, + request->resource->device); + if (pushd_rsc_rep) { + oc_rep_set_pool(&g_rep_instance_memb); + oc_free_rep(pushd_rsc_rep->rep); + + if (!(pushd_rsc_rep->rep = + _create_pushd_rsc_rep(request->request_payload))) { + OC_PUSH_ERR("something wrong!, creating corresponding pushed resource " + "representation faild (%s) ! ", + oc_string(request->resource->uri)); + result = OC_STATUS_INTERNAL_SERVER_ERROR; + } else { +#ifdef OC_PUSHDEBUG +// PRINT("\npushed target resource: %s\n", +// oc_string(pushd_rsc_rep->resource->uri)); +// oc_print_pushd_resource(pushd_rsc_rep->rep); +#endif + if (oc_push_arrived) + oc_push_arrived(pushd_rsc_rep); + } + } else { + OC_PUSH_ERR("something wrong!, can't find corresponding pushed resource " + "representation instance for (%s) ", + oc_string(request->resource->uri)); + result = OC_STATUS_NOT_FOUND; + } + } + + if (result == OC_STATUS_CHANGED && + !(pushd_rsc_rep->resource->properties & OC_DISCOVERABLE)) { + /* + * if this is the first push to this target Resource... make it discoverable + */ + OC_PUSH_DBG("this is the first push to (%s), from now on it will be " + "discoverable...", + oc_string(pushd_rsc_rep->resource->uri)); + oc_resource_set_discoverable(pushd_rsc_rep->resource, true); + } + + oc_send_response(request, result); +} + +/** + * @brief callback for GET of Push Receiver Resource + */ +static void +get_pushrecv(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) +{ + (void)user_data; + + int result = OC_STATUS_OK; + + oc_rep_begin_root_object(); + switch (iface_mask) { + case OC_IF_BASELINE: + oc_process_baseline_interface(request->resource); + /* fall through */ + OC_FALLTHROUGH; + case OC_IF_RW: + /* + * `receivers` object array + */ + oc_rep_open_array(root, receivers); + oc_recvs_t *recvs_instance = (oc_recvs_t *)oc_list_head(g_recvs_list); + while (recvs_instance) { + if (recvs_instance->resource == request->resource) { + oc_recv_t *recv_obj = + (oc_recv_t *)oc_list_head(recvs_instance->receivers); + while (recv_obj) { + /* == open new receiver object == */ + oc_rep_object_array_begin_item(receivers); + /* receiver:receiveruri */ + oc_rep_set_text_string(receivers, receiveruri, + oc_string(recv_obj->receiveruri)); + + /* receiver:rts[] */ + oc_rep_open_array(receivers, rts); + for (int j = 0; + j < (int)oc_string_array_get_allocated_size(recv_obj->rts); + j++) { + oc_rep_add_text_string(rts, + oc_string_array_get_item(recv_obj->rts, j)); + } + oc_rep_close_array(receivers, rts); + + /* == close object == */ + oc_rep_object_array_end_item(receivers); + + recv_obj = recv_obj->next; + } + + break; + } + + recvs_instance = recvs_instance->next; + } + oc_rep_close_array(root, receivers); + break; + default: + break; + } + oc_rep_end_root_object(); + + oc_send_response(request, result); +} + +/** + * @brief purge app resource (`oc_resource_t`) and resource representation + * instance (`oc_pushd_resource_rep_t`) accessed through `uri` in device whose + * index is `device_index` + * + * @param uri URI of app resource to be purged + * @param device_index index of device where the target resource resides + */ +static void +_purge_pushd_rsc(oc_string_t *uri, size_t device_index) +{ + oc_resource_t *pushd_rsc = oc_ri_get_app_resource_by_uri( + oc_string(*uri), oc_string_len(*uri), device_index); + oc_pushd_resource_rep_t *pushd_rsc_rep = + _find_pushd_rsc_rep_by_uri(uri, device_index); + + if (pushd_rsc_rep) { + /* step 1. purge `rep` */ + oc_rep_set_pool(&g_rep_instance_memb); + oc_free_rep(pushd_rsc_rep->rep); + + /* step 2. remove pushed resource representation from `pushed_rsc_rep_list` + */ + oc_list_remove(g_pushd_rsc_rep_list, pushd_rsc_rep); + oc_memb_free(&g_pushd_rsc_rep_instance_memb, pushd_rsc_rep); + } else { + OC_PUSH_ERR( + "can't find resource representation for pushed resource (%s)...", + oc_string(*uri)); + return; + } + + if (pushd_rsc) { + /* step 3. remove pushed Resource from `app_resources` */ + OC_PUSH_DBG("purge pushed resource (%s)...", oc_string(*uri)); + oc_delete_resource(pushd_rsc); + return; + } + OC_PUSH_ERR("can't find pushed resource (%s)...", oc_string(*uri)); +} + +/** + * @brief create app Resource (`oc_resource_t`) and Resource representation + * (`oc_pushd_resource_rep_t`) instance for the pushed Resource + * + * @param recv_obj receiver object that points pushed resource + * @param resource Push Receiver resource + * @return true:success, false:fail + */ +static bool +_create_pushd_rsc(oc_recv_t *recv_obj, const oc_resource_t *resource) +{ + bool result = true; + + /* create Push Receiver Resource */ + oc_resource_t *pushd_rsc = oc_new_resource( + "Pushed Resource", oc_string(recv_obj->receiveruri), 1, resource->device); + + if (pushd_rsc) { + /* + * XXX, if a resource binds empty resource type (""), when a client retrieve + * this it may receive weird value... + */ + oc_resource_bind_resource_type(pushd_rsc, " "); + oc_resource_bind_resource_interface(pushd_rsc, OC_IF_RW | OC_IF_BASELINE); + oc_resource_set_default_interface(pushd_rsc, OC_IF_RW); + /* + * initially this resource should not be discoverable... + * once any resource is pushed to this resource, it will be discoverable... + */ + oc_resource_set_discoverable(pushd_rsc, false); + + oc_resource_set_request_handler(pushd_rsc, OC_GET, get_pushd_rsc, NULL); + oc_resource_set_request_handler(pushd_rsc, OC_POST, post_pushd_rsc, NULL); + /* + * when this pushed resource is deleted.. delete corresponding "receiver" + * object from receivers array of push receiver resource + * => this is done in delete_pushrecv() (delete handler of pushreceiver + * resource) + */ + + if (!oc_add_resource(pushd_rsc)) + result = false; + + /* create resource representation container for this resource */ + oc_pushd_resource_rep_t *pushd_rsc_rep_instance = + (oc_pushd_resource_rep_t *)oc_memb_alloc(&g_pushd_rsc_rep_instance_memb); + if (pushd_rsc_rep_instance) { + pushd_rsc_rep_instance->resource = pushd_rsc; + pushd_rsc_rep_instance->rep = NULL; + oc_list_add(g_pushd_rsc_rep_list, pushd_rsc_rep_instance); + } else { + OC_PUSH_ERR("oc_memb_alloc() error!"); + result = false; + } + } else { + OC_PUSH_ERR("oc_new_resource() error!"); + result = false; + } + + return result; +} + +/** + * @brief remove receiver object array from `recv_obj_list`, + * and app Resource pointed by `receiveruri` of each receiver + * object in the array + * + * @param recvs_instance Resource representation of Push Receiver Resource + */ +static void +_purge_recv_obj_list(oc_recvs_t *recvs_instance) +{ + oc_recv_t *recv_obj = (oc_recv_t *)oc_list_pop(recvs_instance->receivers); + + while (recv_obj) { + OC_PUSH_DBG("purge receiver obj for ( %s (device: %ld) )... ", + oc_string(recv_obj->receiveruri), + recvs_instance->resource->device); + + /* delete app resource pointed by `receiveruri` first.. */ + _purge_pushd_rsc(&recv_obj->receiveruri, recvs_instance->resource->device); + + oc_free_string(&recv_obj->receiveruri); + oc_free_string_array(&recv_obj->rts); + oc_memb_free(&g_recv_instance_memb, recv_obj); + + recv_obj = (oc_recv_t *)oc_list_pop(recvs_instance->receivers); + } +} + +/** + * @brief update existing receiver object with new contents + * + * @param recv_obj existing receiver object + * @param resource app Resource pointed by `recv_obj->receiveruri` + * @param rep payload representation of new receiver object + */ +static void +_update_recv_obj(oc_recv_t *recv_obj, const oc_recvs_t *recvs_instance, + oc_rep_t *rep) +{ + oc_pushd_resource_rep_t *pushd_rsc_rep; + + while (rep) { + switch (rep->type) { + case OC_REP_STRING: + if (!strcmp(oc_string(rep->name), "receiveruri")) { + OC_PUSH_DBG("target receiveruri: \"%s\", new receiveruri: \"%s\"", + oc_string(recv_obj->receiveruri), + oc_string(rep->value.string)); + /* if `receiveruri' is different from existing `receiveruri`, + * update URI of Resource pointed by previous `receiveruri` */ + if (strcmp(oc_string(recv_obj->receiveruri), + oc_string(rep->value.string))) { + pushd_rsc_rep = _find_pushd_rsc_rep_by_uri( + &recv_obj->receiveruri, recvs_instance->resource->device); + + if (pushd_rsc_rep) { + OC_PUSH_DBG("pushed resource representation (\"%s\") is found", + oc_string(pushd_rsc_rep->resource->uri)); + + oc_free_string(&pushd_rsc_rep->resource->uri); + oc_store_uri(oc_string(rep->value.string), + &pushd_rsc_rep->resource->uri); + } + } + + oc_set_string(&recv_obj->receiveruri, oc_string(rep->value.string), + oc_string_len(rep->value.string)); + } + break; + + case OC_REP_STRING_ARRAY: + if (!strcmp(oc_string(rep->name), "rts")) { + oc_free_string_array(&recv_obj->rts); + size_t len = oc_string_array_get_allocated_size(rep->value.array); + oc_new_string_array(&recv_obj->rts, len); + + for (size_t i = 0; i < len; i++) { + oc_string_array_add_item( + recv_obj->rts, oc_string_array_get_item(rep->value.array, i)); + } + } + break; + + default: + OC_PUSH_ERR("something wrong, unexpected Property type: %d", rep->type); + return; + } + rep = rep->next; + } +} + +/** + * @brief create & add new receiver object + * + * @param recvs_instance Resource representation of Push Receiver Resource + * @param rep received new receiver object object + * @return true:success, false:fail + */ +static bool +_create_recv_obj(oc_recvs_t *recvs_instance, oc_rep_t *rep) +{ + bool result = false; + char mandatory_property_check = 0; + oc_recv_t *recv_obj = (oc_recv_t *)oc_memb_alloc(&g_recv_instance_memb); + + if (!recv_obj) { + OC_PUSH_ERR("oc_memb_alloc() error!"); + return result; + } + + while (rep) { + switch (rep->type) { + case OC_REP_STRING: + if (!strcmp(oc_string(rep->name), "receiveruri")) { + oc_new_string(&recv_obj->receiveruri, oc_string(rep->value.string), + oc_string_len(rep->value.string)); + mandatory_property_check |= 0x1; + } + break; + + case OC_REP_STRING_ARRAY: + if (!strcmp(oc_string(rep->name), "rts")) { + size_t len = oc_string_array_get_allocated_size(rep->value.array); + oc_new_string_array(&recv_obj->rts, len); + + for (size_t i = 0; i < len; i++) { + oc_string_array_add_item( + recv_obj->rts, oc_string_array_get_item(rep->value.array, i)); + } + + mandatory_property_check |= 0x2; + } + break; + + default: + OC_PUSH_ERR("something wrong, unexpected Property type: %d", rep->type); + break; + } + rep = rep->next; + } + + if (mandatory_property_check != 0x3) { + oc_memb_free(&g_recv_instance_memb, recv_obj); + return result; + } + + oc_list_add(recvs_instance->receivers, recv_obj); + + /* create app resource corresponding to receiver object */ + OC_PUSH_DBG("new app resource for new receiver obj (\"%s\") is created...", + oc_string(recv_obj->receiveruri)); + if (_create_pushd_rsc(recv_obj, recvs_instance->resource)) + result = true; + + return result; +} + +/** + * @brief validate if receiver object array has any problem or not + * + * @param obj_list receiver object array + * @return true:success, false:fail + */ +static bool +_validate_recv_obj_list(oc_rep_t *obj_list) +{ + oc_rep_t *rep; + bool result = false; + char mandatory_property_check; + + if (!obj_list) { + OC_PUSH_ERR("empty object array!"); + return result; + } + + for (oc_rep_t *recv_obj = obj_list; recv_obj != NULL; + recv_obj = recv_obj->next) { + + mandatory_property_check = 0; + rep = recv_obj->value.object; + + for (; rep != NULL; rep = rep->next) { + switch (rep->type) { + case OC_REP_STRING: + if (!strcmp(oc_string(rep->name), "receiveruri")) { + mandatory_property_check |= 0x1; + } + break; + + case OC_REP_STRING_ARRAY: + if (!strcmp(oc_string(rep->name), "rts")) { + mandatory_property_check |= 0x2; + } + break; + + default: + OC_PUSH_ERR("something wrong, unexpected Property type: %d", rep->type); + goto exit; + } + } + + if (mandatory_property_check != 0x3) { + OC_PUSH_ERR("mandatory Property is missing (%#x)", + 0x3 - mandatory_property_check); + goto exit; + } + } /* for */ + + result = true; + +exit: + return result; +} + +/** + * @brief replace existing receiver object array with new one + * + * @param recvs_instance Resource representation of Push Receiver Resource + * @param rep payload representation of new receiver object array + * @return true:success, false:fail + */ +static bool +_replace_recv_obj_array(oc_recvs_t *recvs_instance, oc_rep_t *rep) +{ + bool result = false; + + if (rep && (rep->type == OC_REP_OBJECT_ARRAY)) { + /* check if received new receiver object array is ok */ + if (!_validate_recv_obj_list(rep->value.object_array)) + goto exit; + + /* if received new receiver object array is ok, do the job... */ + /* remove existing receivers object array */ + _purge_recv_obj_list(recvs_instance); + + /* replace `receivers` obj array with new one */ + for (oc_rep_t *rep_obj = rep->value.object_array; rep_obj != NULL; + rep_obj = rep_obj->next) { + _create_recv_obj(recvs_instance, rep_obj->value.object); + } + + result = true; + } else { + OC_PUSH_ERR("something wrong, unexpected Property type: %d", rep->type); + } + +exit: + return result; +} + +/** + * @brief POST callback for Push Receiver Resource + */ +static void +post_pushrecv(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) +{ + (void)iface_mask; + (void)user_data; + + const char *uri_param = NULL; + int uri_param_len = -1; + oc_recv_t *recv_obj; + oc_recvs_t *recvs_instance; + oc_rep_t *rep = request->request_payload; + int result = OC_STATUS_CHANGED; + + /* try to get "receiveruri" parameter */ + if (request->query) { + uri_param_len = oc_ri_get_query_value(request->query, request->query_len, + "receiveruri", &uri_param); + if (uri_param_len != -1) { + OC_PUSH_DBG( + "received query string: \"%.*s\", found \"receiveruri\": \"%.*s\" ", + (int)request->query_len, request->query, uri_param_len, uri_param); + } + } else { + OC_PUSH_DBG("request->query is NULL"); + } + + /* look up target receivers of target Push Receiver Resource */ + recvs_instance = (oc_recvs_t *)oc_list_head(g_recvs_list); + while (recvs_instance) { + if (recvs_instance->resource == request->resource) { + OC_PUSH_DBG("receivers obj array instance \"%s\"@Device(%ld) is found!", + oc_string(request->resource->uri), request->resource->device); + + if (uri_param_len != -1) { + recv_obj = + _find_recv_obj_by_uri(recvs_instance, uri_param, uri_param_len); + if (recv_obj) { + + /* if the given `receiveruri` parameter is in existing receivers + * array, just update existing receiver object */ + OC_PUSH_DBG("existing receiver obj (\"%.*s\") is found, update it...", + uri_param_len, uri_param); + _update_recv_obj(recv_obj, recvs_instance, rep); + } else { + /* if the given `receiveruri` parameter is not in existing receivers + * array, add new receiver object to the receivers array */ + OC_PUSH_DBG("can't find receiver obj which has uri \"%.*s\", " + "creating new receiver obj...", + uri_param_len, uri_param); + + /* + * if there is already NORMAL resource whose path is same as requested + * target uri, just ignore this request and return error! + */ + if (oc_ri_get_app_resource_by_uri(uri_param, uri_param_len, + recvs_instance->resource->device)) { + OC_PUSH_ERR("can't create receiver obj because its receiveruri is " + "same as existing app resource (\"%.*s\")...", + uri_param_len, uri_param); + result = OC_STATUS_FORBIDDEN; + goto exit; + } + + /* create corresponding receiver object */ + if (!_create_recv_obj(recvs_instance, rep)) { + OC_PUSH_ERR("failed to create receiver obj whose receiveruri is " + "(\"%.*s\")...", + uri_param_len, uri_param); + result = OC_STATUS_BAD_REQUEST; + goto exit; + } + } + } else { + /* if `receiveruri` param is not provided.. + * replace whole existing `receivers` object array with new one.. */ + OC_PUSH_DBG("replace existing receiver obj array with new ones..."); + if (!_replace_recv_obj_array(recvs_instance, rep)) { + OC_PUSH_ERR("failed to replace existing whole receiver objs..."); + result = OC_STATUS_BAD_REQUEST; + goto exit; + } + } + + break; + } + + recvs_instance = recvs_instance->next; + } + +exit: + oc_send_response(request, result); +} + +/** + * @brief DELETE callback for Push Receiver Resource + */ +static void +delete_pushrecv(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) +{ + (void)iface_mask; + (void)user_data; + + const char *uri_param; + int uri_param_len = -1; + oc_recv_t *recv_obj; + oc_recvs_t *recvs_instance; + int result = OC_STATUS_DELETED; + + /* try to get "receiveruri" parameter */ + if (request->query) { + uri_param_len = oc_ri_get_query_value(request->query, request->query_len, + "receiveruri", &uri_param); + if (uri_param_len != -1) { + OC_PUSH_DBG( + "received query string: \"%.*s\", found \"receiveruri\": \"%.*s\" ", + (int)request->query_len, request->query, uri_param_len, uri_param); + } + } else { + OC_PUSH_DBG("request->query is NULL"); + } + + /* look up target receivers of target Push Receiver Resource */ + recvs_instance = (oc_recvs_t *)oc_list_head(g_recvs_list); + while (recvs_instance) { + if (recvs_instance->resource == request->resource) { + OC_PUSH_DBG("receivers obj array instance of push receiver resource " + "(\"%s\") is found!", + oc_string(request->resource->uri)); + + if (uri_param_len != -1) { + recv_obj = + _find_recv_obj_by_uri(recvs_instance, uri_param, uri_param_len); + if (recv_obj) { + /* remove receiver obj from array */ + oc_list_remove(recvs_instance->receivers, recv_obj); + OC_PUSH_DBG("receiver obj is removed from array"); + + /* delete associated resource... */ + _purge_pushd_rsc(&recv_obj->receiveruri, + recvs_instance->resource->device); + OC_PUSH_DBG( + "app resource corresponding to the receiver obj is removed"); + + /* free memory */ + oc_free_string(&recv_obj->receiveruri); + oc_free_string_array(&recv_obj->rts); + oc_memb_free(&g_recv_instance_memb, recv_obj); + } else { + /* if the given `receiveruri` parameter is not in existing receivers + * array, add new receiver object to the receivers array */ +#ifdef OC_PUSHDEBUG + // oc_string_t uri; + // oc_new_string(&uri, uri_param, uri_param_len); + OC_PUSH_DBG( + "can't find receiver object which has uri(\"%.*s\"), ignore it...", + uri_param_len, uri_param); +// oc_free_string(&uri); +#endif + result = OC_STATUS_NOT_FOUND; + } + } else { + /* if `receiveruri` param is not provided.. + * remove whole existing `receivers` object array */ + _purge_recv_obj_list(recvs_instance); + } + + break; + } + + recvs_instance = recvs_instance->next; + } + + oc_send_response(request, result); +} + +/** + * @brief initiate Push Receiver Resource + * + * @details + * for Target Server \n + * - Push Receiver ("oic.r.pushreceiver") \n + * + * @param device_index device index + */ +void +oc_create_pushreceiver_resource(size_t device_index) +{ + /* create Push Receiver Resource */ + oc_resource_t *push_recv = oc_new_resource( + "Push Receiver", PUSHRECEIVERS_RESOURCE_PATH, 1, device_index); + + if (push_recv) { + oc_resource_bind_resource_type(push_recv, "oic.r.pushreceiver"); + oc_resource_bind_resource_interface(push_recv, OC_IF_RW | OC_IF_BASELINE); + oc_resource_set_default_interface(push_recv, OC_IF_RW); + oc_resource_set_discoverable(push_recv, true); + + oc_resource_set_request_handler(push_recv, OC_GET, get_pushrecv, NULL); + oc_resource_set_request_handler(push_recv, OC_POST, post_pushrecv, NULL); + oc_resource_set_request_handler(push_recv, OC_DELETE, delete_pushrecv, + NULL); + + /* + * add struct for `receivers` object list for this Resource to the list + */ + oc_recvs_t *recvs_instance = + (oc_recvs_t *)oc_memb_alloc(&g_recvs_instance_memb); + if (recvs_instance) { + oc_add_resource(push_recv); + recvs_instance->resource = push_recv; + OC_LIST_STRUCT_INIT(recvs_instance, receivers); + oc_list_add(g_recvs_list, recvs_instance); + } else { + OC_PUSH_ERR("oc_memb_alloc() error!"); + oc_delete_resource(push_recv); + } + } else { + OC_PUSH_ERR("oc_new_resource() error!"); + } +} + +void +oc_push_list_init() +{ + oc_list_init(g_ns_list); + oc_list_init(g_recvs_list); + oc_list_init(g_pushd_rsc_rep_list); +} + +/* + * clean up push related data structure + * - for push configuration Resource: they are cleaned when all app Resources + * are removed (see oc_main_shutdown()) + * - for push receivers Resource: free in this function + */ +void +oc_push_free() +{ + oc_recvs_t *recvs_instance; + + OC_PUSH_DBG("begin to free push receiver list!!!"); + + oc_recvs_t *next; + recvs_instance = (oc_recvs_t *)oc_list_head(g_recvs_list); + while (recvs_instance) { + next = recvs_instance->next; + _purge_recv_obj_list(recvs_instance); + OC_PUSH_DBG("free push receiver Resource (device: %ld)... ", + recvs_instance->resource->device); + oc_memb_free(&g_recvs_instance_memb, recvs_instance); + recvs_instance = next; + } +} + +/** + * @brief Response callback for PUSH Update request + * + * @param data response payload + */ +static void +response_to_push_rsc(oc_client_response_t *data) +{ + oc_ns_t *ns_instance = (oc_ns_t *)data->user_data; + + OC_PUSH_DBG("\n => return status code: [ %s ]", + oc_status_to_str(data->code)); + + if (data->code == OC_STATUS_SERVICE_UNAVAILABLE) { + /* + * TODO4ME <2022/4/17> if update request fails... retry to resolve endpoint + * of target device ID... + */ + OC_PUSH_DBG("state of Push Proxy (\"%s\") is changed (%s => %s)", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->state), pp_statestr(OC_PP_TOUT)); + pp_update_state(ns_instance->state, pp_statestr(OC_PP_TOUT)); + } else if (data->code == OC_STATUS_CHANGED) { + OC_PUSH_DBG("state of Push Proxy (\"%s\") is changed (%s => %s)", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->state), pp_statestr(OC_PP_WFU)); + pp_update_state(ns_instance->state, pp_statestr(OC_PP_WFU)); + } else { + /* + * <2022/4/17> check condition to enter ERR + */ + OC_PUSH_DBG("state of Push Proxy (\"%s\") is changed (%s => %s)", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->state), pp_statestr(OC_PP_ERR)); + pp_update_state(ns_instance->state, pp_statestr(OC_PP_ERR)); + } +} + +/** + * @brief send PUSH update request + * + * @param ns_instance composition of `oic.r.notificationselector` + + * `oic.r.pushproxy` + * @return true:success, false:fail + */ +static bool +push_update(oc_ns_t *ns_instance) +{ + oc_resource_t *src_rsc = (oc_resource_t *)ns_instance->user_data; + if (!ns_instance || !src_rsc) { + OC_PUSH_ERR("something wrong! corresponding notification selector source " + "resource is NULL, or updated resource is NULL!"); + return false; + } + + if (!src_rsc->payload_builder) { + OC_PUSH_ERR("payload_builder() of source resource is NULL!"); + return false; + } + + /* + * 1. find `notification selector` which monitors `src_rsc` from `ns_col_list` + * 2. post UPDATE by using URI, endpoint (use oc_sting_to_endpoint()) + */ + if (!oc_init_post(oc_string(ns_instance->targetpath), + &ns_instance->pushtarget_ep, "if=oic.if.rw", + &response_to_push_rsc, HIGH_QOS, ns_instance)) { + OC_PUSH_ERR("Could not init POST"); + return false; + } + /* + * add other properties than "rep" object of "oic.r.pushpayload" Resource + * here. payload_builder() only "rep" object. + * + * payload_builder() doesn't need to have "oc_rep_start_root_object()" and + * "oc_rep_end_root_object()" they should be added here... + */ + oc_rep_begin_root_object(); + + /* anchor */ + char di[OC_UUID_LEN + 10]; + snprintf(di, sizeof(di), "ocf://"); + oc_uuid_to_str(oc_core_get_device_id(ns_instance->resource->device), di + 6, + OC_UUID_LEN); + oc_rep_set_text_string(root, anchor, di); + + /* href (optional) */ + if (oc_string(ns_instance->phref) && + strcmp(oc_string(ns_instance->phref), "")) { + oc_rep_set_text_string(root, href, oc_string(ns_instance->phref)); + } + + /* rt */ + oc_rep_open_array(root, rt); + for (size_t i = 0; i < oc_string_array_get_allocated_size(src_rsc->types); + i++) { + oc_rep_add_text_string(rt, oc_string_array_get_item(src_rsc->types, i)); + } + oc_rep_close_array(root, rt); + + /* if */ + oc_core_encode_interfaces_mask(oc_rep_object(root), src_rsc->interfaces); + + /* build rep object */ + src_rsc->payload_builder(); + + oc_rep_end_root_object(); + + if (!oc_do_post()) { + OC_PUSH_ERR("Could not send POST"); + return false; + } +#ifdef OC_PUSHDEBUG + oc_string_t ep, full_uri; + + oc_endpoint_to_string(&ns_instance->pushtarget_ep, &ep); + if (oc_string_len(ns_instance->targetpath)) { + oc_concat_strings(&full_uri, oc_string(ep), + oc_string(ns_instance->targetpath)); + } else { + oc_new_string(&full_uri, oc_string(ep), oc_string_len(ep)); + } + + OC_PUSH_DBG("push \"%s\" ====> \"%s\"", oc_string(src_rsc->uri), + oc_string(full_uri)); + oc_free_string(&ep); + oc_free_string(&full_uri); +#endif + OC_PUSH_DBG("state of Push Proxy (\"%s\") is changed (%s => %s)", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->state), pp_statestr(OC_PP_WFR)); + pp_update_state(ns_instance->state, pp_statestr(OC_PP_WFR)); + + return true; +} + +OC_PROCESS_THREAD(oc_push_process, ev, data) +{ + oc_resource_t *src_rsc; + oc_ns_t *ns_instance; + char di[OC_UUID_LEN]; + + OC_PROCESS_BEGIN(); + + while (1) { + +#if 0 + int device_count = oc_core_get_num_devices(); + /* create Push Notification Resource per each Device */ + for (int i=0; iuser_data; + + if (!ns_instance || !src_rsc /*|| !ns_instance->user_data*/) { + OC_PUSH_ERR("something wrong! corresponding notification selector " + "source resource is NULL, or updated resource is NULL!"); + break; + } + + /* + * client에서 POST 하는 루틴 참조할 것 (client_multithread_linux.c 참고) + */ + /* + * 1. find `notification selector` which monitors `src_rsc` from + * `ns_col_list` + * 2. post UPDATE by using URI, endpoint (use oc_sting_to_endpoint()) + */ + if (oc_init_post(oc_string(ns_instance->targetpath), + &ns_instance->pushtarget_ep, "if=oic.if.rw", + &response_to_push_rsc, LOW_QOS, NULL)) { + /* + * add other properties than "rep" object of "oic.r.pushpayload" + * Resource here. payload_builder() only adds "rep" object. + * + * payload_builder() doesn't need to have "oc_rep_start_root_object()" + * and "oc_rep_end_root_object()" they should be added here... + */ + + oc_rep_begin_root_object(); + + /* anchor */ + oc_uuid_to_str(oc_core_get_device_id(ns_instance->resource->device), di, + OC_UUID_LEN); + oc_rep_set_text_string(root, anchor, di); + + /* href + * - option + */ + if (oc_string(ns_instance->phref) && + strcmp(oc_string(ns_instance->phref), "")) { + oc_rep_set_text_string(root, href, oc_string(ns_instance->phref)); + } + + /* rt */ + oc_rep_open_array(root, rt); + for (size_t i = 0; + i < oc_string_array_get_allocated_size(src_rsc->types); i++) { + oc_rep_add_text_string(rt, + oc_string_array_get_item(src_rsc->types, i)); + } + oc_rep_close_array(root, rt); + + /* if */ + oc_core_encode_interfaces_mask(oc_rep_object(root), + src_rsc->interfaces); + + src_rsc->payload_builder(); + + oc_rep_end_root_object(); + + if (oc_do_post()) { + OC_PUSH_DBG("Sent POST request"); + } else { + OC_PUSH_ERR("Could not send POST"); + } + } else { + OC_PUSH_ERR("Could not init POST"); + } + } + } + + OC_PROCESS_END() +} + +/** + * @brief check if any of source array is part of target array + * @param target + * @param source + * @return + * false: any of source is not part of target + * true: any of source is part of target + */ +static bool +_check_string_array_inclusion(oc_string_array_t *target, + oc_string_array_t *source) +{ + size_t tgt_len = oc_string_array_get_allocated_size(*target); + size_t src_len = oc_string_array_get_allocated_size(*source); + + if (tgt_len == 0 || src_len == 0) { + OC_PUSH_DBG("source or target string array is empty!"); + return false; + } + + for (size_t i = 0; i < src_len; i++) { + for (size_t j = 0; j < tgt_len; j++) { + if (!strcmp(oc_string_array_get_item(*source, i), + oc_string_array_get_item(*target, j))) { + return true; + } + } + } + + return false; +} + +/** + * @brief trigger PUSH procedure + * + * @param uri path of updated Resource + * @param device_index device index which the updated Resource belongs to + */ +void +oc_resource_state_changed(const char *uri, size_t uri_len, size_t device_index) +{ + oc_resource_t *resource = + oc_ri_get_app_resource_by_uri(uri, uri_len, device_index); + oc_ns_t *ns_instance = (oc_ns_t *)oc_list_head(g_ns_list); + char all_matched = 0x7; + + OC_PUSH_DBG("resource \"%s\"@device(%ld) is updated!", uri, device_index); + + if (!resource) { + OC_PUSH_ERR("there is no resource for \"%s\"@device(%ld)", uri, + device_index); + return; + } + if (!(resource->properties & OC_PUSHABLE)) { + OC_PUSH_ERR("resource \"%s\"@device (%ld) is not pushable!", uri, + device_index); + return; + } + + for (; ns_instance; ns_instance = ns_instance->next) { + if (ns_instance->resource->device != device_index) + continue; + + /* if push proxy is not in "wait for update" state, just skip it... */ + if (strcmp(oc_string(ns_instance->state), pp_statestr(OC_PP_WFU))) + continue; + + if (oc_string(ns_instance->phref)) { + if (strcmp(oc_string(ns_instance->phref), uri)) { + OC_PUSH_DBG("%s:phref exists, but mismatches (phref:%s - uri:%s)", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->phref), uri); + all_matched = 0; + } else { + OC_PUSH_DBG("%s:phref matches (phref:%s - uri:%s)", + oc_string(ns_instance->resource->uri), + oc_string(ns_instance->phref), uri); + } + } else { + OC_PUSH_DBG("%s:phref does not exist", + oc_string(ns_instance->resource->uri)); + all_matched &= 0x6; + } + + if (oc_string_array_get_allocated_size(ns_instance->prt) > 0) { + if (!_check_string_array_inclusion(&ns_instance->prt, &resource->types)) { +#ifdef OC_PUSHDEBUG + PRINT("%s:prt exists, but mismatches (prt: [", + oc_string(ns_instance->resource->uri)); + for (size_t i = 0; + i < oc_string_array_get_allocated_size(ns_instance->prt); i++) { + PRINT("%s ", oc_string_array_get_item(ns_instance->prt, i)); + } + PRINT("] - rt of updated rsc: ["); + for (size_t i = 0; + i < oc_string_array_get_allocated_size(resource->types); i++) { + PRINT("%s ", oc_string_array_get_item(resource->types, i)); + } + PRINT("])\n"); +#endif + all_matched = 0; + } else { +#ifdef OC_PUSHDEBUG + PRINT("%s:prt matches (prt: [", oc_string(ns_instance->resource->uri)); + for (size_t i = 0; + i < oc_string_array_get_allocated_size(ns_instance->prt); i++) { + PRINT("%s ", oc_string_array_get_item(ns_instance->prt, i)); + } + PRINT("] - rt of updated rsc: ["); + for (size_t i = 0; + i < oc_string_array_get_allocated_size(resource->types); i++) { + PRINT("%s ", oc_string_array_get_item(resource->types, i)); + } + PRINT("])\n"); +#endif + } + } else { + OC_PUSH_DBG("%s:prt does not exist", + oc_string(ns_instance->resource->uri)); + all_matched &= 0x5; + } + + if (oc_string_array_get_allocated_size(ns_instance->pif) > 0) { + oc_interface_mask_t pif = 0; + for (int i = 0; + i < (int)oc_string_array_get_allocated_size(ns_instance->pif); i++) { + pif |= oc_ri_get_interface_mask( + oc_string_array_get_item(ns_instance->pif, i), + oc_byte_string_array_get_item_size(ns_instance->pif, i)); + } + + if (!(pif & resource->interfaces)) { + OC_PUSH_DBG( + "%s:pif exists, but mismatches (pif:%#x - if of updated rsc:%#x)", + oc_string(ns_instance->resource->uri), pif, resource->interfaces); + all_matched = 0; + } else { + OC_PUSH_DBG("%s:pif matches (pif:%#x - if of updated rsc:%#x)", + oc_string(ns_instance->resource->uri), pif, + resource->interfaces); + } + } else { + OC_PUSH_DBG("%s:pif does not exist", + oc_string(ns_instance->resource->uri)); + all_matched &= 0x3; + } + + if (all_matched) { + if (!oc_process_is_running(&oc_push_process)) { + OC_PUSH_DBG("oc_push_process is not running!"); + return; + } + + OC_PUSH_DBG("resource \"%s\" matches notification selector \"%s\"!", + oc_string(resource->uri), + oc_string(ns_instance->resource->uri)); + + /* resource is necessary to identify which resource is being pushed.., + * before sending update to target server */ + ns_instance->user_data = resource; + + /* post "event" for Resource which has just been updated */ + if (!push_update(ns_instance)) { + OC_PUSH_ERR("sensing PUSH Update of \"%s\" failed!", + oc_string(resource->uri)); + } +#if 0 + oc_process_post(&oc_push_process, + oc_events[PUSH_RSC_STATE_CHANGED], ns_instance); +#endif + } + all_matched = 0x7; + } +} + +#endif /* OC_HAS_FEATURE_PUSH */ diff --git a/api/oc_rep.c b/api/oc_rep.c index 579981e5aa..a4912c1a2a 100644 --- a/api/oc_rep.c +++ b/api/oc_rep.c @@ -21,6 +21,7 @@ #include "port/oc_assert.h" #include "port/oc_log.h" #include "util/oc_memb.h" +#include "util/oc_features.h" #include @@ -242,6 +243,14 @@ _alloc_rep(void) return rep; } +#ifdef OC_HAS_FEATURE_PUSH +oc_rep_t * +oc_alloc_rep() +{ + return _alloc_rep(); +} +#endif + static void _free_rep(oc_rep_t *rep_value) { diff --git a/api/oc_ri.c b/api/oc_ri.c index fea841a3cc..da281e19e9 100644 --- a/api/oc_ri.c +++ b/api/oc_ri.c @@ -30,6 +30,7 @@ #include "util/oc_list.h" #include "util/oc_memb.h" #include "util/oc_process.h" +#include "util/oc_features.h" #include "oc_buffer.h" #include "oc_core_res.h" @@ -67,6 +68,11 @@ #endif /* OC_OSCORE */ #endif /* OC_SECURITY */ +#ifdef OC_HAS_FEATURE_PUSH +OC_PROCESS_NAME(oc_push_process); +void oc_push_list_init(); +#endif + #ifdef OC_SERVER OC_LIST(g_app_resources); OC_LIST(g_observe_callbacks); @@ -100,6 +106,29 @@ static unsigned int oc_coap_status_codes[__NUM_OC_STATUS_CODES__]; oc_process_event_t oc_events[__NUM_OC_EVENT_TYPES__]; +const char *cli_status_strs[] = { + "OC_STATUS_OK", /* 0 */ + "OC_STATUS_CREATED", /* 1 */ + "OC_STATUS_CHANGED", /* 2 */ + "OC_STATUS_DELETED", /* 3 */ + "OC_STATUS_NOT_MODIFIED", /* 4 */ + "OC_STATUS_BAD_REQUEST", /* 5 */ + "OC_STATUS_UNAUTHORIZED", /* 6 */ + "OC_STATUS_BAD_OPTION", /* 7 */ + "OC_STATUS_FORBIDDEN", /* 8 */ + "OC_STATUS_NOT_FOUND", /* 9 */ + "OC_STATUS_METHOD_NOT_ALLOWED", /* 10 */ + "OC_STATUS_NOT_ACCEPTABLE", /* 11 */ + "OC_STATUS_REQUEST_ENTITY_TOO_LARGE", /* 12 */ + "OC_STATUS_UNSUPPORTED_MEDIA_TYPE", /* 13 */ + "OC_STATUS_INTERNAL_SERVER_ERROR", /* 14 */ + "OC_STATUS_NOT_IMPLEMENTED", /* 15 */ + "OC_STATUS_BAD_GATEWAY", /* 16 */ + "OC_STATUS_SERVICE_UNAVAILABLE", /* 17 */ + "OC_STATUS_GATEWAY_TIMEOUT", /* 18 */ + "OC_STATUS_PROXYING_NOT_SUPPORTED" /* 19 */ +}; + static void set_mpro_status_codes(void) { @@ -178,6 +207,13 @@ oc_status_code(oc_status_t key) return (int)oc_coap_status_codes[key]; } +OC_API +const char * +oc_status_to_str(oc_status_t key) +{ + return cli_status_strs[key]; +} + int oc_ri_get_query_nth_key_value(const char *query, size_t query_len, const char **key, size_t *key_len, @@ -376,6 +412,10 @@ start_processes(void) #ifdef OC_TCP oc_process_start(&oc_session_events, NULL); #endif /* OC_TCP */ + +#ifdef OC_HAS_FEATURE_PUSH + oc_process_start(&oc_push_process, NULL); +#endif } static void @@ -397,6 +437,10 @@ stop_processes(void) #endif /* OC_SECURITY */ oc_process_exit(&message_buffer_handler); + +#ifdef OC_HAS_FEATURE_PUSH + oc_process_exit(&oc_push_process); +#endif } #ifdef OC_SERVER @@ -455,6 +499,10 @@ oc_ri_init(void) oc_list_init(g_timed_callbacks); +#ifdef OC_HAS_FEATURE_PUSH + oc_push_list_init(); +#endif + oc_process_init(); start_processes(); } diff --git a/api/oc_server_api.c b/api/oc_server_api.c index 18b3527c94..877f75f0d5 100644 --- a/api/oc_server_api.c +++ b/api/oc_server_api.c @@ -16,6 +16,7 @@ * ****************************************************************************/ +#include "util/oc_features.h" #include "messaging/coap/engine.h" #include "messaging/coap/oc_coap.h" #include "messaging/coap/separate.h" @@ -421,6 +422,17 @@ oc_resource_set_discoverable(oc_resource_t *resource, bool state) resource->properties &= ~OC_DISCOVERABLE; } +#ifdef OC_HAS_FEATURE_PUSH +void +oc_resource_set_pushable(oc_resource_t *resource, bool state) +{ + if (state) + resource->properties |= OC_PUSHABLE; + else + resource->properties &= ~OC_PUSHABLE; +} +#endif + void oc_resource_set_observable(oc_resource_t *resource, bool state) { diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 6c65ffdc1f..402255a7d9 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -150,6 +150,26 @@ if(UNIX) target_link_libraries(device_builder_server client-server-static) file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/device_builder_server_creds) endif() + + if(OC_PUSH_ENABLED AND OC_COLLECTIONS_IF_CREATE_ENABLED AND OC_DYNAMIC_ALLOCATION_ENABLED) + add_executable(push_originserver_multithread_linux + ${PROJECT_SOURCE_DIR}/push_originserver_multithread_linux.c + ) + target_link_libraries(push_originserver_multithread_linux client-server-static) + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/push_originserver_multithread_linux_creds) + + add_executable(push_targetserver_multithread_linux + ${PROJECT_SOURCE_DIR}/push_targetserver_multithread_linux.c + ) + target_link_libraries(push_targetserver_multithread_linux client-server-static) + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/push_targetserver_multithread_linux_creds) + + add_executable(push_configurator_multithread_linux + ${PROJECT_SOURCE_DIR}/push_configurator_multithread_linux.c + ) + target_link_libraries(push_configurator_multithread_linux client-server-static) + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/push_configurator_multithread_linux_creds) + endif() elseif(WIN32) add_executable(simpleserver ${PROJECT_SOURCE_DIR}/simpleserver_windows.c diff --git a/apps/Readme.md b/apps/Readme.md index 7836fb901f..a83a8d8f11 100644 --- a/apps/Readme.md +++ b/apps/Readme.md @@ -17,183 +17,143 @@ naming convention: ## Example applications -### Arduino.cpp +- ### Arduino.cpp: + Server example application explaining how to use IoTivity on an Arduino. + e.g. how to use IoTivity code in conjunction with the Arduino main loop. -Server example application explaining how to use IoTivity on an Arduino. -e.g. how to use IoTivity code in conjunction with the Arduino main loop. +- ### client_block_linux.c: + Client and Server example with linux main loop. -### client_block_linux.c +- ### client_certification_tests.c: + Client Certification test example + - runs on Linux only. + - uses client_certification_tests_IDD.cbor as introspection data. -Client and Server example with linux main loop. +- ### client_collections_linux.c: + Client example with collections. -### client_certification_tests.c +- ### client_linux.c: + Client example with Linux main loop. -Client Certification test example +- ### client_multithread_linux.c: + Client example on linux implementing multiple threads. -- runs on Linux only. -- uses client_certification_tests_IDD.cbor as introspection data. +- ### client_openthread.c: + Client example on openthread. -### client_collections_linux.c +- ### client_zephyr.c: + Client example on zypher. -Client example with collections. +- ### cloud_certification_tests.c: + Cloud certification example. -### client_linux.c +- ### cloud_client.c: + Client example with cloud API. -Client example with Linux main loop. +- ### cloud_proxy.c: + Cloud proxy example. + - uses cloud_proxy_IDD.cbor as introspection data. + works on Windows & Linux. -### client_multithread_linux.c +- ### cloud_server.c: + Server example with Cloud API. -Client example on linux implementing multiple threads. +- ### introspectionclient.c: + Client example of retrieving introspection device data. -### client_openthread.c +- ### multi_device_client_linux.c: + Client example on linux talking to multiple devices. -Client example on openthread. +- ### multi_device_server_linux.c: + Server example on linux, implementing more than 1 device. -### client_zephyr.c +- ### secure_mcast_client.c: + Client example with simple secure multicast. + Works on Linux only. -Client example on zypher. +- ### secure_mcast_server1.c: + Server example implementing simple secure multicast. + Works on Linux only. use in combination with secure_mcast_client.c. -### cloud_certification_tests.c +- ### secure_mcast_server2.c: + Server example implementing simple secure multicast. + Works on Linux only. use in combination with secure_mcast_client.c. -Cloud certification example. +- ### sensor_interrupt_server_zephyr.c: + Server example implementing a sensor on zypher, showing how to use interupts. -### cloud_client.c +- ### server_arduino.cpp: + Server example on Arduino. -Client example with cloud API. +- ### server_block_linux.c: + Client and server example on linux. -### cloud_proxy.c +- ### server_certification_tests.c: + Server example for certification tests. + - runs on Linux only. + - uses server_certification_tests_IDD.cbor as introspection data. -Cloud proxy example. +- ### server_collections_linux.c: + Server example implementing collections. -- uses cloud_proxy_IDD.cbor as introspection data. +- ### server_linux.c: + Server example on Linux. -works on Windows & Linux. +- ### server_multithread_linux.c: + Server example on Linux, multi threaded. -### cloud_server.c +- ### server_openthread.c: + Server example on openthread. -Server example with Cloud API. +- ### server_rules.c: + Server example implementing rules. + - runs on Linux only. + - uses server_certification_tests_IDD.cbor as introspection data. -### introspectionclient.c +- ### server_zephyr.c: + Client example on zypher. -Client example of retrieving introspection device data. +- ### simpleclient.c: + Client example on Linux. -### multi_device_client_linux.c +- ### simpleclient_windows.c: + Client example on windows. -Client example on linux talking to multiple devices. +- ### simpleserver.c: + Server example on Linux. -### multi_device_server_linux.c +- ### simpleserver_pki.c: + Server example implementing PKI using test certificates. + - runs on Linux only. -Server example on linux, implementing more than 1 device. +- ### simpleserver_windows.c: + Server example on Windows. -### secure_mcast_client.c +- ### smart_home_server_linux.c: + Server example on Linux. + - uses smart_home_server_linux_IDD.cbor as introspection data. -Client example with simple secure multicast. +- ### smart_home_server_with_mock_swupdate.cpp: + Server example implementing a mockup of software update. -Works on Linux only. +- ### smart_lock_linux.c: + Server example of a smart lock on Linux. + - includes commandline controller. -### secure_mcast_server1.c +- ### temp_sensor_client_linux.c: + Client example of temperature sensor. -Server example implementing simple secure multicast. +- ### push_configurator_multithread_linux.c: + PUSH configuration client which configures origin server and target server. -Works on Linux only. -use in combination with secure_mcast_client.c. +- ### push_originserver_multithread_linux.c: + PUSH origin server which has pushable Resource. -### secure_mcast_server2.c +- ### push_targetserver_multithread_linux.c: + PUSH target server which receives PUSH update request from PUSH origin server. + It plays the role of PUSH configurator at the same time. + - for more detail see [OCF Push Notification](./docs/push.md) -Server example implementing simple secure multicast. - -Works on Linux only. -use in combination with secure_mcast_client.c. - -### sensor_interrupt_server_zephyr.c - -Server example implementing a sensor on zypher, showing how to use interupts. - -### server_arduino.cpp - -Server example on Arduino. - -### server_block_linux.c - -Client and server example on linux. - -### server_certification_tests.c - -Server example for certification tests. - -- runs on Linux only. -- uses server_certification_tests_IDD.cbor as introspection data. - -### server_collections_linux.c - -Server example implementing collections. - -### server_linux.c - -Server example on Linux. - -### server_multithread_linux.c - -Server example on Linux, multi threaded. - -### server_openthread.c - -Server example on openthread. - -### server_rules.c - -Server example implementing rules. - -- runs on Linux only. -- uses server_certification_tests_IDD.cbor as introspection data. - -### server_zephyr.c - -Client example on zypher. - -### simpleclient.c - -Client example on Linux. - -### simpleclient_windows.c - -Client example on windows. - -### simpleserver.c - -Server example on Linux. - -### simpleserver_pki.c - -Server example implementing PKI using test certificates. - -- runs on Linux only. - -### simpleserver_windows.c - -Server example on Windows. - -### smart_home_server_linux.c - -Server example on Linux. - -- uses smart_home_server_linux_IDD.cbor as introspection data. - -### smart_home_server_with_mock_swupdate.cpp - -Server example implementing a mockup of software update. - -### smart_lock_linux.c - -Server example of a smart lock on Linux. - -- includes commandline controller. - -### temp_sensor_client_linux.c - -Client example of temperature sensor. - -### Other files - -The JSON files are the introspection files in JSON. -The CBOR files are the introspection files in CBOR. +- ### Other files: + The JSON files are the introspection files in JSON. + The CBOR files are the introspection files in CBOR. diff --git a/apps/docs/push.md b/apps/docs/push.md new file mode 100644 index 0000000000..9b9bf8ea80 --- /dev/null +++ b/apps/docs/push.md @@ -0,0 +1,222 @@ +# OCF Push Notification + +## Introduction +OCF Push Notification lets devices send notifications to other devices without any initial request such as what observation does. +OCF Push Notification is composed of 3 players: **Origin Server**, **Target Server**, **Configuration Client**. + +![push_arch.png](./resources/push_arch.png) +- **Origin Server**: + - An origin Server hosts pushable Resources (3rd LSB of "bm:p" of pushable Resource is 1) and sends push update requests to target Servers. + - An origin Server maintains "Push Configuration Resource" (`"oic.r.pushconfiguration"`) which keeps following information: + - Resources to be pushed. + - A Target to which the contents of a Resource will be pushed. + - A push configuration Resource is a collection Resource. + - An OCF Device which plays origin Server role does play Client role too, because it sends push updates to target servers. +- **Target Server**: + - A target Server receives push update requests from a origin Server. + - A target Server maintains "Push receiver Resource" (`"oic.r.pushreceiver"`) which keeps filtering information for incoming push update requests. It filters Resource type of Resource included in the incoming push update requests and filters target path to which pushed Resource will be stored. +- **Configuration Client**: + - A configuration client updates push configuration Resource in origin Servers and push receiver Resource in target Servers. +
+ +## How to enable PUSH Notification +### build configuration +- makefile + ```bash + # - if PUSH_DEBUG=1 and DEBUG=0, only push notification debug messages are printed + # - if DEBUG=1, push notification debug messages and all other debug messages are printed + # - DYNAMIC=1 is default, so you can omit it + $ make PUSH=1 (PUSH_DEBUG=1) CREATE=1 DYNAMIC=1 + $ sudo make PUSH=1 (PUSH_DEBUG=1) CREATE=1 DYNAMIC=1 install + ``` +- cmake + ```text + OC_COLLECTIONS_IF_CREATE_ENABLED = ON + OC_DYNAMIC_ALLOCATION_ENABLED = ON + OC_PUSH_ENABLED = ON + ``` +
+ +## How to run samples +### 3 Examples +- `push_configurator_multithread_linux`: **Configuration Client** + + ![push_configurator.png](./resources/push_configurator.png) + +- `push_originserver_multithread_linux`: **Origin Server** + + ![push_originserver.png](./resources/push_originserver.png) + +- `push_targetserver_multithread_linux`: **Target Server** + + ![push_targetserver.png](./resources/push_targetserver.png) + +### Onboarding +- If you want quick test, build iotivity-lite without security (`OC_SECURITY_ENABLED=OFF` or `SECURE=0`). +- If iotivity-lite is built with security, refer to following steps: + 1. add ACE to eath of them (use wildcard provisioning) + - allow all ACE permissions (CRUDN) + 3. pair `Configuration Client` and `Origin Server` and `Target Server` + - pair `Configuration Client` and `Origin Server`: let **Configuration Client** updates **push configuration Resource** + - pair `Configuration Client` and `Target Server`: let **Configuration Client** updates **push receiver Resource** + - pair `Origin Server` and `Target Server`: let **Origin Server** send push update request to **Target Server** +- please refer to [README.rst](../../README.rst) for detaild onboarding procedure. + +### Run examples +- **Configuration Client** + 1. Discovery: + find pushable Resource of "oic.r.custom.light" type. + ![push_config_d_resources.png](./resources/push_config_d_resources.png) + 2. Create new PUSH notification selector on origin server, and add new Receiver configuration object to target server: + create / update related configuration Resources. + ![push_config_c_ns.png](./resources/push_config_c_ns.png) + 3. Retrieve PUSH configuration Resource of origin server + ![push_config_r_pushconf.png](./resources/push_config_r_pushconf.png) + 4. Retrieve PUSH receivers Resource of target server + ![push_config_r_pushrecv.png](./resources/push_config_r_pushrecv.png) + 6. Retrieve PUSH origin Resource of origin-server + ![push_config_r_origin_resource.png](./resources/push_config_r_origin_resource.png) + +- **Origin Server** + Change Contents of pushable Resource: press `1` or `2`. + +- **Target Server** + - Whenever press `1` or `2` at Origin Server, you can see following screen. + ![newpush1.png](./resources/newpush1.png) + ![newpush2.png](./resources/newpush2.png) + - press `3` or `4` at Origin Server, you can see nothing changed. +
+ +## API +### Origin Server + +- `oc_resource_set_pushable()` + ```c + /** + * Specify if a resource can be pushable. + * + * @param[in] resource to specify as pushable or non-pushable + * @param[in] state if true the resource will be pushable if false the + * resource will be non-pushable + */ + void oc_resource_set_pushable(oc_resource_t *resource, bool state); + ``` + - Call this function once when you create new pushable Resource. +- `oc_resource_t.payload_builder` + ```c + struct oc_resource_s + { + ... + + #ifdef OC_COLLECTIONS + uint8_t num_links; ///< number of links in the collection + #ifdef OC_HAS_FEATURE_PUSH + oc_payload_callback_t + payload_builder; ///< callback to build contents of PUSH Notification + #endif + #endif /* OC_COLLECTIONS */ + + ... + }; + ``` + - Callback function pointer. This function is necessary to build contents of push update request. + - When iotivity-lite builds contents of push update request, it calls this function. + - Example: + ```c + /* PUSH payload builder example */ + void build_light_payload() + { + oc_rep_open_object(root, rep); + oc_rep_set_int(rep, power, power); + oc_rep_set_int(rep, brightness, brightness); + oc_rep_close_object(root, rep); + } + ``` +- `oc_resource_state_changed()` + ```c + /** + * @brief application should call this function whenever the contents of + * pushable Resource is updated, or Push Notification will not work. + * + * @param[in] uri path of pushable Resource whose contents is just + * updated + * @param[in] device_index index of Device that updated pushable Resource + * belongs to + */ + OC_API + void oc_resource_state_changed(const char *uri, size_t uri_len, size_t device_index); + ``` + - User apps should call this function whenever contents of pushable Resource is changed. + - Example: + ```c + /* example */ + static void change_brightness(void) + { + brightness = (brightness+1)%100; + /* notify PUSH origin Resource is changed */ + oc_resource_state_changed(resource_uri, resource_uri_len, res->device); + } + ``` + +### Target Server +- `oc_set_on_push_arrived()` + ```c + /** + * @brief object used to store Resource pushed to + * "oic.r.pshreceiver:receivers[i].receiveruri" + */ + typedef struct oc_pushd_resource_rep + { + struct oc_pushd_resource_rep *next; + oc_resource_t + *resource; ///< used to point any pushed Resource managed by iotivity-lite + oc_rep_t *rep; ///< payload of pushed Resource + } oc_pushd_resource_rep_t; + + /** + * @brief callback function called whenever new push arrives + */ + typedef void (*oc_on_push_arrived_t)(oc_pushd_resource_rep_t *); + + /** + * @brief set callback function called whenever new push arrives + * + * @param[in] func function name + */ + OC_API + void oc_set_on_push_arrived(oc_on_push_arrived_t func); + ``` + - Use this function to set callback function which will be called whenever new push update request arrives. + - Example: + ```c + /* callback function example */ + void push_arrived(oc_pushd_resource_rep_t *push_payload) + { + printf("new push arrives (path: %s, rt: ", oc_string(push_payload->resource->uri)); + for (size_t i=0; iresource->types); i++) + { + printf("%s ", oc_string_array_get_item(push_payload->resource->types, i)); + } + printf(")\n"); + + oc_print_pushd_resource(push_payload->rep); + } + ``` + +### Common +- Header file + ```c + #include "oc_push.h" + ``` +- `oc_print_pushd_resource()` + ```c + /** + * @brief print payload of Resource in user friendly format + * + * @param[in] payload pointer to the payload to be printed + */ + OC_API + void oc_print_pushd_resource(const oc_rep_t *payload); + ``` + - print out payload contents. +
\ No newline at end of file diff --git a/apps/docs/resources/newpush1.png b/apps/docs/resources/newpush1.png new file mode 100644 index 0000000000..0950a5d6fd Binary files /dev/null and b/apps/docs/resources/newpush1.png differ diff --git a/apps/docs/resources/newpush2.png b/apps/docs/resources/newpush2.png new file mode 100644 index 0000000000..7258ea2cda Binary files /dev/null and b/apps/docs/resources/newpush2.png differ diff --git a/apps/docs/resources/push_arch.png b/apps/docs/resources/push_arch.png new file mode 100644 index 0000000000..d27bb34a44 Binary files /dev/null and b/apps/docs/resources/push_arch.png differ diff --git a/apps/docs/resources/push_config_c_ns.png b/apps/docs/resources/push_config_c_ns.png new file mode 100644 index 0000000000..96065ef937 Binary files /dev/null and b/apps/docs/resources/push_config_c_ns.png differ diff --git a/apps/docs/resources/push_config_d_resources.png b/apps/docs/resources/push_config_d_resources.png new file mode 100644 index 0000000000..7150a5363a Binary files /dev/null and b/apps/docs/resources/push_config_d_resources.png differ diff --git a/apps/docs/resources/push_config_r_origin_resource.png b/apps/docs/resources/push_config_r_origin_resource.png new file mode 100644 index 0000000000..2909cd91ec Binary files /dev/null and b/apps/docs/resources/push_config_r_origin_resource.png differ diff --git a/apps/docs/resources/push_config_r_pushconf.png b/apps/docs/resources/push_config_r_pushconf.png new file mode 100644 index 0000000000..43d7db7841 Binary files /dev/null and b/apps/docs/resources/push_config_r_pushconf.png differ diff --git a/apps/docs/resources/push_config_r_pushrecv.png b/apps/docs/resources/push_config_r_pushrecv.png new file mode 100644 index 0000000000..864479d038 Binary files /dev/null and b/apps/docs/resources/push_config_r_pushrecv.png differ diff --git a/apps/docs/resources/push_configurator.png b/apps/docs/resources/push_configurator.png new file mode 100644 index 0000000000..90d188c30f Binary files /dev/null and b/apps/docs/resources/push_configurator.png differ diff --git a/apps/docs/resources/push_originserver.png b/apps/docs/resources/push_originserver.png new file mode 100644 index 0000000000..3032ad49bc Binary files /dev/null and b/apps/docs/resources/push_originserver.png differ diff --git a/apps/docs/resources/push_targetserver.png b/apps/docs/resources/push_targetserver.png new file mode 100644 index 0000000000..aaf3d3f9dd Binary files /dev/null and b/apps/docs/resources/push_targetserver.png differ diff --git a/apps/push_configurator_multithread_linux.c b/apps/push_configurator_multithread_linux.c new file mode 100644 index 0000000000..79216e09d3 --- /dev/null +++ b/apps/push_configurator_multithread_linux.c @@ -0,0 +1,539 @@ +/**************************************************************************** + * + * Copyright 2021 ETRI All Rights Reserved. + * + * 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. + * + * Created on: Aug 2, 2022, + * Author: Joo-Chul Kevin Lee (rune@etri.re.kr) + * + * + ****************************************************************************/ + +#include "oc_api.h" +#include "port/oc_clock.h" +#include "oc_push.h" +#include +#include +#include +#include + +// define application specific values. +static const char *spec_version = "ocf.1.0.0"; +static const char *data_model_version = "ocf.res.1.0.0"; +static const char *resource_rt = "oic.r.custom.light"; +static const char *device_rt = "oic.d.push"; +static const char *device_name = "push-configurator"; +static const char *manufacturer = "ETRI"; +static const char *recv_path = "/pushed-resource/from-complex-light"; + +pthread_mutex_t mutex; +pthread_cond_t cv; +struct timespec ts; + +pthread_mutex_t app_mutex; +int quit = 0; + +#define MAX_URI_LENGTH (30) +static char rsc_uri[MAX_URI_LENGTH]; +static char push_rsc_uri[MAX_URI_LENGTH]; +static bool resource_found = false; + +#define OC_IPV6_ADDRSTRLEN (59) +static char address[OC_IPV6_ADDRSTRLEN + 1]; +static oc_endpoint_t originserver_ep; +static oc_endpoint_t targetserver_ep; + +#define PING_RETRY_COUNT (4) + +typedef void (*custom_func_t)(oc_endpoint_t *, char *, + oc_resource_properties_t); + +typedef struct +{ + custom_func_t func; +} custom_func_s; + +void +push_arrived(oc_pushd_resource_rep_t *push_payload) +{ + PRINT("new push arrives (path: %s, rt: ", + oc_string(push_payload->resource->uri)); + for (size_t i = 0; + i < oc_string_array_get_allocated_size(push_payload->resource->types); + i++) { + PRINT("%s ", oc_string_array_get_item(push_payload->resource->types, i)); + } + PRINT(")\n"); + + oc_print_pushd_resource(push_payload->rep); +} + +static int +app_init(void) +{ + int ret = oc_init_platform(manufacturer, NULL, NULL); + ret |= oc_add_device("/oic/d", device_rt, device_name, spec_version, + data_model_version, NULL, NULL); + + /* set push callback function which will be called when new PUSH arrives */ + oc_set_on_push_arrived(push_arrived); + + return ret; +} + +static bool +is_resource_found(void) +{ + if (!resource_found) { + printf("Please discovery resource first!\n"); + return false; + } + + return true; +} + +static void +cb_create_notification_selector_response(oc_client_response_t *data) +{ + oc_rep_t *rep = data->payload; + + if (!rep) { + printf("\n => return status: [ %s ] \n\n", oc_status_to_str(data->code)); + return; + } + + printf("\n => return status: [ %s ] \n\n", oc_status_to_str(data->code)); + oc_print_pushd_resource(data->payload); + + return; +} + +static void +create_notification_selector(void) +{ + if (!is_resource_found()) + return; + + if (oc_init_post(PUSHCONFIG_RESOURCE_PATH, &originserver_ep, + "if=oic.if.create", + &cb_create_notification_selector_response, LOW_QOS, NULL)) { + oc_string_t pushtarget_ep_str; + oc_string_t pushtarget_str; + + oc_rep_begin_root_object(); + + oc_rep_open_array(root, rt); + oc_rep_add_text_string(rt, "oic.r.notificationselector"); + oc_rep_add_text_string(rt, "oic.r.pushproxy"); + oc_rep_close_array(root, rt); + + oc_rep_open_array(root, if); + oc_rep_add_text_string(if, "oic.if.rw"); + oc_rep_add_text_string(if, "oic.if.baseline"); + oc_rep_close_array(root, if); + + oc_rep_open_object(root, p); + oc_rep_set_uint(p, bm, 3); + oc_rep_close_object(root, p); + + /* ----- begin of "rep" ----- */ + oc_rep_open_object(root, rep); + + /* phref (optinal) */ + oc_rep_set_text_string(rep, phref, push_rsc_uri); + + /* prt (optinal) */ + oc_rep_open_array(rep, prt); + oc_rep_add_text_string(prt, resource_rt); + oc_rep_close_array(rep, prt); + + /* pushtarget */ + oc_endpoint_to_string(&targetserver_ep, &pushtarget_ep_str); + printf("target server's ep: %s \n", oc_string(pushtarget_ep_str)); + oc_concat_strings(&pushtarget_str, oc_string(pushtarget_ep_str), recv_path); + printf("targetpath: %s \n", oc_string(pushtarget_str)); + oc_rep_set_text_string(rep, pushtarget, oc_string(pushtarget_str)); + + /* pushqif */ + oc_rep_set_text_string(rep, pushqif, "oic.if.rw"); + + /* sourcert */ + oc_rep_open_array(rep, sourcert); + oc_rep_add_text_string(sourcert, "oic.r.pushpayload"); + oc_rep_close_array(rep, sourcert); + + /* state */ + /* ----- end of "rep" ----- */ + oc_rep_close_object(root, rep); + + oc_rep_end_root_object(); + + oc_free_string(&pushtarget_ep_str); + oc_free_string(&pushtarget_str); + } else { + printf("could not initiate oc_init_post()\n"); + return; + } + + if (!oc_do_post()) { + printf("oc_do_post() failed\n"); + } +} + +static void +cb_update_push_receiver_response(oc_client_response_t *data) +{ + (void)data; + + oc_rep_t *rep = data->payload; + + if (!rep) { + printf("\n => return status: [ %s ] \n\n", oc_status_to_str(data->code)); + return; + } + + printf("\n => return status: [ %s ] \n\n", oc_status_to_str(data->code)); + oc_print_pushd_resource(data->payload); + + return; +} + +static void +update_push_receiver(void) +{ + if (!is_resource_found()) + return; + + char query[2048]; + sprintf(query, "receiveruri=%s&if=oic.if.rw", recv_path); + + if (oc_init_post(PUSHRECEIVERS_RESOURCE_PATH, &targetserver_ep, query, + &cb_update_push_receiver_response, LOW_QOS, NULL)) { + /* create a "receiver" object in pushreceiver Resource */ + oc_rep_begin_root_object(); + oc_rep_set_text_string(root, receiveruri, recv_path); + oc_rep_open_array(root, rts); + oc_rep_add_text_string(rts, resource_rt); + oc_rep_close_array(root, rts); + oc_rep_end_root_object(); + } else { + printf("could not initiate oc_init_post()\n"); + return; + } + + if (!oc_do_post()) { + printf("oc_do_post() failed\n"); + } +} + +static void +cb_retrieve_push_origin_rsc_response(oc_client_response_t *data) +{ + printf("RETRIEVE \"%s\":\n", resource_rt); + oc_print_pushd_resource(data->payload); +} + +static void +retrieve_push_origin_rsc(void) +{ + if (!is_resource_found()) + return; + + oc_do_get(push_rsc_uri, &originserver_ep, NULL, + cb_retrieve_push_origin_rsc_response, LOW_QOS, NULL); +} + +static oc_discovery_flags_t +cb_discovery(const char *anchor, const char *uri, oc_string_array_t types, + oc_interface_mask_t iface_mask, oc_endpoint_t *endpoint, + oc_resource_properties_t bm, void *user_data) +{ + oc_discovery_flags_t ret = OC_CONTINUE_DISCOVERY; + + (void)anchor; + (void)iface_mask; + int i; + int uri_len = strlen(uri); + uri_len = (uri_len >= MAX_URI_LENGTH) ? MAX_URI_LENGTH - 1 : uri_len; + + for (i = 0; i < (int)oc_string_array_get_allocated_size(types); i++) { + char *t = oc_string_array_get_item(types, i); + if (strlen(t) == strlen(resource_rt) && + strncmp(t, resource_rt, strlen(t)) == 0) { + strncpy(rsc_uri, uri, uri_len); + rsc_uri[uri_len] = '\0'; + + printf("\nResource %s hosted at endpoints:\n", rsc_uri); + + if (user_data) { + custom_func_s *custom = (custom_func_s *)user_data; + custom->func(endpoint, rsc_uri, bm); + } else { + printf("custom function is not set!"); + goto exit; + } + } + } + +exit: + return ret; +} + +static void +cb_retrieve_pushconf_rsc_response(oc_client_response_t *data) +{ + printf("RETRIEVE \"%s\":\n", PUSHCONFIG_RESOURCE_TYPE); + oc_print_pushd_resource(data->payload); +} + +static void +retrieve_pushconf_rsc(void) +{ + if (!is_resource_found()) + return; + oc_do_get(PUSHCONFIG_RESOURCE_PATH, &originserver_ep, "if=oic.if.b", + cb_retrieve_pushconf_rsc_response, LOW_QOS, NULL); +} + +static void +cb_retrieve_pushreceiver_rsc_response(oc_client_response_t *data) +{ + printf("RETRIEVE \"%s\":\n", PUSHRECEIVERS_RESOURCE_TYPE); + oc_print_pushd_resource(data->payload); +} + +static void +retrieve_pushreceiver_rsc(void) +{ + oc_do_get(PUSHRECEIVERS_RESOURCE_PATH, &targetserver_ep, "if=oic.if.rw", + cb_retrieve_pushreceiver_rsc_response, LOW_QOS, NULL); +} + +static void +find_same_endpoint(oc_endpoint_t *endpoint, char *uri, + oc_resource_properties_t bm) +{ + oc_endpoint_t *ep = endpoint; + while (ep != NULL) { + printf(" |__"); + PRINTipaddr(*ep); + printf("\n"); + + if (oc_endpoint_compare(&originserver_ep, ep) == 0) { + printf(" ===> matched originserver ep is found!\n"); + if (bm & OC_PUSHABLE) { + printf(" ===> Resource %s is PUSHABLE Resource!\n", uri); + strcpy(push_rsc_uri, uri); + resource_found = true; + } + } + + ep = ep->next; + } +} + +static void +signal_event_loop(void) +{ + pthread_cond_signal(&cv); +} + +void +handle_signal(int signal) +{ + (void)signal; + signal_event_loop(); + quit = 1; +} + +static void * +process_func(void *data) +{ + (void)data; + oc_clock_time_t next_event; + + while (quit != 1) { + pthread_mutex_lock(&app_mutex); + next_event = oc_main_poll(); + pthread_mutex_unlock(&app_mutex); + pthread_mutex_lock(&mutex); + if (next_event == 0) { + pthread_cond_wait(&cv, &mutex); + } else { + ts.tv_sec = (next_event / OC_CLOCK_SECOND); + ts.tv_nsec = (next_event % OC_CLOCK_SECOND) * 1.e09 / OC_CLOCK_SECOND; + pthread_cond_timedwait(&cv, &mutex, &ts); + } + pthread_mutex_unlock(&mutex); + } + + pthread_exit(0); +} + +void +print_menu(void) +{ + pthread_mutex_lock(&app_mutex); + printf("=====================================\n"); + printf("1. Discovery\n"); + printf("2. Create new PUSH notification selector on origin server, and add " + "new Receiver configuration object to target server\n"); + printf("3. Retrieve PUSH origin Resource of origin-server\n"); + printf("4. Retrieve PUSH configuration Resource of origin server\n"); + printf("5. Retrieve PUSH receivers Resource of target server\n"); + printf("0. Quit\n"); + printf("=====================================\n"); + pthread_mutex_unlock(&app_mutex); +} + +int +main(void) +{ + int init = 0; + struct sigaction sa; + sigfillset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handle_signal; + sigaction(SIGINT, &sa, NULL); + oc_string_t address_str; + + /* get originserver ep */ + printf("set originserver address(ex. coap+tcp://xxx.xxx.xxx.xxx:yyyy): "); + if (scanf("%59s", address) > 0) { + printf("address: %s\n", address); + } else { + printf("error reading remote address\n"); + return -1; + } + + oc_new_string(&address_str, address, strlen(address)); + + if (oc_string_to_endpoint(&address_str, &originserver_ep, NULL) < 0) { + printf("error parsing originserver endpoint address\n"); + return -1; + } + originserver_ep.version = OCF_VER_1_0_0; + oc_free_string(&address_str); + + /* get targetserver ep */ + printf("set targetserver address(ex. coap+tcp://xxx.xxx.xxx.xxx:yyyy): "); + if (scanf("%59s", address) > 0) { + printf("address: %s\n", address); + } else { + printf("error reading remote address\n"); + return -1; + } + + oc_new_string(&address_str, address, strlen(address)); + + if (oc_string_to_endpoint(&address_str, &targetserver_ep, NULL) < 0) { + printf("error parsing originserver endpoint address\n"); + return -1; + } + originserver_ep.version = OCF_VER_1_0_0; + oc_free_string(&address_str); + + static const oc_handler_t handler = { .init = app_init, + .signal_event_loop = + signal_event_loop }; + +#ifdef OC_STORAGE + oc_storage_config("./push_targetserver_multithread_linux_creds"); +#endif /* OC_STORAGE */ + + if (pthread_mutex_init(&mutex, NULL)) { + printf("pthread_mutex_init failed!\n"); + return -1; + } + + if (pthread_mutex_init(&app_mutex, NULL)) { + printf("pthread_mutex_init failed!\n"); + pthread_mutex_destroy(&mutex); + return -1; + } + + init = oc_main_init(&handler); + if (init < 0) { + printf("oc_main_init failed!(%d)\n", init); + goto exit; + } + + pthread_t thread; + if (pthread_create(&thread, NULL, process_func, NULL) != 0) { + printf("Failed to create main thread\n"); + init = -1; + goto exit; + } + + custom_func_s same_func = { .func = find_same_endpoint }; + + int key; + while (quit != 1) { + print_menu(); + fflush(stdin); + if (!scanf("%d", &key)) { + printf("scanf failed!!!!\n"); + quit = 1; + handle_signal(0); + break; + } + + pthread_mutex_lock(&app_mutex); + switch (key) { + case 1: + /* discover all Resources whose rt is `resource_rt`, and save uri of + * pushable one */ + resource_found = false; + oc_do_ip_discovery(resource_rt, cb_discovery, &same_func); + break; + + case 2: + /* create PUSH notification selector for PUSH origin Resource */ + create_notification_selector(); + /* update PUSH receiver Resource for PUSH origin Resource */ + update_push_receiver(); + break; + case 3: + /* retrieve PUSH origin Resource */ + retrieve_push_origin_rsc(); + break; + case 4: + /* retrieve PUSH configuration Resource */ + retrieve_pushconf_rsc(); + break; + case 5: + /* retrieve PUSH receiver Resource */ + retrieve_pushreceiver_rsc(); + break; + case 0: + quit = 1; + handle_signal(0); + break; + default: + printf("unsupported command.\n"); + break; + } + pthread_mutex_unlock(&app_mutex); + } + + pthread_join(thread, NULL); + printf("pthread_join finish!\n"); + +exit: + oc_main_shutdown(); + + pthread_mutex_destroy(&mutex); + pthread_mutex_destroy(&app_mutex); + return 0; +} diff --git a/apps/push_originserver_multithread_linux.c b/apps/push_originserver_multithread_linux.c new file mode 100644 index 0000000000..216cc0de83 --- /dev/null +++ b/apps/push_originserver_multithread_linux.c @@ -0,0 +1,370 @@ +/**************************************************************************** + * + * Copyright 2021 ETRI All Rights Reserved. + * + * 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. + * + * Created on: Aug 2, 2022, + * Author: Joo-Chul Kevin Lee (rune@etri.re.kr) + * + * + ****************************************************************************/ + +#include "oc_api.h" +#include "oc_core_res.h" +#include "oc_push.h" +#include "port/oc_clock.h" +#include "port/oc_connectivity.h" + +#include +#include +#include + +// define application specific values. +static const char *spec_version = "ocf.1.0.0"; +static const char *data_model_version = "ocf.res.1.0.0"; + +static const char *resource_rt = "oic.r.custom.light"; +static const char *resource_uri = "/living-room/complex-light/1"; +static const char *resource_uri2 = "/living-room/complex-light/2"; + +static const char *device_rt = "oic.d.push"; +static const char *device_rt2 = "oic.d.custom.light"; +static const char *device_name = "push-originserver-Complex_Light"; +static const char *manufacturer = "ETRI"; + +static bool discoverable = true; +static bool observable = true; +static bool pushable = true; + +pthread_mutex_t mutex; +pthread_cond_t cv; +struct timespec ts; + +pthread_mutex_t app_mutex; +oc_resource_t *res; +oc_resource_t *res2; + +int quit = 0; + +static int power; +static int brightness; + +static int power2; +static int brightness2; + +/* + * callback function to be called whenever new PUSH arrives + */ +void +push_arrived(oc_pushd_resource_rep_t *push_payload) +{ + printf("new push arrives (path: %s, rt: ", + oc_string(push_payload->resource->uri)); + for (size_t i = 0; + i < oc_string_array_get_allocated_size(push_payload->resource->types); + i++) { + printf("%s ", oc_string_array_get_item(push_payload->resource->types, i)); + } + printf(")\n"); + + oc_print_pushd_resource(push_payload->rep); +} + +static int +app_init(void) +{ + int ret = oc_init_platform(manufacturer, NULL, NULL); + ret |= oc_add_device("/oic/d", device_rt, device_name, spec_version, + data_model_version, NULL, NULL); + + /* add additional device type */ + oc_device_bind_resource_type(0, device_rt2); + + /* set push callback function which will be called when new PUSH arrives */ + oc_set_on_push_arrived(push_arrived); + + return ret; +} + +static void +cb_retrieve_light1(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) +{ + (void)user_data; + + printf("cb_retrieve_light1() is called\n"); + oc_rep_start_root_object(); + switch (iface_mask) { + case OC_IF_BASELINE: + oc_process_baseline_interface(request->resource); + /* fall through */ + case OC_IF_RW: + oc_rep_set_int(root, brightness, brightness); + oc_rep_set_int(root, power, power); + break; + default: + break; + } + oc_rep_end_root_object(); + oc_send_response(request, OC_STATUS_OK); +} + +static void +cb_retrieve_light2(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) +{ + (void)user_data; + + printf("cb_retrieve_light2() is called\n"); + oc_rep_start_root_object(); + switch (iface_mask) { + case OC_IF_BASELINE: + oc_process_baseline_interface(request->resource); + /* fall through */ + case OC_IF_RW: + oc_rep_set_int(root, brightness, brightness2); + oc_rep_set_int(root, power, power2); + break; + default: + break; + } + oc_rep_end_root_object(); + oc_send_response(request, OC_STATUS_OK); +} + +/* ======== for light #1 ======== */ +static void +change_brightness(void) +{ + brightness = (brightness + 1) % 100; + /* notify PUSH origin Resource is changed */ + oc_resource_state_changed(resource_uri, strlen(resource_uri), res->device); +} + +static void +change_power(void) +{ + power += 5; + oc_resource_state_changed(resource_uri, strlen(resource_uri), res->device); +} + +/* ======== for light #2 ======== */ +static void +change_brightness2(void) +{ + brightness2 = (brightness2 + 1) % 100; + oc_resource_state_changed(resource_uri2, strlen(resource_uri2), res2->device); +} + +static void +change_power2(void) +{ + power2 += 5; + oc_resource_state_changed(resource_uri2, strlen(resource_uri2), res2->device); +} + +/* PUSH payload builder */ +void +build_light_payload() +{ + oc_rep_open_object(root, rep); + oc_rep_set_int(rep, power, power); + oc_rep_set_int(rep, brightness, brightness); + oc_rep_close_object(root, rep); +} + +static void +register_resources(void) +{ + /* light2 is not pushable */ + res2 = oc_new_resource(NULL, resource_uri2, 1, 0); + oc_resource_bind_resource_type(res2, resource_rt); + oc_resource_bind_resource_interface(res2, OC_IF_RW); + oc_resource_set_default_interface(res2, OC_IF_RW); + oc_resource_set_discoverable(res2, discoverable); + oc_resource_set_observable(res2, observable); + oc_resource_set_request_handler(res2, OC_GET, cb_retrieve_light2, NULL); + oc_add_resource(res2); + + /* light1 is pushable */ + res = oc_new_resource(NULL, resource_uri, 1, 0); + oc_resource_bind_resource_type(res, resource_rt); + oc_resource_bind_resource_interface(res, OC_IF_RW); + oc_resource_set_default_interface(res, OC_IF_RW); + oc_resource_set_discoverable(res, discoverable); + oc_resource_set_observable(res, observable); + /* make a Resource pushable */ + oc_resource_set_pushable(res, pushable); + oc_resource_set_request_handler(res, OC_GET, cb_retrieve_light1, NULL); + /* set PUSH payload builder */ + res->payload_builder = build_light_payload; + oc_add_resource(res); +} + +static void +signal_event_loop(void) +{ + pthread_cond_signal(&cv); +} + +void +handle_signal(int signal) +{ + (void)signal; + signal_event_loop(); + quit = 1; +} + +static void * +process_func(void *data) +{ + (void)data; + oc_clock_time_t next_event; + + while (quit != 1) { + pthread_mutex_lock(&app_mutex); + next_event = oc_main_poll(); + pthread_mutex_unlock(&app_mutex); + pthread_mutex_lock(&mutex); + if (next_event == 0) { + pthread_cond_wait(&cv, &mutex); + } else { + ts.tv_sec = (next_event / OC_CLOCK_SECOND); + ts.tv_nsec = (next_event % OC_CLOCK_SECOND) * 1.e09 / OC_CLOCK_SECOND; + pthread_cond_timedwait(&cv, &mutex, &ts); + } + pthread_mutex_unlock(&mutex); + } + + pthread_exit(0); +} + +void +print_menu(void) +{ + pthread_mutex_lock(&app_mutex); + printf("=============== Light A =============\n"); + printf("1. Change brightness(%d) of light #1\n", brightness); + printf("2. Change power(%d) of light #1\n", power); + printf("3. Change brightness(%d) of light #2\n", brightness2); + printf("4. Change power(%d) of light #2\n", power2); + printf("0. Quit\n"); + printf("=====================================\n"); + pthread_mutex_unlock(&app_mutex); +} + +int +main(void) +{ + int init = 0; + struct sigaction sa; + sigfillset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handle_signal; + sigaction(SIGINT, &sa, NULL); + + static const oc_handler_t handler = { .init = app_init, + .signal_event_loop = signal_event_loop, + .register_resources = + register_resources }; + +#ifdef OC_STORAGE + oc_storage_config("./push_originserver_multithread_linux_creds"); +#endif /* OC_STORAGE */ + + if (pthread_mutex_init(&mutex, NULL)) { + printf("pthread_mutex_init failed!\n"); + return -1; + } + + if (pthread_mutex_init(&app_mutex, NULL)) { + printf("pthread_mutex_init failed!\n"); + pthread_mutex_destroy(&mutex); + return -1; + } + + init = oc_main_init(&handler); + if (init < 0) { + printf("oc_main_init failed!(%d)\n", init); + goto exit; + } + + size_t device_num = oc_core_get_num_devices(); + size_t i; + for (i = 0; i < device_num; i++) { + oc_endpoint_t *ep = oc_connectivity_get_endpoints(i); + printf("=== device(%zd) endpoint info. ===\n", i); + while (ep) { + oc_string_t ep_str; + if (oc_endpoint_to_string(ep, &ep_str) == 0) { + printf("%s\n", oc_string(ep_str)); + oc_free_string(&ep_str); + } + ep = ep->next; + } + } + + pthread_t thread; + if (pthread_create(&thread, NULL, process_func, NULL) != 0) { + printf("Failed to create main thread\n"); + init = -1; + goto exit; + } + + int key; + while (quit != 1) { + print_menu(); + fflush(stdin); + if (!scanf("%d", &key)) { + printf("scanf failed!!!!\n"); + quit = 1; + handle_signal(0); + break; + } + + pthread_mutex_lock(&app_mutex); + switch (key) { + case 1: + change_brightness(); + break; + case 2: + change_power(); + break; + case 3: + change_brightness2(); + break; + case 4: + change_power2(); + break; + case 0: + quit = 1; + handle_signal(0); + break; + default: + printf("unsupported command.\n"); + break; + } + pthread_mutex_unlock(&app_mutex); + } + + pthread_join(thread, NULL); + printf("pthread_join finish!\n"); + +exit: + oc_main_shutdown(); + + pthread_mutex_destroy(&mutex); + pthread_mutex_destroy(&app_mutex); + return 0; +} diff --git a/apps/push_targetserver_multithread_linux.c b/apps/push_targetserver_multithread_linux.c new file mode 100644 index 0000000000..8ed28c3e54 --- /dev/null +++ b/apps/push_targetserver_multithread_linux.c @@ -0,0 +1,212 @@ +/**************************************************************************** + * + * Copyright 2021 ETRI All Rights Reserved. + * + * 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. + * + * Created on: Aug 2, 2022, + * Author: Joo-Chul Kevin Lee (rune@etri.re.kr) + * + * + ****************************************************************************/ + +#include "oc_api.h" +#include "oc_core_res.h" +#include "port/oc_clock.h" +#include "oc_push.h" +#include +#include +#include +#include + +// define application specific values. +static const char *spec_version = "ocf.1.0.0"; +static const char *data_model_version = "ocf.res.1.0.0"; +static const char *device_rt = "oic.d.push"; +static const char *device_name = "push-targetserver"; +static const char *manufacturer = "ETRI"; + +pthread_mutex_t mutex; +pthread_cond_t cv; +struct timespec ts; + +pthread_mutex_t app_mutex; +int quit = 0; + +void +push_arrived(oc_pushd_resource_rep_t *push_payload) +{ + printf("new push arrives (path: %s, rt: ", + oc_string(push_payload->resource->uri)); + for (size_t i = 0; + i < oc_string_array_get_allocated_size(push_payload->resource->types); + i++) { + printf("%s ", oc_string_array_get_item(push_payload->resource->types, i)); + } + printf(")\n"); + + oc_print_pushd_resource(push_payload->rep); +} + +static int +app_init(void) +{ + int ret = oc_init_platform(manufacturer, NULL, NULL); + ret |= oc_add_device("/oic/d", device_rt, device_name, spec_version, + data_model_version, NULL, NULL); + + /* set push callback function which will be called when new PUSH arrives */ + oc_set_on_push_arrived(push_arrived); + + return ret; +} + +static void +signal_event_loop(void) +{ + pthread_cond_signal(&cv); +} + +void +handle_signal(int signal) +{ + (void)signal; + signal_event_loop(); + quit = 1; +} + +static void * +process_func(void *data) +{ + (void)data; + oc_clock_time_t next_event; + + while (quit != 1) { + pthread_mutex_lock(&app_mutex); + next_event = oc_main_poll(); + pthread_mutex_unlock(&app_mutex); + pthread_mutex_lock(&mutex); + if (next_event == 0) { + pthread_cond_wait(&cv, &mutex); + } else { + ts.tv_sec = (next_event / OC_CLOCK_SECOND); + ts.tv_nsec = (next_event % OC_CLOCK_SECOND) * 1.e09 / OC_CLOCK_SECOND; + pthread_cond_timedwait(&cv, &mutex, &ts); + } + pthread_mutex_unlock(&mutex); + } + + pthread_exit(0); +} + +void +print_menu(void) +{ + pthread_mutex_lock(&app_mutex); + printf("=====================================\n"); + printf("0. Quit\n"); + printf("=====================================\n"); + pthread_mutex_unlock(&app_mutex); +} + +int +main(void) +{ + int init = 0; + struct sigaction sa; + sigfillset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handle_signal; + sigaction(SIGINT, &sa, NULL); + + static const oc_handler_t handler = { .init = app_init, + .signal_event_loop = + signal_event_loop }; + +#ifdef OC_STORAGE + oc_storage_config("./push_targetserver_multithread_linux_creds"); +#endif /* OC_STORAGE */ + + if (pthread_mutex_init(&mutex, NULL)) { + printf("pthread_mutex_init failed!\n"); + return -1; + } + + if (pthread_mutex_init(&app_mutex, NULL)) { + printf("pthread_mutex_init failed!\n"); + pthread_mutex_destroy(&mutex); + return -1; + } + + init = oc_main_init(&handler); + if (init < 0) { + printf("oc_main_init failed!(%d)\n", init); + goto exit; + } + + size_t device_num = oc_core_get_num_devices(); + size_t i; + for (i = 0; i < device_num; i++) { + oc_endpoint_t *ep = oc_connectivity_get_endpoints(i); + printf("=== device(%zd) endpoint info. ===\n", i); + while (ep) { + oc_string_t ep_str; + if (oc_endpoint_to_string(ep, &ep_str) == 0) { + printf("%s\n", oc_string(ep_str)); + oc_free_string(&ep_str); + } + ep = ep->next; + } + } + + pthread_t thread; + if (pthread_create(&thread, NULL, process_func, NULL) != 0) { + printf("Failed to create main thread\n"); + init = -1; + goto exit; + } + + int key; + while (quit != 1) { + print_menu(); + fflush(stdin); + if (!scanf("%d", &key)) { + printf("scanf failed!!!!\n"); + quit = 1; + handle_signal(0); + break; + } + + pthread_mutex_lock(&app_mutex); + switch (key) { + case 0: + quit = 1; + handle_signal(0); + break; + default: + printf("unsupported command.\n"); + break; + } + pthread_mutex_unlock(&app_mutex); + } + + pthread_join(thread, NULL); + printf("pthread_join finish!\n"); + +exit: + oc_main_shutdown(); + + pthread_mutex_destroy(&mutex); + pthread_mutex_destroy(&app_mutex); + return 0; +} diff --git a/include/oc_api.h b/include/oc_api.h index 13cd722816..7477fd219d 100644 --- a/include/oc_api.h +++ b/include/oc_api.h @@ -29,14 +29,16 @@ #ifndef OC_API_H #define OC_API_H +#include "util/oc_features.h" #include "messaging/coap/oc_coap.h" +#include "port/oc_storage.h" #include "oc_buffer_settings.h" #include "oc_cloud.h" #include "oc_config.h" +#include "oc_export.h" #include "oc_rep.h" #include "oc_ri.h" #include "oc_signal_event_loop.h" -#include "port/oc_storage.h" #ifdef __cplusplus extern "C" { @@ -1137,6 +1139,18 @@ void oc_resource_make_public(oc_resource_t *resource); */ void oc_resource_set_discoverable(oc_resource_t *resource, bool state); +#ifdef OC_HAS_FEATURE_PUSH +/** + * Specify if a resource can be pushable. + * + * @param[in] resource to specify as pushable or non-pushable + * @param[in] state if true the resource will be pushable if false the + * resource will be non-pushable + */ +OC_API +void oc_resource_set_pushable(oc_resource_t *resource, bool state); +#endif + /** * Specify that a resource should notify clients when a property has been * modified. diff --git a/include/oc_helpers.h b/include/oc_helpers.h index 80ec26b0fa..1fc46b7542 100644 --- a/include/oc_helpers.h +++ b/include/oc_helpers.h @@ -21,6 +21,7 @@ #include "util/oc_list.h" #include "util/oc_mmem.h" +#include "oc_export.h" #include #include #include @@ -261,6 +262,16 @@ void _oc_free_array( #endif oc_array_t *ocarray, pool type); +/** + * @brief reset ocstring contents + * + * @param ocstring ocstring to be reset + * @param str not terminated string which will replace current str + * @param str_len size of the string + */ +OC_API +void oc_set_string(oc_string_t *ocstring, const char *str, size_t str_len); + /** * @brief new array * diff --git a/include/oc_push.h b/include/oc_push.h new file mode 100644 index 0000000000..f2ab02bf07 --- /dev/null +++ b/include/oc_push.h @@ -0,0 +1,100 @@ +/**************************************************************************** + * + * Copyright 2021 ETRI All Rights Reserved. + * + * 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. + * + * Created on: Aug 23, 2021, + * Author: Joo-Chul Kevin Lee (rune@etri.re.kr) + * + * + ****************************************************************************/ + +#ifndef OC_PUSH_H +#define OC_PUSH_H + +#include + +#include "oc_config.h" +#include "oc_helpers.h" +#include "oc_rep.h" +#include "oc_ri.h" +#include "oc_endpoint.h" +#include "port/oc_log.h" +#include "util/oc_memb.h" +#include "util/oc_process.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PUSHCONFIG_RESOURCE_PATH "/pushconfig" +#define PUSHCONFIG_RESOURCE_TYPE "oic.r.pushconfiguration" +#define PUSHCONFIG_RESOURCE_NAME "Push Configuration" + +#define PUSHRECEIVERS_RESOURCE_PATH "/pushreceivers" +#define PUSHRECEIVERS_RESOURCE_TYPE "oic.r.pushreceiver" +#define PUSHRECEIVERS_RESOURCE_NAME "Push Receiver Configuration" + +/** + * @brief object used to store Resource pushed to + * "oic.r.pushreceiver:receivers[i].receiveruri" + */ +typedef struct oc_pushd_resource_rep +{ + struct oc_pushd_resource_rep *next; + oc_resource_t + *resource; ///< used to point any pushed Resource managed by iotivity-lite + oc_rep_t *rep; ///< payload of pushed Resource +} oc_pushd_resource_rep_t; + +/** + * @brief callback function called whenever new push arrives + */ +typedef void (*oc_on_push_arrived_t)(oc_pushd_resource_rep_t *); + +/** + * @brief print payload of Resource in user friendly format + * + * @param[in] payload pointer to the payload to be printed + */ +OC_API +void oc_print_pushd_resource(const oc_rep_t *payload); + +/** + * @brief set callback function called whenever new push arrives + * + * @param[in] func function name + */ +OC_API +void oc_set_on_push_arrived(oc_on_push_arrived_t func); + +/** + * @brief application should call this function whenever the contents of + * pushable Resource is updated, or Push Notification will not work. + * + * @param[in] uri path of pushable Resource whose contents is just + * updated + * @param[in] uri_len length of uri + * @param[in] device_index index of Device that updated pushable Resource + * belongs to + */ +OC_API +void oc_resource_state_changed(const char *uri, size_t uri_len, + size_t device_index); + +#ifdef __cplusplus +} +#endif + +#endif /*OC_PUSH_H*/ diff --git a/include/oc_rep.h b/include/oc_rep.h index 18bb048927..d7c4863d65 100644 --- a/include/oc_rep.h +++ b/include/oc_rep.h @@ -23,6 +23,7 @@ #include "deps/tinycbor/src/cbor.h" #include "oc_helpers.h" #include "util/oc_memb.h" +#include "util/oc_features.h" #include #include #include @@ -1073,6 +1074,10 @@ int oc_parse_rep(const uint8_t *payload, size_t payload_size, void oc_free_rep(oc_rep_t *rep); +#ifdef OC_HAS_FEATURE_PUSH +oc_rep_t *oc_alloc_rep(); +#endif + /** * Check for a null value from an `oc_rep_t` * diff --git a/include/oc_ri.h b/include/oc_ri.h index b72b2f0a5b..cff9a4fdcd 100644 --- a/include/oc_ri.h +++ b/include/oc_ri.h @@ -25,6 +25,7 @@ #include "oc_rep.h" #include "oc_uuid.h" #include "util/oc_etimer.h" +#include "util/oc_features.h" #include #ifdef __cplusplus @@ -50,9 +51,12 @@ typedef enum { typedef enum { OC_DISCOVERABLE = (1 << 0), ///< discoverable OC_OBSERVABLE = (1 << 1), ///< observable - OC_SECURE = (1 << 4), ///< secure - OC_PERIODIC = (1 << 6), ///< periodiacal update - OC_SECURE_MCAST = (1 << 8) ///< secure multicast (oscore) +#ifdef OC_HAS_FEATURE_PUSH + OC_PUSHABLE = (1 << 2), ///< pushable +#endif + OC_SECURE = (1 << 4), ///< secure + OC_PERIODIC = (1 << 6), ///< periodiacal update + OC_SECURE_MCAST = (1 << 8) ///< secure multicast (oscore) } oc_resource_properties_t; /** @@ -270,6 +274,14 @@ typedef bool (*oc_set_properties_cb_t)(oc_resource_t *, oc_rep_t *, void *); typedef void (*oc_get_properties_cb_t)(oc_resource_t *, oc_interface_mask_t, void *); +#ifdef OC_HAS_FEATURE_PUSH +/** + * @brief application should define this callback which builds updated contents + * of pushable Resource + */ +typedef void (*oc_payload_callback_t)(); +#endif + /** * @brief properties callback structure * @@ -315,7 +327,11 @@ struct oc_resource_s oc_locn_t tag_locn; ///< tag (value) for location description uint8_t num_observers; ///< amount of observers #ifdef OC_COLLECTIONS - uint8_t num_links; ///< number of links in the collection + uint8_t num_links; ///< number of links in the collection +#ifdef OC_HAS_FEATURE_PUSH + oc_payload_callback_t + payload_builder; ///< callback to build contents of PUSH Notification +#endif #endif /* OC_COLLECTIONS */ uint16_t observe_period_seconds; ///< observe period in seconds }; @@ -414,6 +430,15 @@ void oc_ri_remove_timed_event_callback(void *cb_data, */ int oc_status_code(oc_status_t key); +/** + * @brief convert the status code to string + * + * @param[in] key key the application level key of the code + * @return char* CoAP status code string + */ +OC_API +const char *oc_status_to_str(oc_status_t key); + /** * @brief retrieve the resource by uri and device indes * diff --git a/port/linux/Makefile b/port/linux/Makefile index b42d63ef58..b228f009be 100644 --- a/port/linux/Makefile +++ b/port/linux/Makefile @@ -195,6 +195,17 @@ ifeq ($(SWUPDATE),1) export SWUPDATE endif + +# for PUSH NOTIFICATION +ifeq ($(PUSH), 1) + EXTRA_CFLAGS += -DOC_PUSH + SAMPLES += push_originserver_multithread_linux push_targetserver_multithread_linux push_configurator_multithread_linux +ifeq ($(PUSH_DEBUG), 1) + EXTRA_CFLAGS += -DOC_PUSHDEBUG +endif +endif + + ifneq ($(SECURE),0) SRC += $(addprefix ../../security/,oc_acl.c oc_cred.c oc_doxm.c oc_pstat.c oc_tls.c oc_svr.c oc_store.c oc_pki.c oc_certs.c oc_sp.c oc_keypair.c oc_csr.c oc_roles.c oc_ael.c oc_audit.c oc_sdi.c oc_oscore_engine.c oc_oscore_crypto.c oc_oscore_context.c) SRC_COMMON += $(addprefix $(MBEDTLS_DIR)/library/,${DTLS}) @@ -503,6 +514,19 @@ client_multithread_linux: libiotivity-lite-client.a $(ROOT_DIR)/apps/client_mult @mkdir -p $@_creds ${CC} -o $@ ../../apps/client_multithread_linux.c libiotivity-lite-client.a -DOC_CLIENT ${CFLAGS} ${LIBS} +# push notification samples +push_originserver_multithread_linux: libiotivity-lite-client-server.a $(ROOT_DIR)/apps/push_originserver_multithread_linux.c + @mkdir -p $@_creds + ${CC} -o $@ ../../apps/$@.c libiotivity-lite-client-server.a -DOC_CLIENT -DOC_SERVER ${CFLAGS} ${LIBS} + +push_targetserver_multithread_linux: libiotivity-lite-client-server.a $(ROOT_DIR)/apps/push_targetserver_multithread_linux.c + @mkdir -p $@_creds + ${CC} -o $@ ../../apps/$@.c libiotivity-lite-client-server.a -DOC_CLIENT -DOC_SERVER ${CFLAGS} ${LIBS} + +push_configurator_multithread_linux: libiotivity-lite-client-server.a $(ROOT_DIR)/apps/push_configurator_multithread_linux.c + @mkdir -p $@_creds + ${CC} -o $@ ../../apps/$@.c libiotivity-lite-client-server.a -DOC_CLIENT -DOC_SERVER ${CFLAGS} ${LIBS} + $(SO_DPP_OBJ): $(ROOT_DIR)/apps/streamlined_onboarding/ocf_dpp.c ${CC} -o $@ -c $^ ${CFLAGS} ${SECURITY_HEADERS} ${LIBS} diff --git a/port/linux/readme.md b/port/linux/readme.md index 30c3fdc540..8f30c89b0a 100644 --- a/port/linux/readme.md +++ b/port/linux/readme.md @@ -70,4 +70,14 @@ Option and default setting in the Linux Makefile - WKCORE - enable discovery through IETF /.well-known/core on IETFs multicast ALL COAP NODES \ No newline at end of file + enable discovery through IETF /.well-known/core on IETFs multicast ALL COAP NODES + +- PUSH 0 + + disable push notification + +- PUSH_DEBUG 0 + + disable push notification debug message + (if `DEBUG`=0 and `PUSH_DEBUG`=1, only push notification debug messages are activated. + if `DEBUG`=1, push notification debug messages are activated too regardless of `PUSH_DEBUG`) \ No newline at end of file diff --git a/port/oc_log.h b/port/oc_log.h index c4760813eb..ca6c4d0781 100644 --- a/port/oc_log.h +++ b/port/oc_log.h @@ -192,7 +192,7 @@ extern "C" { } \ } while (0) -#ifdef OC_DEBUG +#if defined(OC_DEBUG) || defined(OC_PUSHDEBUG) #ifdef __ANDROID__ #define OC_LOG(level, ...) \ android_log(level, __FILE__, __func__, __LINE__, __VA_ARGS__) @@ -208,6 +208,7 @@ extern "C" { PRINT("\n"); \ } while (0) +#if defined(OC_DEBUG) #define OC_LOGipaddr(endpoint) \ do { \ PRINT("DEBUG: %s <%s:%d>: ", __FILENAME__, __func__, __LINE__); \ @@ -225,11 +226,22 @@ extern "C" { } while (0) #else #endif /* NO_LOG_BYTES */ +#else +#define OC_LOGipaddr(endpoint) +#define OC_LOGbytes(bytes, length) +#endif /* OC_DEBUG */ #endif /* __ANDROID__ */ +#if defined(OC_DEBUG) #define OC_DBG(...) OC_LOG("D", __VA_ARGS__) #define OC_WRN(...) OC_LOG("W", __VA_ARGS__) #define OC_ERR(...) OC_LOG("E", __VA_ARGS__) +#else +#define OC_DBG(...) +#define OC_WRN(...) +#define OC_ERR(...) +#endif + #else #define OC_LOG(...) #define OC_DBG(...) diff --git a/swig/swig_interfaces/oc_ri.i b/swig/swig_interfaces/oc_ri.i index d732e2b9e9..ceff56e6de 100644 --- a/swig/swig_interfaces/oc_ri.i +++ b/swig/swig_interfaces/oc_ri.i @@ -126,4 +126,5 @@ typedef struct oc_response_buffer_s int code; } oc_response_buffer_t; +#define OC_API %include "oc_ri.h" \ No newline at end of file diff --git a/util/oc_features.h b/util/oc_features.h index 7bc9db830a..3a81940880 100644 --- a/util/oc_features.h +++ b/util/oc_features.h @@ -26,4 +26,9 @@ #define OC_HAS_FEATURE_TCP_ASYNC_CONNECT #endif /* __linux__ && OC_CLIENT && OC_TCP */ +#if defined(OC_PUSH) && defined(OC_SERVER) && defined(OC_CLIENT) && \ + defined(OC_DYNAMIC_ALLOCATION) && defined(OC_COLLECTIONS_IF_CREATE) +#define OC_HAS_FEATURE_PUSH +#endif + #endif /* OC_FEATURES_H */