diff --git a/.gitignore b/.gitignore index f4c39751a101ed5106e8f0706b86b80b59ce0201..fbc335f363a1189f28163fe539ec5f6065896f2b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,17 @@ port/linux/multi_device_server port/linux/onboarding_tool port/linux/server_multithread_linux port/linux/client_multithread_linux +port/linux/cloud_linux +port/linux/cloud_linux_creds/ port/linux/libiotivity-constrained-client.* port/linux/libiotivity-constrained-server.* port/linux/libiotivity-constrained-client-server.* +port/linux/iotivity-constrained-cloud.pc +port/linux/iotivity-constrained-cloud.a +port/linux/iotivity-constrained-cloud.so +port/linux/iotivity-constrained-rd-client.pc +port/linux/iotivity-constrained-rd-client.a +port/linux/iotivity-constrained-rd-client.so port/linux/iotivity-constrained-server.pc port/linux/iotivity-constrained-client.pc port/linux/iotivity-constrained-client-server.pc @@ -40,10 +48,19 @@ port/openthread/output *.o *.cmd *.tmp +*.gcda +*.gcno .clang-format _clang-format port/linux/platformtest port/linux/storage_test +port/linux/apitest +port/linux/cloudaccesstest +port/linux/messagingtest +port/linux/rdclienttest +port/linux/securitytest +port/linux/simpleserver_pki +port/linux/stapptest #creds files port/linux/client_creds @@ -59,5 +76,9 @@ port/linux/multi_device_server_creds port/linux/server_multithread_linux_creds port/linux/client_multithread_linux_creds +#service +service/cloud-access/unittest/obj +service/resource-directory/client/unittest/obj + #vscode setting files -.vscode +.vscode \ No newline at end of file diff --git a/README.rst b/README.rst index 705839868727072270f9bf849b0fdb8c325229ee..e7d6927b54a176242d138f5f0041b08ac995fc09 100644 --- a/README.rst +++ b/README.rst @@ -142,6 +142,9 @@ port//* apps/* contains sample OCF applications. +service/* + contains OCF services + Setup source tree ----------------- @@ -168,6 +171,9 @@ Add ``TCP=1`` to include support for TCP endpoints and CoAP over TCP (RFC 8323). Add ``IPV4=1`` to include IPv4 support in the build. Excluding ``IPV4=1`` produces an IPv6-only build. +Add ``CLOUD=1`` to include OCF Cloud in the build. TCP and IPv4 +are included too. + Building sample applications on Windows --------------------------------------- diff --git a/apps/cloud_linux.c b/apps/cloud_linux.c new file mode 100644 index 0000000000000000000000000000000000000000..66e6023653ed254e13d2150ff057f8ff710dbe64 --- /dev/null +++ b/apps/cloud_linux.c @@ -0,0 +1,204 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ****************************************************************************/ + +#include "cloud.h" +#include "oc_api.h" +#include "oc_core_res.h" +#include "port/oc_clock.h" +#include "rd_client.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 = "core.light"; +static const char *device_rt = "oic.d.cloudDevice"; +static const char *device_name = "Cloud Device"; + +static const char *manufacturer = "ocfcloud.com"; + +pthread_mutex_t mutex; +pthread_cond_t cv; + +oc_resource_t *res1; +oc_resource_t *res2; + +int quit = 0; + +static void cloud_status_handler(cloud_status_t status, void *user_data) { + (void)user_data; + printf("cloud_status: %d\n", (int)status); +} + +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); + ret |= cloud_init(0, cloud_status_handler, NULL); + return ret; +} + +struct light_t { + bool state; + int power; +}; + +struct light_t light1 = {0}; +struct light_t light2 = {0}; + +static void get_handler(oc_request_t *request, oc_interface_mask_t interface, + void *user_data) { + struct light_t *light = (struct light_t *)user_data; + + printf("get_handler:\n"); + + oc_rep_start_root_object(); + switch (interface) { + case OC_IF_BASELINE: + oc_process_baseline_interface(request->resource); + /* fall through */ + case OC_IF_RW: + oc_rep_set_boolean(root, state, light->state); + oc_rep_set_int(root, power, light->power); + oc_rep_set_text_string(root, name, "Light"); + break; + default: + break; + } + oc_rep_end_root_object(); + oc_send_response(request, OC_STATUS_OK); +} + +static void post_handler(oc_request_t *request, oc_interface_mask_t iface_mask, + void *user_data) { + struct light_t *light = (struct light_t *)user_data; + (void)iface_mask; + printf("post_handler:\n"); + oc_rep_t *rep = request->request_payload; + while (rep != NULL) { + char *key = oc_string(rep->name); + printf("key: %s ", key); + if (key && !strcmp(key, "state")) { + switch (rep->type) { + case OC_REP_BOOL: + light->state = rep->value.boolean; + printf("value: %d\n", light->state); + break; + default: + oc_send_response(request, OC_STATUS_BAD_REQUEST); + return; + } + } else if (key && !strcmp(key, "power")) { + switch (rep->type) { + case OC_REP_INT: + light->power = rep->value.integer; + printf("value: %d\n", light->power); + break; + default: + oc_send_response(request, OC_STATUS_BAD_REQUEST); + return; + } + } + rep = rep->next; + } + oc_send_response(request, OC_STATUS_CHANGED); + oc_notify_observers(request->resource); +} + +static void register_resources(void) { + res1 = oc_new_resource(NULL, "/light/1", 1, 0); + oc_resource_bind_resource_type(res1, resource_rt); + oc_resource_bind_resource_interface(res1, OC_IF_RW); + oc_resource_set_default_interface(res1, OC_IF_RW); + oc_resource_set_discoverable(res1, true); + oc_resource_set_observable(res1, true); + oc_resource_set_request_handler(res1, OC_GET, get_handler, &light1); + oc_resource_set_request_handler(res1, OC_POST, post_handler, &light1); + cloud_rd_publish(res1); + oc_add_resource(res1); + + res2 = oc_new_resource(NULL, "/light/2", 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, true); + oc_resource_set_observable(res2, true); + oc_resource_set_request_handler(res2, OC_GET, get_handler, &light2); + oc_resource_set_request_handler(res2, OC_POST, post_handler, &light2); + cloud_rd_publish(res2); + oc_add_resource(res2); +} + +static void signal_event_loop(void) { + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mutex); +} + +void handle_signal(int signal) { + (void)signal; + signal_event_loop(); + quit = 1; +} + +int main(void) { + int init; + 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}; + + oc_storage_config("./cloud_linux_creds"); + + if (pthread_mutex_init(&mutex, NULL) < 0) { + printf("pthread_mutex_init failed!\n"); + return -1; + } + + init = oc_main_init(&handler); + if (init < 0) + return init; + + while (quit != 1) { + oc_clock_time_t next_event = oc_main_poll(); + pthread_mutex_lock(&mutex); + if (next_event == 0) { + pthread_cond_wait(&cv, &mutex); + } else { + struct timespec ts; + 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); + } + + cloud_shutdown(0); + oc_main_shutdown(); + return 0; +} diff --git a/port/linux/Makefile b/port/linux/Makefile index 6af83fd45f2913f16124fb6fc0a4705efb4cea0a..19e2e4be64e356d06e5a407dcde826818257d4ff 100644 --- a/port/linux/Makefile +++ b/port/linux/Makefile @@ -55,6 +55,17 @@ MESSAGING_TEST_DIR = $(ROOT_DIR)/messaging/coap/unittest MESSAGING_TEST_OBJ_DIR = $(MESSAGING_TEST_DIR)/obj MESSAGING_TEST_SRC_FILES := $(wildcard $(MESSAGING_TEST_DIR)/*.cpp) MESSAGING_TEST_OBJ_FILES := $(patsubst $(MESSAGING_TEST_DIR)/%.cpp,$(MESSAGING_TEST_OBJ_DIR)/%.o,$(MESSAGING_TEST_SRC_FILES)) + +CLOUD_TEST_DIR = $(ROOT_DIR)/service/cloud/unittest +CLOUD_TEST_OBJ_DIR = $(CLOUD_TEST_DIR)/obj +CLOUD_TEST_SRC_FILES := $(wildcard $(CLOUD_TEST_DIR)/*.cpp) +CLOUD_TEST_OBJ_FILES := $(patsubst $(CLOUD_TEST_DIR)/%.cpp,$(CLOUD_TEST_OBJ_DIR)/%.o,$(CLOUD_TEST_SRC_FILES)) + +RD_CLIENT_TEST_DIR = $(ROOT_DIR)/service/resource-directory/client/unittest +RD_CLIENT_TEST_OBJ_DIR = $(RD_CLIENT_TEST_DIR)/obj +RD_CLIENT_TEST_SRC_FILES := $(wildcard $(RD_CLIENT_TEST_DIR)/*.cpp) +RD_CLIENT_TEST_OBJ_FILES := $(patsubst $(RD_CLIENT_TEST_DIR)/%.cpp,$(RD_CLIENT_TEST_OBJ_DIR)/%.o,$(RD_CLIENT_TEST_SRC_FILES)) + UNIT_TESTS = apitest platformtest securitytest messagingtest DTLS= aes.c aesni.c arc4.c asn1parse.c asn1write.c base64.c \ @@ -78,6 +89,8 @@ CTIMESTAMP=../../api/c-timestamp/timestamp_format.c ../../api/c-timestamp/timest SRC_COMMON=$(wildcard ../../util/*.c) ${CBOR} ${CTIMESTAMP} SRC=$(wildcard ../../messaging/coap/*.c ../../api/*.c ../../port/linux/*.c) +SRC_CLOUD=$(wildcard ../../service/cloud/src/*.c) +SRC_RD_CLIENT=$(wildcard ../../service/resource-directory/client/src/*.c) HEADERS = $(wildcard ../../include/*.h) HEADERS += ../../port/linux/oc_config.h @@ -88,17 +101,29 @@ HEADERS_UTIL_PT = $(wildcard ../../util/pt/*.h) HEADERS_PORT = $(wildcard ../../port/*.h) HEADERS_TINYCBOR = $(wildcard ../../deps/tinycbor/src/*.h) +CFLAGS_CLOUD=-I../../service/cloud/include +CFLAGS_RD_CLIENT=-I../../service/resource-directory/client/include CFLAGS=-fPIC -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -ffreestanding -Os -fno-stack-protector -ffunction-sections -fdata-sections -fno-reorder-functions -fno-defer-pop -fno-strict-overflow -I./ -I../../include/ -I../../ -std=gnu99 -Wall -Wextra -Werror -pedantic #-Wl,-Map,client.map OBJ_COMMON=$(addprefix obj/,$(notdir $(SRC_COMMON:.c=.o))) OBJ_CLIENT=$(addprefix obj/client/,$(notdir $(SRC:.c=.o))) OBJ_SERVER=$(addprefix obj/server/,$(filter-out oc_obt.o oc_obt_otm_justworks.o oc_obt_otm_randompin.o,$(notdir $(SRC:.c=.o)))) OBJ_CLIENT_SERVER=$(addprefix obj/client_server/,$(notdir $(SRC:.c=.o))) -VPATH=../../messaging/coap/:../../util/:../../api/:../../deps/tinycbor/src/:../../deps/mbedtls/library:../../api/c-timestamp: +OBJ_CLOUD=$(addprefix obj/cloud/,$(notdir $(SRC_CLOUD:.c=.o))) +OBJ_RD_CLIENT=$(addprefix obj/rd_client/,$(notdir $(SRC_RD_CLIENT:.c=.o))) +VPATH=../../messaging/coap/:../../util/:../../api/:../../deps/tinycbor/src/:../../deps/mbedtls/library:../../api/c-timestamp:../../service/cloud/src/:../../service/resource-directory/client/src/: LIBS?= -lm -pthread -lrt SAMPLES = server client temp_sensor simpleserver simpleserver_pki simpleclient client_collections_linux \ server_collections_linux server_block_linux client_block_linux smart_home_server_linux multi_device_server multi_device_client smart_lock server_multithread_linux client_multithread_linux +ifeq ($(CLOUD),1) + HEADERS += $(wildcard ../../service/cloud/include/*.h) + HEADERS += $(wildcard ../../service/resource-directory/client/include/*.h) + CFLAGS += -DOC_CLOUD + TCP=1 + IPV4=1 +endif + OBT = onboarding_tool ifeq ($(DEBUG),1) @@ -141,6 +166,8 @@ ifeq ($(TCP),1) EXTRA_CFLAGS += -DOC_TCP endif + + CFLAGS += $(EXTRA_CFLAGS) ifeq ($(MEMTRACE),1) @@ -152,9 +179,18 @@ SAMPLES_CREDS = $(addsuffix _creds, ${SAMPLES} ${OBT}) CONSTRAINED_LIBS = libiotivity-constrained-server.a libiotivity-constrained-client.a \ libiotivity-constrained-server.so libiotivity-constrained-client.so \ libiotivity-constrained-client-server.so libiotivity-constrained-client-server.a + PC = iotivity-constrained-client.pc iotivity-constrained-server.pc \ iotivity-constrained-client-server.pc +ifeq ($(CLOUD),1) + CONSTRAINED_LIBS += libiotivity-constrained-cloud.a libiotivity-constrained-cloud.so \ + libiotivity-constrained-rd-client.a libiotivity-constrained-rd-client.so + UNIT_TESTS += cloudtest rdclienttest + PC += iotivity-constrained-rd-client.pc iotivity-constrained-cloud.pc + SAMPLES += cloud_linux +endif + all: $(CONSTRAINED_LIBS) $(SAMPLES) $(PC) test: $(UNIT_TESTS) @@ -186,6 +222,20 @@ $(PLATFORM_TEST_OBJ_DIR)/%.o: $(PLATFORM_TEST_DIR)/%.cpp platformtest: $(PLATFORM_TEST_OBJ_FILES) libiotivity-constrained-client-server.a | $(GTEST) $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) -l:gtest_main.a -liotivity-constrained-client-server -L$(OUT_DIR) -L$(GTEST_DIR)/make -lpthread $^ -o $@ +$(CLOUD_TEST_OBJ_DIR)/%.o: $(CLOUD_TEST_DIR)/%.cpp + @mkdir -p ${@D} + $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) $(CFLAGS_CLOUD) -I$(ROOT_DIR)/service/cloud/src -I$(ROOT_DIR)/deps/tinycbor/src -c $< -o $@ + +cloudtest: $(CLOUD_TEST_OBJ_FILES) libiotivity-constrained-cloud.a libiotivity-constrained-rd-client.a libiotivity-constrained-client-server.a | $(GTEST) + $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) $(CFLAGS_CLOUD) -l:gtest_main.a -liotivity-constrained-cloud -liotivity-constrained-rd-client -liotivity-constrained-client-server -L$(OUT_DIR) -L$(GTEST_DIR)/make -lpthread $^ -o $@ + +$(RD_CLIENT_TEST_OBJ_DIR)/%.o: $(RD_CLIENT_TEST_DIR)/%.cpp + @mkdir -p ${@D} + $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) $(CFLAGS_RD_CLIENT) -I$(ROOT_DIR)/deps/tinycbor/src -c $< -o $@ + +rdclienttest: $(RD_CLIENT_TEST_OBJ_FILES) libiotivity-constrained-rd-client.a libiotivity-constrained-client-server.a | $(GTEST) + $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) $(CFLAGS_RD_CLIENT) -l:gtest_main.a -liotivity-constrained-rd-client -liotivity-constrained-client-server -L$(OUT_DIR) -L$(GTEST_DIR)/make -lpthread $^ -o $@ + $(MESSAGING_TEST_OBJ_DIR)/%.o: $(MESSAGING_TEST_DIR)/%.cpp @mkdir -p ${@D} $(CXX) $(GTEST_CPPFLAGS) $(TEST_CXXFLAGS) $(EXTRA_CFLAGS) $(HEADER_DIR) $(MESSAGING_HEADERS) -c $< -o $@ @@ -211,6 +261,14 @@ obj/client_server/%.o: %.c @mkdir -p ${@D} ${CC} -c -o $@ $< ${CFLAGS} -DOC_CLIENT -DOC_SERVER +obj/cloud/%.o: %.c + @mkdir -p ${@D} + ${CC} -c -o $@ $< ${CFLAGS} ${CFLAGS_CLOUD} ${CFLAGS_RD_CLIENT} + +obj/rd_client/%.o: %.c + @mkdir -p ${@D} + ${CC} -c -o $@ $< ${CFLAGS} ${CFLAGS_RD_CLIENT} + obj/server/oc_introspection.o: ../../include/server_introspection.dat.h obj/client/oc_introspection.o: ../../include/server_introspection.dat.h obj/client_server/oc_introspection.o: ../../include/server_introspection.dat.h @@ -233,6 +291,18 @@ libiotivity-constrained-client-server.a: $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) libiotivity-constrained-client-server.so: $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_CLIENT_SERVER) $(LIBS) +libiotivity-constrained-cloud.a: $(OBJ_CLOUD) + $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_CLOUD) + +libiotivity-constrained-cloud.so: $(OBJ_CLOUD) + $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_CLOUD) $(LIBS) + +libiotivity-constrained-rd-client.a: $(OBJ_RD_CLIENT) + $(AR) -rcs $@ $(OBJ_COMMON) $(OBJ_RD_CLIENT) + +libiotivity-constrained-rd-client.so: $(OBJ_RD_CLIENT) + $(CC) -shared -o $@ $(OBJ_COMMON) $(OBJ_RD_CLIENT) $(LIBS) + server: libiotivity-constrained-server.a $(ROOT_DIR)/apps/server_linux.c @mkdir -p $@_creds ${CC} -o $@ ../../apps/server_linux.c libiotivity-constrained-server.a -DOC_SERVER ${CFLAGS} ${LIBS} @@ -287,6 +357,10 @@ multi_device_client: libiotivity-constrained-client.a $(ROOT_DIR)/apps/multi_dev @mkdir -p $@_creds ${CC} -o $@ ../../apps/multi_device_client_linux.c libiotivity-constrained-client.a -DOC_CLIENT ${CFLAGS} ${LIBS} +cloud_linux: libiotivity-constrained-client-server.a libiotivity-constrained-cloud.a libiotivity-constrained-rd-client.a $(ROOT_DIR)/apps/cloud_linux.c + @mkdir -p $@_creds + ${CC} -o $@ ../../apps/cloud_linux.c libiotivity-constrained-client-server.a libiotivity-constrained-cloud.a libiotivity-constrained-rd-client.a -DOC_CLIENT -DOC_SERVER ${CFLAGS} ${CFLAGS_CLOUD} ${CFLAGS_ST} ${CFLAGS_RD_CLIENT} ${LIBS} + ${OBT}: libiotivity-constrained-client.a $(ROOT_DIR)/onboarding_tool/obtmain.c @mkdir -p $@_creds ${CC} -o $@ ../../onboarding_tool/obtmain.c libiotivity-constrained-client.a -DOC_CLIENT ${CFLAGS} ${LIBS} @@ -326,6 +400,24 @@ iotivity-constrained-client-server.pc: iotivity-constrained-client-server.pc.in -e 's,@version@,$(VERSION),' \ -e 's,@extra_cflags@,$(EXTRA_CFLAGS),' +iotivity-constrained-rd-client.pc: iotivity-constrained-rd-client.pc.in + $(SED) > $@ < $< \ + -e 's,@prefix@,$(prefix),' \ + -e 's,@exec_prefix@,$(exec_prefix),' \ + -e 's,@libdir@,$(libdir),' \ + -e 's,@includedir@,$(includedir),' \ + -e 's,@version@,$(VERSION),' \ + -e 's,@extra_cflags@,$(EXTRA_CFLAGS),' + +iotivity-constrained-cloud.pc: iotivity-constrained-cloud.pc.in + $(SED) > $@ < $< \ + -e 's,@prefix@,$(prefix),' \ + -e 's,@exec_prefix@,$(exec_prefix),' \ + -e 's,@libdir@,$(libdir),' \ + -e 's,@includedir@,$(includedir),' \ + -e 's,@version@,$(VERSION),' \ + -e 's,@extra_cflags@,$(EXTRA_CFLAGS),' + ifneq ($(SECURE),0) MBEDTLS_PATCHES ?= $(sort $(wildcard ../../patches/*.patch)) ${MBEDTLS_DIR}/.git: @@ -345,7 +437,7 @@ $(MBEDTLS_PATCH_FILE): ${MBEDTLS_DIR}/.git ${MBEDTLS_PATCHES} endif clean: - rm -rf obj $(PC) $(CONSTRAINED_LIBS) $(API_TEST_OBJ_FILES) $(SECURITY_TEST_OBJ_FILES) $(PLATFORM_TEST_OBJ_FILES) $(MESSAGING_TEST_OBJ_FILES) $(UNIT_TESTS) $(STORAGE_TEST_DIR) + rm -rf obj $(PC) $(CONSTRAINED_LIBS) $(API_TEST_OBJ_FILES) $(SECURITY_TEST_OBJ_FILES) $(PLATFORM_TEST_OBJ_FILES) $(MESSAGING_TEST_OBJ_FILES) $(UNIT_TESTS) $(STORAGE_TEST_DIR) $(CLOUD_TEST_OBJ_FILES) $(RD_CLIENT_TEST_OBJ_FILES) cleanall: clean rm -rf ${all} $(SAMPLES) $(TESTS) ${OBT} ${SAMPLES_CREDS} $(MBEDTLS_PATCH_FILE) diff --git a/port/linux/iotivity-constrained-cloud.pc.in b/port/linux/iotivity-constrained-cloud.pc.in new file mode 100644 index 0000000000000000000000000000000000000000..dacff5cf549311c83350c1cc12eec5275bf14fc7 --- /dev/null +++ b/port/linux/iotivity-constrained-cloud.pc.in @@ -0,0 +1,26 @@ +# Copyright (c) 2019 Jozef Kralik +# Copyright (c) 2018 Samsung Electronics 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. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libiotivity-constrained-cloud +Description: Iotivity constrained resource directory client library +Version: @version@ +Libs.private: -lm -pthread +Cflags: -I${includedir}/iotivity-constrained @extra_cflags@ +Libs: -L${libdir} -liotivity-constrained-cloud diff --git a/port/linux/iotivity-constrained-rd-client.pc.in b/port/linux/iotivity-constrained-rd-client.pc.in new file mode 100644 index 0000000000000000000000000000000000000000..407468e90f57176351228ecf6cda6751ee3ef5cb --- /dev/null +++ b/port/linux/iotivity-constrained-rd-client.pc.in @@ -0,0 +1,26 @@ +# Copyright (c) 2019 Jozef Kralik +# Copyright (c) 2018 Samsung Electronics 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. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libiotivity-constrained-rd-client +Description: Iotivity constrained resource directory client library +Version: @version@ +Libs.private: -lm -pthread +Cflags: -I${includedir}/iotivity-constrained @extra_cflags@ +Libs: -L${libdir} -liotivity-constrained-rd-client diff --git a/service/cloud/include/cloud.h b/service/cloud/include/cloud.h new file mode 100644 index 0000000000000000000000000000000000000000..be6aa1b8373360a0e3723cc70364fb62fadda160 --- /dev/null +++ b/service/cloud/include/cloud.h @@ -0,0 +1,84 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik 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. + * + ****************************************************************************/ + +/** + @brief Cloud API for start cloud service. + @file +*/ + +#ifndef CLOUD_H +#define CLOUD_H + +#include +#include +#include + +/** + @brief Cloud connection status. +*/ +typedef enum { + CLOUD_INITIALIZED = 0, + CLOUD_SIGNED_UP = 1 << 0, + CLOUD_SIGNED_IN = 1 << 1, + CLOUD_REFRESHED_TOKEN = 1 << 2, + CLOUD_FAILED = 1 << 5, + CLOUD_RECONNECTING = 1 << 6, +} cloud_status_t; + +/** + @brief A function pointer for handling the cloud status. + @param status Current status of the cloud. +*/ +typedef void (*cloud_cb_t)(cloud_status_t status, void *user_data); + +#ifdef __cplusplus +extern "C" { +#endif + +/** + @brief Function for create cloud service. + @param device_index Index of the device for an unique identifier. + @param cb Callback function to return the st cloud manager status. + @return Returns 0 if successful, or -1 otherwise. +*/ +int cloud_init(size_t device_index, cloud_cb_t cb, void *user_data); + +/** + @brief Function for stop cloud. + @param device_index Index of the device for an unique identifier. +*/ +void cloud_shutdown(size_t device_index); + +/** + @brief Publish RD resource to Cloud Resource Directory. + @param res The resource for publish to the Cloud RD. + @return Returns 0 if success, otherwise error. +*/ +int cloud_rd_publish(oc_resource_t *res); + +/** + @brief Delete RD resource from Cloud Resource Directory. + @param res The resource for delete from the Cloud RD. +*/ +void cloud_rd_delete(oc_resource_t *res); + +#ifdef __cplusplus +} +#endif + +#endif /* CLOUD_H */ diff --git a/service/cloud/src/cloud.c b/service/cloud/src/cloud.c new file mode 100644 index 0000000000000000000000000000000000000000..7042bbabfc4b1932612ce0368cdbb6ceb25e6b7e --- /dev/null +++ b/service/cloud/src/cloud.c @@ -0,0 +1,232 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik 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. + * + ******************************************************************/ + +#include "cloud.h" +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_collection.h" +#include "oc_core_res.h" +#include "oc_network_monitor.h" +#ifdef OC_SECURITY +#include "security/oc_tls.h" +#endif + +OC_LIST(cloud_context_list); +OC_MEMB(cloud_context_pool, cloud_context_t, OC_MAX_NUM_DEVICES); + +void cloud_manager_cb(cloud_context_t *ctx) { + OC_DBG("cloud manager status changed %d", (int)ctx->store.status); + cloud_rd_manager_status_changed(ctx); + + if (ctx->callback) { + ctx->callback(ctx->store.status, ctx->user_data); + } +} + +void cloud_set_string(oc_string_t *dst, const char *data, size_t len) { + if (oc_string(*dst)) { + oc_free_string(dst); + } + if (data && len) { + oc_new_string(dst, data, len); + } else { + memset(dst, 0, sizeof(*dst)); + } +} + +static oc_event_callback_retval_t start_manager(void *user_data) { + cloud_context_t *ctx = (cloud_context_t *)user_data; + oc_free_endpoint(ctx->cloud_ep); + ctx->cloud_ep = oc_new_endpoint(); + cloud_manager_start(ctx); + return OC_EVENT_DONE; +} + +static void cloud_manager_restart(cloud_context_t *ctx) { + cloud_manager_stop(ctx); + oc_remove_delayed_callback(ctx, start_manager); + oc_set_delayed_callback(ctx, start_manager, 0); +} + +static oc_event_callback_retval_t restart_manager(void *user_data) { + cloud_context_t *ctx = (cloud_context_t *)user_data; + cloud_manager_restart(ctx); + return OC_EVENT_DONE; +} + +void cloud_update_by_resource(cloud_context_t *ctx, + const cloud_conf_update_t *data) { + cloud_manager_stop(ctx); + + if (data->auth_provider && data->auth_provider_len) { + cloud_set_string(&ctx->store.auth_provider, data->auth_provider, + data->auth_provider_len); + } + if (data->access_token && data->access_token_len) { + cloud_set_string(&ctx->store.access_token, data->access_token, + data->access_token_len); + } + if (data->ci_server && data->ci_server_len) { + cloud_set_string(&ctx->store.ci_server, data->ci_server, + data->ci_server_len); + } + if (data->sid && data->sid_len) { + cloud_set_string(&ctx->store.sid, data->sid, data->sid_len); + } + ctx->store.status = CLOUD_INITIALIZED; + cloud_reconnect(ctx); +} + +#ifdef OC_SESSION_EVENTS +static void cloud_ep_session_event_handler(const oc_endpoint_t *endpoint, + oc_session_state_t state) { + cloud_context_t *ctx = cloud_find_context(endpoint->device); + if (ctx) { + OC_DBG("[CM] cloud_ep_session_event_handler ep_state: %d\n", (int)state); + ctx->cloud_ep_state = state; + if (ctx->cloud_ep_state == OC_SESSION_DISCONNECTED) { + cloud_manager_restart(ctx); + } + } +} +#endif /* OC_SESSION_EVENTS */ + +static void cloud_interface_event_handler(oc_interface_event_t event) { + if (event == NETWORK_INTERFACE_UP) { + for (cloud_context_t *ctx = oc_list_head(cloud_context_list); ctx; + ctx = ctx->next) { + switch (ctx->store.status) { + case CLOUD_RECONNECTING: + case CLOUD_INITIALIZED: + cloud_manager_restart(ctx); + } + } + } +} + +int cloud_init(size_t device_index, cloud_cb_t callback, void *user_data) { + cloud_context_t *ctx = (cloud_context_t *)oc_memb_alloc(&cloud_context_pool); + if (!ctx) { + OC_WRN("insufficient memory to create new collection"); + return -1; + } + memset(ctx, 0, sizeof(*ctx)); + + ctx->next = NULL; + ctx->callback = callback; + ctx->user_data = user_data; + ctx->device_index = device_index; + ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; + ctx->cloud_ep = oc_new_endpoint(); + cloud_store_load(&ctx->store); + cloud_manager_start(ctx); + + oc_list_add(cloud_context_list, ctx); + if (!cloud_resource_init(ctx)) { + oc_memb_free(&cloud_context_pool, ctx); + return -1; + } + +#ifdef OC_SESSION_EVENTS + if (oc_list_length(cloud_context_list) == 1) { + oc_add_session_event_callback(cloud_ep_session_event_handler); + oc_add_network_interface_event_callback(cloud_interface_event_handler); + } +#endif /* OC_SESSION_EVENTS */ + + cloud_rd_publish(oc_core_get_resource_by_index(OCF_P, device_index)); + cloud_rd_publish(oc_core_get_resource_by_index(OCF_D, device_index)); + cloud_rd_publish(ctx->cloud_conf); + + return 0; +} + +cloud_context_t *cloud_find_context(size_t device_index) { + cloud_context_t *ctx = oc_list_head(cloud_context_list); + while (ctx != NULL && ctx->device_index != device_index) { + ctx = ctx->next; + } + return ctx; +} + +static void cloud_close_endpoint(oc_endpoint_t *cloud_ep) { + OC_DBG("[CM] cloud_close_endpoint\n"); +#ifdef OC_SESSION_EVENTS +#ifdef OC_SECURITY + oc_tls_peer_t *peer = oc_tls_get_peer(cloud_ep); + if (peer) { + OC_DBG("[CM] cloud_close_endpoint: oc_tls_close_connection\n"); + oc_tls_close_connection(cloud_ep); + } else +#endif /* OC_SECURITY */ + { +#ifdef OC_TCP + OC_DBG("[CM] cloud_close_endpoint: oc_connectivity_end_session\n"); + oc_connectivity_end_session(cloud_ep); +#endif /* OC_TCP */ + } +#endif /* OC_SESSION_EVENTS */ +} + +void cloud_shutdown(size_t device_index) { + cloud_context_t *ctx = cloud_find_context(device_index); + if (!ctx) { + return; + } + oc_list_remove(cloud_context_list, ctx); + +#ifdef OC_SESSION_EVENTS + if (oc_list_length(cloud_context_list) == 0) { + oc_remove_session_event_callback(cloud_ep_session_event_handler); + oc_remove_network_interface_event_callback(cloud_interface_event_handler); + } +#endif /* OC_SESSION_EVENTS */ + oc_remove_delayed_callback(ctx, restart_manager); + oc_remove_delayed_callback(ctx, start_manager); + + cloud_rd_deinit(ctx); + cloud_manager_stop(ctx); + oc_delete_resource(ctx->cloud_conf); + cloud_store_deinit(&ctx->store); + + cloud_close_endpoint(ctx->cloud_ep); + oc_free_endpoint(ctx->cloud_ep); + + oc_memb_free(&cloud_context_pool, ctx); + + OC_DBG("cloud_shutdown for %d", (int)device_index); +} + +void cloud_set_last_error(cloud_context_t *ctx, cloud_error_t error) { + if (error != ctx->last_error) { + ctx->last_error = error; + oc_notify_observers(ctx->cloud_conf); + } +} + +void cloud_reconnect(cloud_context_t *ctx) { + OC_DBG("[CM] cloud_reconnect\n"); +#ifdef OC_SESSION_EVENTS + if (ctx->cloud_ep_state == OC_SESSION_CONNECTED) { + cloud_close_endpoint(ctx->cloud_ep); + return; + } +#endif /* OC_SESSION_EVENTS */ + oc_remove_delayed_callback(ctx, restart_manager); + oc_set_delayed_callback(ctx, restart_manager, 0); +} \ No newline at end of file diff --git a/service/cloud/src/cloud_access.c b/service/cloud/src/cloud_access.c new file mode 100644 index 0000000000000000000000000000000000000000..9e1c3d9e2faaf559c7c87df57beee290f44a647b --- /dev/null +++ b/service/cloud/src/cloud_access.c @@ -0,0 +1,166 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ****************************************************************************/ + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_client_state.h" +#include "oc_core_res.h" +#include "port/oc_log.h" + +/** Account URI.*/ +#ifdef OC_SPEC_VER_OIC +#define OC_RSRVD_ACCOUNT_URI "/oic/account" +#else +#define OC_RSRVD_ACCOUNT_URI "/oic/sec/account" +#endif + +/** Account session URI.*/ +#ifdef OC_SPEC_VER_OIC +#define OC_RSRVD_ACCOUNT_SESSION_URI "/oic/account/session" +#else +#define OC_RSRVD_ACCOUNT_SESSION_URI "/oic/sec/session" +#endif + +/** Account token refresh URI.*/ +#ifdef OC_SPEC_VER_OIC +#define OC_RSRVD_ACCOUNT_TOKEN_REFRESH_URI "/oic/account/tokenrefresh" +#else +#define OC_RSRVD_ACCOUNT_TOKEN_REFRESH_URI "/oic/sec/tokenrefresh" +#endif + +/** Device URI.*/ +#define OC_RSRVD_DEVICE_URI "/oic/device" + +/** To represent grant type with refresh token. */ +#define OC_RSRVD_GRANT_TYPE_REFRESH_TOKEN "refresh_token" + +static bool _oc_sign_up(oc_endpoint_t *endpoint, const char *auth_provider, + const char *auth_code, const char *uid, + const char *access_token, size_t device_index, + oc_response_handler_t handler, void *user_data) { + if (!endpoint || ((!auth_provider || !auth_code) && !access_token) || + !handler) { + OC_ERR("Error of input parameters"); + return false; + } + + if (oc_init_post(OC_RSRVD_ACCOUNT_URI, endpoint, NULL, handler, LOW_QOS, + user_data)) { + char uuid[OC_UUID_LEN] = {0}; + oc_uuid_to_str(oc_core_get_device_id(device_index), uuid, OC_UUID_LEN); + + oc_rep_start_root_object(); + oc_rep_set_text_string(root, di, uuid); + if (auth_provider) + oc_rep_set_text_string(root, authprovider, auth_provider); + if (auth_code) { + oc_rep_set_text_string(root, accesstoken, auth_code); + } else { + if (uid) + oc_rep_set_text_string(root, uid, uid); + oc_rep_set_text_string(root, accesstoken, access_token); + } + oc_rep_set_text_string(root, devicetype, "device"); + oc_rep_end_root_object(); + } else { + OC_ERR("Could not init POST request for sign up"); + return false; + } + + return oc_do_post(); +} + +bool cloud_access_sign_up(oc_endpoint_t *endpoint, const char *auth_provider, + const char *uid, const char *access_token, + size_t device_index, oc_response_handler_t handler, + void *user_data) { + return _oc_sign_up(endpoint, auth_provider, NULL, uid, access_token, + device_index, handler, user_data); +} + +static bool oc_sign_inout(oc_endpoint_t *endpoint, const char *uid, + const char *access_token, size_t device_index, + bool is_sign_in, oc_response_handler_t handler, + void *user_data) { + if (!endpoint || (!uid) || !access_token || !handler) { + OC_ERR("Error of input parameters"); + return false; + } + + if (oc_init_post(OC_RSRVD_ACCOUNT_SESSION_URI, endpoint, NULL, handler, + LOW_QOS, user_data)) { + char uuid[OC_UUID_LEN] = {0}; + oc_uuid_to_str(oc_core_get_device_id(device_index), uuid, OC_UUID_LEN); + + oc_rep_start_root_object(); + oc_rep_set_text_string(root, uid, uid); + oc_rep_set_text_string(root, di, uuid); + oc_rep_set_text_string(root, accesstoken, access_token); + oc_rep_set_boolean(root, login, is_sign_in); + oc_rep_end_root_object(); + } else { + OC_ERR("Could not init POST request for sign in/out"); + return false; + } + + return oc_do_post(); +} + +bool cloud_access_sign_in(oc_endpoint_t *endpoint, const char *uid, + const char *access_token, size_t device_index, + oc_response_handler_t handler, void *user_data) { + return oc_sign_inout(endpoint, uid, access_token, device_index, true, handler, + user_data); +} + +bool cloud_access_sign_out(oc_endpoint_t *endpoint, const char *access_token, + size_t device_index, oc_response_handler_t handler, + void *user_data) { + return oc_sign_inout(endpoint, NULL, access_token, device_index, false, + handler, user_data); +} + +bool cloud_access_refresh_access_token(oc_endpoint_t *endpoint, const char *uid, + const char *refresh_token, + size_t device_index, + oc_response_handler_t handler, + void *user_data) { + if (!endpoint || !uid || !refresh_token || !handler) { + OC_ERR("Error of input parameters"); + return false; + } + + if (oc_init_post(OC_RSRVD_ACCOUNT_TOKEN_REFRESH_URI, endpoint, NULL, handler, + LOW_QOS, user_data)) { + char uuid[OC_UUID_LEN] = {0}; + oc_uuid_to_str(oc_core_get_device_id(device_index), uuid, OC_UUID_LEN); + + oc_rep_start_root_object(); + oc_rep_set_text_string(root, uid, uid); + oc_rep_set_text_string(root, di, uuid); + oc_rep_set_text_string(root, granttype, OC_RSRVD_GRANT_TYPE_REFRESH_TOKEN); + oc_rep_set_text_string(root, refreshtoken, refresh_token); + oc_rep_end_root_object(); + } else { + OC_ERR("Could not init POST request for refresh access token"); + return false; + } + + return oc_do_post(); +} diff --git a/service/cloud/src/cloud_error.h b/service/cloud/src/cloud_error.h new file mode 100644 index 0000000000000000000000000000000000000000..f96111966112f9532a1708612ea02a50e6dd2298 --- /dev/null +++ b/service/cloud/src/cloud_error.h @@ -0,0 +1,29 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik 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. + * + ****************************************************************************/ + +#ifndef CLOUD_ERROR_H +#define CLOUD_ERROR_H + +typedef enum { + CLOUD_OK = 0, + CLOUD_ERROR_RESPONSE = 1, + CLOUD_ERROR_CONNECT = 2, + CLOUD_ERROR_REFRESH_ACCESS_TOKEN = 3, +} cloud_error_t; + +#endif // CLOUD_ERROR_H \ No newline at end of file diff --git a/service/cloud/src/cloud_internal.h b/service/cloud/src/cloud_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..f166add3d88b668e758f778f918f31bfd4e01134 --- /dev/null +++ b/service/cloud/src/cloud_internal.h @@ -0,0 +1,124 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ****************************************************************************/ + +#ifndef CLOUD_INTERNAL_H +#define CLOUD_INTERNAL_H + +#include +#include + +#include "cloud.h" +#include "cloud_error.h" +#include "oc_api.h" + +typedef struct cloud_store_s { + oc_string_t ci_server; + oc_string_t auth_provider; + oc_string_t uid; + oc_string_t access_token; + oc_string_t refresh_token; + oc_string_t sid; + + uint8_t status; + size_t device; +} cloud_store_t; + +typedef struct cloud_context_s { + struct cloud_context_s *next; + + size_t device_index; + + cloud_cb_t callback; + void *user_data; + + cloud_store_t store; + + oc_session_state_t cloud_ep_state; + oc_endpoint_t *cloud_ep; + uint8_t retry_count; + uint8_t retry_refresh_token_count; + cloud_error_t last_error; + uint16_t expires_in; + + oc_link_t *rd_publish_resources; + oc_link_t *rd_published_resources; + oc_link_t *rd_delete_resources; + bool rd_delete_all; + + oc_resource_t *cloud_conf; +} cloud_context_t; + +typedef struct cloud_conf_update_s { + char *access_token; /**< Access Token resolved with an auth code. */ + size_t access_token_len; + char *auth_provider; /**< Auth Provider ID*/ + size_t auth_provider_len; + char *ci_server; /**< Cloud Interface Server URL which an Enrollee is going to + registered. */ + size_t ci_server_len; + char *sid; /**< OCF Cloud Identity as defined in OCF CNC 2.0 Spec. */ + size_t sid_len; +} cloud_conf_update_t; + +#ifdef __cplusplus +extern "C" { +#endif + +void cloud_store_dump_async(const cloud_store_t *store); +void cloud_store_load(cloud_store_t *store); +void cloud_store_dump(const cloud_store_t *store); +void cloud_store_deinit(cloud_store_t *store); + +cloud_context_t *cloud_find_context(size_t device_index); +void cloud_manager_cb(cloud_context_t *ctx); +void cloud_set_string(oc_string_t *dst, const char *data, size_t len); +void cloud_set_last_error(cloud_context_t *ctx, cloud_error_t error); +void cloud_update_by_resource(cloud_context_t *ctx, + const cloud_conf_update_t *data); +void cloud_reconnect(cloud_context_t *ctx); + +bool cloud_resource_init(cloud_context_t *ctx); + +void cloud_rd_manager_status_changed(cloud_context_t *ctx); +void cloud_rd_deinit(cloud_context_t *ctx); + +void cloud_manager_start(cloud_context_t *ctx); +void cloud_manager_stop(cloud_context_t *ctx); + +bool cloud_access_sign_up(oc_endpoint_t *endpoint, const char *auth_provider, + const char *uid, const char *access_token, + size_t device_index, oc_response_handler_t handler, + void *user_data); +bool cloud_access_sign_in(oc_endpoint_t *endpoint, const char *uid, + const char *access_token, size_t device_index, + oc_response_handler_t handler, void *user_data); +bool cloud_access_sign_out(oc_endpoint_t *endpoint, const char *access_token, + size_t device_index, oc_response_handler_t handler, + void *user_data); +bool cloud_access_refresh_access_token(oc_endpoint_t *endpoint, const char *uid, + const char *refresh_token, + size_t device_index, + oc_response_handler_t handler, + void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif // CLOUD_INTERNAL_H \ No newline at end of file diff --git a/service/cloud/src/cloud_manager.c b/service/cloud/src/cloud_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..ae8705e595df1cfe5c7e3a3941e9b31f75954337 --- /dev/null +++ b/service/cloud/src/cloud_manager.c @@ -0,0 +1,453 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ****************************************************************************/ + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_endpoint.h" +#include "port/oc_log.h" +#include "rd_client.h" +#include "util/oc_list.h" +#include "util/oc_memb.h" +#include + +#define ACCESS_TOKEN_KEY "accesstoken" +#define REFRESH_TOKEN_KEY "refreshtoken" +#define REDIRECTURI_KEY "redirecturi" +#define USER_ID_KEY "uid" +#define EXPIRESIN_KEY "expiresin" + +#define PING_DELAY 20 +#define PING_DELAY_ON_TIMEOUT 3 +#define MAX_CONTEXT_SIZE (2) +#define MAX_RETRY_COUNT (5) +#define REFRESH_TOKEN_DELAY (30 * 60) +#define REFRESH_TOKEN_DELAY_ON_SIGN_IN 3 + +struct oc_memb rep_objects_pool = {sizeof(oc_rep_t), 0, 0, 0, 0}; + +static void cloud_start_process(cloud_context_t *ctx); +static oc_event_callback_retval_t sign_up(void *data); +static oc_event_callback_retval_t sign_in(void *data); +static oc_event_callback_retval_t refresh_token(void *data); +static oc_event_callback_retval_t send_ping(void *data); + +static uint16_t session_timeout[5] = {3, 60, 1200, 24000, 60}; +static uint8_t message_timeout[5] = {1, 2, 4, 8, 10}; + +static oc_event_callback_retval_t callback_handler(void *data) { + cloud_context_t *ctx = (cloud_context_t *)data; + cloud_manager_cb(ctx); + + return OC_EVENT_DONE; +} + +static oc_rep_t *get_res_payload(oc_client_response_t *data) { +#ifndef RES_PAYLOAD_PARSE + return data->payload; +#else + OC_DBG("[CM] get_res_payload - %d, %d", rep_objects_pool.size, + rep_objects_pool.num); + oc_rep_t *payload = NULL; + oc_rep_set_pool(&rep_objects_pool); + int err = oc_parse_rep(data->payload, data->payload_len, &payload); + if (err != 0) { + OC_ERR("Error parsing payload!"); + } + return payload; +#endif /* RES_PAYLOAD_PARSE */ +} + +static void free_res_payload(oc_rep_t *payload) { +#ifdef RES_PAYLOAD_PARSE + oc_free_rep(payload); +#else + (void)payload; +#endif /* RES_PAYLOAD_PARSE */ +} + +void cloud_manager_start(cloud_context_t *ctx) { + OC_DBG("[CM] cloud_manager_start\n"); + if (ctx->store.status == CLOUD_SIGNED_IN || + ctx->store.status == CLOUD_REFRESHED_TOKEN) + ctx->store.status = CLOUD_SIGNED_UP; + cloud_start_process(ctx); +} + +void cloud_manager_stop(cloud_context_t *ctx) { + OC_DBG("[CM] cloud_manager_stop\n"); + oc_remove_delayed_callback(ctx, sign_up); + oc_remove_delayed_callback(ctx, sign_in); + oc_remove_delayed_callback(ctx, send_ping); + oc_remove_delayed_callback(ctx, refresh_token); + oc_remove_delayed_callback(ctx, callback_handler); +} + +static void reconnect(cloud_context_t *ctx) { + if (ctx->store.status != CLOUD_RECONNECTING) { + ctx->store.status = CLOUD_RECONNECTING; + oc_set_delayed_callback(ctx, callback_handler, 0); + } + oc_remove_delayed_callback(ctx, refresh_token); + cloud_reconnect(ctx); +} + +static bool is_refresh_token_retry_over(cloud_context_t *ctx) { + if (ctx->retry_refresh_token_count < MAX_RETRY_COUNT) + return false; + + reconnect(ctx); + return true; +} + +static bool is_retry_over(cloud_context_t *ctx) { + if (ctx->retry_count < MAX_RETRY_COUNT) + return false; + + reconnect(ctx); + return true; +} + +static void cloud_start_process(cloud_context_t *ctx) { + ctx->retry_count = 0; + + if (ctx->store.status == CLOUD_INITIALIZED) { + oc_set_delayed_callback(ctx, sign_up, session_timeout[0]); + } else { + if (oc_string(ctx->store.refresh_token) && + oc_string_len(ctx->store.refresh_token) > 0) { + oc_set_delayed_callback(ctx, refresh_token, session_timeout[0]); + } else { + oc_set_delayed_callback(ctx, sign_in, session_timeout[0]); + } + } + _oc_signal_event_loop(); +} + +static void sign_up_handler(oc_client_response_t *data) { + cloud_context_t *ctx = (cloud_context_t *)data->user_data; + OC_DBG("[CM] sign up handler(%d)\n", data->code); + + if (ctx->store.status != CLOUD_INITIALIZED && + ctx->store.status != CLOUD_RECONNECTING) + return; + + oc_rep_t *payload = get_res_payload(data); + + if (data->code != OC_STATUS_CHANGED) + goto error; + + char *value = NULL; + size_t size = 0; + + if (oc_rep_get_string(payload, ACCESS_TOKEN_KEY, &value, &size) && size > 0) { + cloud_set_string(&ctx->store.access_token, value, size); + } + + value = NULL; + size = 0; + bool reconnect = false; + if (oc_rep_get_string(payload, REDIRECTURI_KEY, &value, &size) && size > 0) { + char *ci_server = oc_string(ctx->store.ci_server); + if (!ci_server || oc_string_len(ctx->store.ci_server) != size || + strcmp(ci_server, value)) { + reconnect = true; + } + cloud_set_string(&ctx->store.ci_server, value, size); + } + + value = NULL; + size = 0; + if (oc_rep_get_string(payload, REFRESH_TOKEN_KEY, &value, &size) && + size > 0) { + cloud_set_string(&ctx->store.refresh_token, value, size); + } + + value = NULL; + size = 0; + if (oc_rep_get_string(payload, USER_ID_KEY, &value, &size) && size > 0) { + cloud_set_string(&ctx->store.uid, value, size); + } + + int64_t expires_in = 0; + if (oc_rep_get_int(payload, EXPIRESIN_KEY, &expires_in) && expires_in > 0 && + expires_in <= UINT16_MAX) { + ctx->expires_in = (uint16_t)expires_in; + } else { + ctx->expires_in = 0; + } + + cloud_store_dump_async(&ctx->store); + oc_remove_delayed_callback(ctx, sign_up); + ctx->retry_count = 0; + cloud_set_last_error(ctx, CLOUD_OK); + free_res_payload(payload); + + if (reconnect) { + ctx->store.status = CLOUD_RECONNECTING; + cloud_reconnect(ctx); + } else { + ctx->store.status = CLOUD_SIGNED_UP; + oc_set_delayed_callback(ctx, callback_handler, 0); + oc_set_delayed_callback(ctx, sign_in, message_timeout[ctx->retry_count]); + } + return; + +error: + cloud_set_last_error(ctx, CLOUD_ERROR_RESPONSE); + oc_remove_delayed_callback(ctx, sign_up); + if (data->code != OC_STATUS_UNAUTHORIZED) { + oc_set_delayed_callback(ctx, sign_up, message_timeout[ctx->retry_count]); + ctx->store.status = CLOUD_RECONNECTING; + } else { + ctx->store.status = CLOUD_FAILED; + } + + oc_set_delayed_callback(ctx, callback_handler, 0); + free_res_payload(payload); +} + +static oc_event_callback_retval_t sign_up(void *data) { + cloud_context_t *ctx = (cloud_context_t *)data; + + if (ctx->store.status == CLOUD_INITIALIZED || + ctx->store.status == CLOUD_RECONNECTING) { + OC_DBG("[CM] try sign up(%d)\n", ctx->retry_count); + ctx->retry_count++; + if (!is_retry_over(ctx)) { + bool cannotConnect = true; + if (oc_string(ctx->store.ci_server) && + 0 == oc_string_to_endpoint(&ctx->store.ci_server, ctx->cloud_ep, + NULL) && + cloud_access_sign_up( + ctx->cloud_ep, oc_string(ctx->store.auth_provider), + oc_string(ctx->store.uid), oc_string(ctx->store.access_token), + ctx->device_index, sign_up_handler, ctx)) { + cannotConnect = false; + } + if (cannotConnect) { + cloud_set_last_error(ctx, CLOUD_ERROR_CONNECT); + } + oc_set_delayed_callback(ctx, sign_up, session_timeout[ctx->retry_count]); + } + } + + return OC_EVENT_DONE; +} + +static void sign_in_handler(oc_client_response_t *data) { + cloud_context_t *ctx = (cloud_context_t *)data->user_data; + + OC_DBG("[CM] sign in handler(%d)\n", data->code); + + if (ctx->store.status != CLOUD_SIGNED_UP && + ctx->store.status != CLOUD_REFRESHED_TOKEN && + ctx->store.status != CLOUD_RECONNECTING) + return; + if (data->code != OC_STATUS_CHANGED) + goto error; + oc_rep_t *payload = get_res_payload(data); + + oc_remove_delayed_callback(ctx, sign_in); + ctx->retry_count = 0; + ctx->store.status = CLOUD_SIGNED_IN; + cloud_set_last_error(ctx, CLOUD_OK); + oc_set_delayed_callback(ctx, callback_handler, 0); + oc_set_delayed_callback(ctx, send_ping, PING_DELAY); + if (ctx->expires_in) { + oc_set_delayed_callback(ctx, refresh_token, ctx->expires_in); + } + free_res_payload(payload); + return; + +error: + + oc_remove_delayed_callback(ctx, sign_in); + cloud_set_last_error(ctx, CLOUD_ERROR_RESPONSE); + if (data->code != OC_STATUS_UNAUTHORIZED) { + ctx->store.status = CLOUD_RECONNECTING; + oc_set_delayed_callback(ctx, sign_in, message_timeout[ctx->retry_count]); + } else { + if (oc_string(ctx->store.refresh_token) && + oc_string_len(ctx->store.refresh_token) > 0) { + ctx->store.status = CLOUD_RECONNECTING; + oc_remove_delayed_callback(ctx, refresh_token); + oc_set_delayed_callback(ctx, refresh_token, session_timeout[0]); + } else { + ctx->store.status = CLOUD_FAILED; + } + } + oc_set_delayed_callback(ctx, callback_handler, 0); +} + +static oc_event_callback_retval_t sign_in(void *data) { + cloud_context_t *ctx = (cloud_context_t *)data; + + if (ctx->store.status == CLOUD_SIGNED_UP || + ctx->store.status == CLOUD_REFRESHED_TOKEN || + ctx->store.status == CLOUD_RECONNECTING) { + OC_DBG("[CM] try sign in(%d)\n", ctx->retry_count); + ctx->retry_count++; + if (!is_retry_over(ctx)) { + bool cannotConnect = true; + if (0 == oc_string_to_endpoint(&ctx->store.ci_server, ctx->cloud_ep, + NULL) && + cloud_access_sign_in(ctx->cloud_ep, oc_string(ctx->store.uid), + oc_string(ctx->store.access_token), + ctx->device_index, sign_in_handler, ctx)) { + cannotConnect = false; + } + if (cannotConnect) { + cloud_set_last_error(ctx, CLOUD_ERROR_CONNECT); + } + oc_set_delayed_callback(ctx, sign_in, session_timeout[ctx->retry_count]); + } + } + + return OC_EVENT_DONE; +} + +static void refresh_token_handler(oc_client_response_t *data) { + cloud_context_t *ctx = (cloud_context_t *)data->user_data; + if (ctx->store.status != CLOUD_SIGNED_IN && + ctx->store.status != CLOUD_SIGNED_UP && + ctx->store.status != CLOUD_RECONNECTING) { + return; + } + OC_DBG("[CM] refresh token handler(%d)\n", data->code); + + oc_rep_t *payload = get_res_payload(data); + + if (data->code != OC_STATUS_CHANGED) + goto error; + + char *access_value = NULL, *refresh_value = NULL; + size_t access_size = 0, refresh_size = 0; + int64_t expires_in = 0; + oc_rep_get_string(payload, ACCESS_TOKEN_KEY, &access_value, &access_size); + oc_rep_get_string(payload, REFRESH_TOKEN_KEY, &refresh_value, &refresh_size); + oc_rep_get_int(payload, EXPIRESIN_KEY, &expires_in); + + if (!access_value || !refresh_value) { + goto error; + } + cloud_set_string(&ctx->store.access_token, access_value, access_size); + cloud_set_string(&ctx->store.refresh_token, refresh_value, refresh_size); + if (expires_in > 0 && expires_in <= UINT16_MAX) { + ctx->expires_in = (uint16_t)expires_in; + } else { + ctx->expires_in = 0; + } + cloud_store_dump_async(&ctx->store); + + oc_remove_delayed_callback(ctx, send_ping); + oc_remove_delayed_callback(ctx, refresh_token); + cloud_set_last_error(ctx, CLOUD_OK); + ctx->retry_count = 0; + ctx->retry_refresh_token_count = 0; + ctx->store.status = CLOUD_REFRESHED_TOKEN; + oc_set_delayed_callback(ctx, sign_in, session_timeout[ctx->retry_count]); + oc_set_delayed_callback(ctx, callback_handler, 0); + free_res_payload(payload); + return; + +error: + cloud_set_last_error(ctx, CLOUD_ERROR_REFRESH_ACCESS_TOKEN); + + oc_remove_delayed_callback(ctx, refresh_token); + cloud_set_last_error(ctx, CLOUD_ERROR_RESPONSE); + if (data->code != OC_STATUS_UNAUTHORIZED) { + oc_set_delayed_callback(ctx, refresh_token, + message_timeout[ctx->retry_refresh_token_count]); + ctx->store.status = CLOUD_RECONNECTING; + } else { + ctx->store.status = CLOUD_FAILED; + } + oc_set_delayed_callback(ctx, callback_handler, 0); + free_res_payload(payload); +} + +static oc_event_callback_retval_t refresh_token(void *data) { + cloud_context_t *ctx = (cloud_context_t *)data; + if (ctx->store.status != CLOUD_SIGNED_IN && + ctx->store.status != CLOUD_SIGNED_UP && + ctx->store.status != CLOUD_RECONNECTING) { + return OC_EVENT_DONE; + } + OC_DBG("[CM] try refresh token(%d)\n", ctx->retry_refresh_token_count); + + ctx->retry_refresh_token_count++; + if (!is_refresh_token_retry_over(ctx)) { + bool cannotConnect = true; + if (0 == + oc_string_to_endpoint(&ctx->store.ci_server, ctx->cloud_ep, NULL) && + cloud_access_refresh_access_token( + ctx->cloud_ep, oc_string(ctx->store.uid), + oc_string(ctx->store.refresh_token), ctx->device_index, + refresh_token_handler, ctx)) { + cannotConnect = false; + } + if (cannotConnect) { + cloud_set_last_error(ctx, CLOUD_ERROR_REFRESH_ACCESS_TOKEN); + } + oc_set_delayed_callback(ctx, sign_in, + session_timeout[ctx->retry_refresh_token_count]); + } + + return OC_EVENT_DONE; +} + +static void send_ping_handler(oc_client_response_t *data) { + cloud_context_t *ctx = (cloud_context_t *)data->user_data; + if (ctx->store.status != CLOUD_SIGNED_IN) { + return; + } + OC_DBG("[CM] send ping handler(%d)\n", data->code); + + if (data->code == OC_PING_TIMEOUT) + goto error; + + oc_remove_delayed_callback(ctx, send_ping); + ctx->retry_count = 0; + oc_set_delayed_callback(ctx, send_ping, PING_DELAY); + return; + +error: + oc_remove_delayed_callback(ctx, send_ping); + oc_set_delayed_callback(ctx, send_ping, PING_DELAY_ON_TIMEOUT); + if (data->code == OC_PING_TIMEOUT) { + cloud_set_last_error(ctx, CLOUD_ERROR_CONNECT); + } +} + +static oc_event_callback_retval_t send_ping(void *data) { + cloud_context_t *ctx = (cloud_context_t *)data; + if (ctx->store.status != CLOUD_SIGNED_IN) { + return OC_EVENT_DONE; + } + + OC_DBG("[CM] try send ping(%d)\n", ctx->retry_count); + ctx->retry_count++; + if (!is_retry_over(ctx)) { + if (!oc_send_ping(false, ctx->cloud_ep, 1, send_ping_handler, ctx)) { + cloud_set_last_error(ctx, CLOUD_ERROR_CONNECT); + } + } + + return OC_EVENT_DONE; +} diff --git a/service/cloud/src/cloud_rd.c b/service/cloud/src/cloud_rd.c new file mode 100644 index 0000000000000000000000000000000000000000..4addc0dea56197b4729d8c4e4a829932f5628d74 --- /dev/null +++ b/service/cloud/src/cloud_rd.c @@ -0,0 +1,260 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik 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. + * + ******************************************************************/ + +#include "cloud.h" +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_collection.h" +#include "rd_client.h" + +#define ONE_HOUR 3600 +#define OC_RSRVD_LINKS "links" +#define OC_RSRVD_HREF "href" +#define OC_RSRVD_INSTANCEID "ins" + +static oc_link_t *rd_link_find(oc_link_t *head, oc_resource_t *res) { + oc_link_t *iter = head; + while (iter != NULL && iter->resource != res) { + iter = iter->next; + } + return iter; +} + +static void rd_link_add(oc_link_t **head, oc_link_t *link) { + if (!head) { + return; + } + if (!*head) { + *head = link; + return; + } + oc_list_add((oc_list_t)*head, link); +} + +static oc_link_t *rd_link_pop(oc_link_t **head) { + if (!head || !*head) { + return NULL; + } + oc_link_t *link = *head; + *head = link->next; + link->next = NULL; + return link; +} + +static void rd_link_free(oc_link_t **head) { + for (oc_link_t *link = rd_link_pop(head); link != NULL; + link = rd_link_pop(head)) { + oc_delete_link(link); + } +} + +static oc_link_t *rd_link_find_by_href(oc_link_t *head, const char *href, + size_t href_size) { + oc_link_t *iter = head; + while (iter != NULL && oc_string_len(iter->resource->uri) != href_size && + !strncmp(oc_string(iter->resource->uri), href, href_size)) { + iter = iter->next; + } + return iter; +} + +static oc_link_t *rd_link_remove(oc_link_t **head, oc_link_t *l) { + if (l) { + if (l == *head) { + return rd_link_pop(head); + } + oc_list_remove((oc_list_t)*head, l); + l->next = NULL; + } + return l; +} + +static oc_link_t *rd_link_remove_by_resource(oc_link_t **head, + oc_resource_t *res) { + return rd_link_remove(head, rd_link_find(*head, res)); +} + +static void publish_resources_handler(oc_client_response_t *data) { + cloud_context_t *ctx = (cloud_context_t *)data->user_data; + OC_DBG("[CRD] publish resources handler(%d)\n", data->code); + + if (ctx->store.status != CLOUD_SIGNED_IN) + return; + if (data->code != OC_STATUS_CHANGED) + goto error; + + oc_rep_t *link = NULL; + if (oc_rep_get_object_array(data->payload, OC_RSRVD_LINKS, &link)) { + while (link) { + char *href = NULL; + size_t href_size = 0; + int64_t instace_id = -1; + if (oc_rep_get_string(link->value.object, OC_RSRVD_HREF, &href, + &href_size) && + oc_rep_get_int(link->value.object, OC_RSRVD_INSTANCEID, + &instace_id)) { + oc_link_t *l = + rd_link_find_by_href(ctx->rd_publish_resources, href, href_size); + if (l) { + char buf[16]; + int n = snprintf(buf, sizeof(buf) - 1, "%lld", (long long)instace_id); + if (n < 1) { + continue; + } + if (oc_string(l->ins)) { + oc_free_string(&l->ins); + } + if (n > 0) { + oc_new_string(&l->ins, buf, n); + } else { + memset(&l->ins, 0, sizeof(l->ins)); + } + rd_link_remove(&ctx->rd_publish_resources, l); + rd_link_add(&ctx->rd_publish_resources, l); + } + } + link = link->next; + } + } + + return; + +error : {} +} + +static void publish_resources(cloud_context_t *ctx) { + if (ctx->store.status != CLOUD_SIGNED_IN) { + return; + } + + rd_publish(ctx->cloud_ep, ctx->rd_publish_resources, ctx->device_index, + publish_resources_handler, LOW_QOS, ctx); +} + +int cloud_rd_publish(oc_resource_t *res) { + cloud_context_t *ctx = cloud_find_context(res->device); + if (ctx == NULL) { + return -1; + } + oc_link_t *publish = rd_link_find(ctx->rd_publish_resources, res); + if (publish) { + return 0; + } + oc_link_t *published = rd_link_find(ctx->rd_published_resources, res); + if (published) { + return 0; + } + oc_link_t *delete = + rd_link_remove_by_resource(&ctx->rd_delete_resources, res); + if (delete) { + oc_delete_link(delete); + } + + oc_link_t *link = oc_new_link(res); + rd_link_add(&ctx->rd_publish_resources, link); + publish_resources(ctx); + return 0; +} + +static void move_published_to_publish_resources(cloud_context_t *ctx) { + while (ctx->rd_published_resources) { + oc_link_t *link = rd_link_pop(&ctx->rd_published_resources); + rd_link_add(&ctx->rd_publish_resources, link); + } +} + +static oc_event_callback_retval_t publish_published_resources(void *data) { + cloud_context_t *ctx = (cloud_context_t *)data; + move_published_to_publish_resources(ctx); + publish_resources(ctx); + return OC_EVENT_CONTINUE; +} + +static void delete_resources_handler(oc_client_response_t *data) { + cloud_context_t *ctx = (cloud_context_t *)data->user_data; + OC_DBG("[CRD] delete resources handler(%d)\n", data->code); + + if (ctx->store.status != CLOUD_SIGNED_IN) + return; + if (data->code != OC_STATUS_DELETED) + goto error; + while (ctx->rd_delete_resources) { + oc_link_t *link = rd_link_pop(&ctx->rd_delete_resources); + oc_delete_link(link); + } + +error : {} +} + +static void delete_resources(cloud_context_t *ctx, bool all) { + if (ctx->store.status != CLOUD_SIGNED_IN) { + return; + } + + if (all) { + rd_delete(ctx->cloud_ep, NULL, ctx->device_index, delete_resources_handler, + LOW_QOS, ctx); + return; + } + if (ctx->rd_delete_resources) { + rd_delete(ctx->cloud_ep, ctx->rd_delete_resources, ctx->device_index, + delete_resources_handler, LOW_QOS, ctx); + } +} + +void cloud_rd_manager_status_changed(cloud_context_t *ctx) { + if (ctx->store.status == CLOUD_SIGNED_IN) { + publish_published_resources(ctx); + delete_resources(ctx, false); + oc_remove_delayed_callback(ctx, publish_published_resources); + oc_set_delayed_callback(ctx, publish_published_resources, ONE_HOUR); + } else { + oc_remove_delayed_callback(ctx, publish_published_resources); + } +} + +void cloud_rd_deinit(cloud_context_t *ctx) { + oc_remove_delayed_callback(ctx, publish_published_resources); + + rd_link_free(&ctx->rd_delete_resources); + rd_link_free(&ctx->rd_published_resources); + rd_link_free(&ctx->rd_publish_resources); +} + +void cloud_rd_delete(oc_resource_t *res) { + cloud_context_t *ctx = cloud_find_context(res->device); + if (ctx == NULL) { + return; + } + oc_link_t *publish = + rd_link_remove_by_resource(&ctx->rd_publish_resources, res); + if (publish != NULL) { + oc_delete_link(publish); + } + oc_link_t *published = + rd_link_remove_by_resource(&ctx->rd_published_resources, res); + if (published != NULL) { + if (published->resource) { + // dont hold link on resource, because it can points to deleted resource. + published->resource->num_links--; + published->resource = NULL; + } + rd_link_add(&ctx->rd_delete_resources, published); + delete_resources(ctx, false); + } +} \ No newline at end of file diff --git a/service/cloud/src/cloud_resource.c b/service/cloud/src/cloud_resource.c new file mode 100644 index 0000000000000000000000000000000000000000..eac14795708ef088df1a5feb6b7821b95c6bf232 --- /dev/null +++ b/service/cloud/src/cloud_resource.c @@ -0,0 +1,134 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik 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. + * + ****************************************************************************/ + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_config.h" + +#define OC_RSRVD_RES_TYPE_COAPCLOUDCONF "oic.r.coapcloudconf" +#define OC_RSRVD_URI_COAPCLOUDCONF "/CoapCloudConfResURI" +#define OC_RSRVD_ACCESSTOKEN "at" +#define OC_RSRVD_AUTHPROVIDER "apn" +#define OC_RSRVD_CISERVER "cis" +#define OC_RSRVD_SERVERID "sid" +#define OC_RSRVD_LAST_ERROR_CODE "clec" + +static void cloud_response(cloud_context_t *ctx) { + oc_rep_start_root_object(); + oc_process_baseline_interface(ctx->cloud_conf); + oc_rep_set_text_string(root, apn, + (oc_string(ctx->store.auth_provider) != NULL + ? oc_string(ctx->store.auth_provider) + : "")); + oc_rep_set_text_string( + root, cis, + (oc_string(ctx->store.ci_server) ? oc_string(ctx->store.ci_server) : "")); + oc_rep_set_text_string( + root, sid, (oc_string(ctx->store.sid) ? oc_string(ctx->store.sid) : "")); + oc_rep_set_text_string(root, at, ""); + oc_rep_set_int(root, clec, (int)ctx->last_error); + + oc_rep_end_root_object(); +} + +static void get_cloud(oc_request_t *request, oc_interface_mask_t interface, + void *user_data) { + cloud_context_t *ctx = (cloud_context_t *)user_data; + OC_DBG("GET request received"); + + if (interface != OC_IF_BASELINE) { + OC_ERR("Resource does not support this interface: %d", interface); + oc_send_response(request, OC_STATUS_BAD_REQUEST); + return; + } + + cloud_response(ctx); + oc_send_response(request, OC_STATUS_OK); +} + +static bool cloud_update_from_request(cloud_context_t *ctx, + oc_request_t *request) { + bool changed = false; + cloud_conf_update_t data; + memset(&data, 0, sizeof(data)); + + if (oc_rep_get_string(request->request_payload, OC_RSRVD_ACCESSTOKEN, + &data.access_token, &data.access_token_len)) { + changed = true; + } + + if (oc_rep_get_string(request->request_payload, OC_RSRVD_AUTHPROVIDER, + &data.auth_provider, &data.auth_provider_len)) { + changed = true; + } + + if (oc_rep_get_string(request->request_payload, OC_RSRVD_CISERVER, + &data.ci_server, &data.ci_server_len)) { + changed = true; + } + + // OCF 2.0 spec version added sid property. + if (oc_rep_get_string(request->request_payload, OC_RSRVD_SERVERID, &data.sid, + &data.sid_len)) { + changed = true; + } + + if (changed) { + cloud_update_by_resource(ctx, &data); + } + return changed; +} + +static void post_cloud(oc_request_t *request, oc_interface_mask_t interface, + void *user_data) { + cloud_context_t *ctx = (cloud_context_t *)user_data; + OC_DBG("POST request received"); + + if (interface != OC_IF_BASELINE) { + OC_ERR("Resource does not support this interface: %d", interface); + oc_send_response(request, OC_STATUS_BAD_REQUEST); + return; + } + + bool changed = cloud_update_from_request(ctx, request); + cloud_response(ctx); + oc_send_response(request, + changed ? OC_STATUS_CHANGED : OC_STATUS_NOT_MODIFIED); +} + +bool cloud_resource_init(cloud_context_t *ctx) { + oc_resource_t *res = oc_new_resource("cloud", OC_RSRVD_URI_COAPCLOUDCONF, 1, + ctx->device_index); + if (!res) { + OC_WRN("insufficient memory to create resource for cloud resource"); + return false; + } + + oc_resource_bind_resource_type(res, OC_RSRVD_RES_TYPE_COAPCLOUDCONF); + oc_resource_set_discoverable(res, true); + oc_resource_set_observable(res, true); + oc_resource_set_request_handler(res, OC_GET, get_cloud, ctx); + oc_resource_set_request_handler(res, OC_POST, post_cloud, ctx); + if (!oc_add_resource(res)) { + oc_delete_resource(res); + return false; + } + ctx->cloud_conf = res; + + return true; +} diff --git a/service/cloud/src/cloud_store.c b/service/cloud/src/cloud_store.c new file mode 100644 index 0000000000000000000000000000000000000000..5c6ceb20eed3744a67fb259d0540dfea2e42489f --- /dev/null +++ b/service/cloud/src/cloud_store.c @@ -0,0 +1,251 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ******************************************************************/ + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_rep.h" +#ifdef OC_DYNAMIC_ALLOCATION +#include +#endif /* OC_DYNAMIC_ALLOCATION */ + +#define CLOUD_STORE_NAME "cloud" + +#define CLOUD_TAG_MAX (32) + +#ifndef OC_SECURITY +// dummy storage +int oc_storage_config(const char *store) { + (void)store; + return 0; +} + +long oc_storage_read(const char *store, uint8_t *buf, size_t size) { + (void)store; + (void)buf; + (void)size; + return -1; +} + +long oc_storage_write(const char *store, uint8_t *buf, size_t size) { + (void)store; + (void)buf; + (void)size; + return -1; +} + +#endif + +static int cloud_store_load_internal(const char *store_name, + cloud_store_t *store); +static void gen_cloud_tag(const char *name, size_t device_index, + char *cloud_tag); + +void cloud_store_load(cloud_store_t *store) { + char cloud_tag[CLOUD_TAG_MAX]; + gen_cloud_tag(CLOUD_STORE_NAME, store->device, cloud_tag); + cloud_store_load_internal(cloud_tag, store); +} + +static void gen_cloud_tag(const char *name, size_t device_index, + char *cloud_tag) { + int cloud_tag_len = + snprintf(cloud_tag, CLOUD_TAG_MAX, "%s_%zd", name, device_index); + cloud_tag_len = (cloud_tag_len < CLOUD_TAG_MAX - 1) ? cloud_tag_len + 1 + : CLOUD_TAG_MAX - 1; + cloud_tag[cloud_tag_len] = '\0'; +} + +static void encode_cloud_with_map(CborEncoder *object_map, + const cloud_store_t *store) { + oc_rep_set_text_string(*object, ci_server, oc_string(store->ci_server)); + oc_rep_set_text_string(*object, auth_provider, + oc_string(store->auth_provider)); + oc_rep_set_text_string(*object, uid, oc_string(store->uid)); + oc_rep_set_text_string(*object, access_token, oc_string(store->access_token)); + oc_rep_set_text_string(*object, refresh_token, + oc_string(store->refresh_token)); + oc_rep_set_int(*object, status, store->status); +} + +static void cloud_store_encode(const cloud_store_t *store) { + oc_rep_start_root_object(); + encode_cloud_with_map(&root_map, store); + oc_rep_end_root_object(); +} + +static long cloud_store_dump_internal(const char *store_name, + const cloud_store_t *store) { + if (!store_name || !store) { + return -1; + } + +#ifdef OC_DYNAMIC_ALLOCATION + uint8_t *buf = malloc(OC_MAX_APP_DATA_SIZE); + if (!buf) + return -1; +#else /* OC_DYNAMIC_ALLOCATION */ + uint8_t buf[OC_MAX_APP_DATA_SIZE]; +#endif /* !OC_DYNAMIC_ALLOCATION */ + + oc_rep_new(buf, OC_MAX_APP_DATA_SIZE); + // Dumping cloud and accesspoint information. + cloud_store_encode(store); + long size = oc_rep_get_encoded_payload_size(); + if (size > 0) { + size = oc_storage_write(store_name, buf, size); + } + +#ifdef OC_DYNAMIC_ALLOCATION + free(buf); +#endif /* OC_DYNAMIC_ALLOCATION */ + + return size; +} + +void cloud_store_dump(const cloud_store_t *store) { + char cloud_tag[CLOUD_TAG_MAX]; + gen_cloud_tag(CLOUD_STORE_NAME, store->device, cloud_tag); + // Calling dump for cloud and access point info + cloud_store_dump_internal(cloud_tag, store); +} + +static oc_event_callback_retval_t cloud_store_dump_handler(void *data) { + cloud_store_t *store = (cloud_store_t *)data; + cloud_store_dump(store); + return OC_EVENT_DONE; +} + +void cloud_store_dump_async(const cloud_store_t *store) { + oc_set_delayed_callback((void *)store, cloud_store_dump_handler, 0); + _oc_signal_event_loop(); +} + +static int cloud_store_decode(oc_rep_t *rep, cloud_store_t *store) { + oc_rep_t *t = rep; + int len = 0; + + while (t != NULL) { + len = oc_string_len(t->name); + switch (t->type) { + case OC_REP_STRING: + if (len == 9 && memcmp(oc_string(t->name), "ci_server", 9) == 0) { + cloud_set_string(&store->ci_server, oc_string(t->value.string), + oc_string_len(t->value.string)); + } else if (len == 13 && + memcmp(oc_string(t->name), "auth_provider", 13) == 0) { + cloud_set_string(&store->auth_provider, oc_string(t->value.string), + oc_string_len(t->value.string)); + } else if (len == 3 && memcmp(oc_string(t->name), "uid", 3) == 0) { + cloud_set_string(&store->uid, oc_string(t->value.string), + oc_string_len(t->value.string)); + } else if (len == 12 && + memcmp(oc_string(t->name), "access_token", 12) == 0) { + cloud_set_string(&store->access_token, oc_string(t->value.string), + oc_string_len(t->value.string)); + } else if (len == 13 && + memcmp(oc_string(t->name), "refresh_token", 13) == 0) { + cloud_set_string(&store->refresh_token, oc_string(t->value.string), + oc_string_len(t->value.string)); + } else { + OC_ERR("[CLOUD_STORE] Unknown property %s", oc_string(t->name)); + return -1; + } + break; + case OC_REP_INT: + if (len == 6 && memcmp(oc_string(t->name), "status", 6) == 0) { + store->status = t->value.integer; + } else { + OC_ERR("[CLOUD_STORE] Unknown property %s", oc_string(t->name)); + return -1; + } + break; + default: + OC_ERR("[CLOUD_STORE] Unknown property %s", oc_string(t->name)); + return -1; + } + t = t->next; + } + return 0; +} + +void cloud_store_initialize(cloud_store_t *store) { + cloud_set_string(&store->ci_server, NULL, 0); + cloud_set_string(&store->auth_provider, NULL, 0); + cloud_set_string(&store->uid, NULL, 0); + cloud_set_string(&store->access_token, NULL, 0); + cloud_set_string(&store->refresh_token, NULL, 0); + cloud_set_string(&store->sid, NULL, 0); + store->status = 0; +} + +static int cloud_store_load_internal(const char *store_name, + cloud_store_t *store) { + if (!store_name || !store) { + return -1; + } + + int ret = 0; + oc_rep_t *rep; + +#ifdef OC_DYNAMIC_ALLOCATION + uint8_t *buf = malloc(OC_MAX_APP_DATA_SIZE); + if (!buf) { + OC_ERR("[CLOUD_STORE] alloc failed!\n"); + return -1; + } +#else /* OC_DYNAMIC_ALLOCATION */ + uint8_t buf[OC_MAX_APP_DATA_SIZE]; +#endif /* !OC_DYNAMIC_ALLOCATION */ + long size = 0; + size = oc_storage_read(store_name, buf, OC_MAX_APP_DATA_SIZE); + if (size > 0) { +#ifndef OC_DYNAMIC_ALLOCATION + char rep_objects_alloc[OC_MAX_NUM_REP_OBJECTS]; + oc_rep_t rep_objects_pool[OC_MAX_NUM_REP_OBJECTS]; + memset(rep_objects_alloc, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(char)); + memset(rep_objects_pool, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(oc_rep_t)); + struct oc_memb rep_objects = {sizeof(oc_rep_t), OC_MAX_NUM_REP_OBJECTS, + rep_objects_alloc, (void *)rep_objects_pool, + 0}; +#else /* !OC_DYNAMIC_ALLOCATION */ + struct oc_memb rep_objects = {sizeof(oc_rep_t), 0, 0, 0, 0}; +#endif /* OC_DYNAMIC_ALLOCATION */ + oc_rep_set_pool(&rep_objects); + oc_parse_rep(buf, (uint16_t)size, &rep); + ret = cloud_store_decode(rep, store); + oc_rep_set_pool(&rep_objects); // Reset representation pool + oc_free_rep(rep); + } else { + cloud_store_initialize(store); + ret = -2; + } +#ifdef OC_DYNAMIC_ALLOCATION + free(buf); +#endif /* OC_DYNAMIC_ALLOCATION */ + return ret; +} + +void cloud_store_deinit(cloud_store_t *store) { + cloud_set_string(&store->ci_server, NULL, 0); + cloud_set_string(&store->auth_provider, NULL, 0); + cloud_set_string(&store->uid, NULL, 0); + cloud_set_string(&store->access_token, NULL, 0); + cloud_set_string(&store->refresh_token, NULL, 0); + cloud_set_string(&store->sid, NULL, 0); +} \ No newline at end of file diff --git a/service/cloud/unittest/cloud_access_test.cpp b/service/cloud/unittest/cloud_access_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a42b3f1f4e472d5b4457841df7386edf27409186 --- /dev/null +++ b/service/cloud/unittest/cloud_access_test.cpp @@ -0,0 +1,153 @@ +/****************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ******************************************************************/ + +#include + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_endpoint.h" + +class TestCloudAccess: public testing::Test +{ + public: + static oc_handler_t s_handler; + static oc_endpoint_t s_endpoint; + + static void onPostResponse(oc_client_response_t *data) + { + (void) data; + } + + static int appInit(void) + { + int result = oc_init_platform("OCFCloud", NULL, NULL); + result |= oc_add_device("/oic/d", "oic.d.light", "Cloud's Light", + "ocf.1.0.0", "ocf.res.1.0.0", NULL, NULL); + return result; + } + + static void signalEventLoop(void) + { + } + + protected: + static void SetUpTestCase() + { + s_handler.init = &appInit; + s_handler.signal_event_loop = &signalEventLoop; + int ret = oc_main_init(&s_handler); + ASSERT_EQ(0, ret); + + oc_string_t ep_str; + oc_new_string(&ep_str, "coap://224.0.1.187:5683", 23); + oc_string_to_endpoint(&ep_str, &s_endpoint, NULL); + oc_free_string(&ep_str); + } + + static void TearDownTestCase() + { + oc_main_shutdown(); + } +}; +oc_handler_t TestCloudAccess::s_handler; +oc_endpoint_t TestCloudAccess::s_endpoint; + +TEST_F(TestCloudAccess, cloud_access_sign_up_p) +{ + // When + bool ret = cloud_access_sign_up(&s_endpoint, "auth_provider", "uid", "access_token", 0, onPostResponse, NULL); + + // Then + EXPECT_TRUE(ret); +} + +TEST_F(TestCloudAccess, cloud_access_sign_up_f) +{ + // Given + oc_endpoint_t *ep = NULL; + + // When + bool ret = cloud_access_sign_up(ep, NULL, NULL, NULL , 0,NULL, NULL); + + // Then + EXPECT_FALSE(ret); +} + +TEST_F(TestCloudAccess, cloud_access_sign_in_p) +{ + // When + bool ret = cloud_access_sign_in(&s_endpoint, "uid", "access_token", 0, onPostResponse, NULL); + + // Then + EXPECT_TRUE(ret); +} + +TEST_F(TestCloudAccess, cloud_access_sign_in_f) +{ + // Given + oc_endpoint_t *ep = NULL; + + // When + bool ret = cloud_access_sign_in(ep, NULL, NULL, 0, NULL, NULL); + + // Then + EXPECT_FALSE(ret); +} + +TEST_F(TestCloudAccess, cloud_access_sign_out_p) +{ + // When + bool ret = cloud_access_sign_out(&s_endpoint, "access_token", 0, onPostResponse, NULL); + + // Then + EXPECT_TRUE(ret); +} + +TEST_F(TestCloudAccess, cloud_access_sign_out_f) +{ + // Given + oc_endpoint_t *ep = NULL; + + // When + bool ret = cloud_access_sign_out(ep, NULL, 0, NULL, NULL); + + // Then + EXPECT_FALSE(ret); +} + +TEST_F(TestCloudAccess,cloud_access_refresh_access_token_p) +{ + // When + bool ret = cloud_access_refresh_access_token(&s_endpoint, "uid", "refresh_token", 0, onPostResponse, NULL); + + // Then + EXPECT_TRUE(ret); +} + +TEST_F(TestCloudAccess,cloud_access_refresh_access_token_f) +{ + // Given + oc_endpoint_t *ep = NULL; + + // When + bool ret = cloud_access_refresh_access_token(ep, NULL, NULL, 0, NULL, NULL); + + // Then + EXPECT_FALSE(ret); +} \ No newline at end of file diff --git a/service/cloud/unittest/cloud_manager_test.cpp b/service/cloud/unittest/cloud_manager_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3307b6aa1da5c75bc488940e4f00b19efd12bcec --- /dev/null +++ b/service/cloud/unittest/cloud_manager_test.cpp @@ -0,0 +1,148 @@ +/****************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ******************************************************************/ + +#include + +#include "cloud_internal.h" +#include "oc_api.h" + +class TestCloudManager : public testing::Test +{ + public: + static oc_handler_t s_handler; + static cloud_context_t s_context; + static pthread_mutex_t mutex; + static pthread_cond_t cv; + + static void onPostResponse(oc_client_response_t *data) + { + (void)data; + } + + static int appInit(void) + { + int result = oc_init_platform("OCFCloud", NULL, NULL); + result |= oc_add_device("/oic/d", "oic.d.light", "Lamp", + "ocf.1.0.0", "ocf.res.1.0.0", NULL, NULL); + return result; + } + + static void signalEventLoop(void) + { + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mutex); + } + + static oc_event_callback_retval_t quitEvent(void *data) + { + bool *quit = (bool *)data; + *quit = true; + return OC_EVENT_DONE; + } + + static void poolEvents(uint16_t seconds) + { + bool quit = false; + oc_set_delayed_callback(&quit, quitEvent, seconds); + + while (true) + { + pthread_mutex_lock(&mutex); + oc_clock_time_t next_event = oc_main_poll(); + if (quit) + { + pthread_mutex_unlock(&mutex); + break; + } + if (next_event == 0) + { + pthread_cond_wait(&cv, &mutex); + } + else + { + struct timespec ts; + 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); + } + } + + protected: + static void SetUpTestCase() + { + s_handler.init = &appInit; + s_handler.signal_event_loop = &signalEventLoop; + int ret = oc_main_init(&s_handler); + ASSERT_EQ(0, ret); + + memset(&s_context, 0, sizeof(s_context)); + } + + static void TearDownTestCase() + { + oc_main_shutdown(); + } +}; + +oc_handler_t TestCloudManager::s_handler; +cloud_context_t TestCloudManager::s_context; +pthread_mutex_t TestCloudManager::mutex; +pthread_cond_t TestCloudManager::cv; + +TEST_F(TestCloudManager, cloud_manager_start_initialized_f) +{ + // When + s_context.store.status = CLOUD_INITIALIZED; + cloud_manager_start(&s_context); + poolEvents(5); + cloud_manager_stop(&s_context); + + // Then + EXPECT_EQ(CLOUD_ERROR_CONNECT, s_context.last_error); + EXPECT_EQ(CLOUD_INITIALIZED, s_context.store.status); +} + +TEST_F(TestCloudManager, cloud_manager_start_signed_up_f) +{ + // When + s_context.store.status = CLOUD_SIGNED_UP; + cloud_manager_start(&s_context); + poolEvents(5); + cloud_manager_stop(&s_context); + + // Then + EXPECT_EQ(CLOUD_ERROR_CONNECT, s_context.last_error); + EXPECT_EQ(CLOUD_SIGNED_UP, s_context.store.status); +} + +TEST_F(TestCloudManager, cloud_manager_start_signed_in_f) +{ + // When + s_context.store.status = CLOUD_SIGNED_IN; + cloud_manager_start(&s_context); + poolEvents(5); + cloud_manager_stop(&s_context); + + // Then + EXPECT_EQ(CLOUD_ERROR_CONNECT, s_context.last_error); + EXPECT_EQ(CLOUD_SIGNED_UP, s_context.store.status); +} \ No newline at end of file diff --git a/service/cloud/unittest/cloud_rd_test.cpp b/service/cloud/unittest/cloud_rd_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..67714e46da4c7feb84c4f45f7fdaa53f76c5c4f7 --- /dev/null +++ b/service/cloud/unittest/cloud_rd_test.cpp @@ -0,0 +1,155 @@ +/****************************************************************** + * + * Copyright 2019 Jozef Kralik 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. + * + ******************************************************************/ + +#include + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_collection.h" + +class TestCloudRD : public testing::Test +{ + public: + static oc_handler_t s_handler; + static pthread_mutex_t mutex; + static pthread_cond_t cv; + + static void onPostResponse(oc_client_response_t *data) + { + (void)data; + } + + static int appInit(void) + { + int result = oc_init_platform("OCFCloud", NULL, NULL); + result |= oc_add_device("/oic/d", "oic.d.light", "Lamp", + "ocf.1.0.0", "ocf.res.1.0.0", NULL, NULL); + result |= cloud_init(0, NULL, NULL); + return result; + } + + static void signalEventLoop(void) + { + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mutex); + } + + static oc_event_callback_retval_t quitEvent(void *data) + { + bool *quit = (bool *)data; + *quit = true; + return OC_EVENT_DONE; + } + + static void poolEvents(uint16_t seconds) + { + bool quit = false; + oc_set_delayed_callback(&quit, quitEvent, seconds); + + while (true) + { + pthread_mutex_lock(&mutex); + oc_clock_time_t next_event = oc_main_poll(); + if (quit) + { + pthread_mutex_unlock(&mutex); + break; + } + if (next_event == 0) + { + pthread_cond_wait(&cv, &mutex); + } + else + { + struct timespec ts; + 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); + } + } + + protected: + static void SetUpTestCase() + { + s_handler.init = &appInit; + s_handler.signal_event_loop = &signalEventLoop; + int ret = oc_main_init(&s_handler); + ASSERT_EQ(0, ret); + } + + static oc_resource_t* findResource(oc_link_t* head, oc_resource_t* res) { + for (oc_link_t* l = head; l; l = l->next ) { + if (l->resource == res) { + return l->resource; + } + } + return nullptr; + } + + static void TearDownTestCase() + { + cloud_shutdown(0); + oc_main_shutdown(); + } +}; + +oc_handler_t TestCloudRD::s_handler; +pthread_mutex_t TestCloudRD::mutex; +pthread_cond_t TestCloudRD::cv; + +TEST_F(TestCloudRD, cloud_publish_f) +{ + // When + int ret = cloud_rd_publish(NULL); + + // Then + ASSERT_EQ(-1, ret); +} + +TEST_F(TestCloudRD, cloud_publish_p) +{ + // When + oc_resource_t *res1 = oc_new_resource(NULL, "/light/1", 1, 0); + oc_resource_bind_resource_type(res1, "test"); + int ret = cloud_rd_publish(res1); + + // Then + ASSERT_EQ(0, ret); + cloud_context_t* ctx = cloud_find_context(0); + ASSERT_NE(NULL, ctx); + ASSERT_NE(NULL, ctx->rd_publish_resources); + EXPECT_EQ(res1, findResource(ctx->rd_publish_resources, res1)); +} + +TEST_F(TestCloudRD, cloud_delete) +{ + // When + oc_resource_t *res1 = oc_new_resource(NULL, "/light/1", 1, 0); + oc_resource_bind_resource_type(res1, "test"); + int ret = cloud_rd_publish(res1); + ASSERT_EQ(0, ret); + cloud_rd_delete(res1); + + // Then + cloud_context_t* ctx = cloud_find_context(0); + ASSERT_NE(NULL, ctx); + EXPECT_EQ(NULL, findResource(ctx->rd_publish_resources, res1)); +} \ No newline at end of file diff --git a/service/cloud/unittest/cloud_resource_test.cpp b/service/cloud/unittest/cloud_resource_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..133585f97e742a2cc8bec5bd78fcbbed12aba765 --- /dev/null +++ b/service/cloud/unittest/cloud_resource_test.cpp @@ -0,0 +1,116 @@ +/****************************************************************** + * + * Copyright 2019 Jozef Kralik 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. + * + ******************************************************************/ + +#include + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_collection.h" + +class TestCloudResource : public testing::Test +{ + public: + static oc_handler_t s_handler; + static pthread_mutex_t mutex; + static pthread_cond_t cv; + static cloud_context_t s_context; + + static void onPostResponse(oc_client_response_t *data) + { + (void)data; + } + + static int appInit(void) + { + int result = oc_init_platform("OCFCloud", NULL, NULL); + result |= oc_add_device("/oic/d", "oic.d.light", "Lamp", + "ocf.1.0.0", "ocf.res.1.0.0", NULL, NULL); + return result; + } + + static void signalEventLoop(void) + { + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mutex); + } + + static oc_event_callback_retval_t quitEvent(void *data) + { + bool *quit = (bool *)data; + *quit = true; + return OC_EVENT_DONE; + } + + static void poolEvents(uint16_t seconds) + { + bool quit = false; + oc_set_delayed_callback(&quit, quitEvent, seconds); + + while (true) + { + pthread_mutex_lock(&mutex); + oc_clock_time_t next_event = oc_main_poll(); + if (quit) + { + pthread_mutex_unlock(&mutex); + break; + } + if (next_event == 0) + { + pthread_cond_wait(&cv, &mutex); + } + else + { + struct timespec ts; + 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); + } + } + + protected: + static void SetUpTestCase() + { + s_handler.init = &appInit; + s_handler.signal_event_loop = &signalEventLoop; + int ret = oc_main_init(&s_handler); + ASSERT_EQ(0, ret); + + memset(&s_context, 0, sizeof(s_context)); + } + + static void TearDownTestCase() + { + oc_main_shutdown(); + } +}; + +oc_handler_t TestCloudResource::s_handler; +pthread_mutex_t TestCloudResource::mutex; +pthread_cond_t TestCloudResource::cv; +cloud_context_t TestCloudResource::s_context; + +TEST_F(TestCloudResource, cloud_resource) +{ + // When + bool ret = cloud_resource_init(&s_context); + EXPECT_TRUE(ret); +} \ No newline at end of file diff --git a/service/cloud/unittest/cloud_store_test.cpp b/service/cloud/unittest/cloud_store_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..12abeee5682c953f7e87cc62afecfa89fb0bef22 --- /dev/null +++ b/service/cloud/unittest/cloud_store_test.cpp @@ -0,0 +1,199 @@ +/****************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ******************************************************************/ + +#include + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_collection.h" + +class TestCloudStore : public testing::Test +{ + public: + static oc_handler_t s_handler; + static pthread_mutex_t mutex; + static pthread_cond_t cv; + static cloud_context_t s_context; + + static void onPostResponse(oc_client_response_t *data) + { + (void)data; + } + + static int appInit(void) + { + int result = oc_init_platform("OCFCloud", NULL, NULL); + result |= oc_add_device("/oic/d", "oic.d.light", "Lamp", + "ocf.1.0.0", "ocf.res.1.0.0", NULL, NULL); + return result; + } + + static void signalEventLoop(void) + { + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mutex); + } + + static oc_event_callback_retval_t quitEvent(void *data) + { + bool *quit = (bool *)data; + *quit = true; + return OC_EVENT_DONE; + } + + static void poolEvents(uint16_t seconds) + { + bool quit = false; + oc_set_delayed_callback(&quit, quitEvent, seconds); + + while (true) + { + pthread_mutex_lock(&mutex); + oc_clock_time_t next_event = oc_main_poll(); + if (quit) + { + pthread_mutex_unlock(&mutex); + break; + } + if (next_event == 0) + { + pthread_cond_wait(&cv, &mutex); + } + else + { + struct timespec ts; + 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); + } + } + + protected: + static void SetUpTestCase() + { + s_handler.init = &appInit; + s_handler.signal_event_loop = &signalEventLoop; + int ret = oc_main_init(&s_handler); + ASSERT_EQ(0, ret); + + memset(&s_context, 0, sizeof(s_context)); + } + + static void TearDownTestCase() + { + oc_main_shutdown(); + } +}; + +oc_handler_t TestCloudStore::s_handler; +pthread_mutex_t TestCloudStore::mutex; +pthread_cond_t TestCloudStore::cv; +cloud_context_t TestCloudStore::s_context; + +#define ACCESS_TOKEN ("access_token") +#define AUTH_PROVIDER ("auth_provider") +#define CI_SERVER ("ci_server") +#define DEVICE (1234) +#define EXPIRES_IN (5678) +#define REFRESH_TOKEN ("refresh_token") +#define SID ("sid") +#define STATUS (CLOUD_SIGNED_IN) +#define UID ("uid") + + +TEST_F(TestCloudStore, dump_async) +{ + cloud_store_t store; + oc_new_string(&store.access_token, ACCESS_TOKEN, strlen(ACCESS_TOKEN)); + oc_new_string(&store.auth_provider, AUTH_PROVIDER, strlen(AUTH_PROVIDER)); + oc_new_string(&store.ci_server, CI_SERVER, strlen(CI_SERVER)); + store.device = DEVICE; + store.expires_in = EXPIRES_IN; + oc_new_string(&store.refresh_token, REFRESH_TOKEN, strlen(REFRESH_TOKEN)); + oc_new_string(&store.sid, SID, strlen(SID)); + store.status = STATUS; + oc_new_string(&store.uid, UID, strlen(UID)); + + cloud_store_dump_async(&store); + poolEvents(1); + + cloud_store_t store1; + memset(&store1, 0, sizeof(store1)); + cloud_store_load(&store1); +#ifdef OC_SECURE + EXPECT_STREQ(oc_string(store.access_token), oc_string(store1.access_token)); + EXPECT_STREQ(oc_string(store.auth_provider), oc_string(store1.auth_provider)); + EXPECT_STREQ(oc_string(store.ci_server), oc_string(store1.ci_server)); + EXPECT_EQ(store.device, store1.device); + EXPECT_EQ(store.expires_in, store1.expires_in); + EXPECT_STREQ(oc_string(store.refresh_token),oc_string(store1.refresh_token)); + EXPECT_STREQ(oc_string(store.sid), oc_string(store1.sid)); + EXPECT_EQ(store.status, store1.status); + EXPECT_STREQ(oc_string(store.uid), oc_string(store1.uid)); +#else + EXPECT_STREQ("ocfcloud.com", oc_string(store1.auth_provider)); + EXPECT_STREQ("coap+tcp://devices.ocfcloud.com:5684", oc_string(store1.ci_server)); +#endif +} + +TEST_F(TestCloudStore, load_defaults) +{ + cloud_store_t store1; + memset(&store1, 0, sizeof(store1)); + cloud_store_load(&store1); + + EXPECT_STREQ("ocfcloud.com", oc_string(store1.auth_provider)); + EXPECT_STREQ("coap+tcp://devices.ocfcloud.com:5684", oc_string(store1.ci_server)); +} + +TEST_F(TestCloudStore, dump) +{ + cloud_store_t store; + oc_new_string(&store.access_token, ACCESS_TOKEN, strlen(ACCESS_TOKEN)); + oc_new_string(&store.auth_provider, AUTH_PROVIDER, strlen(AUTH_PROVIDER)); + oc_new_string(&store.ci_server, CI_SERVER, strlen(CI_SERVER)); + store.device = DEVICE; + store.expires_in = EXPIRES_IN; + oc_new_string(&store.refresh_token, REFRESH_TOKEN, strlen(REFRESH_TOKEN)); + oc_new_string(&store.sid, SID, strlen(SID)); + store.status = STATUS; + oc_new_string(&store.uid, UID, strlen(UID)); + + cloud_store_dump(&store); + cloud_store_t store1; + memset(&store1, 0, sizeof(store1)); + cloud_store_load(&store1); +#ifdef OC_SECURE + EXPECT_STREQ(oc_string(store.access_token), oc_string(store1.access_token)); + EXPECT_STREQ(oc_string(store.auth_provider), oc_string(store1.auth_provider)); + EXPECT_STREQ(oc_string(store.ci_server), oc_string(store1.ci_server)); + EXPECT_EQ(store.device, store1.device); + EXPECT_EQ(store.expires_in, store1.expires_in); + EXPECT_STREQ(oc_string(store.refresh_token),oc_string(store1.refresh_token)); + EXPECT_STREQ(oc_string(store.sid), oc_string(store1.sid)); + EXPECT_EQ(store.status, store1.status); + EXPECT_STREQ(oc_string(store.uid), oc_string(store1.uid)); +#else + EXPECT_STREQ("ocfcloud.com", oc_string(store1.auth_provider)); + EXPECT_STREQ("coap+tcp://devices.ocfcloud.com:5684", oc_string(store1.ci_server)); +#endif +} \ No newline at end of file diff --git a/service/cloud/unittest/cloud_test.cpp b/service/cloud/unittest/cloud_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..20a91c8c27ccab96aa61eaa151b0aea1e515f6a5 --- /dev/null +++ b/service/cloud/unittest/cloud_test.cpp @@ -0,0 +1,196 @@ +/****************************************************************** + * + * Copyright 2019 Jozef Kralik 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. + * + ******************************************************************/ + +#include + +#include "cloud_internal.h" +#include "oc_api.h" +#include "oc_collection.h" + +class TestCloud : public testing::Test +{ + public: + static oc_handler_t s_handler; + static pthread_mutex_t mutex; + static pthread_cond_t cv; + + static void onPostResponse(oc_client_response_t *data) + { + (void)data; + } + + static int appInit(void) + { + int result = oc_init_platform("OCFCloud", NULL, NULL); + result |= oc_add_device("/oic/d", "oic.d.light", "Lamp", + "ocf.1.0.0", "ocf.res.1.0.0", NULL, NULL); + return result; + } + + static void signalEventLoop(void) + { + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mutex); + } + + static oc_event_callback_retval_t quitEvent(void *data) + { + bool *quit = (bool *)data; + *quit = true; + return OC_EVENT_DONE; + } + + static void poolEvents(uint16_t seconds) + { + bool quit = false; + oc_set_delayed_callback(&quit, quitEvent, seconds); + + while (true) + { + pthread_mutex_lock(&mutex); + oc_clock_time_t next_event = oc_main_poll(); + if (quit) + { + pthread_mutex_unlock(&mutex); + break; + } + if (next_event == 0) + { + pthread_cond_wait(&cv, &mutex); + } + else + { + struct timespec ts; + 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); + } + } + + protected: + static void SetUpTestCase() + { + s_handler.init = &appInit; + s_handler.signal_event_loop = &signalEventLoop; + int ret = oc_main_init(&s_handler); + ASSERT_EQ(0, ret); + } + + static void update_status(cloud_status_t status, void* data) { + cloud_status_t* u_status = (cloud_status_t*) data; + *u_status = status; + } + + static void TearDownTestCase() + { + oc_main_shutdown(); + } +}; + +oc_handler_t TestCloud::s_handler; +pthread_mutex_t TestCloud::mutex; +pthread_cond_t TestCloud::cv; + + +TEST_F(TestCloud, cloud_find_context) +{ + int ret = cloud_init(0, NULL, NULL); + EXPECT_EQ(0, ret); + EXPECT_NE(NULL, cloud_find_context(0)); + EXPECT_EQ(NULL, cloud_find_context(1)); + cloud_shutdown(0); +} + + +TEST_F(TestCloud, cloud_status) +{ + cloud_status_t status; + memset(&status, 0, sizeof(status)); + int ret = cloud_init(0, update_status, &status); + EXPECT_EQ(0, ret); + cloud_context_t* ctx = cloud_find_context(0); + ASSERT_NE(NULL, ctx); + ctx->store.status = CLOUD_RECONNECTING; + cloud_manager_cb(ctx); + EXPECT_EQ(ctx->store.status, status); + cloud_shutdown(0); +} + +TEST_F(TestCloud, cloud_set_string) +{ + oc_string_t str; + memset(&str, 0, sizeof(str)); + cloud_set_string(&str, "a", 1); + EXPECT_STREQ("a", oc_string(str)); + + cloud_set_string(&str, NULL, 1); + EXPECT_EQ(NULL, oc_string(str)); + + cloud_set_string(&str, "b", 0); + EXPECT_EQ(NULL, oc_string(str)); +} + + +TEST_F(TestCloud, cloud_set_last_error) +{ + int ret = cloud_init(0, NULL, NULL); + ASSERT_EQ(0, ret); + + cloud_context_t* ctx = cloud_find_context(0); + ASSERT_NE(NULL, ctx); + + int err = 123; + + cloud_set_last_error(ctx, (cloud_error_t)err); + ASSERT_EQ((cloud_error_t)err, ctx->last_error); + + cloud_shutdown(0); +} + +TEST_F(TestCloud, cloud_update_by_resource) +{ + int ret = cloud_init(0, NULL, NULL); + ASSERT_EQ(0, ret); + + cloud_context_t* ctx = cloud_find_context(0); + ASSERT_NE(NULL, ctx); + ctx->store.status = CLOUD_FAILED; + + cloud_conf_update_t data; + data.access_token = (char*) "access_token"; + data.access_token_len = strlen(data.access_token); + data.auth_provider = (char*) "auth_provider"; + data.auth_provider_len = strlen(data.auth_provider); + data.ci_server = (char*) "ci_server"; + data.ci_server_len = strlen("ci_server"); + data.sid = (char*) "sid"; + data.sid_len = strlen(data.sid); + + cloud_update_by_resource(ctx, &data); + + EXPECT_STREQ(data.access_token, oc_string(ctx->store.access_token)); + EXPECT_STREQ(data.auth_provider, oc_string(ctx->store.auth_provider)); + EXPECT_STREQ(data.ci_server, oc_string(ctx->store.ci_server)); + EXPECT_STREQ(data.sid, oc_string(ctx->store.sid)); + EXPECT_EQ(CLOUD_INITIALIZED, ctx->store.status); + + cloud_shutdown(0); +} \ No newline at end of file diff --git a/service/resource-directory/client/include/rd_client.h b/service/resource-directory/client/include/rd_client.h new file mode 100644 index 0000000000000000000000000000000000000000..c9da5c19ec17026a2d6b2fe5f54df9889112453b --- /dev/null +++ b/service/resource-directory/client/include/rd_client.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ****************************************************************************/ + +/** + @brief Resource Directory API of IoTivity-constrained for client. + @file +*/ + +#ifndef RD_CLIENT_H +#define RD_CLIENT_H + +#include "oc_client_state.h" +#include "oc_ri.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Resource Directory URI used to Discover RD and Publish resources.*/ +#define OC_RSRVD_RD_URI "/oic/rd" + +/** + @brief Publish RD resource to Resource Directory. + @param endpoint The endpoint of the RD. + @param links This is the resource which we need to register to RD. + If null, oic/p and oic/d resources will be published. + @param device_index Index of the device for an unique identifier. + @param handler To refer to the request sent out on behalf of calling this API. + @param qos Quality of service. + @param user_data The user data passed from the registration function. + @return Returns true if success. +*/ +bool rd_publish(oc_endpoint_t *endpoint, oc_link_t *links, size_t device_index, + oc_response_handler_t handler, oc_qos_t qos, void *user_data); + +/** + @brief Delete RD resource from Resource Directory. + @param endpoint The endpoint of the RD. + @param links This is the resource which we need to delete to RD. + @param device_index Index of the device for an unique identifier. + @param handler To refer to the request sent out on behalf of calling this API. + @param qos Quality of service. + @param user_data The user data passed from the registration function. + @return Returns true if success. +*/ +bool rd_delete(oc_endpoint_t *endpoint, oc_link_t *links, size_t device_index, + oc_response_handler_t handler, oc_qos_t qos, void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif /* RD_CLIENT_H */ diff --git a/service/resource-directory/client/src/rd_client.c b/service/resource-directory/client/src/rd_client.c new file mode 100644 index 0000000000000000000000000000000000000000..c88086eff687b69aca0d5c19b581dfd5e978c74f --- /dev/null +++ b/service/resource-directory/client/src/rd_client.c @@ -0,0 +1,148 @@ +/**************************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ****************************************************************************/ + +#include "rd_client.h" +#include "oc_api.h" +#include "oc_collection.h" +#include "oc_core_res.h" +#include "port/oc_log.h" +#include + +#define RD_PUBLISH_TTL 86400 + +static void _add_resource_payload(CborEncoder *parent, oc_resource_t *resource, + char *rel, char *ins) { + if (!parent || !resource) { + OC_ERR("Error of input parameters"); + return; + } + oc_rep_start_object(parent, links); + oc_rep_set_text_string(links, href, oc_string(resource->uri)); + oc_rep_set_string_array(links, rt, resource->types); + oc_core_encode_interfaces_mask(oc_rep_object(links), resource->interfaces); + if (rel) + oc_rep_set_text_string(links, rel, rel); + int ins_int = 0; + if (ins) + ins_int = atoi(ins); + oc_rep_set_int(links, ins, ins_int); + oc_rep_set_object(links, p); + oc_rep_set_uint(p, bm, + (uint8_t)(resource->properties & ~(OC_PERIODIC | OC_SECURE))); + oc_rep_close_object(links, p); + oc_rep_end_object(parent, links); +} + +static bool rd_publish_with_device_id(oc_endpoint_t *endpoint, oc_link_t *links, + const char *id, const char *name, + oc_response_handler_t handler, + oc_qos_t qos, void *user_data) { + if (!endpoint || !id || !links || !handler) { + OC_ERR("Error of input parameters"); + return false; + } + + if (oc_init_post(OC_RSRVD_RD_URI, endpoint, "rt=oic.wk.rdpub", handler, qos, + user_data)) { + oc_rep_start_root_object(); + oc_rep_set_text_string(root, di, id); + oc_rep_set_text_string(root, n, name); + oc_rep_set_int(root, lt, RD_PUBLISH_TTL); + + oc_rep_set_array(root, links); + oc_link_t *link = links; + while (link != NULL) { + _add_resource_payload(oc_rep_array(links), link->resource, + oc_string_array_get_item(link->rel, 0), + oc_string(link->ins)); + link = link->next; + } + oc_rep_close_array(root, links); + oc_rep_end_root_object(); + } else { + OC_ERR("Could not init POST request for rd publish"); + return false; + } + + return oc_do_post(); +} + +bool rd_publish(oc_endpoint_t *endpoint, oc_link_t *links, size_t device_index, + oc_response_handler_t handler, oc_qos_t qos, void *user_data) { + char uuid[OC_UUID_LEN] = {0}; + oc_device_info_t *device_info = oc_core_get_device_info(device_index); + if (!device_info) + return false; + oc_uuid_to_str(&device_info->di, uuid, OC_UUID_LEN); + + bool status = false; + if (!links) { + oc_link_t *link_p = + oc_new_link(oc_core_get_resource_by_index(OCF_P, device_index)); + oc_link_t *link_d = + oc_new_link(oc_core_get_resource_by_index(OCF_D, device_index)); + oc_list_add((oc_list_t)link_p, link_d); + + status = rd_publish_with_device_id(endpoint, link_p, uuid, + oc_string(device_info->name), handler, + qos, user_data); + oc_delete_link(link_p); + oc_delete_link(link_d); + } else { + status = rd_publish_with_device_id(endpoint, links, uuid, + oc_string(device_info->name), handler, + qos, user_data); + } + + return status; +} + +static bool rd_delete_with_device_id(oc_endpoint_t *endpoint, oc_link_t *links, + const char *id, + oc_response_handler_t handler, + oc_qos_t qos, void *user_data) { + if (!endpoint || !id || !handler) { + OC_ERR("Error of input parameters"); + return false; + } + + oc_string_t uri_query; + memset(&uri_query, 0, sizeof(oc_string_t)); + oc_concat_strings(&uri_query, "di=", id); + while (links) { + oc_concat_strings(&uri_query, "&ins=", oc_string(links->ins)); + } + + bool ret = oc_do_delete(OC_RSRVD_RD_URI, endpoint, oc_string(uri_query), + handler, qos, user_data); + oc_free_string(&uri_query); + return ret; +} + +bool rd_delete(oc_endpoint_t *endpoint, oc_link_t *links, size_t device_index, + oc_response_handler_t handler, oc_qos_t qos, void *user_data) { + char uuid[OC_UUID_LEN] = {0}; + oc_device_info_t *device_info = oc_core_get_device_info(device_index); + if (!device_info) + return false; + oc_uuid_to_str(&device_info->di, uuid, OC_UUID_LEN); + + return rd_delete_with_device_id(endpoint, links, uuid, handler, qos, + user_data); +} diff --git a/service/resource-directory/client/unittest/rd_client_test.cpp b/service/resource-directory/client/unittest/rd_client_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..30e1bc7f2aae66ef8970659553295d1ecfe665fb --- /dev/null +++ b/service/resource-directory/client/unittest/rd_client_test.cpp @@ -0,0 +1,111 @@ +/****************************************************************** + * + * Copyright 2019 Jozef Kralik All Rights Reserved. + * Copyright 2018 Samsung Electronics 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. + * + ******************************************************************/ + +#include + +#include "oc_api.h" +#include "oc_endpoint.h" +#include "rd_client.h" + +class TestRDClient: public testing::Test +{ + public: + static oc_handler_t s_handler; + static oc_endpoint_t s_endpoint; + + static void onPostResponse(oc_client_response_t *data) + { + (void) data; + } + + static int appInit(void) + { + int result = oc_init_platform("OCFCloud", NULL, NULL); + result |= oc_add_device("/oic/d", "oic.d.light", "Jaehong's Light", + "ocf.1.0.0", "ocf.res.1.0.0", NULL, NULL); + return result; + } + + static void signalEventLoop(void) + { + } + + protected: + static void SetUpTestCase() + { + s_handler.init = &appInit; + s_handler.signal_event_loop = &signalEventLoop; + int ret = oc_main_init(&s_handler); + ASSERT_EQ(0, ret); + + oc_string_t ep_str; + oc_new_string(&ep_str, "coap://224.0.1.187:5683", 23); + oc_string_to_endpoint(&ep_str, &s_endpoint, NULL); + oc_free_string(&ep_str); + } + + static void TearDownTestCase() + { + oc_main_shutdown(); + } +}; +oc_handler_t TestRDClient::s_handler; +oc_endpoint_t TestRDClient::s_endpoint; + +TEST_F(TestRDClient, rd_publish_p) +{ + // When + bool ret = rd_publish(&s_endpoint, NULL, 0, onPostResponse, LOW_QOS, NULL); + + // Then + EXPECT_TRUE(ret); +} + +TEST_F(TestRDClient, rd_publish_f) +{ + // Given + oc_endpoint_t *ep = NULL; + + // When + bool ret = rd_publish(ep, NULL, 0, NULL, LOW_QOS, NULL); + + // Then + EXPECT_FALSE(ret); +} + +TEST_F(TestRDClient, rd_delete_p) +{ + // When + bool ret = rd_delete(&s_endpoint, NULL, 0, onPostResponse, LOW_QOS, NULL); + + // Then + EXPECT_TRUE(ret); +} + +TEST_F(TestRDClient, rd_delete_f) +{ + // Given + oc_endpoint_t *ep = NULL; + + // When + bool ret = rd_delete(ep, NULL, 0, NULL, LOW_QOS, NULL); + + // Then + EXPECT_FALSE(ret); +}