From: Gaganpreet Kaur Date: Thu, 23 Feb 2017 04:19:48 +0000 (+0530) Subject: Initial commit of the Bridging Project's Mini Plugin Manager + 1 plugin. X-Git-Tag: 1.3.0~592 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=373e21b8105fc608971c4b51fa255f92f0f3c39a;p=platform%2Fupstream%2Fiotivity.git Initial commit of the Bridging Project's Mini Plugin Manager + 1 plugin. This commit is comprised of the Mini Plugin Manager framework along with 1 plugin and 1 stubbed plugin. This commit allows a user to test the full end-to-end story of the MPM. Change-Id: I3914963442cb175b438c3e3d8ff2d244f8eb339b Signed-off-by: Joseph Morrow Signed-off-by: vijendrx Signed-off-by: Mandeep Shetty Signed-off-by: skambalx Signed-off-by: Gaganpreet Kaur Reviewed-on: https://gerrit.iotivity.org/gerrit/16707 Tested-by: jenkins-iotivity Reviewed-by: George Nash Reviewed-by: Ossama Othman Reviewed-by: Todd Malsbary --- diff --git a/.gitignore b/.gitignore index c94f3b6..ccdaa84 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,7 @@ extlibs/bluez/bluez extlibs/mbedtls/mbedtls extlibs/raxmpp/raxmpp extlibs/yaml/yaml +extlibs/rapidjson/rapidjson # Ignore editor (e.g. Emacs) backup and autosave files *~ diff --git a/Readme.scons.txt b/Readme.scons.txt index c1a94a4..7deaf4a 100644 --- a/Readme.scons.txt +++ b/Readme.scons.txt @@ -8,7 +8,7 @@ $ sudo apt-get install git-core scons ssh build-essential g++ doxygen valgrind Install external libraries: - $ sudo apt-get install libboost-dev libboost-program-options-dev libboost-thread-dev uuid-dev libssl-dev libtool libglib2.0-dev + $ sudo apt-get install libboost-dev libboost-program-options-dev libboost-thread-dev uuid-dev libssl-dev libtool libglib2.0-dev libcap-dev libcurl4-openssl-dev autotools-dev autoconf Build release binaries: $ scons diff --git a/SConstruct b/SConstruct index 74b4589..38bf323 100644 --- a/SConstruct +++ b/SConstruct @@ -72,6 +72,9 @@ SConscript(build_dir + 'cloud/SConscript') # Build "plugin interface" sub-project SConscript(build_dir + 'plugins/SConscript') +# Build "bridging" sub-project +SConscript(build_dir + 'bridging/SConscript') + # Append targets information to the help information, to see help info, execute command line: # $ scon [options] -h env.PrintTargets() diff --git a/bridging/SConscript b/bridging/SConscript new file mode 100644 index 0000000..eac2c98 --- /dev/null +++ b/bridging/SConscript @@ -0,0 +1,49 @@ +#****************************************************************** +# +# Copyright 2017 Intel Mobile Communications GmbH 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. +# +#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +## +# Bridging build script +## + +import os.path + +Import('env') + +target_os = env.get('TARGET_OS') +build_sample = 'ON' +src_dir = env.get('SRC_DIR') + +# rapidjson fetch +SConscript(os.path.join(env.get('SRC_DIR'), 'extlibs', 'rapidjson', 'SConscript')) + +if target_os not in ['android', 'arduino', 'darwin', 'ios', 'tizen', 'msys_nt', 'windows']: + + SConscript(os.path.join('common', 'SConscript')) + + SConscript(os.path.join('mini_plugin_manager', 'SConscript')) + + SConscript(os.path.join('mpm_client', 'SConscript')) + + SConscript(os.path.join('plugins', 'lifx_plugin', 'SConscript')) + +# SConscript(os.path.join('plugins', 'hue_plugin', 'SConscript')) + +# SConscript(os.path.join('plugins', 'nest_plugin', 'SConscript')) + +# SConscript(os.path.join('plugins', 'lyric_plugin', 'SConscript')) diff --git a/bridging/common/ConcurrentIotivityUtils.cpp b/bridging/common/ConcurrentIotivityUtils.cpp new file mode 100644 index 0000000..0c77e4b --- /dev/null +++ b/bridging/common/ConcurrentIotivityUtils.cpp @@ -0,0 +1,193 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 "octypes.h" +#include "ConcurrentIotivityUtils.h" +#include "ocpayload.h" +#include "logger.h" + +#define TAG "CONCURRENT_IOTIVITY_UTILS" + +using namespace OC::Bridging; + +// Static member initializations. +std::unique_ptr>> ConcurrentIotivityUtils::m_queue; + +void ConcurrentIotivityUtils::startWorkerThreads() +{ + if (m_threadStarted) + { + throw "Work Queue Processor already started"; + } + m_processWorkQueueThread = std::thread(&ConcurrentIotivityUtils::processWorkQueue, this); + m_ocProcessThread = std::thread(&ConcurrentIotivityUtils::callOCProcess, this); + m_threadStarted = true; +} + +void ConcurrentIotivityUtils::stopWorkerThreads() +{ + m_shutDownOCProcessThread = true; + m_queue->shutdown(); + m_processWorkQueueThread.join(); + m_ocProcessThread.join(); + m_threadStarted = false; +} + +OCStackResult ConcurrentIotivityUtils::queueCreateResource(const std::string &uri, + const std::string &resourceType, + const std::string &interface, OCEntityHandler entityHandler, + void *callbackParam, uint8_t resourceProperties) +{ + std::unique_ptr item = make_unique( + uri, resourceType, interface, entityHandler, callbackParam, resourceProperties + ); + + m_queue->put(std::move(item)); + + return OC_STACK_OK; +} + +OCStackResult ConcurrentIotivityUtils::respondToRequest(OCEntityHandlerRequest *request, + OCRepPayload *payload, OCEntityHandlerResult responseCode) +{ + std::unique_ptr response = make_unique(); + + response->requestHandle = request->requestHandle; + response->resourceHandle = request->resource; + response->ehResult = responseCode; + + // Clone a copy since this allocation is going across thread boundaries. + response->payload = (OCPayload *) OCRepPayloadClone(payload); + + if (payload != NULL && response->payload == NULL) + { + return OC_STACK_NO_MEMORY; + } + + std::unique_ptr item = make_unique(std::move(response)); + m_queue->put(std::move(item)); + + return OC_STACK_OK; +} + +OCStackResult ConcurrentIotivityUtils::respondToRequestWithError(OCEntityHandlerRequest *request, + const std::string &errorMessage, + OCEntityHandlerResult errorCode) +{ + OCRepPayload *errorPayload = NULL; + + if (!errorMessage.empty()) + { + errorPayload = OCRepPayloadCreate(); + + if (!errorPayload) + { + return OC_STACK_NO_MEMORY; + } + + OCRepPayloadSetPropString(errorPayload, "x.org.iotivity.error", errorMessage.c_str()); + } + + OCStackResult res = respondToRequest(request, errorPayload, errorCode); + + if (errorPayload) + { + OCRepPayloadDestroy(errorPayload); + } + + return res; +} + +OCStackResult ConcurrentIotivityUtils::queueNotifyObservers(const std::string &resourceUri) +{ + std::unique_ptr item = make_unique(resourceUri); + m_queue->put(std::move(item)); + return OC_STACK_OK; +} + +OCStackResult ConcurrentIotivityUtils::queueDeleteResource(const std::string &uri) +{ + std::unique_ptr item = make_unique(uri); + m_queue->put(std::move(item)); + return OC_STACK_OK; +} + +bool ConcurrentIotivityUtils::getUriFromHandle(OCResourceHandle handle, std::string &uri) +{ + const char *uri_c = OCGetResourceUri(handle); + + if (uri_c == NULL) + { + return false; + } + + uri = uri_c; + return true; +} + +void ConcurrentIotivityUtils::getKeyValueParams(const std::string &query, + std::map &keyValueMap) +{ + if (query.empty()) + { + return; + } + + std::stringstream ss(query); + + std::string keyValuePair; + + while (std::getline(ss, keyValuePair, '&')) + { + auto keyValueSeparator = keyValuePair.find('='); + + if (keyValueSeparator == std::string::npos) + { + continue; + } + + std::string key = keyValuePair.substr(0, keyValueSeparator); + std::string value = keyValuePair.substr(keyValueSeparator + 1); + + keyValueMap[key] = value; + } +} + +bool ConcurrentIotivityUtils::isRequestForDefaultInterface(const std::string &query) +{ + if (query.empty()) + { + return false; + } + std::map keyValueParams; + + getKeyValueParams(query, keyValueParams); + + auto it = keyValueParams.find(OC_RSRVD_INTERFACE); + + if (it == keyValueParams.end()) + { + return false; + } + + return it->second == OC_RSRVD_INTERFACE_DEFAULT; +} diff --git a/bridging/common/SConscript b/bridging/common/SConscript new file mode 100644 index 0000000..061b721 --- /dev/null +++ b/bridging/common/SConscript @@ -0,0 +1,87 @@ +#****************************************************************** +# +# Copyright 2017 Intel Mobile Communications GmbH 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. +# +#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +## +# MPM Common Plugin build script +## + +import os +import os.path + +Import('env') + +target_os = env.get('TARGET_OS') +src_dir = env.get('SRC_DIR') +bridging_path = os.path.join(src_dir, 'bridging') + +mpmcommon_env = env.Clone() + +print "Reading MPM Common Plugin script" + +def maskFlags(flags): + flags = [flags.replace('-Wl,--no-undefined', '' ) for flags in flags] + return flags + +###################################################################### +# Build flags +###################################################################### +mpmcommon_env.PrependUnique(CPPPATH = [ + os.path.join(src_dir, 'resource', 'include'), + ]) +mpmcommon_env.AppendUnique(CPPPATH = [ os.path.join(bridging_path, 'include'), + ]) + +if target_os not in ['arduino', 'windows']: + mpmcommon_env.AppendUnique(CPPDEFINES = ['WITH_POSIX']) + mpmcommon_env.AppendUnique(CXXFLAGS = ['-std=c++0x', '-Wall', '-Wextra', '-Werror', '-fpic']) + +if target_os in ['darwin','ios']: + mpmcommon_env.AppendUnique(CPPDEFINES = ['_DARWIN_C_SOURCE']) + +mpmcommon_env.AppendUnique(RPATH = [mpmcommon_env.get('BUILD_DIR')]) +mpmcommon_env.AppendUnique(LIBPATH = [mpmcommon_env.get('BUILD_DIR')]) +mpmcommon_env['LINKFLAGS'] = maskFlags(env['LINKFLAGS']) + +if mpmcommon_env.get('LOGGING'): + mpmcommon_env.AppendUnique(CPPDEFINES = ['TB_LOG']) + +mpmcommon_env.PrependUnique(LIBS = ['m', + 'octbstack', + 'ocsrm', + 'connectivity_abstraction', + 'coap', + 'curl' ]) + +##################################################################### +# Source files and Target(s) +###################################################################### +mpmcommon_src = [ + os.path.join(bridging_path, 'common', 'pluginIf.cpp'), + os.path.join(bridging_path, 'common', 'pluginServer.cpp'), + os.path.join(bridging_path, 'common', 'pipeHandler.cpp'), + os.path.join(bridging_path, 'common', 'messageHandler.cpp'), + os.path.join(bridging_path, 'common', 'curlClient.cpp'), + os.path.join(bridging_path, 'common', 'pluginProcess.cpp'), + os.path.join(bridging_path, 'common', 'ConcurrentIotivityUtils.cpp') + ] + +mpmcommon_env.AppendUnique(MPMCOMMON_SRC = mpmcommon_src) +mpmcommonlib = mpmcommon_env.StaticLibrary('mpmcommon', mpmcommon_env.get('MPMCOMMON_SRC')) +mpmcommon_env.InstallTarget(mpmcommonlib, 'mpmcommon') +mpmcommon_env.UserInstallTargetLib(mpmcommonlib, 'mpmcommon') diff --git a/bridging/common/curlClient.cpp b/bridging/common/curlClient.cpp new file mode 100644 index 0000000..658619b --- /dev/null +++ b/bridging/common/curlClient.cpp @@ -0,0 +1,184 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 "curlClient.h" +#include +#include "logger.h" + +using namespace std; +using namespace OC::Bridging; + +#define TAG "CURL_CLIENT" + +#define DEFAULT_CURL_TIMEOUT_SECONDS 60L + + +size_t CurlClient::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t realsize = size * nmemb; + MemoryChunk *mem = static_cast(userp); + + mem->memory = static_cast(realloc(mem->memory, mem->size + realsize + 1)); + if (mem->memory == NULL) + { + OIC_LOG(ERROR, TAG, "not enough memory!"); + return 0; + } + + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +int CurlClient::decomposeHeader(const char *header, std::vector &headers) +{ + size_t npos = 0; + if (NULL == header) + { + return MPM_RESULT_INVALID_PARAMETER; + } + + std::string header_s = header; + + npos = header_s.find("\r\n"); + while (npos != std::string::npos) + { + std::string s = header_s.substr(0, npos); + headers.push_back(s); + header_s = header_s.substr(npos + 2); + npos = header_s.find("\r\n"); + } + + return MPM_RESULT_OK; +} + +int CurlClient::doInternalRequest(const std::string &url, + const std::string &method, + const std::vector &inHeaders, + const std::string &request, + const std::string &username, + std::vector &outHeaders, + std::string &response) +{ + int result = MPM_RESULT_OK; + CURL *curl = NULL; + CURLcode res = CURLE_OK; + struct curl_slist *headers = NULL; + MemoryChunk rsp_body; + MemoryChunk rsp_header; + m_lastResponseCode = INVALID_RESPONSE_CODE; //initialize recorded code value in case of + //early return + + curl = curl_easy_init(); + if (curl != NULL) + { + curl_easy_reset(curl); + + for (unsigned int i = 0; i < inHeaders.size(); i++) + { + headers = curl_slist_append(headers, inHeaders[i].c_str()); + if (NULL == headers) + { + OIC_LOG(ERROR, TAG, "curl_slist_append failed"); + result = MPM_RESULT_OUT_OF_MEMORY; + goto CLEANUP; + } + } + + // Expect the transfer to complete within DEFAULT_CURL_TIMEOUT seconds + curl_easy_setopt(curl, CURLOPT_TIMEOUT, DEFAULT_CURL_TIMEOUT_SECONDS); + + // Set CURLOPT_VERBOSE to 1L below to see detailed debugging + // information on curl operations. + curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp_body); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &rsp_header); + if (CURLUSESSL_NONE != m_useSsl) + { + curl_easy_setopt(curl, CURLOPT_USE_SSL, m_useSsl); + } + + if (!username.empty()) + { + curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str()); + } + + if (!method.empty()) + { + // NOTE: The documentation for CURLOPT_CUSTOMREQUEST only lists HTTP, FTP, IMAP, POP3, and SMTP + // as valid options, although it says all this option does is change the string used in + // the request. (Basically, don't know whether this option has any effect as currently + // used? + + /// only required for GET, PUT, DELETE + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.c_str()); + } + + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + OIC_LOG_V(ERROR, TAG, "curl_easy_perform failed with %lu", (unsigned long) res); + result = MPM_RESULT_NETWORK_ERROR; + goto CLEANUP; + } + + if (CURLE_OK != curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &m_lastResponseCode)) + { + OIC_LOG(WARNING, TAG, "curl_easy_getinfo(CURLINFO_RESPONSE_CODE) failed."); + m_lastResponseCode = INVALID_RESPONSE_CODE; + } + + response = rsp_body.memory; + + decomposeHeader(rsp_header.memory, outHeaders); + } + else + { + OIC_LOG(ERROR, TAG, "curl_easy_init failed"); + result = MPM_RESULT_INTERNAL_ERROR; + } + +CLEANUP: + if (NULL != headers) + { + curl_slist_free_all(headers); + } + + free(rsp_body.memory); + + free(rsp_header.memory); + + if (NULL != curl) + { + curl_easy_cleanup(curl); + } + + return result; +} diff --git a/bridging/common/messageHandler.cpp b/bridging/common/messageHandler.cpp new file mode 100644 index 0000000..acf0811 --- /dev/null +++ b/bridging/common/messageHandler.cpp @@ -0,0 +1,358 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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. +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// + +/* This file contains the plugin server implementation. Most of this + * implementation is reused for other plugins. There is NO customization + * required of functions in this file to accommodate plugin specific code. + */ + + +#include "iotivity_config.h" +#ifdef HAVE_UNISTD_H +#include +#endif +#include "oic_malloc.h" +#include "pluginIf.h" +#include "pluginServer.h" +#include "cbor.h" +#include "logger.h" +#include "StringConstants.h" + +#define TAG "MESSAGE_HANDLER" + +#define NAME "NAME" +#define MANUFACTURER "MF" +#define DEVICETYPE "DEVICE_TYPE" +#define PLUGINSPECIFICDETAILS "PluginSpecificDetails" +#define RESOURCES "RESOURCES" + +#define VERIFY_CBOR_SUCCESS(log_tag, err, log_message) \ + if ((CborNoError != (err)) && (CborErrorOutOfMemory != (err))) \ + { \ + if ((log_tag) && (log_message)) \ + { \ + OIC_LOG_V(ERROR, TAG, "cbor error - %s \n", log_message); \ + } \ + } \ + +MPMCommonPluginCtx *g_com_ctx; + +void MPMRequestHandler(MPMPipeMessage *pipe_message, MPMPluginCtx *ctx) +{ + OIC_LOG(DEBUG, TAG, "Inside request_handler"); + + switch (pipe_message->msgType) + { + case MPM_SCAN: + OIC_LOG(DEBUG, TAG, "plugin_scan called"); + pluginScan(ctx, pipe_message); + break; + case MPM_ADD: + OIC_LOG(DEBUG, TAG, "plugin_add called"); + pluginAdd(ctx, pipe_message); + break; + case MPM_REMOVE: + OIC_LOG(DEBUG, TAG, "plugin_remove called"); + pluginRemove(ctx, pipe_message); + break; + case MPM_RECONNECT: + OIC_LOG(DEBUG, TAG, "Plugin reconnect called"); + pluginReconnect(ctx, pipe_message); + break; + default:OIC_LOG(DEBUG, TAG, "Currently not supported"); + break; + } +} + +MPMResult MPMSendResponse(const void *response, size_t size, MPMMessageType type) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + + OIC_LOG_V(DEBUG, TAG, "Inside response_handler and size of payload = %d", (int)size); + + MPMPipeMessage pipe_message; + + pipe_message.payloadSize = size; + pipe_message.msgType = type; + pipe_message.payload = (uint8_t *)response; + + result = MPMWritePipeMessage(g_com_ctx->parent_reads_fds.write_fd, &pipe_message); + + return result; +} + +static int64_t AddTextStringToMap(CborEncoder *map, const char *key, size_t keylen, + const char *value) +{ + int64_t err = cbor_encode_text_string(map, key, keylen); + if (CborNoError != err) + { + return err; + } + return cbor_encode_text_string(map, value, strlen(value)); +} + +static int64_t AddstructureToMap(CborEncoder *map, const char *key, size_t keylen, + const char *value, size_t valueSize) +{ + int64_t err = cbor_encode_text_string(map, key, keylen); + if (CborNoError != err) + { + return err; + } + return cbor_encode_text_string(map, value, valueSize); +} + +int64_t MPMFormMetaData(MPMResourceList *list, MPMDeviceSpecificData *deviceDetails, + uint8_t *buff, size_t size, void *details, size_t payloadSize) +{ + + CborEncoder encoder; + int64_t err = CborNoError; + CborEncoder rootArray, rootMap, linkMap; + CborEncoder linkArray; + MPMResourceList *temp = NULL; + + cbor_encoder_init(&encoder, buff, size, 0); + + err = cbor_encoder_create_array(&encoder, &rootArray, 1); + VERIFY_CBOR_SUCCESS(TAG, err, " Creating Root Array"); + + err = cbor_encoder_create_map(&rootArray, &rootMap, CborIndefiniteLength); + VERIFY_CBOR_SUCCESS(TAG, err, "Creating Root MAP"); + + if (deviceDetails) + { + err = AddTextStringToMap(&rootMap, NAME, sizeof(NAME) - 1, + deviceDetails->devName); + VERIFY_CBOR_SUCCESS(TAG, err, "Adding device name"); + + err = AddTextStringToMap(&rootMap, MANUFACTURER, sizeof(MANUFACTURER) - 1, + deviceDetails->manufacturerName); + VERIFY_CBOR_SUCCESS(TAG, err, "Adding Manufacture name"); + + err = AddTextStringToMap(&rootMap, DEVICETYPE, sizeof(DEVICETYPE) - 1, + deviceDetails->devType); + VERIFY_CBOR_SUCCESS(TAG, err, "Adding Device Type"); + } + + if (details) + { + err = AddstructureToMap(&rootMap, PLUGINSPECIFICDETAILS, sizeof(PLUGINSPECIFICDETAILS) - 1, + (const char *)details, payloadSize); + VERIFY_CBOR_SUCCESS(TAG, err, "Adding Plugin specific Details"); + } + + err = cbor_encode_text_string(&rootMap, RESOURCES, sizeof(RESOURCES) - 1); + VERIFY_CBOR_SUCCESS(TAG, err, "Encoding Resources string"); + + err = cbor_encoder_create_array(&rootMap, &linkArray, CborIndefiniteLength); + VERIFY_CBOR_SUCCESS(TAG, err, "Creating Link Array"); + + for ( ; list ; ) + { + temp = list; + OIC_LOG_V(DEBUG, TAG, " href - %s\n rt - %s\n if - %s\n bm - %d\n", list->href, list->rt, + list->interfaces, list->bitmap); + // resource map inside the links array. + err = cbor_encoder_create_map(&linkArray, &linkMap, 4); + VERIFY_CBOR_SUCCESS(TAG, err, "Creating Link Map"); + + err = AddTextStringToMap(&linkMap, OC::Key::RESOURCETYPESKEY.c_str(), + OC::Key::RESOURCETYPESKEY.size(), + list->rt); + VERIFY_CBOR_SUCCESS(TAG, err, "Adding Resource type"); + + err = AddTextStringToMap(&linkMap, OC::Key::URIKEY.c_str(), OC::Key::URIKEY.size(), + list->href); + VERIFY_CBOR_SUCCESS(TAG, err, "Adding OC::Key::URIKEY"); + + err = AddTextStringToMap(&linkMap, OC::Key::INTERFACESKEY.c_str(), + OC::Key::INTERFACESKEY.size(), list->interfaces); + VERIFY_CBOR_SUCCESS(TAG, err, "Adding Resource Interface"); + + err = cbor_encode_text_string(&linkMap, OC::Key::BMKEY.c_str(), OC::Key::BMKEY.size()); + VERIFY_CBOR_SUCCESS(TAG, err, "Encoding Bitmap string"); + + err = cbor_encode_int(&linkMap, list->bitmap); + VERIFY_CBOR_SUCCESS(TAG, err, "encoding bit map"); + + // close link map inside link array + err = cbor_encoder_close_container(&linkArray, &linkMap); + VERIFY_CBOR_SUCCESS(TAG, err, "Closing link map"); + + list = list -> next; + OICFree(temp); + } + + // Close links array inside the root map. + err = cbor_encoder_close_container(&rootMap, &linkArray); + VERIFY_CBOR_SUCCESS(TAG, err, "Closing link array"); + + // close root map inside the root array. + err = cbor_encoder_close_container(&rootArray, &rootMap); + VERIFY_CBOR_SUCCESS(TAG, err, "Closing Root Map"); + + // Close the final root array. + err = cbor_encoder_close_container(&encoder, &rootArray); + VERIFY_CBOR_SUCCESS(TAG, err, "Closing root Array"); + + return err; +} + +void MPMParseMetaData(const uint8_t *buff, size_t size, MPMResourceList **list, void **details) +{ + int64_t err = CborNoError; + CborValue rootMapValue, linkMapValue; + CborValue resourceMapValue; + CborValue curVal; + CborValue rootValue; + CborParser parser; + int bitmap; + + err = cbor_parser_init(buff, size, 0, &parser, &rootValue); + VERIFY_CBOR_SUCCESS(TAG, err, "Parser cbor init"); + + if (cbor_value_is_array(&rootValue)) + { + OIC_LOG_V(DEBUG, TAG, "ENCODED DATA - %s ", (char *)buff); + err = cbor_value_enter_container(&rootValue, &rootMapValue); + VERIFY_CBOR_SUCCESS(TAG, err, "Entering root array"); + if (!cbor_value_is_map(&rootMapValue)) + { + OIC_LOG(ERROR, TAG, "ERROR, Malformed packet"); + return ; + } + + if (cbor_value_is_map(&rootMapValue)) + { + // Parsing device details + err = cbor_value_map_find_value(&rootMapValue, NAME, &curVal); + VERIFY_CBOR_SUCCESS(TAG, err, "finding Name in map"); + if (cbor_value_is_valid(&curVal)) + { + if (cbor_value_is_text_string(&curVal)) + { + size_t len = 0; + char *input = NULL; + err = cbor_value_dup_text_string(&curVal, &input, &len, NULL); + VERIFY_CBOR_SUCCESS(TAG, err, "Duplicating name string"); + OIC_LOG_V(DEBUG, TAG, "\"NAME\":%s\n", input); + free(input); + } + } + } + + err = cbor_value_map_find_value(&rootMapValue, MANUFACTURER, &curVal); + VERIFY_CBOR_SUCCESS(TAG, err, "Finding Manufacturer details in map"); + if (cbor_value_is_valid(&curVal)) + { + if (cbor_value_is_text_string(&curVal)) + { + size_t len = 0; + char *input = NULL; + err = cbor_value_dup_text_string(&curVal, &input, &len, NULL); + VERIFY_CBOR_SUCCESS(TAG, err, " Copying Text string"); + OIC_LOG_V(DEBUG, TAG, "\"MF\":%s\n", input); + free(input); + } + } + + err = cbor_value_map_find_value(&rootMapValue, PLUGINSPECIFICDETAILS, &curVal); + VERIFY_CBOR_SUCCESS(TAG, err, " Finding PLUGINSPECIFICDETAILS in map "); + if (cbor_value_is_valid(&curVal)) + { + if (cbor_value_is_text_string(&curVal)) + { + size_t len = 0; + char *input = NULL; + err = cbor_value_dup_text_string(&curVal, &input, &len, NULL); + VERIFY_CBOR_SUCCESS(TAG, err, " Copying Text string"); + *details = (void *)input; + } + } + + err = cbor_value_map_find_value(&rootMapValue, RESOURCES, &linkMapValue); + VERIFY_CBOR_SUCCESS(TAG, err, " Finding RESOURCES in map "); + // Enter the links array and start iterating through the array processing + // each resource which shows up as a map. + if (cbor_value_is_valid(&linkMapValue)) + { + + err = cbor_value_enter_container(&linkMapValue, &resourceMapValue); + VERIFY_CBOR_SUCCESS(TAG, err, " Entering Link map "); + while (cbor_value_is_map(&resourceMapValue)) + { + MPMResourceList *tempPtr; + tempPtr = (MPMResourceList *) OICCalloc(1, sizeof(MPMResourceList)); + if (tempPtr == NULL) + { + OIC_LOG(ERROR, TAG, "calloc failed"); + return; + } + size_t len = 0; + char *input = NULL; + err = cbor_value_map_find_value(&resourceMapValue, OC::Key::URIKEY.c_str(), &curVal); + VERIFY_CBOR_SUCCESS(TAG, err, " Finding Uri in map "); + + err = cbor_value_dup_text_string(&curVal, &input, &len, NULL); + VERIFY_CBOR_SUCCESS(TAG, err, " Copying Text string"); + strncpy(tempPtr->href, input, MPM_MAX_LENGTH_64); + OIC_LOG_V(DEBUG, TAG, "\"ref\":%s\n", input); + free(input); + input = NULL; + + err = cbor_value_map_find_value(&resourceMapValue, OC::Key::RESOURCETYPESKEY.c_str(), &curVal); + VERIFY_CBOR_SUCCESS(TAG, err, " Finding Rt in link map "); + err = cbor_value_dup_text_string(&curVal, &input, &len, NULL); + VERIFY_CBOR_SUCCESS(TAG, err, " Copying Text string"); + strncpy(tempPtr->rt, input, MPM_MAX_LENGTH_64); + OIC_LOG_V(DEBUG, TAG, "\"rt\":%s\n", input); + free(input); + input = NULL; + + err = cbor_value_map_find_value(&resourceMapValue, OC::Key::INTERFACESKEY.c_str(), &curVal); + VERIFY_CBOR_SUCCESS(TAG, err, " Finding If's in link map "); + err = cbor_value_dup_text_string(&curVal, &input, &len, NULL); + VERIFY_CBOR_SUCCESS(TAG, err, " Copying Text string"); + strncpy(tempPtr->interfaces, input, MPM_MAX_LENGTH_64); + OIC_LOG_V(DEBUG, TAG, "\"if\":%s\n", input); + free(input); + input = NULL; + + err = cbor_value_map_find_value(&resourceMapValue, OC::Key::BMKEY.c_str(), &curVal); + VERIFY_CBOR_SUCCESS(TAG, err, " Finding Bms in link map "); + if (cbor_value_is_integer(&curVal)) + { + err = cbor_value_get_int(&curVal, &bitmap); + VERIFY_CBOR_SUCCESS(TAG, err, " Getting bit map value fromx link map "); + tempPtr->bitmap = bitmap; + OIC_LOG_V(DEBUG, TAG, "\"bm\":%d\n", bitmap); + } + + tempPtr->next = *list; + *list = tempPtr; + err = cbor_value_advance(&resourceMapValue); + VERIFY_CBOR_SUCCESS(TAG, err, "in resource map value advance"); + } + } + } +} diff --git a/bridging/common/pipeHandler.cpp b/bridging/common/pipeHandler.cpp new file mode 100644 index 0000000..eb97286 --- /dev/null +++ b/bridging/common/pipeHandler.cpp @@ -0,0 +1,132 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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. +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// + +/** + * This file is mainly intended for writing and reading the messages over the pipe, + * This is the common code functionality where either client or MPM can call these + * functions to write to the pipe and read from the pipe. + */ + +#include +#include +#include "messageHandler.h" +#include "iotivity_config.h" +#ifdef HAVE_UNISTD_H +#include +#endif +#include "platform_features.h" +#include "oic_malloc.h" +#include "logger.h" +#include + +#define TAG "PIPE_HANDLER" + +MPMResult MPMWritePipeMessage(int fd, const MPMPipeMessage *pipe_message) +{ + ssize_t ret = 0; + OIC_LOG(DEBUG, TAG, "writing message over pipe"); + + OIC_LOG_V(DEBUG, TAG, "Message type = %d, payload size = %" PRIuPTR, pipe_message->msgType, + pipe_message->payloadSize); + + ret = write(fd, &pipe_message->payloadSize, sizeof(size_t)); + if (ret < 0) + { + OIC_LOG_V(ERROR, TAG, "Error writing message over the pipe - [%s]", strerror(errno)); + return MPM_RESULT_INTERNAL_ERROR; + } + + ret = write(fd, &pipe_message->msgType, sizeof(MPMMessageType)); + if (ret < 0) + { + OIC_LOG_V(ERROR, TAG, "Error writing message over the pipe - [%s]", strerror(errno)); + return MPM_RESULT_INTERNAL_ERROR; + } + + if (pipe_message->payloadSize > 0) + { + ret = write(fd, pipe_message->payload, pipe_message->payloadSize); + + if (ret < 0) + { + OIC_LOG_V(ERROR, TAG, "Error writing message over the pipe - [%s]", strerror(errno)); + return MPM_RESULT_INTERNAL_ERROR; + } + } + + return MPM_RESULT_OK; +} + + +ssize_t MPMReadPipeMessage(int fd, MPMPipeMessage *pipe_message) +{ + ssize_t ret = 0, bytesRead =0; + OIC_LOG(DEBUG, TAG, "reading message from pipe"); + OIC_LOG_V(DEBUG, TAG, "Message type = %d, payload size = %" PRIuPTR , pipe_message->msgType, + pipe_message->payloadSize); + + ret = read(fd, &pipe_message->payloadSize, sizeof(size_t)); + if (ret < 0) + { + OIC_LOG_V(ERROR, TAG, "Error Reading message from the pipe - [%s]", strerror(errno)); + return ret; + } + bytesRead = ret; + + ret = read(fd, &pipe_message->msgType, sizeof(MPMMessageType)); + if (ret < 0) + { + OIC_LOG_V(ERROR, TAG, "Error Reading message from the pipe - [%s]", strerror(errno)); + return ret; + } + bytesRead += ret; + + + if (pipe_message->msgType == MPM_NOMSG) + { + bytesRead = 0; + } + else if (pipe_message->payloadSize > 0 && pipe_message->payloadSize <= SIZE_MAX) + { + pipe_message->payload = (uint8_t *) OICCalloc(1, pipe_message->payloadSize); + if (!pipe_message->payload) + { + OIC_LOG(ERROR, TAG, "failed to allocate memory"); + bytesRead = 0; + } + else + { + ret = read(fd, (void*)pipe_message->payload, pipe_message->payloadSize); + if (ret < 0) + { + OIC_LOG_V(ERROR, TAG, "Error Reading message from the pipe - [%s]", strerror(errno)); + return ret; + } + bytesRead += ret; + } + } + else + { + pipe_message->payload = NULL; + OIC_LOG(DEBUG, TAG, "no payload received"); + } + return bytesRead; +} diff --git a/bridging/common/pluginIf.cpp b/bridging/common/pluginIf.cpp new file mode 100644 index 0000000..e4b5353 --- /dev/null +++ b/bridging/common/pluginIf.cpp @@ -0,0 +1,378 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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. +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// + +/* This file contains the "C" plugin interface implementation */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "messageHandler.h" +#include "logger.h" +#include "oic_malloc.h" +#include "mpmErrorCode.h" +#include "pluginIf.h" +#include "pluginServer.h" + +#define TAG "PLUGIN_IF" + +/* this function waits for a child process to signal that it is complete + * @param[in] child_pid child process id + * @param[in] timeout time to wait for child to complete + */ +static void waitForChildProcessToComplete(pid_t child_pid, int32_t timeout); + + +/* This is a timed wait for pipe write; the written value is returned in the passed + * in message buffer. + * @param[in] fd_to_parent File descriptor + * @param[out] message Message from the child + * @param[in] timeout Time to wait for pipe write in seconds + */ +static void timedWaitForPipeWrite(int fd_to_parent, MPMPipeMessage *message, + int32_t timeout); + + +/** + * The purpose of this function is to create a context or instance of the + * plugin. There is nothing stopping one from creating more than one + * instance of the plugin. Currently it is expected that only one instance + * is created. + * + * @return A pointer to the context data of this plugin that is managed + * by this plugin. + */ +static MPMCommonPluginCtx *create() +{ + /* always try to create a new instance */ + MPMCommonPluginCtx *ctx = (MPMCommonPluginCtx *) OICCalloc(1, sizeof(MPMCommonPluginCtx)); + + if (ctx == NULL) + { + OIC_LOG(ERROR, TAG, "Unable to allocate context."); + } + return ctx; +} + +/** + * This function forks a new process and starts an OCF server in that + * process. The plan is to only allow one fork per instance of + * the plugin. There is a check for this, the start will fail if you + * call start twice on the same instance. + * + * @param[in] ctx the plugin context created with the "create" function + * + * @return 0 on success, 1 on error + */ +static int start(MPMCommonPluginCtx *ctx) +{ + int result = 1; + + if (ctx && !ctx->started) + { + pid_t pid; + MPMPipeMessage pipe_message; + + pipe_message.payloadSize = 0; + pipe_message.msgType = MPM_NOMSG; + pipe_message.payload = NULL; + + /* Create unnamed pipes for IPC prior to the fork so that both + * parent and child have the same information on the unnamed + * pipes. + */ + int parent_result = pipe(&(ctx->parent_reads_fds.read_fd)); + if (parent_result == -1) + { + OIC_LOG(ERROR, TAG, "Failed to create IPC unnamed pipe for parent."); + return result; + } + int child_result = pipe(&(ctx->child_reads_fds.read_fd)); + + if (child_result == -1) + { + OIC_LOG(ERROR, TAG, "Failed to create IPC unnamed pipe for child."); + close(ctx->parent_reads_fds.read_fd); + close(ctx->parent_reads_fds.write_fd); + return result; + } + + switch (pid = fork()) + { + case 0: + /* Child(plugin) process. + * Close unused edges of unnamed pipes from the + * child's perspective. Child is not going + * write to the child's read pipe nor is the child + * going to read from the parent's read pipe + */ + close(ctx->child_reads_fds.write_fd); + close(ctx->parent_reads_fds.read_fd); + + /* Start the OCF server. This is a blocking call and will + return only when the plugin stops*/ + MPMPluginService(ctx); + + OIC_LOG(INFO, TAG, "Child process complete."); + + /* Close the other sides of the pipes from + * the child's perspective + */ + close(ctx->child_reads_fds.read_fd); + close(ctx->parent_reads_fds.write_fd); + + exit(0); + break; + + default: + + /* Parent process */ + ctx->child_pid = pid; + + /* The parent is not going to read + * from the child's read pipe and the parent is not + * going to write the parents read pipe. + */ + close(ctx->child_reads_fds.read_fd); + close(ctx->parent_reads_fds.write_fd); + + /* The plugin may fail to create or start. + * The parent must wait here for some time to + * learn what happened. + */ + timedWaitForPipeWrite(ctx->parent_reads_fds.read_fd, + &pipe_message, + MPM_TIMEOUT_VAL_IN_SEC); + if (pipe_message.msgType == MPM_DONE) + { + /* plugin was successful with its create and start */ + OIC_LOG_V(INFO, TAG, "Child: %d started successfully", ctx->child_pid); + result = 0; + ctx->started = true; + } + else + { + /* We have a problem, and the plugin is NOT going to load. + */ + OIC_LOG_V(ERROR, TAG, "Child: %d failed, either plugin create or start.", + ctx->child_pid); + + OIC_LOG(ERROR, TAG, "Forced completion of the child"); + + /* The parent must wait here until the child process is dead. + * but this time we are not going to wait, we are only going + * to force completion + */ + waitForChildProcessToComplete(ctx->child_pid, 0); + + /* Let's close the rest of the pipe interfaces. Sides of the pipes + * from the parent's perspective + */ + close(ctx->child_reads_fds.write_fd); + close(ctx->parent_reads_fds.read_fd); + } + + OICFree((void*)pipe_message.payload); + + break; + + case -1: + perror("fork"); + OIC_LOG(ERROR, TAG, "Fork returned error."); + break; + } + } + else + { + OIC_LOG(ERROR, TAG, "Plugin instance already forked or has not been created."); + } + return (result); +} + +/** + * This function writes a stop message over the pipe to indicate the child + * to stop + * + * @param[in] ctx the plugin context created with the "create" function + */ +static void stop(MPMCommonPluginCtx *ctx) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + + if (ctx && ctx->started) + { + /* IPC message variable */ + MPMPipeMessage pipe_message; + + pipe_message.payloadSize = 0; + pipe_message.msgType = MPM_STOP; + pipe_message.payload = NULL; + + result = MPMWritePipeMessage(ctx->child_reads_fds.write_fd, &pipe_message); + if (result != MPM_RESULT_OK) + { + OIC_LOG(ERROR, TAG, "Failed to write to pipe for stop"); + return; + } + OIC_LOG_V(INFO, TAG, "Parent telling the child process pid: %d to stop.", ctx->child_pid); + + /* the parent must wait here until the child process is dead */ + waitForChildProcessToComplete(ctx->child_pid, MPM_TIMEOUT_VAL_IN_SEC); + + ctx->started = false; + } + else + { + OIC_LOG(INFO, TAG, "Stop has no effect; Plugin has not been started"); + } + return; +} + +/** + * This function calls the "stop" function follwed by destruction of the + * context created with the "create" function + * + * @param[in] ctx the plugin context created with the "create" function + */ +static void destroy(MPMCommonPluginCtx *ctx) +{ + if (ctx && ctx->started) + { + stop(ctx); + } + OICFree(ctx); +} + +static void waitForChildProcessToComplete(pid_t child_pid, int32_t timeout) +{ + int32_t waittime = 0; + int status = 0; + pid_t wpid = 0; + + do + { + wpid = waitpid(child_pid, &status, WNOHANG); + if (wpid == 0) + { + if (waittime < timeout) + { + OIC_LOG_V(INFO, TAG, "Parent waiting on child: %d second(s): %d", + child_pid, waittime); + sleep(1); + waittime++; + } + else + { + OIC_LOG_V(INFO, TAG, "Parent forced stopping of child: %d", child_pid); + kill(child_pid, SIGKILL); + break; + } + } + } + while ((wpid == 0) && (waittime <= timeout)); + + if (WIFEXITED(status)) + { + OIC_LOG_V(INFO, TAG, "Child: %d completed \"exited\" with status: %d", + child_pid, WEXITSTATUS(status)); + } + else if (WIFSIGNALED(status)) + { + OIC_LOG_V(INFO, TAG, "Child: %d completed \"signaled\" with status: %d", + child_pid, WTERMSIG(status)); + } +} + +static void timedWaitForPipeWrite(int fd, MPMPipeMessage *msg, int32_t timeout) +{ + if (NULL != msg) + { + struct timeval tv; + fd_set fdset; + int nfd = -1; + int32_t waittime = 0; + ssize_t nbytes = 0; + + /* wait for 1 second on each time through the loop for up to timeout */ + tv.tv_sec = 0; + tv.tv_usec = 0; + + do + { + FD_ZERO(&(fdset)); + FD_SET(fd, &(fdset)); + sleep(1); /* tried to set the timeout on select and it was not reliable */ + nfd = select(fd + 1, &(fdset), NULL, NULL, &tv); + if (nfd == -1) + { + OIC_LOG_V(ERROR, TAG, "select error :[%s]", strerror(errno)); + break; + } + else if (nfd) + { + if (FD_ISSET(fd, &(fdset))) + { + nbytes = MPMReadPipeMessage(fd, msg); + } + } + else + { + /* got nothing case */ + OIC_LOG_V(INFO, TAG, "Parent waiting on child seconds(s): %d", waittime); + } + + waittime++; + + } + while ((nbytes == 0) && (waittime <= timeout)); + } +} + +typedef MPMCommonPluginCtx *(*create_t)(); +typedef int (*start_t)(MPMCommonPluginCtx *ctx); +typedef void (*stop_t)(MPMCommonPluginCtx *ctx); +typedef void (*destroy_t)(MPMCommonPluginCtx *ctx); + +typedef struct plugin_runtime_tag +{ + create_t create; + start_t start; + stop_t stop; + destroy_t destroy; +} MPMPluginRuntime; + +// This is the symbol table that will be loaded by the mini plugin manager. +// This is the "interface" between the mini plugin manager and the plugin. +MPMPluginRuntime plugin_funcs = +{ + create, + start, + stop, + destroy +}; + diff --git a/bridging/common/pluginProcess.cpp b/bridging/common/pluginProcess.cpp new file mode 100644 index 0000000..46044b2 --- /dev/null +++ b/bridging/common/pluginProcess.cpp @@ -0,0 +1,26 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 "pluginServer.h" + +void MPMPluginSpecificProcess(void) +{ +} diff --git a/bridging/common/pluginServer.cpp b/bridging/common/pluginServer.cpp new file mode 100644 index 0000000..eb251dc --- /dev/null +++ b/bridging/common/pluginServer.cpp @@ -0,0 +1,491 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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. +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// + +/* This file contains the plugin server implementation. Most of this + * implementation is reused for other plugins. There is NO customization + * required of functions in this file to accommodate plugin specific code. + */ + +#include +#include +#include +#include +#include "iotivity_config.h" +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "messageHandler.h" +#include "pluginServer.h" +#include "oic_malloc.h" +#include "pluginIf.h" +#include "ocpayload.h" +#include "logger.h" +#include "WorkQueue.h" +#include "ConcurrentIotivityUtils.h" +#include +#include + +#define TAG "PLUGIN_SERVER" +#define OC_KEY_VALUE_DELIMITER "=" + +using namespace OC::Bridging; + +//function prototypes + +/** + * This function initializes all the boilerplate items for the OCF server. + * it relies strictly on the "C" layer of the OCF stack. This is common + * code for all plugins. + * + * @param[in] ctx The context structure used by this implementation + * @param[in] deviceName Name of the device as defined by the plugin + * @param[in] resourceType Resource type as defined by the plugin + * + * @returns MPM_RESULT_OK if no errors, MPM_RESULT_INTERNAL_ERROR if initialization failure + */ +static MPMResult initInfrastructure(MPMCommonPluginCtx *ctx, const char *deviceName, + const char *resourceType); + +/** + * this function is a blocking function that holds the iotivity processing + * loop. this OCF server relies strictly on the "C" layer of the OCF stack. + * This is common code for all plugins. + * + * @param[in,out] result Child process status + * @param[in] ctx The context structure used by this implementation + * + * @return MPM_RESULT_OK if no errors, MPM_RESULT_INTERNAL_ERROR if maintenance failure + */ +static MPMResult maintainInfrastructure(MPMResult result, MPMCommonPluginCtx *ctx); + +const char *g_date_of_manufacture = NULL; +const char *g_firmware_version = NULL; +const char *g_manufacturer_name = "Intel"; +const char *g_operating_system_version = NULL; +const char *g_hardware_version = NULL; +const char *g_platform_id = "ce530bf4-40ab-11e6-9cca-ff46602aca08"; +const char *g_manufacturer_url = NULL; +const char *g_model_number = NULL; +const char *g_platform_version = NULL; +const char *g_support_url = NULL; +const char *g_version = NULL; +const char *g_system_time = NULL; + +static pthread_t processMessageFromPipeThread; + +/* plugin specific context storage point. the plugin owns the allocation + * and freeing of its own context + */ +MPMPluginCtx *g_plugin_context = NULL; + + +/* Secure Virtual Resource database for Iotivity Server + * It contains Server's Identity and the PSK credentials + * of other devices which the server trusts + */ +static char CRED_FILE[] = "./oic_svr_db_server.json"; +extern MPMCommonPluginCtx *g_com_ctx; + +FILE *serverFOpen(const char *path, const char *mode) +{ + if (0 == strcmp(path, OC_SECURITY_DB_DAT_FILE_NAME)) + { + return fopen(CRED_FILE, mode); + } + else + { + return fopen(path, mode); + } +} + +std::unique_ptr iotivityUtils = NULL; + +/** + * This is a non blocking pipe read function + * + * @param[in] fd file descriptor from where messages are to be read + * @param[in] com_ctx common context + * @param[in] ctx plugin specific context + * + * @return false if STOP request has come from the MPM, true if STOP request + * has not come from the MPM + */ +bool processMessagesFromMPM(int fd, MPMCommonPluginCtx *com_ctx, MPMPluginCtx *ctx) +{ + struct timeval tv; + fd_set fdset; + int nfd = -1; + ssize_t nbytes = 0; + bool shutdown = false; + MPMPipeMessage pipe_message; + g_com_ctx = com_ctx; + + tv.tv_sec = 15; + tv.tv_usec = 0; + + pipe_message.payloadSize = 0; + pipe_message.msgType = MPM_NOMSG; + pipe_message.payload = NULL; + + FD_ZERO(&(fdset)); + FD_SET(fd, &(fdset)); + nfd = select(fd + 1, &(fdset), NULL, NULL, &tv); + if (nfd == -1) + { + OIC_LOG_V(ERROR, TAG, "select error: %s", strerror(errno)); + } + else + { + if (FD_ISSET(fd, &(fdset))) + { + nbytes = MPMReadPipeMessage(fd, &pipe_message); + if (nbytes == 0) + { + OIC_LOG(DEBUG, TAG, "EOF was read and file descriptor was found to be closed"); + shutdown = true; + } + if (nbytes > 0) + { + if (pipe_message.msgType == MPM_STOP) + { + shutdown = true; + } + else + { + MPMRequestHandler(&pipe_message, ctx); + } + } + + OICFree((void*)pipe_message.payload); + + } + } + return (shutdown); +} + +MPMResult MPMPluginService(MPMCommonPluginCtx *ctx) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + if (ctx == NULL) + { + OIC_LOG(ERROR, TAG, "Plugin context is NULL"); + goto HandleError; + } + + // plugin create is in the individual plugin. + result = pluginCreate(&g_plugin_context); + + if (result != MPM_RESULT_OK || g_plugin_context == NULL) + { + OIC_LOG_V(ERROR, TAG, "Creation failed result: %d", result); + goto HandleError; + } + + // initialize the OCF infrastructure to be setup for a server + result = initInfrastructure(ctx, g_plugin_context->device_name, g_plugin_context->resource_type); + + if (result != MPM_RESULT_OK) + { + OIC_LOG_V(ERROR, TAG, "Error (%d) initializing OCF infrastructure", result); + goto HandleError; + } + if (ctx->reconnect_file_name != NULL) + { + strncpy(g_plugin_context->reconnect_file_name, ctx->reconnect_file_name, + strlen(ctx->reconnect_file_name)); + } + else + { + memset(g_plugin_context->reconnect_file_name, 0, MPM_MAX_FILE_NAME_LENGTH); + } + // plugin start is in the individual plugin. + result = pluginStart(g_plugin_context); + + if (result != MPM_RESULT_OK) + { + OIC_LOG_V(ERROR, TAG, "Failed to start %s plugin result: %d", g_plugin_context->device_name, + result); + } + +HandleError : + /* Sends the status of the child to Parent and + * in the case of success it act as blocking call. + */ + result = maintainInfrastructure(result, ctx); + + return (result); +} + +static MPMResult setPlatformInfoParams(OCPlatformInfo &platform_info) +{ + if ((strlen(g_manufacturer_name) > MAX_MANUFACTURER_NAME_LENGTH)) + { + OIC_LOG(ERROR, TAG, "Manufacture name string length exceeded max length"); + return MPM_RESULT_INTERNAL_ERROR; + } + + if (g_manufacturer_url != NULL && (strlen(g_manufacturer_url) > MAX_MANUFACTURER_URL_LENGTH)) + { + OIC_LOG(ERROR, TAG, "Url string length exceeded max length"); + return MPM_RESULT_INTERNAL_ERROR; + } + + platform_info.platformID = const_cast (g_platform_id); + platform_info.manufacturerName = const_cast (g_manufacturer_name); + platform_info.manufacturerUrl = const_cast (g_manufacturer_url); + platform_info.modelNumber = const_cast (g_model_number); + platform_info.dateOfManufacture = const_cast (g_date_of_manufacture); + platform_info.platformVersion = const_cast (g_platform_version); + platform_info.operatingSystemVersion = const_cast (g_operating_system_version); + platform_info.hardwareVersion = const_cast (g_hardware_version); + platform_info.firmwareVersion = const_cast (g_firmware_version); + platform_info.supportUrl = const_cast (g_support_url); + platform_info.systemTime = const_cast (g_system_time); + + return MPM_RESULT_OK; +} + +static MPMResult setDeviceInfoParams(const char *deviceName, const char *resourceType, + OCDeviceInfo &device_info) +{ + if (!deviceName || deviceName[0] == '\0') + { + OIC_LOG(ERROR, TAG, "Device name is NULL"); + return MPM_RESULT_INVALID_PARAMETER; + } + device_info.deviceName = const_cast (deviceName); + + OCStringLL *stringll = NULL; + + OCResourcePayloadAddStringLL(&stringll, OC_RSRVD_RESOURCE_TYPE_DEVICE); + OCResourcePayloadAddStringLL(&stringll, resourceType); + + device_info.types = stringll; + + device_info.dataModelVersions = NULL; + device_info.specVersion = NULL; + + return MPM_RESULT_OK; +} + +static MPMResult initInfrastructure(MPMCommonPluginCtx *ctx, const char *deviceName, + const char *resourceType) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + + uint16_t port = 0; + + if (ctx != NULL) + { + /* Create the unnamed pipe listener thread that will clear the + * child's stay in the loop variable when the parent wants to + * shut down the child process. + */ + // Initialize Persistent Storage for SVR database + static OCPersistentStorage ps = {g_plugin_context->open, fread, fwrite, fclose, unlink}; + OCRegisterPersistentStorageHandler(&ps); + + OCStackResult ocResult = OCInit(NULL, port, OC_SERVER); + if (ocResult != OC_STACK_OK) + { + OIC_LOG(ERROR, TAG, "OCStack init error"); + return result; + } + + std::unique_ptr>> q = + std::unique_ptr>> + (new WorkQueue>()); + + iotivityUtils = make_unique(std::move(q)); + iotivityUtils->startWorkerThreads(); + + OCPlatformInfo platform_info; + if (setPlatformInfoParams(platform_info) != MPM_RESULT_OK) + { + OIC_LOG(ERROR, TAG, "Platform info setting failed locally!"); + return result; + } + + if (OCSetPlatformInfo(platform_info) != OC_STACK_OK) + { + OIC_LOG(ERROR, TAG, "Platform Registration failed!"); + return result; + } + + OCDeviceInfo device_info = {NULL, NULL, NULL, NULL}; + if (setDeviceInfoParams(deviceName, resourceType, device_info) != MPM_RESULT_OK) + { + OIC_LOG(ERROR, TAG, "Device info setting failed locally!"); + return result; + } + + if (OCSetDeviceInfo(device_info) != OC_STACK_OK) + { + OIC_LOG(ERROR, TAG, "Device Registration failed!"); + return result; + } + // Iotivity does not take ownership of 'types' and clones itself a copy. + OCFreeOCStringLL(device_info.types); + } + return MPM_RESULT_OK; +} + +void *processMessageFromPipeThreadProc(void *arg) +{ + MPMCommonPluginCtx *ctx = (MPMCommonPluginCtx *)arg; + while (true) + { + bool result = processMessagesFromMPM(ctx->child_reads_fds.read_fd, ctx, g_plugin_context); + if (result != MPM_RESULT_OK) + { + OIC_LOG(INFO, TAG, "Leaving processMessageFromPipeThreadProc "); + break; + } + } + pthread_exit(NULL); + return NULL; +} + +MPMResult maintainInfrastructure(MPMResult result, MPMCommonPluginCtx *ctx) +{ + MPMPipeMessage pipe_message; + void *res; + + if (ctx != NULL) + { + /* child tells status to parent prior to getting in the + * processing loop + */ + if (result == MPM_RESULT_OK) + { + pipe_message.msgType = MPM_DONE; + pipe_message.payloadSize = 0; + pipe_message.payload = NULL; + result = MPMWritePipeMessage(ctx->parent_reads_fds.write_fd, &pipe_message); + } + else + { + pipe_message.msgType = MPM_ERROR; + pipe_message.payloadSize = 0; + pipe_message.payload = NULL; + result = MPMWritePipeMessage(ctx->parent_reads_fds.write_fd, &pipe_message); + } + + if (result == MPM_RESULT_OK) + { + pthread_create(&processMessageFromPipeThread, NULL, processMessageFromPipeThreadProc, ctx); + pthread_join(processMessageFromPipeThread, &res); + } + else + { + return result; + } + + OIC_LOG_V(INFO, TAG, "Stopping %s plugin", g_plugin_context->device_name); + + result = pluginStop(g_plugin_context); + + if (result != MPM_RESULT_OK) + { + OIC_LOG_V(ERROR, TAG, "Error(%d) stopping plugin %s", result, g_plugin_context->device_name); + } + + result = pluginDestroy(g_plugin_context); + if (result != MPM_RESULT_OK) + { + OIC_LOG_V(ERROR, TAG, "Error(%d) stopping plugin %s", result, g_plugin_context->device_name); + } + + iotivityUtils->stopWorkerThreads(); + + OCStackResult ocResult = OCStop(); + + if (ocResult != OC_STACK_OK) + { + OIC_LOG_V(ERROR, TAG, "Error(%d) stopping Iotivity", ocResult); + result = MPM_RESULT_INTERNAL_ERROR; + } + } + else + { + OIC_LOG(ERROR, TAG, "Bad context handed to maintain ocf infrastructure"); + } + return (result); +} + +MPMResult MPMExtractFiltersFromQuery(char *query, char **filterOne, char **filterTwo) +{ + + char *key = NULL; + char *value = NULL; + char *restOfQuery = NULL; + int numKeyValuePairsParsed = 0; + + *filterOne = NULL; + *filterTwo = NULL; + + if (!query) + { + // This is fine. This call is stemming from a non-entity handler request. + return MPM_RESULT_OK; + } + + char *keyValuePair = strtok_r(query, OC_QUERY_SEPARATOR, &restOfQuery); + + while (keyValuePair) + { + if (numKeyValuePairsParsed >= 2) + { + OIC_LOG(ERROR, TAG, "More than 2 queries params in URI."); + return MPM_RESULT_INVALID_PARAMETER; + } + + key = strtok_r(keyValuePair, OC_KEY_VALUE_DELIMITER, &value); + + if (!key || !value) + { + return MPM_RESULT_INVALID_PARAMETER; + } + else if (strncasecmp(key, OC_RSRVD_INTERFACE, sizeof(OC_RSRVD_INTERFACE) - 1) == 0) + { + *filterOne = value; // if + } + else if (strncasecmp(key, OC_RSRVD_RESOURCE_TYPE, sizeof(OC_RSRVD_INTERFACE) - 1) == 0) + { + *filterTwo = value; // rt + } + else + { + OIC_LOG_V(ERROR, TAG, "Unsupported query key: %s", key); + return MPM_RESULT_INVALID_PARAMETER; + } + ++numKeyValuePairsParsed; + + keyValuePair = strtok_r(NULL, OC_QUERY_SEPARATOR, &restOfQuery); + } + if (*filterOne != NULL || *filterTwo != NULL) + { + OIC_LOG_V(ERROR, TAG, "Extracted params if: %s and rt: %s.", *filterOne, *filterTwo); + } + + return MPM_RESULT_OK; +} diff --git a/bridging/include/ConcurrentIotivityUtils.h b/bridging/include/ConcurrentIotivityUtils.h new file mode 100644 index 0000000..c55927f --- /dev/null +++ b/bridging/include/ConcurrentIotivityUtils.h @@ -0,0 +1,229 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 _CONCURRENTIOTIVITYUTILS_H_ +#define _CONCURRENTIOTIVITYUTILS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "IotivityWorkItem.h" +#include "WorkQueue.h" +#include "ocstack.h" +#include "octypes.h" + +namespace OC +{ + namespace Bridging + { + template + std::unique_ptr make_unique(Args &&... args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } + + /** + * Provides a synchronized C++ wrapper over the Iotivity CSDK. + * Accepts workItems from the plugins for common operations. + * A consumer thread processes these worker items and makes calls into Iotivity. + * Another thread calls OCProcess() to check for network requests. + */ + class ConcurrentIotivityUtils + { + private: + + static std::unique_ptr>> m_queue; + std::mutex m_iotivityApiCallMutex; + + std::thread m_processWorkQueueThread, m_ocProcessThread; + bool m_threadStarted; + bool m_shutDownOCProcessThread; + static const int OCPROCESS_SLEEP_MICROSECONDS = 200000; + + // Fetches work item from queue and processes it. + void processWorkQueue() + { + while (true) + { + std::unique_ptr workItem; + bool fetchedWorkItem = m_queue->get(&workItem); + + if (fetchedWorkItem) + { + std::lock_guard lock(m_iotivityApiCallMutex); + workItem->process(); + } + else + { + break; + } + } + } + + void callOCProcess() + { + while (!m_shutDownOCProcessThread) + { + { + std::lock_guard lock(m_iotivityApiCallMutex); + OCProcess(); + } + // Hopefully it's enough for other threads to be scheduled + // instead of the spin here. OCProcess is very lightweight though. + usleep(OCPROCESS_SLEEP_MICROSECONDS); + } + } + + public: + + + ConcurrentIotivityUtils(std::unique_ptr>> + queueToMonitor) + { + m_queue = std::move(queueToMonitor); + m_threadStarted = false; + m_shutDownOCProcessThread = false; + } + + /** + * Starts 2 worker threads. One to service the concurrent work queue to call + * into Iotivity. One to process network requests by calling OCProcess() + */ + void startWorkerThreads(); + + /** + * Stops the 2 worker threads started by startWorkerThreads. @see startWorkerThreads + */ + void stopWorkerThreads(); + + /** + * Gets the string uri associated with an Iotivity handle. + * @warning This function is not thread safe and should only be called from entityHandler + * specified when creating the resource. The entityhandler is called with the + * Iotivity access mutex locked and this function does not modify anything + * in the stack. + * + * @param[in] handle handle for the resource + * @param[out] uri uri associated with the handle + * + * @return true if the resource is found and uri will be populated, else false. + */ + bool static getUriFromHandle(OCResourceHandle handle, std::string &uri); + + /** + * Sends out OBSERVE notifications for the resource with the given uri. + * Notifications are sent out using OC_NA_QOS. + * + * @param[in] resourceUri resource uri for fetching handle and notifying + * + * @return OCStackResult OC_STACK_OK on success, some other value upon failure. + */ + OCStackResult static queueNotifyObservers(const std::string &resourceUri); + + /** + * Create an Iotivity resource with the given properties. + * + * @param[in] uri + * @param[in] resourceType + * @param[in] interface + * @param[in] entityHandler Callback function that will be called on requests to this resource. + * @param[in] callbackParam + * @param[in] resourceProperties + * + * @return OCStackResult OC_STACK_OK on success, some other value upon failure. + */ + OCStackResult static + queueCreateResource(const std::string &uri, const std::string &resourceType, + const std::string &interface, + OCEntityHandler entityHandler, void *callbackParam, + uint8_t resourceProperties); + + /** + * Delete the Iotivity resource given in the uri. + * + * @param[in] uri + * + * @return OCStackResult OC_STACK_OK on success, some other value upon failure. + */ + OCStackResult static queueDeleteResource(const std::string &uri); + + /** + * Send a response to a request. + * + * @param[in] request OCEntityHandleRequest type that was handed in the entityhandler. + * @param[in] payload The response payload. This is cloned and callee still has ownership + * and the onus to free this. + * @param[in] responseCode The response code of type OCEntityHandlerResult in ocstack.h + * + * @return OCStackResult OC_STACK_OK on success, some other value upon failure. + */ + OCStackResult static + respondToRequest(OCEntityHandlerRequest *request, OCRepPayload *payload, + OCEntityHandlerResult responseCode); + + /** + * Respond with an error message. Internally calls + * ConcurrentIotivityUtils::respondToRequest() after creating + * a payload containing the error message. The error message + * key will be x.org.iotivity.error and the value will be + * errorMessage. + * + * @param[in] request EntityHandler request + * @param[in] errorMessage May be NULL. + * @param[in] errorCode entity handler result + * + * @return OCStackResult OC_STACK_OK on success, some other value upon failure. + */ + OCStackResult static respondToRequestWithError(OCEntityHandlerRequest *request, + const std::string &errorMessage, + OCEntityHandlerResult errorCode); + + /** + * Parse the query parameter as a keyValueMap + * + * @param[in] query query to be parsed + * @param[in,out] keyValueMap key value map of the query + */ + void static getKeyValueParams(const std::string &query, + std::map &keyValueMap); + + /** + * Can be called from the entity handler to handle requests for default interface + * + * @param[in] query + * + * @return true if request for default interface (oic.if.baseline) else false. + */ + bool static isRequestForDefaultInterface(const std::string &query); + + }; + } +} + +#endif //_CONCURRENTIOTIVITYUTILS_H_ diff --git a/bridging/include/IotivityWorkItem.h b/bridging/include/IotivityWorkItem.h new file mode 100644 index 0000000..b6cbcaf --- /dev/null +++ b/bridging/include/IotivityWorkItem.h @@ -0,0 +1,182 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 _IOTIVITYWORKITEM_H_ +#define _IOTIVITYWORKITEM_H_ + +#include "ocstack.h" +#include "octypes.h" +#include "ocpayload.h" +#include "logger.h" +#include + +#define LOG "IOTIVITY_WORK_ITEM" + +namespace OC +{ + namespace Bridging + { + /** + * Basic workitem to act as a base class so more specific + * child classes can populate fields and + */ + class IotivityWorkItem + { + public: + virtual void process() = 0; + virtual ~IotivityWorkItem() {}; + + protected: + std::string m_uri; + }; + + /** + * Creates an object used to create Iotivity resources. + */ + class CreateResourceItem : public IotivityWorkItem + { + public: + CreateResourceItem( + std::string uri, + std::string resourceType, + std::string interface, + OCEntityHandler entityHandler, + void *callbackParam, + uint8_t resourceProperties + ) + : m_resourceType(resourceType) + , m_interface(interface) + , m_entityHandler(entityHandler) + , m_callbackParam(callbackParam) + , m_resourceProperties(resourceProperties) + { + m_uri = uri; + } + + virtual void process() + { + OCResourceHandle handle; + + OCStackResult res = OCCreateResource(&handle, m_resourceType.c_str(), + m_interface.c_str(), + m_uri.c_str(), + m_entityHandler, + m_callbackParam, + m_resourceProperties); + + if (res == OC_STACK_OK) + { + OIC_LOG_V(INFO, LOG, "Created and saved %s", m_uri.c_str()); + } + else + { + OIC_LOG_V(ERROR, LOG, "Failed to create %s", m_uri.c_str()); + } + } + private: + std::string m_resourceType; + std::string m_interface; + OCEntityHandler m_entityHandler; + void *m_callbackParam; + uint8_t m_resourceProperties; + }; + + /** + * Creates an object used to create send Iotivity responses. + */ + class SendResponseItem : public IotivityWorkItem + { + public: + SendResponseItem(std::unique_ptr response) + : m_response(std::move(response)) + {} + + virtual void process() + { + OCDoResponse((m_response).get()); + OCPayloadDestroy(m_response->payload); + } + + private: + std::unique_ptr m_response; + }; + + /** + * Creates an object used to notify observers of Iotivity resources. + */ + class NotifyObserversItem : public IotivityWorkItem + { + public: + NotifyObserversItem(const std::string &uri) + { + m_uri = uri; + } + + virtual void process() + { + OCResourceHandle handle = OCGetResourceHandleAtUri(m_uri.c_str()); + + if (!handle) + { + OIC_LOG_V(ERROR, LOG, "No handle for %s. Not notifying observers.", m_uri.c_str()); + return; + } + OCNotifyAllObservers(handle, OC_NA_QOS); + } + + }; + + /** + * Creates an object used to delete Iotivity resources. + */ + class DeleteResourceItem : public IotivityWorkItem + { + public: + DeleteResourceItem(const std::string &uri) + { + m_uri = uri; + } + + virtual void process() + { + OCResourceHandle handle = OCGetResourceHandleAtUri(m_uri.c_str()); + + if (!handle) + { + OIC_LOG_V(ERROR, LOG, "No handle for %s. Nothing to delete", m_uri.c_str()); + return; + } + + OCStackResult res = OCDeleteResource(handle); + if (res == OC_STACK_OK) + { + OIC_LOG_V(INFO, LOG, "Deleted %s", m_uri.c_str()); + } + else + { + OIC_LOG_V(ERROR, LOG, "Failed to delete %s", m_uri.c_str()); + } + } + + }; + } +} +#endif // _IOTIVITYWORKITEM_H_ diff --git a/bridging/include/JsonHelper.h b/bridging/include/JsonHelper.h new file mode 100644 index 0000000..b531083 --- /dev/null +++ b/bridging/include/JsonHelper.h @@ -0,0 +1,110 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 __JSONHELPER_H__ +#define __JSONHELPER_H__ + +#include "rapidjson.h" +#include "document.h" +#include "stringbuffer.h" +#include "writer.h" +#include + +#include + +class JsonHelper +{ + public: + + static std::string toString(rapidjson::Value &doc) + { + rapidjson::StringBuffer sb; + std::string jsonRep; + + rapidjson::Writer writer(sb); + doc.Accept(writer); + return sb.GetString(); + } + + static std::string toString(rapidjson::Value::ConstMemberIterator &it) + { + rapidjson::StringBuffer sb; + std::string jsonRep; + + rapidjson::Writer writer(sb); + it->value.Accept(writer); + return sb.GetString(); + } + + template + static void setMember(rapidjson::Document &doc, std::string obj, + std::string name, T &value) + { + if (doc.HasMember(obj.c_str())) + { + rapidjson::Document::AllocatorType &allocator = doc.GetAllocator(); + doc[obj.c_str()].RemoveMember(name.c_str()); + rapidjson::Value nameValue(name.c_str(), allocator); + doc[obj.c_str()].AddMember(nameValue, value, allocator); + } + } + + template + static void setMember(rapidjson::Document &doc, const std::string &name, T &value) + { + if (doc.HasMember(name.c_str())) + { + doc[name.c_str()] = value; + } + else + { + rapidjson::Document::AllocatorType &allocator = doc.GetAllocator(); + rapidjson::Value nameValue(name.c_str(), allocator); + doc.AddMember(nameValue, value, allocator); + } + + } + + template + static bool getMember(rapidjson::Document &doc, std::string name, T &value) + { + if (doc.HasMember(name.c_str())) + { + if (typeid(T) == typeid(std::string)) + { + value = doc[name.c_str()].GetString(); + } + return true; + } + return false; + } + + static void tryRemoveMember(rapidjson::Document &doc, std::string name) + { + if (doc.HasMember(name.c_str())) + { + doc.RemoveMember(name.c_str()); + } + } +}; + +#endif /* __JSON_HELPER_H__ */ diff --git a/bridging/include/WorkQueue.h b/bridging/include/WorkQueue.h new file mode 100644 index 0000000..115697d --- /dev/null +++ b/bridging/include/WorkQueue.h @@ -0,0 +1,112 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 _WORKQUEUE_H_ +#define _WORKQUEUE_H_ + +#include +#include +#include +#include +#include +#include "iotivity_config.h" +#ifdef HAVE_UNISTD_H +#include +#endif + +namespace OC +{ + namespace Bridging + { + /** + * Provides a generic, minimal thread safe queue. + */ + template + class WorkQueue + { + private: + + std::queue m_workQueue; + std::mutex m_workQueueMutex; + std::condition_variable m_cv; + bool m_signalToShutDown; + + public: + + inline WorkQueue() + { + m_signalToShutDown = false; + } + + /** + * Puts the arg in the queue and notifies all threads waiting + * to fetch things from the queue. + * + * @para[in] m item The item to insert into the queue. + */ + void put(T item) + { + std::unique_lock lock(m_workQueueMutex); + + m_workQueue.push(std::move(item)); + m_cv.notify_all(); + } + + /** + * Blocking function to fetch an item from the queue. + * + * @param[in] item The item to insert into the queue. + * @return true if an item is fetched from the queue. + false if the queue is shutdown. + */ + bool get(T *item) + { + std::unique_lock lock(m_workQueueMutex); + + m_cv.wait(lock, [this]() + { + return m_workQueue.size() > 0 || m_signalToShutDown; + }); + + if (m_signalToShutDown) + { + return false; + } + + *item = std::move(m_workQueue.front()); + m_workQueue.pop(); + return true; + } + + /** + * Notifies all waiting threads that the queue is being shut down. + */ + void shutdown() + { + std::unique_lock lock(m_workQueueMutex); + m_signalToShutDown = true; + m_cv.notify_all(); + } + }; + } +} + +#endif // _WORKQUEUE_H_ diff --git a/bridging/include/curlClient.h b/bridging/include/curlClient.h new file mode 100644 index 0000000..c8217f3 --- /dev/null +++ b/bridging/include/curlClient.h @@ -0,0 +1,177 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 _CURLCLIENT_H_ +#define _CURLCLIENT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mpmErrorCode.h" +#include "StringConstants.h" + +namespace OC +{ + namespace Bridging + { + const char CURL_CONTENT_TYPE_JSON[] = "content-type: application/json"; + const char CURL_CONTENT_TYPE_URL_ENCODED[] = "content-type: application/x-www-form-urlencoded"; + const char CURL_HEADER_ACCEPT_JSON[] = "accept: application/json"; + + const long INVALID_RESPONSE_CODE = 0; + + class CurlClient + { + + public: + enum class CurlMethod + { + GET, PUT, POST, DELETE, HEAD + }; + + virtual ~CurlClient() { } + + long getLastResponseCode() + { + return m_lastResponseCode; + } + + CurlClient(CurlMethod method, const std::string &url) + { + if (url.empty()) + { + throw "Curl method or url is empty"; + } + + m_method = getCurlMethodString(method); + m_url = url; + m_useSsl = CURLUSESSL_TRY; + } + + CurlClient &setRequestHeaders(std::vector &requestHeaders) + { + m_requestHeaders = requestHeaders; + return *this; + } + + CurlClient &addRequestHeader(const std::string &header) + { + m_requestHeaders.push_back(header); + return *this; + } + + CurlClient &setUserName(const std::string &userName) + { + m_username = userName; + return *this; + } + + CurlClient &setRequestBody(std::string &requestBody) + { + m_requestBody = requestBody; + return *this; + } + + CurlClient &setUseSSLOption(curl_usessl sslOption) + { + m_useSsl = sslOption; + return *this; + } + + int send() + { + return doInternalRequest(m_url, m_method, m_requestHeaders, m_requestBody, m_username, m_outHeaders, + m_response); + } + + std::string getResponseBody() + { + return m_response; + } + + std::vector getResponseHeaders() + { + return m_outHeaders; + } + + + private: + + std::string getCurlMethodString(CurlMethod method) + { + if (method == CurlMethod::GET) return OC::PlatformCommands::GET; + else if (method == CurlMethod::PUT) return OC::PlatformCommands::PUT; + else if (method == CurlMethod::POST) return OC::PlatformCommands::POST; + else if (method == CurlMethod::DELETE) return OC::PlatformCommands::DELETE; + else if (method == CurlMethod::HEAD) return "HEAD"; + + else throw std::runtime_error("Invalid CurlMethod"); + } + + std::string m_url; + std::string m_method; + std::vector m_requestHeaders; + std::string m_requestBody; + std::string m_username; + std::string m_response; + std::vector m_outHeaders; + + /// Indicates whether to use CURLOPT_USE_SSL option in doInternalRequest. + /// Curl default is no SSL (CURLUSESSL_NONE). Specify one of the other CURL SSL options + /// (for example, CURLUSESSL_TRY) if you need to perform SSL transactions. + curl_usessl m_useSsl; + + static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); + + // Represents contiguous memory to hold a HTTP response. + typedef struct _MemoryChunk + { + _MemoryChunk() : size(0) + { + memory = static_cast(malloc(1)); + } + + char *memory; + size_t size; + + } MemoryChunk; + + int decomposeHeader(const char *header, std::vector &headers); + + + int doInternalRequest(const std::string &url, + const std::string &method, + const std::vector &inHeaders, + const std::string &request, + const std::string &username, + std::vector &outHeaders, + std::string &response); + + long m_lastResponseCode; + }; + } // namespace Bridging +} // namespace OC +#endif // _CURLCLIENT_H_ diff --git a/bridging/include/messageHandler.h b/bridging/include/messageHandler.h new file mode 100644 index 0000000..65a7858 --- /dev/null +++ b/bridging/include/messageHandler.h @@ -0,0 +1,167 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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. +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// + +/* This file contains the plugin server implementation. Most of this + * implementation is reused for other plugins. There is NO customization + * required of functions in this file to accommodate plugin specific code. + */ + +#ifndef _MESSAGEHANDLER_H +#define _MESSAGEHANDLER_H + +#include +#include +#include "mpmErrorCode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MPM_MAX_URI_LEN 256 +#define MPM_MAX_FILE_NAME_LENGTH 300 +#define MPM_MAX_LENGTH_32 32 +#define MPM_MAX_LENGTH_64 64 +#define MPM_MAX_LENGTH_256 256 +#define MPM_MAX_UNIQUE_ID_LEN 128 +#define MPM_MAX_METADATA_LEN 3000 + +/* Enum to specify the action type*/ +typedef enum +{ + MPM_NOMSG = 0, + MPM_SCAN, + MPM_ADD, + MPM_DELETE, + MPM_REMOVE, + MPM_RECONNECT, + MPM_STOP, + MPM_DONE, + MPM_ERROR +} MPMMessageType; + +/** + * This structure represents the format of the message exchanged + * between MPM and Plugin over the pipe. + */ +typedef struct +{ + /** size of the payload. */ + size_t payloadSize; + + /** type of the message [MPM_SCAN, MPM_ADD etc] */ + MPMMessageType msgType; + + /** Payload to be sent and received */ + const uint8_t *payload; +} MPMPipeMessage; + +/** + * This structure represents the add response message coming from + * the plugins to mpm library + */ +typedef struct +{ + char uri[MPM_MAX_URI_LEN]; + char metadata[MPM_MAX_METADATA_LEN]; +} MPMAddResponse; + +/** + * This structure represents the resource list used for + * creating reconnect metadata + */ +typedef struct MPMResourceList +{ + char href[MPM_MAX_URI_LEN]; + char relative[MPM_MAX_LENGTH_64]; + char interfaces[MPM_MAX_LENGTH_64]; + char rt[MPM_MAX_LENGTH_64]; + int bitmap; + struct MPMResourceList *next; +} MPMResourceList; + +/** + * This structure represents the device specific data of the plugin + * which is a part of reconnect metadata + */ +typedef struct +{ + char devName[MPM_MAX_LENGTH_64]; + char devType[MPM_MAX_LENGTH_64]; + char manufacturerName[MPM_MAX_LENGTH_256]; +} MPMDeviceSpecificData; + +/** + * This function writes messages to the pipe + * @param[in] fd file descriptor + * @param[in] pipe_message message to be written + * + * @return MPM_RESULT_OK on success, MPM_RESULT_INTERNAL_PARAMETER on failure +*/ +MPMResult MPMWritePipeMessage(int fd, const MPMPipeMessage *pipe_message); + +/** + * This function reads messages from the pipe + * @param[in] fd file descriptor + * @param[in,out] pipe_message for storing the read message. + * + * @return number of bytes read +*/ +ssize_t MPMReadPipeMessage(int fd, MPMPipeMessage *pipe_message); + + +/** + * This function encodes the metadata received from the plugin + * @param[in] list A list of resources supported by the device + * @param[in] deviceDetails Plugin specific details to be encoded + * @param[in] buff The metadata stream to be filled with encoded data + * @param[in] size Size of the metadata stream to be encoded + * @param[in] details To hold plugin specific device details to be encoded + * @param[in] payloadSize Size of the plugin specific device details + * + * @return zero on no error, non-zero on occurrence of some error + */ +int64_t MPMFormMetaData(MPMResourceList *list, MPMDeviceSpecificData *deviceDetails, uint8_t *buff, + size_t size, void *details, size_t payloadSize); + +/** + * This function decodes and parse the metadata received from + * the client as a part of reconnect request + * @param[in] buffer The encoded metadata stream + * @param[in] size Size of the encoded metadata stream + * @param[out] list Reference to location of resource details + * @param[out] details To hold plugin specific device details after parsing + */ +void MPMParseMetaData(const uint8_t *buffer, size_t size, MPMResourceList **list, void **details); + +/** + * This function sends response coming from the plugins to mpm library + * @param[in] response Response to be sent + * @param[in] size Size of the response. + * @param[in] type Type of response (scan response, add response etc) + * @return MPM_RESULT_OK on success else MPM_RESULT_INTERNAL_ERROR + */ +MPMResult MPMSendResponse(const void *response, size_t size, MPMMessageType type); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus + +#endif diff --git a/bridging/include/mpmErrorCode.h b/bridging/include/mpmErrorCode.h new file mode 100644 index 0000000..8e5e9a8 --- /dev/null +++ b/bridging/include/mpmErrorCode.h @@ -0,0 +1,82 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 _MPMERRORCODE_H_ +#define _MPMERRORCODE_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Definitions for error codes for the Bridging + */ + +typedef enum +{ + MPM_CB_RESULT_KEEP = 1, + MPM_CB_RESULT_DELETE = 2, + MPM_CB_RESULT_OK = 3, + MPM_CB_RESULT_ERROR = 4 +} MPMCbResult; + +typedef enum +{ + MPM_RESULT_OK = 0, + MPM_RESULT_CREATED_FAILED, /*< Module has failed during create. */ + + MPM_RESULT_INVALID_HANDLE, /*< The handle is invalid. */ + MPM_RESULT_INVALID_PARAMETER, /*< Parameters are invalid. */ + MPM_RESULT_INTERNAL_ERROR, /*< Some unknown error occurred. */ + MPM_RESULT_INVALID_VERSION, /*< Invalid plugin API version. */ + + MPM_RESULT_MISSING_API, /*< One or more expected API is missing in the callback. */ + MPM_RESULT_NOT_IMPLEMENTED, /*< API function is not implemented. */ + MPM_RESULT_OUT_OF_MEMORY, /*< Memory allocation failure. */ + MPM_RESULT_ERROR_ADD_MESSAGE_QUEUE, /*< add message queue failure. */ + MPM_RESULT_LOADLIBRARY_FAILED, + MPM_RESULT_INSUFFICIENT_BUFFER, + MPM_RESULT_DUPLICATE_API_CALL, + MPM_RESULT_FILE_NOT_OPEN, + MPM_RESULT_FILE_ALREADY_OPEN, + MPM_RESULT_FILE_NOT_CLOSED, + MPM_RESULT_NOT_STARTED, /*< Module has not been started. */ + MPM_RESULT_STARTED_FAILED, /*< Module has failed during start. */ + MPM_RESULT_ALREADY_STARTED, /*< Module already started. */ + MPM_RESULT_NOT_STOPPED, + MPM_RESULT_ALREADY_CREATED, /*< Module already created. */ + MPM_RESULT_NOT_AUTHORIZED, /*< Not authorized */ + MPM_RESULT_NOT_PRESENT, /*< Not present or available */ + MPM_RESULT_NETWORK_ERROR, + MPM_RESULT_JSON_ERROR, + MPM_RESULT_MEMORY_ERROR, + MPM_RESULT_INVALID_DATA, + MPM_RESULT_INDEX_OUT_OF_BOUNDS, /*< Specified an index too great for array. */ + MPM_RESULT_UNEXPECTED_RESULT /*< Result code did not match expected response */ +} MPMResult; + +#ifdef __cplusplus +} +#endif // #ifdef __cpluscplus + +#endif /* _MPMERRORCODEH_ */ diff --git a/bridging/include/pluginIf.h b/bridging/include/pluginIf.h new file mode 100644 index 0000000..97dbc3d --- /dev/null +++ b/bridging/include/pluginIf.h @@ -0,0 +1,106 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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. +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// + +/* This file contains the "C" plugin interface definition. This header file + * and its corresponding implementation file is intended to be shared among + * ALL plugins. Modification of this file is not necessary for the construction + * of a new plugin. + */ + +#ifndef _PLUGINIF_H_ +#define _PLUGINIF_H_ + +#include +#include +#include +#include +#include "messageHandler.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This is a clear way to store file descriptors for unnamed pipes. Each pipe + * has a read end and a write end. + */ +struct MPMPipe +{ + int read_fd; + int write_fd; +}; + +/** + * structure to represent the plugin context + * which is common between all the plugins. + */ +struct MPMCommonPluginCtx +{ + /** + * Unnamed pipes are used in this implementation of the common plugin code, + * however the unnamed pipes are NOT exposed outside of the common plugin code. + * One of the unnamed pipes is used by the child to tell parent that the + * child has created and started the plugin content section successfully or not. + * The other unnamed pipe is used by the parent to stop the child which in turn + * stops and destroys the plugin content. As you know the unnamed pipes may be + * only used with related processes (i.e. having the same parent). The pipes exist + * as long as their descriptors are open. + */ + MPMPipe parent_reads_fds; + MPMPipe child_reads_fds; + + /** + * The "started" variable is used by the parent process to not permit + * more than one fork to happen per instance of the plugin. + */ + bool started; + + /** + * The main thread in the child process needs to know when it should + * leave the OCF process loop. + */ + bool exit_process_loop; + char reconnect_file_name[MPM_MAX_FILE_NAME_LENGTH]; + + /** + * Save the child's process handle to wait on it being signaled later + */ + pid_t child_pid; + +}; + +/** time out value */ +#define MPM_TIMEOUT_VAL_IN_SEC 60 + +/** + * This function is a OCF server. The function does not return unless there is an + * error or this main thread was signaled by the parent process main thread. + * @param[in] plugin_ctx plugin specific context + * + * @return MPM_RESULT_OK if no errors, MPM_RESULT_INTERNAL_ERROR if error + */ +MPMResult MPMPluginService(MPMCommonPluginCtx *plugin_ctx); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus + +#endif /* __PLUGIN_IF_H__ */ diff --git a/bridging/include/pluginServer.h b/bridging/include/pluginServer.h new file mode 100644 index 0000000..3d29953 --- /dev/null +++ b/bridging/include/pluginServer.h @@ -0,0 +1,144 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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. +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// + +/* This file contains the interface between the plugin's OCF server and the + * plugin specific implmentation + */ + +#ifndef __PLUGINSERVER_H__ +#define __PLUGINSERVER_H__ + +#include "messageHandler.h" +#include "pluginIf.h" +#include "mpmErrorCode.h" +#include "ocstack.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MPM_THREAD_PROCESS_SLEEPTIME 5 + +/** + * Plugin context is owned by the plugin specific content provider (you). + */ +struct MPMPluginCtx +{ + /** + * This plugin specific content provider has chosen to use a worker thread + * to manage all the devices. Keeping track if the thread is started. + */ + bool started; + + /** + * The thread is staying around for the lifetime of the plugin instance. + * This state variable is required to tell the thread leave the thread + * function when that time comes. + */ + bool stay_in_process_loop; + + FILE *(*open)(const char *path, const char *mode); + + /** + * The name of the plugin being started. This name must follow OCF specification + * for '/oic/d' device name. + */ + const char *device_name; + char reconnect_file_name[MPM_MAX_FILE_NAME_LENGTH]; + + /** + * The resource type with device of the plugin being started. This name must follow OCF specification + * for /oic/d. Some examples would be 'oic.d.light' and 'oic.d.smartlock'. + */ + const char *resource_type; + + /** + * Save the thread handle so there is a possibility to wait on thread + * handles for synchronization purposes. + */ + pthread_t thread_handle; +}; + +/** + * These functions are called by the plugin's own Iotivity server which is + * common code for all plugins. There is one Iotivity server per plugin. The APIs + * shown below permit the plugin to initialize and to allow the plugin specific + * code to call other Iotivity csdk APIs. The implementation of these functions is found + * in the plugin specific code. + * + * @param[in] plugin_specific_ctx Plugin specific context + * @param[in] message Message received from the client via mpm library + * + * @return MPM_RESULT_OK if no error, error specific value if any error occurs + */ +MPMResult pluginCreate(MPMPluginCtx **plugin_specific_ctx); + +MPMResult pluginStart(MPMPluginCtx *plugin_specific_ctx); + +MPMResult pluginStop(MPMPluginCtx *plugin_specific_ctx); + +MPMResult pluginDestroy(MPMPluginCtx *plugin_specific_ctx); + +MPMResult pluginScan(MPMPluginCtx *ctx, MPMPipeMessage *message); + +MPMResult pluginAdd(MPMPluginCtx *ctx, MPMPipeMessage *message); + +MPMResult pluginRemove(MPMPluginCtx *ctx, MPMPipeMessage *message); + +MPMResult pluginReconnect(MPMPluginCtx *ctx, MPMPipeMessage *message); + +/** + * This function is added for Zigbee and kept as empty function + * for all other plugins to avoid using ifdef(s) + */ +void MPMPluginSpecificProcess(void); + +/** + * This function handles the requests from mpm library to plugin + * @param[in] message The message to received over the pipe from the mpm library + * @param[in] ctx Context of the plugin to which the message has to be forwarded to + */ +void MPMRequestHandler(MPMPipeMessage *message, MPMPluginCtx *ctx); + + +/** + * Stolen from: IOTIVITY/resource/csdk/stack/src/resource.c:ExtractFiltersFromQuery() + * + * Function will extract 0, 1 or 2 filters from query. + * More than 2 filters or unsupported filters will result in error. + * If both filters are of the same supported type, the 2nd one will be picked. + * Resource and device filters in the SAME query are NOT validated + * and resources will likely not clear filters. + * + * @param[in] query The query string from which filters are to be extracted + * @param[in,out] filterOne The first filter string + * @param[in,out] filterTwo The second filter string + * + * @return MPM_RESULT_OK if no error, MPM_RESULT_INVALID_PARAMETER if any error + */ +MPMResult MPMExtractFiltersFromQuery(char *query, char **filterOne, char **filterTwo); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus + +#endif /* __PLUGIN_SERVER_H__ */ diff --git a/bridging/mini_plugin_manager/SConscript b/bridging/mini_plugin_manager/SConscript new file mode 100644 index 0000000..a6913e0 --- /dev/null +++ b/bridging/mini_plugin_manager/SConscript @@ -0,0 +1,75 @@ +#****************************************************************** +# +# Copyright 2017 Intel Mobile Communications GmbH 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. +# +#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +## +# Mini Plugin Manager build script +## + +import os.path + +Import('env') + +target_os = env.get('TARGET_OS') +src_dir = env.get('SRC_DIR') +bridging_path = os.path.join(src_dir, 'bridging') + +print "Reading MPM script" + +###################################################################### +# Build flags +###################################################################### +env.PrependUnique(CPPPATH = [ os.path.join(src_dir, 'resource', 'c_common', 'oic_string', 'include'), + os.path.join(src_dir, 'extlibs', 'rapidjson', 'include', 'rapidjson') + ]) +env.AppendUnique(CPPPATH = [ os.path.join(bridging_path, 'include') + ]) + +if target_os not in ['arduino', 'windows']: + env.AppendUnique(CPPDEFINES = ['WITH_POSIX']) + env.AppendUnique(CXXFLAGS = ['-std=c++0x', '-Wall', '-Wextra', '-Werror']) + +if target_os in ['darwin','ios']: + env.AppendUnique(CPPDEFINES = ['_DARWIN_C_SOURCE']) + +env.AppendUnique(RPATH = [env.get('BUILD_DIR')]) +env.AppendUnique(LIBPATH = [env.get('BUILD_DIR')]) + +if env.get('LOGGING'): + env.AppendUnique(CPPDEFINES = ['TB_LOG']) + + +env.PrependUnique(LIBS = ['mpmcommon']) +##################################################################### +# Source files and Target(s) +###################################################################### +minipluginmanager_src = [ + os.path.join(bridging_path, 'mini_plugin_manager', 'miniPluginManager.cpp'), + ] + +env.AppendUnique(MINIPLUGINMANAGER_SRC =minipluginmanager_src) + +print "Include path is %s" % env.get('CPPPATH') +print "Files path is %s" % env.get('MINIPLUGINMANAGER_SRC') +if target_os in ['android', 'tizen']: + mpmlib = env.SharedLibrary('minipluginmanager', env.get('MINIPLUGINMANAGER_SRC')) +else: + mpmlib = env.StaticLibrary('minipluginmanager', env.get('MINIPLUGINMANAGER_SRC')) +env.InstallTarget(mpmlib, 'minipluginmanager') +env.UserInstallTargetLib(mpmlib, 'minipluginmanager') + diff --git a/bridging/mini_plugin_manager/miniPluginManager.cpp b/bridging/mini_plugin_manager/miniPluginManager.cpp new file mode 100644 index 0000000..d0bebbe --- /dev/null +++ b/bridging/mini_plugin_manager/miniPluginManager.cpp @@ -0,0 +1,461 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 +#include "iotivity_config.h" +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "MINI_PLUGIN_MANAGER" + +#include "oic_malloc.h" +#include "pluginIf.h" +#include "miniPluginManager.h" +#include "messageHandler.h" +#include "logger.h" + +pthread_t readResponsethreadhandle; + +bool exitResponseThread = false; + +/******************************************************************************* + * type defines and structure definitions go here + ******************************************************************************/ + +typedef MPMCommonPluginCtx *(*create_t)(); + +typedef int32_t (*start_t)(MPMCommonPluginCtx *ctx); + +typedef void (*stop_t)(MPMCommonPluginCtx *ctx); + +typedef void (*destroy_t)(MPMCommonPluginCtx *ctx); + +typedef struct plugin_runtime_tag +{ + create_t create; + start_t start; + stop_t stop; + destroy_t destroy; +} MPMPluginRunTime; + +/** + * structure representing plugin context + */ +typedef struct plugin_context_tag +{ + /** function pointers to the plugin life cycle methods */ + MPMPluginRunTime lifecycle; + + /** plugin handle */ + void *handle; + + /** common plugin context */ + MPMCommonPluginCtx *plugin_ctx; + + /** plugin shared object name */ + char shared_object_name[MAX_SHARED_OBJECT_NAME_LENGTH]; + + MPMCallback callbackClient; +} MPMPluginContext; + +std::vector g_LoadedPlugins; + +/** + * This is MPM library API to start the thread for handling response + * messages from the plugins (child processes) + */ + +void startReadResponseThread(); + +/** + * This is MPM library API to terminate the thread for handling response + * messages from the plugins (child processes) + */ +void stopReadResponseThread(); + +/** + * This function runs as a thread which is running to handle + * the response messages from the plugins(child processes) + */ +static void *readResponse(void *) +{ + int status = 0; + fd_set readfds; + struct timeval tv; + + std::vector::iterator loadedPluginsItr; + + std::vector *loadedPlugins = &g_LoadedPlugins; + + tv.tv_sec = 5; + tv.tv_usec = 0; + + while (true) + { + if (exitResponseThread == true) + { + OIC_LOG(DEBUG, TAG, "Exiting readResponse thread"); + break; + } + int maxFd = -1; + + FD_ZERO(&(readfds)); + + loadedPluginsItr = loadedPlugins->begin(); + + for ( ; loadedPluginsItr != loadedPlugins->end(); ) + { + MPMCommonPluginCtx *ctx = (*loadedPluginsItr).plugin_ctx; + if (ctx->started) + { + FD_SET(ctx->parent_reads_fds.read_fd, &(readfds)); + if (maxFd < ctx->parent_reads_fds.read_fd) + { + maxFd = ctx->parent_reads_fds.read_fd; + } + } + loadedPluginsItr++; + } + + if (-1 == select(maxFd + 1, &(readfds), NULL, NULL, &tv)) + { + continue; + } + + loadedPluginsItr = loadedPlugins->begin(); + + for ( ; loadedPluginsItr != loadedPlugins->end(); ) + { + MPMCommonPluginCtx *ctx = (*loadedPluginsItr).plugin_ctx; + if (ctx == NULL) + { + loadedPluginsItr++; + continue; + } + if (ctx->started) + { + if (FD_ISSET(ctx->parent_reads_fds.read_fd, &(readfds))) + { + pid_t childStat = waitpid(ctx->child_pid, &status, WNOHANG); + MPMPipeMessage pipe_message; + ssize_t readbytes = 0; + + pipe_message.payloadSize = 0; + pipe_message.msgType = MPM_NOMSG; + pipe_message.payload = NULL; + readbytes = MPMReadPipeMessage(ctx->parent_reads_fds.read_fd, &pipe_message); + if ((childStat != 0) || (readbytes <= 0)) + { + OIC_LOG_V(DEBUG, TAG, "Plugin %s is exited", + (*loadedPluginsItr).shared_object_name); + if (pipe_message.payloadSize > 0) + { + OICFree((void*)pipe_message.payload); + pipe_message.payload = NULL; + pipe_message.payloadSize = 0; + } + ctx->started = false; + } + else + { + (*loadedPluginsItr).callbackClient((uint32_t)pipe_message.msgType, + (MPMMessage)pipe_message.payload, + pipe_message.payloadSize, + (*loadedPluginsItr).shared_object_name); + + pipe_message.msgType = MPM_NOMSG; + if (pipe_message.payloadSize > 0) + { + OICFree((void*)pipe_message.payload); + pipe_message.payload = NULL; + pipe_message.payloadSize = 0; + } + + } + } + } + loadedPluginsItr++; + } + + sleep(1); + } + + return (void *)loadedPlugins; +} + +MPMResult MPMLoad(MPMPluginHandle *pluginHandle, const char *pluginName, MPMCallback callback, + const char *reconnect_file_name) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + MPMPluginRunTime *functionSymbolTable; + + if ((pluginName == NULL)) + { + OIC_LOG(ERROR, TAG, "PluginName is NULL"); + return result; + } + + MPMPluginContext *plugin_instance = (MPMPluginContext *)OICCalloc(1, sizeof(MPMPluginContext)); + + if (plugin_instance == NULL) + { + OIC_LOG(ERROR, TAG, "Unable to allocate context."); + return MPM_RESULT_MEMORY_ERROR; + } + plugin_instance->plugin_ctx = NULL; + + /* + * Now let's load the plugin and resolve the exported functions of the plugin + * The exported functions of interest: create, start, stop, destroy + */ + plugin_instance->handle = dlopen(pluginName, RTLD_LAZY); + + if (!plugin_instance->handle) + { + OIC_LOG_V(ERROR, TAG, "Error loading %s", pluginName); + OIC_LOG_V(ERROR, TAG, "Error message %s", dlerror()); + goto CLEANUP; + } + + strncpy(plugin_instance->shared_object_name, pluginName, MAX_SHARED_OBJECT_NAME_LENGTH-1); + + OIC_LOG(DEBUG, TAG, "Resolving function symbols"); + functionSymbolTable = (MPMPluginRunTime *) dlsym(plugin_instance->handle, "plugin_funcs"); + + if (functionSymbolTable == NULL) + { + OIC_LOG_V(ERROR, TAG, "Error loading function symbols from %s", pluginName); + goto CLEANUP; + } + + plugin_instance->lifecycle.create = functionSymbolTable->create; + plugin_instance->lifecycle.start = functionSymbolTable->start; + plugin_instance->lifecycle.stop = functionSymbolTable->stop; + plugin_instance->lifecycle.destroy = functionSymbolTable->destroy; + + // Time to call the entry points (create and start) + OIC_LOG_V(INFO, TAG, "Calling create on \"%s\":", pluginName); + + plugin_instance->plugin_ctx = (*(plugin_instance->lifecycle.create))(); + + if (NULL == plugin_instance->plugin_ctx) + { + OIC_LOG_V(ERROR, TAG, "Failed to create %s", pluginName); + goto CLEANUP; + } + if (reconnect_file_name != NULL) + { + strncpy(plugin_instance->plugin_ctx->reconnect_file_name, reconnect_file_name, + MPM_MAX_FILE_NAME_LENGTH - 1); + } + else + { + plugin_instance->plugin_ctx->reconnect_file_name[0] = '\0'; + } + OIC_LOG_V(INFO, TAG, "Calling start on \"%s\":", pluginName); + + result = (MPMResult)(*(plugin_instance->lifecycle.start))(plugin_instance->plugin_ctx); + if (MPM_RESULT_OK == result) + { + OIC_LOG(INFO, TAG, "Plugin start successful."); + *pluginHandle = (MPMPluginHandle)(plugin_instance); + + MPMPluginContext p_context; + p_context.lifecycle.create = plugin_instance->lifecycle.create; + p_context.lifecycle.start = plugin_instance->lifecycle.start; + p_context.lifecycle.stop = plugin_instance->lifecycle.stop; + p_context.lifecycle.destroy = plugin_instance->lifecycle.destroy; + p_context.handle = plugin_instance->handle; + + p_context.plugin_ctx = plugin_instance->plugin_ctx; + + strncpy(p_context.shared_object_name, plugin_instance->shared_object_name, + sizeof(p_context.shared_object_name) - 1); + p_context.shared_object_name[sizeof(p_context.shared_object_name) - 1] = '\0'; + + p_context.callbackClient = callback; + + g_LoadedPlugins.push_back(p_context); + + if (g_LoadedPlugins.size() == 1) + { + startReadResponseThread(); + } + + return result; + } + + OIC_LOG_V(ERROR, TAG, "Failed to start \"%s\":", pluginName); + OIC_LOG(ERROR, TAG, "Are the authorization files correct?"); + +CLEANUP: + if (plugin_instance->plugin_ctx) + { + OICFree(plugin_instance->plugin_ctx); + } + OICFree(plugin_instance); + return MPM_RESULT_CREATED_FAILED; +} + +void startReadResponseThread() +{ + //Create a thread to handle the responses from plugin processes + int error = pthread_create(&readResponsethreadhandle, NULL, readResponse, NULL); + if (error != 0) + { + OIC_LOG(ERROR, TAG, "readResponse thread could not be started"); + } +} + +void stopReadResponseThread() +{ + /* set this variable to terminate the readResponse thread */ + exitResponseThread = true; + //terminate readResponse thread + pthread_join(readResponsethreadhandle, NULL); +} + +MPMResult MPMUnload(MPMPluginHandle pluginHandle) +{ + if (pluginHandle == NULL) + { + OIC_LOG(ERROR, TAG, "plugin_ctx in NULL"); + return MPM_RESULT_INTERNAL_ERROR; + } + + MPMPluginContext *plugin_instance = (MPMPluginContext *)(pluginHandle); + + /* stop and destroy the plugin */ + OIC_LOG_V(INFO, TAG, "Calling stop on \"%s\":", plugin_instance->shared_object_name); + (*(plugin_instance->lifecycle.stop))(plugin_instance->plugin_ctx); + + OIC_LOG_V(INFO, TAG, "Calling destroy on \"%s\":", plugin_instance->shared_object_name); + (*(plugin_instance->lifecycle.destroy))(plugin_instance->plugin_ctx); + dlclose(plugin_instance->handle); + + std::vector::iterator p_itr; + for (p_itr = g_LoadedPlugins.begin(); p_itr != g_LoadedPlugins.end(); ) + { + if (strncmp((*p_itr).shared_object_name, plugin_instance->shared_object_name, + strlen((*p_itr).shared_object_name)) == 0) + { + OIC_LOG_V(INFO, TAG, "plugin name %s", plugin_instance->shared_object_name); + p_itr = g_LoadedPlugins.erase(p_itr); + break; + } + else + { + p_itr++; + } + } + + OICFree(plugin_instance); + + if (g_LoadedPlugins.size() == 0) + { + stopReadResponseThread(); + } + + return MPM_RESULT_OK; +} + +/** + * This function performs SCAN, ADD, REMOVE and RECONNECT functionality depending on + * MPMMessageType paramaeter. + * @param[in] pluginHandle Handle of the plugin to which the message + * is to be sent + * @param[in] message Message to be sent over the pipe to the plugin + * @param[in] size Size of the message in bytes + * @param[in] type Type of the message (MPM_SCAN, MPM_ADD, etc.) + * + * @return MPM_RESULT_OK if success, MPM_RESULT_INTERNAL_ERROR upon failure +*/ +MPMResult MPMSendPipeMessage(MPMPluginHandle pluginHandle, MPMMessage message, size_t size, + MPMMessageType type) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + if (pluginHandle == NULL) + { + OIC_LOG(ERROR, TAG, "plugin_ctx in NULL"); + return result; + } + + MPMPluginContext *plugin_instance = (MPMPluginContext *)(pluginHandle); + + if (plugin_instance->plugin_ctx->started) + { + OIC_LOG_V(DEBUG, TAG, "the plugin %s is started", plugin_instance->shared_object_name); + MPMPipeMessage pipe_message; + + pipe_message.msgType = type; + pipe_message.payloadSize = size; + pipe_message.payload = (uint8_t *)message; + MPMCommonPluginCtx *ctx = (MPMCommonPluginCtx *) (plugin_instance->plugin_ctx); + result = MPMWritePipeMessage(ctx->child_reads_fds.write_fd, &pipe_message); + + pipe_message.msgType = MPM_NOMSG; + pipe_message.payloadSize = 0; + + } + else + { + OIC_LOG_V(ERROR, TAG, "the plugin %s is not yet started", plugin_instance->shared_object_name); + } + return result; +} + +MPMResult MPMScan(MPMPluginHandle pluginHandle, MPMMessage message, size_t size) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + result = MPMSendPipeMessage(pluginHandle, message, size, MPM_SCAN); + return result; +} + +MPMResult MPMAddDevice(MPMPluginHandle pluginHandle, MPMMessage message, size_t size) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + result = MPMSendPipeMessage(pluginHandle, message, size, MPM_ADD); + return result; +} + +MPMResult MPMRemoveDevice(MPMPluginHandle pluginHandle, MPMMessage message, size_t size) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + result = MPMSendPipeMessage(pluginHandle, message, size, MPM_REMOVE); + return result; +} + +MPMResult MPMReconnectDevice(MPMPluginHandle pluginHandle, MPMMessage message, size_t size) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + result = MPMSendPipeMessage(pluginHandle, message, size, MPM_RECONNECT); + return result; +} diff --git a/bridging/mini_plugin_manager/miniPluginManager.h b/bridging/mini_plugin_manager/miniPluginManager.h new file mode 100644 index 0000000..3e92376 --- /dev/null +++ b/bridging/mini_plugin_manager/miniPluginManager.h @@ -0,0 +1,140 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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. +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// + +/* This file contains the "C" plugin interface definition. This header file + * and its corresponding implementation file is intended to be shared among + * ALL plugins. Modification of this file is not necessary for the construction + * of a new plugin. + */ + +#include +#include +#include +#include "mpmErrorCode.h" +#include + +#ifndef __MINI_PLUGIN_MANAGER_H__ +#define __MINI_PLUGIN_MANAGER_H__ + +#ifdef __cplusplus +extern "C" { +#endif +/******************************************************************************* + * defines go here + ******************************************************************************/ + +#define MAX_SHARED_OBJECT_NAME_LENGTH 300 +#define MAX_SHARED_OBJECTS_LOADED 20 + +/******************************************************************************* + * type defines and structure definitions go here + ******************************************************************************/ + +typedef void *MPMPluginHandle; +typedef void *MPMMessage; + +/** MPMCallback - for receiving the responses from the plugin */ +typedef MPMCbResult (* MPMCallback)(uint32_t msgType, MPMMessage message, size_t size, + const char *pluginName); + +/** + * This method sends a scan request to the plugin + * @paran[in] pluginHandle Plugin handle for the specific plugin + * @param[in] message Scan request to be passed from the client to + * the mini_plugin_manager + * @param[in] size Size of the message + * + * @return MPM_RESULT_OK if no errors, else ERROR CODES defined in mpmErrorCode.h in case of error +*/ +MPMResult MPMScan(MPMPluginHandle pluginHandle, MPMMessage message, size_t size); + +/** + * This method sends a add device request to the plugin + * @param[in] pluginHandle Plugin handle for the specific plugin + * @param[in] message Add request to be passed from the client to + * the mini_plugin_manager + * @param[in] size Size of the message + * + * @return MPM_RESULT_OK if no error, else ERROR CODES defined in mpmErrorCode.h in case of error +*/ +MPMResult MPMAddDevice(MPMPluginHandle pluginHandle, MPMMessage message, size_t size); + +/** + * This method sends a reconnect request to the plugin + * @param[in] pluginHandle Plugin handle for the specific plugin + * @param[in] message Request request to be passed from the client to + * the mini_plugin_manager + * @param[in] size Size of the message + * + * @return MPM_RESULT_OK if no error, else ERROR CODES defined in mpmErrorCode.h in case of error +*/ +MPMResult MPMReconnectDevice(MPMPluginHandle pluginHandle, MPMMessage message, size_t size); + +/** + * This method sends a remove device request to the plugin + * @param[in] pluginHandle Plugin handle for the specific plugin + * @param[in] message Remove request to be passed from the client to + * the mini_plugin_manager + * @param[in] size Size of the message + * + * @return MPM_RESULT_OK if no error, else ERROR CODES defined in mpmErrorCode.h in case of error +*/ +MPMResult MPMRemoveDevice(MPMPluginHandle pluginHandle, MPMMessage message, size_t size); + +/** + * This is MPM library API to load the Plugins. It performs + * following functionality as: + * 1. Allocates memory to plugin_ctx + * 2. Opens shared library + * 3. Resolves the function symbols + * 4. Calls create() and start() for the plugins + * + * @param[out] pluginHandle Plugin Handle to be handed back to + * mpm_client application + * @param[in] pluginName Name of the shared library to be loaded. + * @param[in] callback Callback function to be invoked upon SARR responses from + * plugin to the client + * @param[in] filename Reconnect filename each plugin would maintain, currently not used + * + * @return MPM_RESULT_OK if no errors, else ERROR CODES defined in mpmErrorCode.h in case of error + */ +MPMResult MPMLoad(MPMPluginHandle *pluginHandle, const char *pluginName, MPMCallback callback, + const char *filename); + +/** + * This is MPM library API to unload the Plugins. It performs + * following functionality as: + * 1. Calls stop() and destroy() for the plugin + * 2. Closes shared library + * 3. deallocates memory allocated to plugin_ctx + * + * @param[in] pluginHandle Plugin handle sent by the + * mpm_client application + * + * @return MPM_RESULT_OK if no errors, MPM_RESULT_INTERNAL_ERROR if stack process error + */ +MPMResult MPMUnload(MPMPluginHandle pluginHandle); + +#ifdef __cplusplus +} +#endif // #ifdef __cplusplus + +#endif /* __MINI_PLUGIN_MANAGER_H__ */ diff --git a/bridging/mpm_client/MPMSampleClient.cpp b/bridging/mpm_client/MPMSampleClient.cpp new file mode 100644 index 0000000..08e2d8d --- /dev/null +++ b/bridging/mpm_client/MPMSampleClient.cpp @@ -0,0 +1,606 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sqlite3.h" +#include + +#include "miniPluginManager.h" + +using namespace std; + +#define TERMINAL_BOLD_TEXT_RED "\033[1;31m" +#define TERMINAL_ATTRIBUTE_RESET "\033[0m" + +#define BIND_INDEX_FIRST 1 +#define BIND_INDEX_SECOND 2 +#define BIND_INDEX_THIRD 3 +#define SQLITE_INSERT_T "INSERT INTO RECONNECT VALUES(?,?,?)" +#define SQLITE_GET_RECONNECT_FILE "SELECT RECONNECT_FILE from RECONNECT where PLUGIN_NAME=?" +#define SQLITE_DELETE_DEVICE "DELETE FROM RECONNECT WHERE URI=?" +#define SQLITE_SELECT_RECONNECT_FILE "SELECT RECONNECT_FILE from RECONNECT where URI=?" + +#define RECONNECT_FILE_LENGTH 260 + +map g_loadedPlugins; +std::atomic_bool keepEventLoop(true); +bool autoAddMode = false; + +std::string rawUsageMessage = + R"( +Usage: sample_app [OPTION]... PLUGINS_TO_LOAD... + +Load and unload plugins, send them commands after loading etc. +Give valid inputs! This is just an example client and input is not sanitized to keep +the client minimal. + +-n Start plugins in NON secure mode. Default is secure. + +-a Start this client in auto add mode. + This will cause the client send an ADD message with the same message as in + the scan response. + +-h View this message. +)"; + +template +void log(T t) +{ + cout << TERMINAL_BOLD_TEXT_RED << t << TERMINAL_ATTRIBUTE_RESET; +} +template +void log(T t, Args... args) +{ + cout << TERMINAL_BOLD_TEXT_RED << t << TERMINAL_ATTRIBUTE_RESET; + log(args...); +} + +enum pluginCommands { SCAN, ADD, REMOVE, UNLOAD, MAX_COMMANDS }; + +/** + * Macro to verify sqlite success. + * eg: VERIFY_NON_NULL(TAG, ptrData, ERROR,OC_STACK_ERROR); + */ +#define PDM_VERIFY_SQLITE_OK(tag, arg, logLevel) do{ if (SQLITE_OK != (arg)) \ + { log("Error in " #arg ", Error Message: ", \ + sqlite3_errmsg(db), "\n"); return; }}while (0) + +sqlite3 *db; + +void printUsage() +{ + std::cout << rawUsageMessage << "\n\n"; +} + +static int sqlCallback(void *data, int argc, char **argv, char **azColName) +{ + int i; + + log("Data: ", (const char*)data, "\n"); + for(i=0; i::iterator itr = g_loadedPlugins.begin(); itr != g_loadedPlugins.end(); + ++itr) + { + log("\t", no, ":", itr->first, "\n"); + ++no; + } +} + +void printCommands() +{ + log("Commands:", "\t" , SCAN , ":" , "SCAN ", ADD , ":" , "ADD ", REMOVE , ":" , "REMOVE " + , UNLOAD , ":" , "UNLOAD" , "\n"); +} + +void unloadCommand(MPMPluginHandle handle) +{ + std::string uri; + for (map::iterator itr = g_loadedPlugins.begin(); itr != g_loadedPlugins.end(); + ++itr) + { + if (itr->second == handle) + { + uri = itr->first; + } + } + log("Unloading ", uri, "\n"); + MPMUnload(handle); + g_loadedPlugins.erase(uri); +} + +void addCommand(MPMPluginHandle handle) +{ + std::string message; + log("What message to send to ADD (Usually URI of a \"scanned\" resource) : "); + std::cin >> message; + + log("Sending ", message.c_str(), "\n"); + MPMAddDevice(handle, const_cast (message.c_str()), message.size() + 1); +} + +void removeCommand(MPMPluginHandle handle) +{ + std::string message; + log("What message to send to REMOVE (Usually URI of a \"added\" resource) : "); + std::cin >> message; + + log("Sending ", message.c_str(), "\n"); + MPMRemoveDevice(handle, const_cast (message.c_str()), message.size() + 1); +} + +void scanCommand(MPMPluginHandle handle) +{ + MPMScan(handle, NULL, 0); +} + + +void processCommand (MPMPluginHandle handle, int commandNo) +{ + switch(commandNo) + { + case UNLOAD: + unloadCommand(handle); + break; + case SCAN: + scanCommand(handle); + break; + case ADD: + addCommand(handle); + break; + case REMOVE: + removeCommand(handle); + break; + default: + log("Invalid command\n"); + break; + } +} +void getCommandEventLoop() +{ + while (keepEventLoop) + { + std::string input; + int plugin_no = -1, commandNo = -1; + + std::cout << endl << endl; + if (g_loadedPlugins.empty()) + { + log("No plugins loaded. We're done here. Bye!\n"); + break; + } + printPlugins(); + std::cout << "\n"; + printCommands(); + log("Enter blank line to see more logs.\n"); + log("Enter plugin_no command_no : "); + while (getline(cin, input )) + { + if (input != "\n") + { + stringstream ss(input); + ss >> plugin_no >> commandNo; + break; + } + } + + if (commandNo != -1 || plugin_no != -1) + { + MPMPluginHandle selectedPlugin = NULL; + int i = 0; + + for (map::iterator itr = g_loadedPlugins.begin(); + itr != g_loadedPlugins.end(); + ++itr) + { + if (i == plugin_no) + { + selectedPlugin = itr->second; + break; + } + ++i; + } + processCommand (selectedPlugin, commandNo); + } + } + for (map::iterator itr = g_loadedPlugins.begin(); + itr != g_loadedPlugins.end(); + ++itr) + { + unloadCommand(itr->second); + } +} + +void loadCommandLinePlugins (std::vector &pluginsToLoad) +{ + for (uint32_t i = 0; i < pluginsToLoad.size(); ++i) + { + log("Loading ", pluginsToLoad[i], "\n"); + loadPlugin(pluginsToLoad[i]); + } +} + +void signalHandler(int signal) +{ + (void) signal; + keepEventLoop = false; +} + +void openDatabase() +{ + int rc; + char *zErrMsg = 0; + std::string sql; + rc = sqlite3_open("reconnect.db", &db); + if (rc) + { + log("Can't open database: ", sqlite3_errmsg(db), "\n"); + return; + } + else + { + log("Opened database successfully\n"); + } + /* Create SQL statement */ + sql = "CREATE TABLE IF NOT EXISTS RECONNECT(" \ + "URI TEXT PRIMARY KEY NOT NULL," \ + "PLUGIN_NAME TEXT NOT NULL," \ + "RECONNECT_FILE TEXT NOT NULL);"; + + /* Execute SQL statement */ + rc = sqlite3_exec(db, sql.c_str(), NULL, 0, &zErrMsg); + if (rc != SQLITE_OK) + { + log("SQL error: ", zErrMsg, "\n"); + sqlite3_free(zErrMsg); + } + else + { + log("Table created successfully\n"); + } +} + +void reconnect() +{ + FILE *fp = NULL; + + size_t len = -1; + int ret = 0; + char * metadata = NULL; + sqlite3_stmt *stmt = 0; + int res = 0; + for (map::iterator itr = g_loadedPlugins.begin(); + itr != g_loadedPlugins.end(); + ++itr) + { + res = sqlite3_prepare_v2(db, SQLITE_GET_RECONNECT_FILE, + strlen(SQLITE_GET_RECONNECT_FILE) + 1, &stmt, NULL); + PDM_VERIFY_SQLITE_OK(TAG, res, ERROR); + + char * plugin_name = (char *) calloc( 1, MAX_SHARED_OBJECT_NAME_LENGTH); + if (plugin_name == NULL) + { + log("calloc failed","\n"); + return ; + } + memcpy(plugin_name, (*itr).first.c_str(), (*itr).first.length()); + + res = sqlite3_bind_text(stmt, BIND_INDEX_FIRST, plugin_name, MAX_SHARED_OBJECT_NAME_LENGTH, SQLITE_STATIC); + PDM_VERIFY_SQLITE_OK(TAG, res, ERROR); + while (SQLITE_ROW == sqlite3_step(stmt)) + { + char filename[RECONNECT_FILE_LENGTH]; + const unsigned char *ptr = sqlite3_column_text(stmt, 0); + if (ptr == NULL) + { + log("An internal error occured\n"); + continue; + } + memcpy(filename, ptr, RECONNECT_FILE_LENGTH); + log("reconnect filename is: ", filename, "\n"); + fp = fopen(filename, "r"); + if (fp == NULL) + { + log("File could not be opened\n"); + continue; + } + ret = getline(&metadata, &len, fp); + if (ret == -1) + { + fclose(fp); + continue; + } + fclose(fp); + MPMReconnectDevice(itr->second, metadata, MPM_MAX_METADATA_LEN); + } + printf("no columns returned\n"); + free(metadata); + metadata = NULL; + free(plugin_name); + sqlite3_finalize(stmt); + } +} + + +int main(int argc, char const *argv[]) +{ + vector pluginsToLoad; + pluginsToLoad.clear(); + + for (int i = 1; i < argc; ++i) + { + if (strcmp(argv[i], "-n") == 0) + { + setenv("NONSECURE", "true", 1); + } + else if (strcmp(argv[i], "-a") == 0) + { + autoAddMode = true; + } + else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) + { + printUsage(); + return 0; + } + else + { + pluginsToLoad.push_back(std::string(argv[i])); + } + } + + if (!pluginsToLoad.empty()) + { + loadCommandLinePlugins(pluginsToLoad); + } + else + { + log("At least one plugin needed\n"); + return 0; + } + + openDatabase(); + + std::signal(SIGINT, signalHandler); + + reconnect(); + + std::thread eventLoopThread = std::thread(getCommandEventLoop); + eventLoopThread.join(); + + sqlite3_close(db); + return 0; +} diff --git a/bridging/mpm_client/README b/bridging/mpm_client/README new file mode 100644 index 0000000..67f2309 --- /dev/null +++ b/bridging/mpm_client/README @@ -0,0 +1,31 @@ +General: +The "mpm_sample_client" is intended to show how you can +programmatically control the functionality of the +"minipluginmanager" library. + +1. "mpm_sample_client" awaits user input before doing + anything. You will be prompted with CLI controls + to flex the minipluginmanager library's APIs. + + The only modes of operation are controlled via + command line arguments upon startup. + + + You may issue the flag "-n" such that all + resources created by any plugin are created in + IoTivity's non-secure mode (i.e. no DTLS + encryption is used for the representation of + that particular resource). + (If this flag is not passed in, all + resources will be created in IoTivity's + secure mode (all requests/responses will + use DTLS encryption). + + + Only one plugin will be loaded with the name + specified + + + Operations performed by the "mpm_sample_client" are + as follows: + - Scan - Device Discovery + - Add - Create iotivity resource + - Remove - Delete iotivity resource + - Unload - to unload the plugin and exit mpm_sample_client diff --git a/bridging/mpm_client/SConscript b/bridging/mpm_client/SConscript new file mode 100644 index 0000000..16987e9 --- /dev/null +++ b/bridging/mpm_client/SConscript @@ -0,0 +1,83 @@ +#****************************************************************** +# +# Copyright 2017 Intel Mobile Communications GmbH 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. +# +#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Import('env') +import os +import os.path +target_os = env.get('TARGET_OS') +target_arch = env.get('TARGET_ARCH') +mpmclient_env = env.Clone() +src_dir = env.get('SRC_DIR') +bridging_dir = os.path.join(src_dir, 'bridging') + +###################################################################### +# Build flags +###################################################################### + +def maskFlags(flags): + flags = [flags.replace('-Wl,--no-undefined', '' ) for flags in flags] + return flags +mpmclient_env.PrependUnique(CPPPATH = [ + os.path.join(bridging_dir, 'include'), + os.path.join(bridging_dir, 'mini_plugin_manager'), + os.path.join(bridging_dir, 'mpm_client') + ]) + +# Note: Each of the plugin names need to be listed here after 'minipluginmanager'. +# Otherwise you will need to prepend LD_LIBRARY_PATH to your execution command. +mpmclient_env.PrependUnique(LIBS = ['minipluginmanager']) + +if target_arch in ['x86_64', 'arm64']: + mpmclient_env.AppendUnique(CPPFLAGS = ['-Llib64']) +else: + mpmclient_env.AppendUnique(CPPFLAGS = ['-Llib']) +mpmclient_env.AppendUnique(LIBS = ['pthread']) + +mpmclient_env.AppendUnique(CPPDEFINES = ['TB_LOG']) + +mpmclient_env.AppendUnique(LIBS = ['m', + 'octbstack', + 'ocsrm', + 'connectivity_abstraction', + 'coap', + 'curl' ]) + +mpmclient_env.AppendUnique(RPATH = [os.path.join(env.get('BUILD_DIR'), 'bridging', 'plugins')]) + +if target_os in ['linux', 'tizen']: + mpmclient_env.ParseConfig('pkg-config --cflags --libs sqlite3') +else: + mpmclient_env.AppendUnique(CPPPATH = ['#/extlibs/sqlite3']) + +mpmclient_env['LINKFLAGS'] = maskFlags(env['LINKFLAGS']) +mpmclient_env.AppendUnique(LINKFLAGS = ['-Wl,--allow-shlib-undefined']) +###################################################################### +# Source files and Targets +###################################################################### +mpm_sample_client = mpmclient_env.Program('mpm_sample_client', + ['MPMSampleClient.cpp']) + +list_of_samples = [mpm_sample_client] + +Alias("samples", list_of_samples) + +env.AppendTarget('samples') + + diff --git a/bridging/plugins/lifx_plugin/README b/bridging/plugins/lifx_plugin/README new file mode 100644 index 0000000..55c8b31 --- /dev/null +++ b/bridging/plugins/lifx_plugin/README @@ -0,0 +1,66 @@ +General: +To use this plugin, a config file "lifx.cnf" needs to be populated and placed in +the same directory as the mpm client (i.e. "mpm_client" or "mpm_client_sarr") +generated by IoTivity's build system. + +Note: A LifX light bulb with a non-proxied internet connection is required to use +this plugin. All tests/verifications have been with a "LifX Color 1000 A19" bulb. +This plugin will NOT provision your light bulb for you. You will need to perform +the set-up steps through the LifX app (this can be found in either the iOS or +Android app store) and your own LifX user account. + +What should this file look like? + + See sample "lifx.cnf.sample" file with contents as shown + below(without spaces, tabs or quotes): + + " + + cdf1519ec9998380fff7abcaf03d404401063cec7a2972dc68d4eef3e6f328e2: + + " + +Where to put this file? + The placement of the lifx.cnf file should be where + your mpm client is also: + + /out////bridging/src/mpm_client + + Example: /out/linux/x86_64/release/bridging/src/mpm_client + Depending on your build configuration, the path may + look mildly different. + +What is this key or token? + + The LifX mapping requires usage of the LifX + Developer's API. The usage of this API requires + that every user/developer has their own HTTP API + Token.This HTTP API Token allows the + liblifxplugin.so to perform actions within your + LifX cloud account on your behalf. + +Where can I obtain the token as shown in the above example? + + Use the same username and password you used to set + up your LifX bulb at the following address to + obtain a token and select "Generate Token": + + https://cloud.lifx.com/settings + + Append a colon ":" to the end of the token and + place it in your lifx.cnf file. Do not share this + token with anyone. This token can give someone + complete access to your LifX account. Be careful. + +How can I start this plugin? + + Use the binary executable 'mpm_sample_client' to load and + control this plugin. + + More information on these clients can be found at + /bridging/src/mpm_client/README. + +For proper documentation of this plugin, Mini Plugin +Manager, the client applications, and other plugins, please +perform a query on the "Bridging" or "Bridging Project" at +wiki.iotivity.org. diff --git a/bridging/plugins/lifx_plugin/SConscript b/bridging/plugins/lifx_plugin/SConscript new file mode 100644 index 0000000..914aa8e --- /dev/null +++ b/bridging/plugins/lifx_plugin/SConscript @@ -0,0 +1,98 @@ +#****************************************************************** +# +# Copyright 2017 Intel Mobile Communications GmbH 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. +# +#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +## +# LifX Plugin build script +## + +import os +import os.path + +Import('env') + +target_os = env.get('TARGET_OS') +src_dir = env.get('SRC_DIR') +bridging_path = os.path.join(src_dir, 'bridging') + +lifx_env = env.Clone() + +print "Reading LifX Plugin script" + +###################################################################### +# Build flags +###################################################################### + +def maskFlags(flags): + flags = [flags.replace('-Wl,--no-undefined', '' ) for flags in flags] + return flags + +lifx_env.PrependUnique(CPPPATH = [ os.path.join(src_dir, 'resource', 'c_common', 'oic_malloc', 'include'), + os.path.join(src_dir, 'resource', 'c_common', 'oic_string', 'include'), + os.path.join(src_dir, 'resource', 'c_common'), + os.path.join(src_dir, 'resource', 'oc_logger', 'include'), + os.path.join(src_dir, 'resource', 'csdk', 'logger', 'include'), + os.path.join(src_dir, 'resource', 'csdk', 'include'), + os.path.join(src_dir, 'resource', 'csdk', 'stack', 'include'), + os.path.join(src_dir, 'resource', 'include'), + os.path.join(src_dir, 'extlibs', 'cjson'), + os.path.join(src_dir, 'extlibs', 'tinycbor', 'src'), + os.path.join(src_dir, 'extlibs', 'rapidjson', 'rapidjson', 'include', 'rapidjson') + ]) +lifx_env.AppendUnique(CPPPATH = [ os.path.join(bridging_path, 'include'), + os.path.join(bridging_path, 'plugins', 'lifx_plugin', 'lifx_objects') + ]) + +if target_os not in ['arduino', 'windows']: + lifx_env.AppendUnique(CPPDEFINES = ['WITH_POSIX']) + +if target_os in ['darwin','ios']: + lifx_env.AppendUnique(CPPDEFINES = ['_DARWIN_C_SOURCE']) + +if 'g++' in lifx_env.get('CXX'): + lifx_env.AppendUnique(CXXFLAGS = ['-std=c++0x', '-Wall', '-Wextra', '-Werror']) + +lifx_env.AppendUnique(RPATH = [lifx_env.get('BUILD_DIR')]) +lifx_env.AppendUnique(LIBPATH = [lifx_env.get('BUILD_DIR')]) + +if lifx_env.get('LOGGING'): + lifx_env.AppendUnique(CPPDEFINES = ['TB_LOG']) + +lifx_env['LINKFLAGS'] = maskFlags(env['LINKFLAGS']) +lifx_env.AppendUnique(LINKFLAGS = ['-Wl,--allow-shlib-undefined']) +lifx_env.AppendUnique(LINKFLAGS = ['-Wl,--whole-archive', lifx_env.get('BUILD_DIR') +'libmpmcommon.a','-Wl,-no-whole-archive']) + +lifx_env.AppendUnique(LIBS = ['m', + 'octbstack', + 'ocsrm', + 'connectivity_abstraction', + 'coap', + 'curl' ]) + +##################################################################### +# Source files and Target(s) +###################################################################### +lifx_src = [ + os.path.join(bridging_path, 'plugins', 'lifx_plugin', 'lifxResource.cpp'), + os.path.join(bridging_path, 'plugins', 'lifx_plugin', 'lifx_objects', 'lifx.cpp'), + ] + +lifx_env.AppendUnique(LIFX_SRC = lifx_src) +lifxlib = lifx_env.SharedLibrary('lifxplugin', lifx_env.get('LIFX_SRC')) +lifx_env.InstallTarget(lifxlib, 'lifxplugin') +lifx_env.UserInstallTargetLib(lifxlib, 'lifxplugin') diff --git a/bridging/plugins/lifx_plugin/lifx.cnf.sample b/bridging/plugins/lifx_plugin/lifx.cnf.sample new file mode 100644 index 0000000..5438990 --- /dev/null +++ b/bridging/plugins/lifx_plugin/lifx.cnf.sample @@ -0,0 +1 @@ +cdf1519ec9998380fff7abcaf03d404401063cec7a2972dc68d4eef3e6f328e2: diff --git a/bridging/plugins/lifx_plugin/lifxResource.cpp b/bridging/plugins/lifx_plugin/lifxResource.cpp new file mode 100644 index 0000000..e33bef5 --- /dev/null +++ b/bridging/plugins/lifx_plugin/lifxResource.cpp @@ -0,0 +1,691 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "pluginServer.h" +#include "ocpayload.h" +#include "oic_malloc.h" +#include "oic_string.h" +#include "lifx.h" +#include "logger.h" +#include "ConcurrentIotivityUtils.h" + +MPMPluginCtx *g_pluginCtx = NULL; + +#define DEVICE_NAME "LIFX_BULB" +#define DEVICE_TYPE "LIGHT_BULB" +#define MANUFACTURER_NAME "LIFX" +#define BM 3 +#define MAX_ACCESS_TOKEN_LENGTH 1024 +#define TAG "LIFX_RESOURCE" + +static const char* OIC_BINARY_SWITCH = "oic.r.switch.binary"; +static const char* OIC_BRIGHTNESS_LIGHT = "oic.r.light.brightness"; + +const uint BINARY_SWITCH_CALLBACK = 0; +const uint BRIGHTNESS_CALLBACK = 1; + +static const std::string BINARY_SWITCH_RELATIVE_URI = "/switch"; +static const std::string BRIGHTNESS_RELATIVE_URI = "/brightness"; + +char accessToken[MAX_ACCESS_TOKEN_LENGTH]; +const static char CRED_FILE[] = "./oic_svr_db_lifx.dat"; + +using namespace OC::Bridging; + +typedef struct +{ + char id[MPM_MAX_LENGTH_64]; + char uuid[MPM_MAX_LENGTH_64]; + char label[MPM_MAX_LENGTH_64]; + char user[MPM_MAX_LENGTH_256]; +} LightDetails; + +std::map uriToLifxLightMap; +std::map addedLights; +std::mutex addedLightsLock; + +// Forward declarations. +static void *lightMonitorThread(void *pointer); +OCEntityHandlerResult resourceEntityHandler(OCEntityHandlerFlag flag, + OCEntityHandlerRequest *request, void *callbackParam); + +static LifxLightSharedPtr getLifXLightFromOCFResourceUri(std::string resourceUri); + +FILE *lifxSecurityFile(const char *, const char *mode) +{ + return fopen(CRED_FILE, mode); +} + +MPMResult pluginCreate(MPMPluginCtx **pluginSpecificCtx) +{ + if (g_pluginCtx != NULL) + { + OIC_LOG(ERROR, TAG, "Plugin is already created."); + return MPM_RESULT_ALREADY_CREATED; + } + + MPMPluginCtx *ctx = (MPMPluginCtx *) OICCalloc(1, sizeof(MPMPluginCtx)); + + if (ctx == NULL) + { + OIC_LOG(ERROR, TAG, "Allocation of plugin context failed"); + return MPM_RESULT_INTERNAL_ERROR; + } + + *pluginSpecificCtx = ctx; + g_pluginCtx = ctx; + + ctx->device_name = "Lifx Translator"; + ctx->resource_type = "oic.d.light"; + ctx->open = lifxSecurityFile; + + + FILE *fp = fopen("./lifx.cnf", "r"); + + if (NULL == fp) + { + OIC_LOG(ERROR, TAG, "error loading lifx.cnf file."); + return MPM_RESULT_INTERNAL_ERROR; + } + + if (fgets(accessToken, MAX_ACCESS_TOKEN_LENGTH - 1, fp) == NULL) + { + OIC_LOG(ERROR, TAG, "Failed to read ./lifx.cnf"); + fclose(fp); + return MPM_RESULT_INTERNAL_ERROR; + } + accessToken[strlen(accessToken)-1] = '\0'; + fclose(fp); + + return MPM_RESULT_OK; +} + +MPMResult pluginStart(MPMPluginCtx *ctx) +{ + MPMResult result = MPM_RESULT_INVALID_PARAMETER; + int error = 0; + + if (ctx->started) + { + OIC_LOG(INFO, TAG, "Plugin is already started."); + return MPM_RESULT_ALREADY_STARTED; + } + + ctx->stay_in_process_loop = true; + + //@todo Maybe move this to plugin_add()? once the first light is added? + error = pthread_create(&(ctx->thread_handle), NULL, lightMonitorThread, ctx); + if (error == 0) + { + ctx->started = true; + result = MPM_RESULT_OK; + } + else + { + OIC_LOG_V(ERROR, TAG, "Can't create plugin specific thread :[%s]", strerror(errno)); + pluginStop(ctx); + result = MPM_RESULT_STARTED_FAILED; + } + + OIC_LOG_V(INFO, TAG, "Plugin start return value:%d.", result); + return (result); +} + +MPMResult pluginScan(MPMPluginCtx *, MPMPipeMessage *) +{ + std::vector lightsScanned; + + MPMResult result = LifxLight::getLights(accessToken, lightsScanned); + + for (uint32_t i = 0; i < lightsScanned.size(); ++i) + { + LifxLightSharedPtr light = lightsScanned[i]; + + OIC_LOG_V(INFO, TAG, "Found %s bulb %s(%s).", light->state.connected ? "CONNECTED" : "OFFLINE", + light->config.id.c_str(), light->config.label.c_str()); + + if (!light->state.connected) + { + OIC_LOG(INFO, TAG, "Ignoring OFFLINE light"); + continue; + } + + std::string uri = "/lifx/" + light->config.id; + + if (uriToLifxLightMap.find(uri) != uriToLifxLightMap.end()) + { + OIC_LOG_V(INFO, TAG, "Already found %s. Ignoring", uri.c_str()); + continue; + } + + uriToLifxLightMap[uri] = light; + + MPMSendResponse(uri.c_str(), uri.size(), MPM_SCAN); + } + if (result != MPM_RESULT_OK) + { + OIC_LOG_V(ERROR, TAG, "Failed to fetch lights with error (%d)", result); + return MPM_RESULT_INTERNAL_ERROR; + } + + return MPM_RESULT_OK; + +} + +bool isSecureEnvironmentSet() +{ + char *non_secure_env = getenv("NONSECURE"); + + if (non_secure_env != NULL && (strcmp(non_secure_env, "true") == 0)) + { + OIC_LOG(INFO, TAG, "Creating NON SECURE resources"); + return false; + } + OIC_LOG(INFO, TAG, "Creating SECURE resources"); + return true; +} + +MPMResult createPayloadForMetadata(MPMResourceList **list, const char *uri, const char *res_type, + const char *interface) +{ + MPMResourceList *tempPtr; + tempPtr = (MPMResourceList *) OICCalloc(1, sizeof(MPMResourceList)); + if (!tempPtr) + { + OIC_LOG_V(ERROR, TAG, "failed to allocate memory for tempPtr"); + return MPM_RESULT_OUT_OF_MEMORY; + } + strncpy(tempPtr->rt, res_type, MPM_MAX_LENGTH_64); + strncpy(tempPtr->href, uri, MPM_MAX_URI_LEN); + strncpy(tempPtr->interfaces, interface, MPM_MAX_LENGTH_64); + tempPtr->bitmap = BM; + tempPtr->next = *list; + *list = tempPtr; + return MPM_RESULT_OK; +} + +/*** + * Creates 2 OCF resources for a LifxLight. One for the switch to turn it on and off + * and one for brightness. + * + * @param[in] uri Base uri. Switch and brightness uris are baseUri appended with "/switch" & "/brightness" + * @return MPM_RESULT_OK + */ +MPMResult createOCFResources(const std::string &uri) +{ + uint8_t resourceProperties = (OC_OBSERVABLE | OC_DISCOVERABLE); + if (isSecureEnvironmentSet()) + { + resourceProperties |= OC_SECURE; + } + + std::string switchUri = uri + BINARY_SWITCH_RELATIVE_URI; + ConcurrentIotivityUtils::queueCreateResource(switchUri, OIC_BINARY_SWITCH, OC_RSRVD_INTERFACE_ACTUATOR, + resourceEntityHandler, + (void *) BINARY_SWITCH_CALLBACK, resourceProperties); + + std::string brightnessUri = uri + BRIGHTNESS_RELATIVE_URI; + ConcurrentIotivityUtils::queueCreateResource(brightnessUri, OIC_BRIGHTNESS_LIGHT, OC_RSRVD_INTERFACE_ACTUATOR, + resourceEntityHandler, + (void *) BRIGHTNESS_CALLBACK, resourceProperties); + + return MPM_RESULT_OK; +} + +MPMResult deleteOCFResources(const std::string &uri) +{ + ConcurrentIotivityUtils::queueDeleteResource(uri + BINARY_SWITCH_RELATIVE_URI); + ConcurrentIotivityUtils::queueDeleteResource(uri + BRIGHTNESS_RELATIVE_URI); + return MPM_RESULT_OK; +} + +MPMResult pluginAdd(MPMPluginCtx *, MPMPipeMessage * message) +{ + if (message->payloadSize <= 0 && message->payload == NULL) + { + OIC_LOG(ERROR, TAG, "No payload received, failed to add device"); + return MPM_RESULT_INTERNAL_ERROR; + } + + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + uint8_t *buff = NULL; + MPMResourceList *list = NULL; + MPMDeviceSpecificData deviceConfiguration; + LightDetails pluginSpecificDetails; + std::string user; + memset(&pluginSpecificDetails, 0, sizeof(LightDetails)); + memset(&deviceConfiguration, 0, sizeof(MPMDeviceSpecificData)); + + std::string uri = reinterpret_cast(message->payload); + + std::lock_guard lock(addedLightsLock); + if (addedLights.find(uri) != addedLights.end()) + { + OIC_LOG_V(ERROR, TAG, "%s already added", uri.c_str()); + return MPM_RESULT_ALREADY_CREATED; + } + if (uriToLifxLightMap.find(uri) == uriToLifxLightMap.end()) + { + OIC_LOG_V(ERROR, TAG, "%s was NOT discovered in a scan", uri.c_str()); + return MPM_RESULT_INTERNAL_ERROR; + } + + createOCFResources(uri); + + buff = (uint8_t *)OICCalloc(1, MPM_MAX_METADATA_LEN); + if (buff == NULL) + { + OIC_LOG_V(ERROR, TAG, "Failed to allocate memory for reconnect buffer"); + return MPM_RESULT_OUT_OF_MEMORY; + } + + std::string switchUri = uri + BINARY_SWITCH_RELATIVE_URI; + result = createPayloadForMetadata(&list, switchUri.c_str(), OIC_BINARY_SWITCH, + OC_RSRVD_INTERFACE_ACTUATOR); + + std::string brightnessUri = uri + BRIGHTNESS_RELATIVE_URI; + result = createPayloadForMetadata(&list, brightnessUri.c_str(), OIC_BRIGHTNESS_LIGHT, + OC_RSRVD_INTERFACE_ACTUATOR); + if (result == MPM_RESULT_OUT_OF_MEMORY) + { + OIC_LOG(ERROR, TAG, "Failed to create payload for metadata"); + return result; + } + + LifxLightSharedPtr targetLight = uriToLifxLightMap[uri]; + targetLight->getUser(user); + + // filling plugin specific details + strncpy(pluginSpecificDetails.id, targetLight->config.id.c_str(), MPM_MAX_LENGTH_64); + strncpy(pluginSpecificDetails.label, targetLight->config.label.c_str(), MPM_MAX_LENGTH_64); + strncpy(pluginSpecificDetails.uuid, targetLight->config.uuid.c_str(), MPM_MAX_LENGTH_64); + strncpy(pluginSpecificDetails.user, user.c_str(), MPM_MAX_LENGTH_256); + + // filling device specific details + strncpy(deviceConfiguration.devName, DEVICE_NAME, MPM_MAX_LENGTH_64); + strncpy(deviceConfiguration.devType, DEVICE_TYPE, MPM_MAX_LENGTH_64); + strncpy(deviceConfiguration.manufacturerName, MANUFACTURER_NAME, MPM_MAX_LENGTH_256); + + MPMFormMetaData(list, &deviceConfiguration, buff, MPM_MAX_METADATA_LEN, &pluginSpecificDetails, + sizeof(pluginSpecificDetails)); + + addedLights[uri] = uriToLifxLightMap[uri]; + + MPMAddResponse response; + strncpy(response.uri, uri.c_str(), MPM_MAX_URI_LEN); + memcpy(response.metadata, buff, MPM_MAX_METADATA_LEN); + + size_t size = sizeof(MPMAddResponse); + + MPMSendResponse(&response, size, MPM_ADD); + + OICFree(buff); + return result; +} + +MPMResult pluginRemove(MPMPluginCtx *, MPMPipeMessage *message) +{ + if (message->payloadSize <= 0 && message->payload == NULL) + { + OIC_LOG(ERROR, TAG, "No paylaod received, failed to remove device"); + return MPM_RESULT_INTERNAL_ERROR; + } + std::string uri = reinterpret_cast(message->payload); + OIC_LOG_V(DEBUG, TAG, "device uri to be removed - %s ", uri.c_str()); + + std::lock_guard lock(addedLightsLock); + if (addedLights.find(uri) == addedLights.end()) + { + OIC_LOG(ERROR, TAG, "Device to be removed is not added yet"); + return MPM_RESULT_NOT_PRESENT; + } + + deleteOCFResources(uri); + + addedLights.erase(uri); + uriToLifxLightMap.erase(uri); + + MPMSendResponse(uri.c_str(), uri.size(), MPM_REMOVE); + return MPM_RESULT_OK; +} + +MPMResult pluginReconnect(MPMPluginCtx *, MPMPipeMessage *message) +{ + MPMResourceList *list = NULL, *temp = NULL; + void *pluginSpecificDetails = NULL; + + if (message->payloadSize <= 0 && message->payload == NULL) + { + OIC_LOG(ERROR, TAG, "No paylaod received, failed to reconnect"); + return MPM_RESULT_INTERNAL_ERROR; + } + + MPMParseMetaData(message->payload, MPM_MAX_METADATA_LEN, &list, &pluginSpecificDetails); + + LightDetails *lightDetails = (LightDetails *)pluginSpecificDetails; + + OIC_LOG_V(DEBUG, TAG, "\n Reconnect Details \nid - %s\nuuid - %s\nlabel - %s\nuser - %s\n", + lightDetails->id, lightDetails->uuid, lightDetails->label, lightDetails->user); + + LifxLight::lightState state; + LifxLight::lightConfig cfg(lightDetails->id, lightDetails->uuid, lightDetails->label); + std::string uri = "/lifx/" + cfg.id; + std::shared_ptr light = std::make_shared(state, cfg, lightDetails->user); + + createOCFResources(uri); + uriToLifxLightMap[uri] = light; + addedLights[uri] = uriToLifxLightMap[uri]; + + while (list) + { + temp = list; + list = list->next; + OICFree(temp); + } + free(lightDetails); + return MPM_RESULT_OK; +} + +MPMResult pluginStop(MPMPluginCtx *pluginSpecificCtx) +{ + MPMResult result = MPM_RESULT_OK; + + MPMPluginCtx *ctx = pluginSpecificCtx; + + if (NULL != ctx && g_pluginCtx != NULL) + { + addedLights.clear(); + uriToLifxLightMap.clear(); + + if (ctx->started) + { + ctx->stay_in_process_loop = false; + pthread_join(ctx->thread_handle, NULL); + ctx->started = false; + } + } + + OIC_LOG_V(INFO, TAG, "Plugin stop's return value:%d", result); + return (result); + +} + +MPMResult pluginDestroy(MPMPluginCtx *pluginSpecificCtx) +{ + MPMResult result = MPM_RESULT_INTERNAL_ERROR; + MPMPluginCtx *ctx = (MPMPluginCtx *) pluginSpecificCtx; + + if (ctx != NULL && g_pluginCtx != NULL) + { + if (ctx->started) + { + pluginStop(pluginSpecificCtx); + } + // freeing the resource allocated in create + OICFree(ctx); + g_pluginCtx = NULL; + result = MPM_RESULT_OK; + } + + OIC_LOG_V(INFO, TAG, "Plugin destroy's return value:%d", result); + + return (result); +} + +OCRepPayload *addCommonLifXProperties(const LifxLightSharedPtr &l, OCRepPayload *payload) +{ + if (!OCRepPayloadSetPropString(payload, "x.com.intel.label", l->config.label.c_str())) + { + throw std::runtime_error("failed to set label"); + } + + if (!OCRepPayloadSetPropDouble(payload, "x.com.intel.secondsSinceLastSeen", + l->state.secondsSinceLastSeen)) + { + throw std::runtime_error("failed to set secondsSinceLastSeen"); + } + return payload; +} + +OCRepPayload *getBinarySwitchPayload(LifxLightSharedPtr l) +{ + std::unique_ptr payload {OCRepPayloadCreate(), + OCRepPayloadDestroy }; + + if (!payload) + { + throw std::runtime_error("payload cannot be NULL"); + } + + if (!OCRepPayloadSetPropBool(payload.get(), "value", l->state.power)) + { + throw std::runtime_error("failed to set binary switch value in the payload"); + } + return addCommonLifXProperties(l, payload.release()); +} + +OCRepPayload *getBrightnessPayload(LifxLightSharedPtr l) +{ + std::unique_ptr payload {OCRepPayloadCreate(), + OCRepPayloadDestroy }; + if (!payload) + { + throw std::runtime_error("payload cannot be NULL"); + } + + // Convert LifX [0.0, 1.0] to OCF [0, 100]. + if (!OCRepPayloadSetPropInt(payload.get(), "brightness", (int64_t) (l->state.brightness * 100.0))) + { + throw std::runtime_error("failed to set brightness"); + } + return addCommonLifXProperties(l, payload.release()); + +} + +OCRepPayload *processGetRequest(LifxLightSharedPtr l, uintptr_t resourceTypeInCallback) +{ + if (resourceTypeInCallback == BINARY_SWITCH_CALLBACK) + { + return getBinarySwitchPayload(l); + } + else if (resourceTypeInCallback == BRIGHTNESS_CALLBACK) + { + return getBrightnessPayload(l); + } + return NULL; +} + +OCEntityHandlerResult processBinarySwitchUpdate(OCRepPayload *payload, LifxLightSharedPtr l) +{ + bool power = false; + if (!OCRepPayloadGetPropBool(payload, "value", &power)) + { + throw std::runtime_error("Payload must contain \"value\""); + } + + MPMResult result = l->setPower(power); + + if (result != MPM_RESULT_OK) + { + throw std::runtime_error("Error setting power for PUT request"); + } + return OC_EH_OK; +} + +OCEntityHandlerResult processBrightnessUpdate(OCRepPayload *payload, LifxLightSharedPtr l) +{ + int64_t ocfBrightness = 0; + if (!OCRepPayloadGetPropInt(payload, "brightness", &ocfBrightness)) + { + throw std::runtime_error("Payload must contain \"brightness\""); + } + // OCF brightness is [0, 100] and Lifx is [0.0, 1.00] + double lifxBrightness = ocfBrightness / 100.0; + + MPMResult result = l->setBrightness(lifxBrightness); + + if (result != MPM_RESULT_OK) + { + throw std::runtime_error("Error setting brightness for PUT request"); + } + return OC_EH_OK; +} + +OCEntityHandlerResult processPutRequest(OCRepPayload *payload, LifxLightSharedPtr l, + uintptr_t resourceTypeInCallback) +{ + if (payload == NULL) + { + throw std::runtime_error("PUT payload cannot be NULL"); + } + if (resourceTypeInCallback == BINARY_SWITCH_CALLBACK) + { + return processBinarySwitchUpdate(payload, l); + } + else if (resourceTypeInCallback == BRIGHTNESS_CALLBACK) + { + return processBrightnessUpdate(payload, l); + } + return OC_EH_OK; +} + +static LifxLightSharedPtr getLifXLightFromOCFResourceUri(std::string resourceUri) +{ + OIC_LOG_V(INFO, TAG, "Request for %s", resourceUri.c_str()); + std::lock_guard lock(addedLightsLock); + for (auto uriToLifXPair : addedLights) + { + if (resourceUri.find(uriToLifXPair.first) != std::string::npos) + { + return uriToLifXPair.second; + } + } + throw std::runtime_error("Resource " + resourceUri + " not found"); +} + +OCEntityHandlerResult resourceEntityHandler(OCEntityHandlerFlag , + OCEntityHandlerRequest *request, + void *cb) +{ + uintptr_t callBackParamResourceType = (uintptr_t) cb; + OCEntityHandlerResult result = OC_EH_OK; + MPMResult res = MPM_RESULT_OK; + + try + { + std::string uri; + ConcurrentIotivityUtils::getUriFromHandle(request->resource, uri); + + LifxLightSharedPtr targetLight = getLifXLightFromOCFResourceUri(uri); + + switch (request->method) + { + case OC_REST_GET: + // Empty GET case as actual request will be processed after the switch case. + break; + + case OC_REST_PUT: + case OC_REST_POST: + + res = (MPMResult)processPutRequest((OCRepPayload *) request->payload, targetLight, + callBackParamResourceType); + if (res != MPM_RESULT_OK) + result = OC_EH_ERROR; + break; + + default: + OIC_LOG_V(INFO, TAG, "Unsupported method (%d) recieved", request->method); + ConcurrentIotivityUtils::respondToRequestWithError(request, "Unsupported method received", + OC_EH_METHOD_NOT_ALLOWED); + return OC_EH_OK; + } + + OCRepPayload *responsePayload = processGetRequest(targetLight, callBackParamResourceType); + ConcurrentIotivityUtils::respondToRequest(request, responsePayload, result); + OCRepPayloadDestroy(responsePayload); + } + catch (const std::exception &exp) + { + ConcurrentIotivityUtils::respondToRequestWithError(request, exp.what(), OC_EH_ERROR); + return OC_EH_OK; + } + + return OC_EH_OK; +} + +/* This function does not look for new lights. Only monitors existing lights. + * @param[in] pluginSpecificCtx plugin specific context + */ +void *lightMonitorThread(void *pluginSpecificCtx) +{ + MPMPluginCtx *ctx = (MPMPluginCtx *) pluginSpecificCtx; + if (ctx != NULL) + { + while (ctx->stay_in_process_loop) + { + addedLightsLock.lock(); + + for (auto itr : addedLights) + { + LifxLightSharedPtr l = itr.second; + if (!l) + { + continue; + } + LifxLight::lightState oldState = l->state; + + l->refreshState(); + + LifxLight::lightState newState = l->state; + + if (oldState.power != newState.power) + { + ConcurrentIotivityUtils::queueNotifyObservers(itr.first + BINARY_SWITCH_RELATIVE_URI); + } + if (fabs(oldState.brightness - newState.brightness) > + 0.00001) // Lazy epsilon for double equals check. + { + ConcurrentIotivityUtils::queueNotifyObservers(itr.first + BRIGHTNESS_RELATIVE_URI); + } + if (oldState.connected != newState.connected) + { + OIC_LOG_V(INFO, TAG, "%s is %s", l->config.id.c_str(), + l->state.connected ? "ONLINE" : "OFFLINE"); + } + } + + addedLightsLock.unlock(); + sleep(MPM_THREAD_PROCESS_SLEEPTIME); + } + OIC_LOG(INFO, TAG, "Leaving LIFX monitor thread"); + } + pthread_exit(NULL); +} diff --git a/bridging/plugins/lifx_plugin/lifx_objects/lifx.cpp b/bridging/plugins/lifx_plugin/lifx_objects/lifx.cpp new file mode 100644 index 0000000..19babc7 --- /dev/null +++ b/bridging/plugins/lifx_plugin/lifx_objects/lifx.cpp @@ -0,0 +1,255 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 "stringbuffer.h" +#include "writer.h" +#include "curlClient.h" +#include "iotivity_config.h" +#ifdef HAVE_UNISTD_H +#include +#endif +#include "document.h" +#include +#include +#include +#include "lifx.h" +#include "logger.h" + +#define TAG "LIFX" +using namespace OC::Bridging; + +MPMResult parseCloudResponse(const std::string response) +{ + rapidjson::Document doc; + doc.SetObject(); + + if (doc.Parse(response.c_str()).HasParseError()) + { + OIC_LOG(ERROR, TAG, "Parse error in set Power"); + return MPM_RESULT_JSON_ERROR; + } + + if (doc.HasMember("error")) + { + throw std::runtime_error(doc["error"].GetString()); + } + + if (doc.HasMember("results")) + { + const rapidjson::Value &results = doc["results"]; + + if (results.Size() != 1) + { + OIC_LOG(INFO, TAG, "Multi element results. Only considering the first"); + } + + for (rapidjson::SizeType i = 0; i < results.Size(); ++i) + { + // This'll only consider the first one anyway as we only expect 1 element + // This'll have to be changed when we can change multiple light states at once. + std::string status = results[i]["status"].GetString(); + + return status == "ok" ? MPM_RESULT_OK : MPM_RESULT_INTERNAL_ERROR; + } + } + return MPM_RESULT_OK; +} + +MPMResult static parseLightsFromCloudResponse(const std::string response, const std::string user, + std::vector> &parsedLights) +{ + rapidjson::Document lightsJsonResponse; + lightsJsonResponse.SetObject(); + + if (lightsJsonResponse.Parse<0>(response.c_str()).HasParseError()) + { + OIC_LOG_V(ERROR, TAG, "Error parsing JSON:\n%s", response.c_str()); + return MPM_RESULT_JSON_ERROR; + } + + if (!lightsJsonResponse.IsArray()) + { + OIC_LOG_V(ERROR, TAG, "Response is not an array.\n%s", response.c_str()); + return MPM_RESULT_JSON_ERROR; + } + if (lightsJsonResponse.Empty()) + { + OIC_LOG(ERROR, TAG, "Response is empty"); + return MPM_RESULT_JSON_ERROR; + } + + int i = 0; + + for (rapidjson::Value::ConstValueIterator itr = lightsJsonResponse.Begin(); + itr != lightsJsonResponse.End(); ++itr) + { + if (lightsJsonResponse[i].IsObject()) + { + LifxLight::lightConfig cfg; + cfg.id = lightsJsonResponse[i]["id"].GetString(); + cfg.uuid = lightsJsonResponse[i]["uuid"].GetString(); + cfg.label = lightsJsonResponse[i]["label"].GetString(); + + LifxLight::lightState state; + + std::string powerStr = lightsJsonResponse[i]["power"].GetString(); + + state.power = powerStr == "on"; + + state.brightness = lightsJsonResponse[i]["brightness"].GetDouble(); + state.connected = lightsJsonResponse[i]["connected"].GetBool(); + state.secondsSinceLastSeen = lightsJsonResponse[i]["seconds_since_seen"].GetDouble(); + + std::shared_ptr light = std::make_shared(state, cfg, user); + + parsedLights.push_back(light); + + ++i; + } + else + { + OIC_LOG(ERROR, TAG, "Json blob doesn't have an expected object to be parsed."); + return MPM_RESULT_INTERNAL_ERROR; + } + } + + return MPM_RESULT_OK; +} + +MPMResult LifxLight::setState(std::string &stateRequest) +{ + if (this->user.empty()) + { + throw std::runtime_error("Light not created in valid state by constructor. No \"user\" found"); + } + + CurlClient cc = CurlClient(CurlClient::CurlMethod::PUT, uri + "/state") + .addRequestHeader(CURL_HEADER_ACCEPT_JSON) + .setUserName(user) + .setRequestBody(stateRequest); + + OIC_LOG_V(INFO, TAG, "Request %s", stateRequest.c_str()); + OIC_LOG_V(INFO, TAG, "Uri %s", uri.c_str()); + + int curlCode = cc.send(); + std::string response = cc.getResponseBody(); + + OIC_LOG_V(INFO, TAG, "Response %s", response.c_str()); + + if (curlCode != MPM_RESULT_OK) + { + OIC_LOG_V(ERROR, TAG, "PUT request for power failed. Error code %d", curlCode); + return MPM_RESULT_INTERNAL_ERROR; + } + // Small sleep to let the light inform the cloud of any state changes. + sleep(2); + refreshState(); + + return parseCloudResponse(response); +} + +MPMResult LifxLight::setPower(bool power) +{ + std::string setPowerRequest = std::string("power=") + (power ? "on" : "off"); + return setState(setPowerRequest); +} + +MPMResult LifxLight::setBrightness(double brightness) +{ + if (brightness < 0.0) brightness = 0.0; + else if (brightness > 1.0) brightness = 1.0; + + std::string setBrightnessRequest = std::string("brightness=") + std::to_string(brightness); + + return setState(setBrightnessRequest); +} + +// Limit calls to refreshState. LifX APIs limit calls +// from an access token to 60 calls every 60 seconds +// as made clear @ https://api.developer.lifx.com/docs/rate-limits +MPMResult LifxLight::refreshState() +{ + if (this->user.empty()) + { + throw std::runtime_error("Light not created in valid state by constructor. No \"user\" found"); + } + + CurlClient cc = CurlClient(CurlClient::CurlMethod::GET, uri) + .addRequestHeader(CURL_HEADER_ACCEPT_JSON) + .setUserName(user); + + int curlCode = cc.send(); + + if (curlCode != MPM_RESULT_OK) + { + OIC_LOG_V(ERROR, TAG, "GET request for light failed with error %d", curlCode); + return MPM_RESULT_INTERNAL_ERROR; + } + + std::string response = cc.getResponseBody(); + + std::vector> parsedLights; + MPMResult parseResult = parseLightsFromCloudResponse(response, this->user, parsedLights); + + if (parseResult != MPM_RESULT_OK) + { + return parseResult; + } + + if (parsedLights.size() != 1) + { + OIC_LOG_V(ERROR, TAG, "This is irregular! Instead of 1 light, returned %ld " , parsedLights.size()); + return MPM_RESULT_JSON_ERROR; + } + + std::shared_ptr light = parsedLights[0]; // Only 1 light expected here. + + if (light->config.uuid != this->config.uuid) + { + OIC_LOG_V(ERROR, TAG, "%s in response, when request made by %s", light->config.uuid.c_str(), + this->config.uuid.c_str()); + return MPM_RESULT_JSON_ERROR; + } + + this->state = light->state; + return MPM_RESULT_OK; +} + +MPMResult LifxLight::getLights(const std::string user, + std::vector > &lights) +{ + CurlClient cc = CurlClient(CurlClient::CurlMethod::GET, LIFX_LIST_LIGHTS_URI) + .addRequestHeader(CURL_HEADER_ACCEPT_JSON) + .setUserName(user); + + int curlCode = cc.send(); + + if (curlCode != MPM_RESULT_OK) + { + OIC_LOG_V(ERROR, TAG, "GET request for lights failed. Error code %d", curlCode); + return MPM_RESULT_INTERNAL_ERROR; + } + + std::string response = cc.getResponseBody(); + + return parseLightsFromCloudResponse(response, user, lights); +} + diff --git a/bridging/plugins/lifx_plugin/lifx_objects/lifx.h b/bridging/plugins/lifx_plugin/lifx_objects/lifx.h new file mode 100644 index 0000000..aaf65fd --- /dev/null +++ b/bridging/plugins/lifx_plugin/lifx_objects/lifx.h @@ -0,0 +1,106 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 __LIFX_H__ +#define __LIFX_H__ + + +#include +#include +#include + +#define LIFX_BASE_URI "https://api.lifx.com/v1/lights" +#define LIFX_LIST_LIGHTS_URI LIFX_BASE_URI "/all" + +class LifxLight +{ + public: + + typedef struct _lightState + { + double brightness; + bool power; + bool connected; + double secondsSinceLastSeen; + } lightState; + + typedef struct _lightConfig + { + std::string id; + std::string uuid; + std::string label; + _lightConfig() {} + _lightConfig(std::string light_id, std::string light_uuid, std::string light_label) + { + id = light_id; + uuid = light_uuid; + label = light_label; + } + } lightConfig; + + LifxLight() {} + virtual ~LifxLight() {} + + LifxLight(lightState &state, lightConfig &config, const std::string &user) + { + this->user = user; + this->state = state; + this->config = config; + this->uri = std::string(LIFX_BASE_URI).append("/").append(config.id); + } + + /*Limit calls to refreshState. LifX API's limit calls + * from an access token and only allow 60 calls every 60 seconds + * as made clear @ https://api.developer.lifx.com/docs/rate-limits + */ + MPMResult refreshState(); + + MPMResult setPower(bool power); + + /** + * Sets the brightness for this bulb. + * @param[in] brightness Is in the range [0.0, 1.0]. + * Values outside the interval are set to the nearest endpoint. + * @return MPM_RESULT_OK if success, else appropriate error code on error + */ + MPMResult setBrightness(double brightness); + + MPMResult static getLights(const std::string user, + std::vector > &lights); + + void getUser(std::string &userID) + { + userID = user; + } + + lightState state; + lightConfig config; + std::string uri; + + private: + std::string user; + + MPMResult setState(std::string &setPowerRequest); +}; + +typedef std::shared_ptr LifxLightSharedPtr; + +#endif // __LIFX_H__ diff --git a/bridging/plugins/stub_plugin/stub_plugin.cpp b/bridging/plugins/stub_plugin/stub_plugin.cpp new file mode 100644 index 0000000..51de37b --- /dev/null +++ b/bridging/plugins/stub_plugin/stub_plugin.cpp @@ -0,0 +1,125 @@ +//****************************************************************** +// +// Copyright 2017 Intel Mobile Communications GmbH 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 +#include +#include +#include +#include +#include +#include +#include "logger.h" + +#define TAG "STUB_PLUGIN" + +MPMPluginCtx *g_plugin_ctx = NULL; +static char CRED_FILE[] = "./oic_svr_db_sample.dat"; + +FILE *sec_file(const char *, const char *mode) +{ + return fopen(CRED_FILE, mode); +} + +MPMResult pluginCreate(MPMPluginCtx **pluginSpecificCtx) +{ + MPMPluginCtx *ctx = (MPMPluginCtx *) calloc(1, sizeof(MPMPluginCtx)); + if (ctx == NULL) + { + OIC_LOG(ERROR, TAG, "Allocation of plugin context failed"); + return MPM_RESULT_INTERNAL_ERROR; + } + *pluginSpecificCtx = ctx; + g_plugin_ctx = ctx; + ctx->device_name = "Stub plugin"; + ctx->resource_type = "oic.d.stub"; + ctx->open = sec_file; + return MPM_RESULT_OK; +} + +MPMResult pluginStart(MPMPluginCtx *ctx) +{ + ctx->stay_in_process_loop = true; + OIC_LOG(INFO, TAG, "Plugin start called!"); + return MPM_RESULT_OK; +} + +void echoResponse(MPMPipeMessage *message, std::string type) +{ + std::string s = type + " response echo"; + MPMSendResponse(s.c_str(), s.size(), message->msgType); +} + +MPMResult pluginScan(MPMPluginCtx *, MPMPipeMessage *message) +{ + OIC_LOG(INFO, TAG, "Scan called!"); + // Send back scan response to the client. + echoResponse(message, "SCAN"); + return MPM_RESULT_OK; +} + +MPMResult pluginAdd(MPMPluginCtx *, MPMPipeMessage *message) +{ + OIC_LOG(INFO, TAG, "Add called! Create Iotivity resources here based on what the client says"); + echoResponse(message, "ADD"); + return MPM_RESULT_OK; +} + +MPMResult pluginRemove(MPMPluginCtx *, MPMPipeMessage *message) +{ + OIC_LOG(INFO, TAG, "Remove called! Remove iotivity resources here based on what the client says"); + echoResponse(message, "REMOVE"); + return MPM_RESULT_OK; +} + +MPMResult pluginReconnect(MPMPluginCtx *, MPMPipeMessage *message) +{ + OIC_LOG(INFO, TAG, + "Reconnect called! Reconnect to devices, create resources from the message/cloud/db/file."); + echoResponse(message, "ADD"); + return MPM_RESULT_OK; +} + +MPMResult pluginStop(MPMPluginCtx *) +{ + OIC_LOG(INFO, TAG, "Stop called !"); + return MPM_RESULT_OK; +} + + +MPMResult pluginDestroy(MPMPluginCtx *pluginSpecificCtx) +{ + OIC_LOG(INFO, TAG, "Destroy called"); + if (!pluginSpecificCtx) + { + assert(g_plugin_ctx); + + if (pluginSpecificCtx->started) + { + pluginStop(pluginSpecificCtx); + } + // freeing the resource allocated in create + free(pluginSpecificCtx); + g_plugin_ctx = NULL; + } + + return (MPM_RESULT_OK); +} diff --git a/extlibs/rapidjson/SConscript b/extlibs/rapidjson/SConscript new file mode 100644 index 0000000..5a1e708 --- /dev/null +++ b/extlibs/rapidjson/SConscript @@ -0,0 +1,24 @@ +import os, string, sys + +Import('env') + +env = env.Clone() +rapidjson_env = env.Clone() + +target_os = env.get('TARGET_OS') +target_arch = env.get('TARGET_ARCH') + +host_os = sys.platform + +###################################################################### +# Build flags +###################################################################### +src_dir = env.get('SRC_DIR') +path = os.path.join(src_dir, 'extlibs', 'rapidjson', 'rapidjson') + +# check 'rapidjson' library, if it doesn't exits, ask user to download it +if not os.path.exists(path): + rapidjson_env = Environment(ENV = os.environ) + rapidjson_zip = env.Download('v1.0.2.zip', 'https://github.com/miloyip/rapidjson/archive/v1.0.2.zip') + rapidjson_dir = env.UnpackAll('rapidjson', rapidjson_zip) + os.system("mv rapidjson-1.0.2 rapidjson") diff --git a/resource/c_common/platform_features.h b/resource/c_common/platform_features.h index 08cbce4..a419707 100644 --- a/resource/c_common/platform_features.h +++ b/resource/c_common/platform_features.h @@ -90,4 +90,15 @@ # define OC_CLOSE_SOCKET(s) close(s) #endif +#ifndef SIZE_MAX +/* Some systems fail to define SIZE_MAX in , even though C99 requires it... + * Conversion from signed to unsigned is defined in 6.3.1.3 (Signed and unsigned integers) p2, + * which says: "the value is converted by repeatedly adding or subtracting one more than the + * maximum value that can be represented in the new type until the value is in the range of the + * new type." + * So -1 gets converted to size_t by adding SIZE_MAX + 1, which results in SIZE_MAX. + */ +# define SIZE_MAX ((size_t)-1) +#endif + #endif