| /* |
| * |
| * Copyright (c) 2013-2017 Nest Labs, Inc. |
| * 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. |
| */ |
| |
| /** |
| * @file |
| * The main class defined herein - WeaveServiceManager - defines |
| * an object, generally a singleton, that implements the Weave |
| * Service Directory Profile. This profile allows applications |
| * using Weave to request a connection to a particular Weave |
| * service using a predefined service endpoint. The connect() |
| * call takes callbacks that the service directory sub-layer |
| * invokes when the requested connection is complete or an error |
| * occurs. |
| * |
| * The underlying protocol is described in the document: |
| * |
| * Nest Weave - Service Directory Protocol |
| * |
| * which currently defines two messages: |
| * |
| * 1) A service endpoint query may be sent by a Weave node when |
| * that node wishes to request directory information from |
| * another node or a service entity. The service endpoint |
| * query message has no fields beyond the Weave exchange |
| * header. |
| * |
| * 2) A service endpoint response containing directory |
| * information, which shall be sent by a node or service |
| * entity in response to a successful service endpoint query. |
| * |
| * In addition to its primary function as a directory lookup |
| * protocol, the Service Directory Protocol supports time |
| * synchronization by allowing the Weave service to optionally |
| * insert time fields in the service endpoint response. |
| */ |
| |
| #include <Weave/Core/WeaveCore.h> |
| #include <Weave/Core/WeaveMessageLayer.h> |
| #include <Weave/Profiles/ProfileCommon.h> |
| #include <Weave/Support/CodeUtils.h> |
| |
| #include "ServiceDirectory.h" |
| |
| #include <Weave/Support/WeaveFaultInjection.h> |
| #include <SystemLayer/SystemStats.h> |
| |
| #if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY |
| |
| using namespace ::nl::Inet; |
| using namespace ::nl::Weave; |
| using namespace ::nl::Weave::Encoding; |
| using namespace ::nl::Weave::Encoding::LittleEndian; |
| using namespace ::nl::Weave::TLV; |
| using namespace ::nl::Weave::Profiles; |
| using namespace ::nl::Weave::Profiles::ServiceDirectory; |
| using namespace ::nl::Weave::Profiles::StatusReporting; |
| |
| /** |
| * @def ARRAY_SIZE(a) |
| * |
| * @brief |
| * This macro calculates the number of elements in a the given array. |
| * |
| * @param[in] a An array, which we need to know the size of. |
| * |
| */ |
| #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) |
| |
| /** |
| * @brief |
| * This method is a trampoline which calls the actual handler |
| * WeaveServiceManager::onConnectionComplete() . |
| * |
| * @param[in] aConnection A pointer to the Weave connection object which has |
| * been completed. |
| * |
| * @param[in] aError An Weave error code indicating if there was any error |
| * during connection setup. |
| */ |
| static void handleSDConnectionComplete(WeaveConnection *aConnection, WEAVE_ERROR aError) |
| { |
| WeaveServiceManager *manager = (WeaveServiceManager *)aConnection->AppState; |
| |
| WeaveLogProgress(ServiceDirectory, "handleSDConnectionComplete() <= %s", nl::ErrorStr(aError)); |
| |
| if (manager) |
| manager->onConnectionComplete(aError); |
| } |
| |
| /** |
| * @brief |
| * This method is a trampoline which calls the actual handler |
| * WeaveServiceManager::ConnectRequest::onConnectionComplete() . |
| * |
| * @param[in] aConnection A pointer to the Weave connection object which has |
| * been completed. |
| * |
| * @param[in] aError An Weave error code indicating if there was any error |
| * during connection setup. |
| */ |
| static void handleAppConnectionComplete(WeaveConnection *aConnection, WEAVE_ERROR aError) |
| { |
| WeaveServiceManager::ConnectRequest *request = (WeaveServiceManager::ConnectRequest *)aConnection->AppState; |
| |
| WeaveLogProgress(ServiceDirectory, "handleAppConnectionComplete() <= %s", nl::ErrorStr(aError)); |
| |
| if (request) |
| request->onConnectionComplete(aError); |
| } |
| |
| /** |
| * @brief |
| * This is the handler that's set in the WeaveConnection to handle closure. |
| * |
| * Note that it is distinct from the "connection closed" handler that is set |
| * in the ExchangeContext during a conversation. |
| * |
| * @param[in] aConnection A pointer to the Weave connection object which has |
| * been closed. |
| * |
| * @param[in] aError An Weave error code indicating if there was any error |
| * causing the connection to be closed. |
| */ |
| static void handleConnectionClosed(WeaveConnection *aConnection, WEAVE_ERROR aError) |
| { |
| aConnection->Close(); |
| |
| WeaveLogProgress(ServiceDirectory, "handleConnectionClosed() <= %s", nl::ErrorStr(aError)); |
| } |
| |
| /** |
| * @brief |
| * This method is a trampoline which calls the actual handler |
| * WeaveServiceManager::onConnectionClosed() . |
| * |
| * @param[in] aExchangeCtx A pointer to the exchange context object which is |
| * associated with this conversation. |
| * |
| * @param[in] aConnection A pointer to the Weave connection object which has |
| * been closed. |
| * |
| * @param[in] aError An Weave error code indicating if there was any error |
| * causing the connection to be closed. |
| */ |
| static void ecHandleConnectionClosed(ExchangeContext *aExchangeCtx, |
| WeaveConnection *aConnection, |
| WEAVE_ERROR aError) |
| { |
| WeaveServiceManager *manager = (WeaveServiceManager *)aConnection->AppState; |
| |
| /* |
| * Connection is just closed by peer, which is not really |
| * expected. This handler is called if the service closes the |
| * connection instead of sending any response. If the connection |
| * is closed gracefully, the error code passed by the lower layers |
| * can be WEAVE_NO_ERROR. In that case the error code is replaced |
| * with CONNECTION_CLOSED_UNEXPECTEDLY because this event is |
| * considered as an error protocol-wise. |
| */ |
| |
| if (WEAVE_NO_ERROR == aError) |
| { |
| aError = WEAVE_ERROR_CONNECTION_CLOSED_UNEXPECTEDLY; |
| } |
| |
| WeaveLogProgress(ServiceDirectory, "ecHandleConnectionClosed() <= %s", nl::ErrorStr(aError)); |
| |
| if (manager) |
| manager->onConnectionClosed(aError); |
| } |
| |
| /** |
| * @brief |
| * This method is a trampoline which calls the actual handler |
| * WeaveServiceManager::onResponseReceived() . |
| * |
| * @param[in] aExchangeCtx A pointer to the exchange context object which is |
| * associated with this conversation. |
| * |
| * @param[in] anAddrInfo A read-only pointer to the IP header of this message. |
| * |
| * @param[in] aMsgHeader A read-only pointer to the Weave header of this message. |
| * |
| * @param[in] aProfileId The ID of the Weave profile this message belongs to. |
| * @param[in] aMsgType The profile-specific type of this message. |
| * @param[in] aMsg A pointer to a buffer with the content of this message. |
| */ |
| static void handleResponseMsg(ExchangeContext *aExchangeCtx, |
| const IPPacketInfo *anAddrInfo, |
| const WeaveMessageInfo *aMsgInfo, |
| uint32_t aProfileId, |
| uint8_t aMsgType, |
| PacketBuffer *aMsg) |
| { |
| WeaveServiceManager *manager = (WeaveServiceManager *)aExchangeCtx->AppState; |
| |
| WeaveLogProgress(ServiceDirectory, "handleResponseMsg()"); |
| |
| if (manager) |
| manager->onResponseReceived(aProfileId, aMsgType, aMsg); |
| } |
| |
| /** |
| * @brief |
| * This method is a trampoline which calls the actual handler |
| * WeaveServiceManager::onResponseTimeout() . |
| * |
| * @param[in] aExchangeCtx A pointer to the exchange context object which |
| * is associated with this conversation. |
| */ |
| static void handleResponseTimeout(ExchangeContext *aExchangeCtx) |
| { |
| WeaveServiceManager *manager = (WeaveServiceManager *)aExchangeCtx->AppState; |
| |
| WeaveLogProgress(ServiceDirectory, "handleResponseTimeout()"); |
| |
| if (manager) |
| manager->onResponseTimeout(); |
| } |
| |
| /** |
| * @brief |
| * This method initializes the WeaveServiceManager instance. |
| * |
| * Note that init() must be called to further initialize this instance. |
| * |
| */ |
| WeaveServiceManager::WeaveServiceManager() |
| { |
| mExchangeManager = NULL; |
| mCache.base = NULL; |
| mCache.length = 0; |
| mConnection = NULL; |
| mExchangeContext = NULL; |
| mServiceEndpointQueryBegin = NULL; |
| mServiceEndpointQueryEndWithTimeInfo = NULL; |
| |
| freeConnectRequests(); |
| |
| clearWorkingState(); |
| clearCacheState(); |
| } |
| |
| /** |
| * This method destructs the WeaveServiceManager instance. |
| */ |
| WeaveServiceManager::~WeaveServiceManager() |
| { |
| mExchangeManager = NULL; |
| mCache.base = NULL; |
| mCache.length = 0; |
| mServiceEndpointQueryBegin = NULL; |
| mServiceEndpointQueryEndWithTimeInfo = NULL; |
| |
| reset(); |
| } |
| |
| /** |
| * @brief This method initializes the service manager object. |
| * |
| * In order to be used, a service manager object must be |
| * initialized. After a successful call to this method, clients can start |
| * calling connect(), lookup(), and other methods. |
| * |
| * @param [in] aExchangeMgr A pointer to the exchange |
| * manager to use for all service directory profile exchanges. |
| * |
| * @param [in] aCache A pointer to a buffer which can be used to |
| * cache directory information. |
| * |
| * @param [in] aCacheLen The length in bytes of the cache. |
| * |
| * @param [in] aAccessor The callback, as defined in ServiceDirectory.h |
| * to invoke in order to load the root directory as a starting point for |
| * directory lookup. |
| * |
| * @param [in] aDirAuthMode The authentication mode to |
| * use when talking to the directory service. |
| * |
| * @param [in] aServiceEndpointQueryBegin |
| * A function pointer of type OnServiceEndpointQueryBegin, that is called |
| * at the start of a service directory request and allows application code |
| * to mark the time if it wishes to use the time synchronization offered |
| * by the service directory protocol. |
| * |
| * @param [in] aServiceEndpointQueryEndWithTimeInfo |
| * A function pointer of type OnServiceEndpointQueryEndWithTimeInfo, that is |
| * called on receipt of a service directory that allows applications to |
| * synchronize with the Weave service using the time fields given in |
| * the response. This callback would be made after the service manager |
| * receives a response with time information. The cache should already be |
| * filled successfully before the callback is made. |
| * |
| * @return #WEAVE_ERROR_INVALID_ARGUMENT if a function |
| * argument is invalid; otherwise, #WEAVE_NO_ERROR. |
| */ |
| WEAVE_ERROR WeaveServiceManager::init(WeaveExchangeManager *aExchangeMgr, |
| uint8_t *aCache, |
| uint16_t aCacheLen, |
| RootDirectoryAccessor aAccessor, |
| WeaveAuthMode aDirAuthMode, |
| OnServiceEndpointQueryBegin aServiceEndpointQueryBegin, |
| OnServiceEndpointQueryEndWithTimeInfo aServiceEndpointQueryEndWithTimeInfo) |
| { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| |
| WeaveLogProgress(ServiceDirectory, "init()"); |
| |
| VerifyOrExit(aExchangeMgr && aCache && aCacheLen > 0 && aAccessor, err = WEAVE_ERROR_INVALID_ARGUMENT); |
| |
| mExchangeManager = aExchangeMgr; |
| mCache.base = aCache; |
| mCache.length = aCacheLen; |
| mDirAndSuffTableSize = 0; |
| |
| mAccessor = aAccessor; |
| mDirAuthMode = aDirAuthMode; |
| mServiceEndpointQueryBegin = aServiceEndpointQueryBegin; |
| mServiceEndpointQueryEndWithTimeInfo = aServiceEndpointQueryEndWithTimeInfo; |
| |
| cleanupExchangeContext(); |
| |
| clearCacheState(); |
| |
| finalizeConnectRequests(); |
| |
| exit: |
| |
| return err; |
| } |
| |
| /** |
| * @brief This method requests connect to a Weave service. |
| * |
| * This is the top-level connect call. It essentially produces a |
| * secure connection to the Weave service given a service endpoint and |
| * an authentication mode or dies trying. |
| * |
| * This method can only be called after successful call to init(), |
| * and a connection request can be potentially canceled by cancel(). |
| * |
| * This method can be called before the local cache is filled with |
| * data from either default provisioned data or a trip to the directory |
| * service. Service manager would just queue the request before the cache |
| * content can be determined. |
| * |
| * @param [in] aServiceEp The service endpoint identifier, |
| * as defined in ServiceDirectory.h, for the service of interest. |
| * |
| * @param [in] aAuthMode The authentication mode to use |
| * when connecting to the service of interest. |
| * |
| * @param [in] aAppState A pointer to an application state object, |
| * passed to the callbacks as an argument. |
| * |
| * @param [in] aStatusHandler A callback to invoke in the case of an error |
| * that occurs before the connection is completed. |
| * |
| * @param [in] aConnectionCompleteHandler |
| * A callback to invoke in the case where the requested connection is |
| * completed. Note that the connection may fail with an Weave error code. |
| * |
| * @param [in] aConnectTimeoutMsecs |
| * The optional TCP connect timeout in milliseconds. |
| * |
| * @param [in] aConnectIntf |
| * The optional interface over which the connection is to be established. |
| * |
| * @return #WEAVE_NO_ERROR on success; otherwise, a respective error code. |
| */ |
| WEAVE_ERROR WeaveServiceManager::connect(uint64_t aServiceEp, |
| WeaveAuthMode aAuthMode, |
| void *aAppState, |
| StatusHandler aStatusHandler, |
| WeaveConnection::ConnectionCompleteFunct aConnectionCompleteHandler, |
| const uint32_t aConnectTimeoutMsecs, |
| const InterfaceId aConnectIntf) |
| { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| ConnectRequest *req = NULL; |
| |
| WeaveLogProgress(ServiceDirectory, "connect(%llx...)", aServiceEp); |
| |
| if (mCacheState == kServiceMgrState_Initial) |
| { |
| WeaveLogProgress(ServiceDirectory, "initial"); |
| |
| /* |
| * when the service manager state is "initial" the state of the |
| * service cache is assumed to be empty or unknown. in this case |
| * the only way forward is to get the root directory from the |
| * service config and install it. |
| */ |
| |
| err = mAccessor(mCache.base, mCache.length); |
| SuccessOrExit(err); |
| |
| mDirectory.base = mCache.base; |
| mDirectory.length = 1; |
| |
| mCacheState = kServiceMgrState_Resolving; |
| } |
| |
| if (mCacheState == kServiceMgrState_Resolving) |
| { |
| WeaveLogProgress(ServiceDirectory, "resolving"); |
| |
| /* |
| * when the state is "resolving" it means that the cache at least |
| * has something in it and is awaiting resolution. in this case we |
| * have to fire off a service directory query. |
| */ |
| |
| mConnection = mExchangeManager->MessageLayer->NewConnection(); |
| VerifyOrExit(mConnection, err = WEAVE_ERROR_NO_MEMORY); |
| |
| err = lookupAndConnect(mConnection, |
| kServiceEndpoint_Directory, |
| mDirAuthMode, |
| this, |
| handleSDConnectionComplete, |
| WEAVE_CONFIG_SERVICE_DIR_CONNECT_TIMEOUT_MSECS); |
| SuccessOrExit(err); |
| /* |
| * at this point, the possible values for mCacheState are: |
| * |
| * - kServiceMgrState_Resolving - if everything went well, |
| * |
| * - kServiceMgrState_Initial - if the lookupAndConnect |
| * invoked the callback synchronously with an error. That |
| * case results in call chain |
| * WeaveServiceManager::handleSDConnectionComplete -> |
| * WeaveServiceManager::onConnectionComplete -> |
| * WeaveServiceManager::fail -> |
| * WeaveServiceManager::clearCacheState |
| */ |
| |
| if (mCacheState == kServiceMgrState_Resolving) |
| { |
| mCacheState = kServiceMgrState_Waiting; |
| } |
| else |
| { |
| ExitNow(err = WEAVE_ERROR_CONNECTION_ABORTED); |
| } |
| } |
| |
| /* |
| * OK. here the state is either "waiting" in which case |
| * we've kicked off an SD request and we can't do anything |
| * else until the response comes back or it's "resolved" |
| * in which case we're good to go. in either case, we |
| * queue up a connect request. |
| */ |
| |
| req = getAvailableRequest(); |
| VerifyOrExit(req, err = WEAVE_ERROR_WELL_EMPTY); |
| |
| err = req->init(this, |
| aServiceEp, |
| aAuthMode, |
| aAppState, |
| aStatusHandler, |
| aConnectionCompleteHandler, |
| aConnectTimeoutMsecs, |
| aConnectIntf); |
| SuccessOrExit(err); |
| |
| if (mCacheState == kServiceMgrState_Waiting) |
| { |
| WeaveLogProgress(ServiceDirectory, "waiting"); |
| } |
| |
| else if (mCacheState == kServiceMgrState_Resolved) |
| { |
| WeaveLogProgress(ServiceDirectory, "resolved"); |
| |
| err = lookupAndConnect(req->mConnection, |
| req->mServiceEp, |
| req->mAuthMode, |
| req, |
| handleAppConnectionComplete, |
| req->mConnectTimeoutMsecs, |
| req->mConnIntf); |
| } |
| |
| else |
| { |
| err = WEAVE_ERROR_INCORRECT_STATE; |
| } |
| |
| |
| exit: |
| |
| if (err != WEAVE_NO_ERROR) |
| { |
| WeaveLogProgress(ServiceDirectory, "connect: %s", ErrorStr(err)); |
| |
| if (mConnection && mCacheState == kServiceMgrState_Resolving) |
| { |
| /* |
| * Note that if the cache state is "waiting" we don't want |
| * to close the connection exactly because the connection |
| * is what we're waiting for. |
| */ |
| |
| cleanupExchangeContext(err); |
| } |
| if (req) |
| { |
| req->finalize(); |
| } |
| } |
| |
| return err; |
| } |
| |
| /** |
| * @brief This method looks up directory information for a service endpoint. |
| * |
| * If the service directory has been resolved, i.e. if there has been |
| * a successful connect() operation, then this method will return a |
| * directory entry given a service endpoint identifier. |
| * |
| * @param [in] aServiceEp The identifier of the service endpoint to |
| * look up. |
| * |
| * @param [out] aControlByte A pointer to the place to write the directory |
| * entry control byte. |
| * |
| * @param [out] aDirectoryEntry A pointer-pointer to be directed to the |
| * directory entry. |
| * |
| * @retval #WEAVE_NO_ERROR on success; otherwise, a respective error code. |
| * |
| * @retval #WEAVE_ERROR_INVALID_SERVICE_EP if the given service endpoint is |
| * not found. |
| * |
| * @retval #WEAVE_ERROR_INVALID_DIRECTORY_ENTRY_TYPE if directory contains an |
| * unknown directory entry type. |
| */ |
| WEAVE_ERROR WeaveServiceManager::lookup(uint64_t aServiceEp, uint8_t *aControlByte, uint8_t **aDirectoryEntry) |
| { |
| WEAVE_ERROR err = WEAVE_ERROR_INVALID_SERVICE_EP; |
| uint8_t *p = mDirectory.base; |
| uint16_t entryLen = 0; |
| bool found = false; |
| *aControlByte = 0; |
| *aDirectoryEntry = NULL; |
| |
| WeaveLogProgress(ServiceDirectory, "lookup()"); |
| |
| WEAVE_FAULT_INJECT(nl::Weave::FaultInjection::kFault_ServiceManager_Lookup, |
| memset(&aServiceEp, 0x0F, sizeof(aServiceEp))); |
| |
| for (uint8_t i = 0; i < mDirectory.length; i++) |
| { |
| uint8_t entryCtrlByte = Read8(p); |
| uint64_t svcEp = Read64(p); |
| |
| if (svcEp == aServiceEp) |
| { |
| // found it. |
| // break out of the loop |
| |
| WeaveLogProgress(ServiceDirectory, "found [%x,%llx]", entryCtrlByte, svcEp); |
| |
| *aControlByte = entryCtrlByte; |
| *aDirectoryEntry = p; |
| |
| found = true; |
| err = WEAVE_NO_ERROR; |
| |
| break; |
| } |
| |
| // skip over this entry |
| |
| err = calculateEntryLength(p, entryCtrlByte, &entryLen); |
| SuccessOrExit(err); |
| |
| p += entryLen; |
| } |
| |
| if (!found) |
| { |
| err = WEAVE_ERROR_INVALID_SERVICE_EP; |
| } |
| |
| exit: |
| WeaveLogProgress(ServiceDirectory, "lookup() => %s", ErrorStr(err)); |
| |
| return err; |
| } |
| |
| /** |
| * Add the overriding directory entry of a hostname and port id at the beginning |
| * of the directory list. |
| */ |
| WEAVE_ERROR WeaveServiceManager::replaceOrAddCacheEntry(uint16_t port, |
| const char *hostName, |
| uint8_t hostLen, |
| uint64_t serviceEndpointId) |
| { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| uint8_t *p = mDirectory.base; |
| uint8_t listCtrl = 0; |
| uint8_t itemCtrl = 0; |
| // Parameters for lookup. |
| uint8_t ctrlByte; |
| uint8_t *entry = NULL; |
| uint16_t entryLength = 0; |
| uint8_t bottomPortionLen = 0; |
| |
| // Byte length for the overriding entry that needs to be inserted at the beginning of directory. |
| // 1(host/port list length byte) + 8(Service Endpoint Id) + 1(hostId type byte) + 1(string len) |
| // + hostLen(hostname string size) + 2(port Id) |
| uint16_t overrideEntryTotalLen = 1 + 8 + 1 + 1 + hostLen + 2; |
| |
| // Perform lookup of the Service endpoint to replace an existing entry. |
| |
| err = lookup(serviceEndpointId, &ctrlByte, &entry); |
| if (err == WEAVE_NO_ERROR) |
| { |
| // Found an entry to replace. Calculate length of the entry. |
| |
| err = calculateEntryLength(entry, ctrlByte, &entryLength); |
| SuccessOrExit(err); |
| |
| // Add a check for the length incorporating the new entry. |
| |
| VerifyOrExit(overrideEntryTotalLen + mDirAndSuffTableSize - entryLength < mCache.length, |
| err = WEAVE_ERROR_NO_MEMORY); |
| |
| // Delete entry by moving up everything after the replaced entry to fill the created gap. |
| bottomPortionLen = mDirAndSuffTableSize - (entry + entryLength - p); |
| memmove(entry, entry + entryLength, bottomPortionLen); |
| |
| // Reduce the overall size by this entry length. |
| |
| mDirAndSuffTableSize -= entryLength; |
| } |
| |
| // Make space for the new entry by moving the directory down the cache by the appropriate length. |
| |
| memmove(p + overrideEntryTotalLen, p, mDirAndSuffTableSize); |
| |
| // Write the host portlist control byte. |
| // Host port list length = 1, reserved = 0, entry type = 01; |
| |
| listCtrl = (1 & kMask_HostPortListLen) | ((1 << 6) & kMask_DirectoryEntryType); |
| Write8(p, listCtrl); |
| |
| // Write the Service Endpoint ID |
| |
| Write64(p, serviceEndpointId); |
| |
| // Write the item control byte. |
| // HostID type = Fully qualified(00), Suffix index present = 0, port Id present = 1; |
| |
| itemCtrl = (1 << 3) & kMask_PortIdPresent; |
| Write8(p, itemCtrl); |
| |
| // Write the Host Id string length |
| |
| Write8(p, hostLen); |
| |
| // Write the Host Id string |
| |
| memcpy(p, hostName, hostLen); |
| |
| // Skip over the hostName |
| |
| p += hostLen; |
| |
| // Write the port Id |
| |
| Write16(p, port); |
| |
| // Update the directory length by this new entry and write it in the directory control byte. |
| |
| mDirectory.length++; |
| |
| exit: |
| return err; |
| } |
| |
| /** |
| * @brief This method cancels a connect request. |
| * |
| * This method cancels a connect request given the service endpoint ID and the |
| * application state object passed in at request time as identifiers. |
| * If it is the last connect request, this method clears up any pending service |
| * directory connection state as well. |
| * |
| * @param [in] aServiceEp The service endpoint ID of the request being canceled. |
| * |
| * @param [in] anAppState A pointer to the app state object given to the |
| * connect() call. |
| */ |
| void WeaveServiceManager::cancel(uint64_t aServiceEp, void *aAppState) |
| { |
| int activeRequests = 0; |
| ConnectRequest *req; |
| |
| WeaveLogProgress(ServiceDirectory, "cancel()"); |
| |
| for (uint8_t i = 0; i < ARRAY_SIZE(mConnectRequestPool); i++) |
| { |
| req = &mConnectRequestPool[i]; |
| |
| if (req->isAllocatedTo(aServiceEp, aAppState)) |
| req->finalize(); |
| |
| else if (!req->isFree()) |
| activeRequests++; |
| } |
| |
| if (activeRequests == 0) |
| { |
| /* |
| * in principle we could be in one of two states here - |
| * waiting or resolved. if we're waiting then we need to set |
| * the state back to resolving so that next time around it |
| * will send out a directory request. otherwise, just leave |
| * the state alone. |
| */ |
| |
| if (mCacheState == kServiceMgrState_Waiting) |
| mCacheState = kServiceMgrState_Resolving; |
| |
| /* |
| * now clean up the exchange state being used to request |
| * service directory info. |
| */ |
| |
| cleanupExchangeContext(WEAVE_ERROR_CONNECTION_CLOSED_UNEXPECTEDLY); |
| } |
| } |
| |
| /** |
| * @brief This method invalidates the service directory cache. |
| * |
| * This method sets the service directory cache state so that on the next |
| * request the service manager will issue a service directory query. |
| * |
| * This version of the method - here for backward compatibility - |
| * takes and logs an error then calls unresolve(void) . |
| * |
| * @param [in] aError The error that triggered this operation. |
| * |
| * @sa unresolve(void) |
| */ |
| void WeaveServiceManager::unresolve(WEAVE_ERROR aError) |
| { |
| WeaveLogProgress(ServiceDirectory, "unresolve: %s", ErrorStr(aError)); |
| |
| unresolve(); |
| } |
| |
| /** |
| * @brief This method invalidates the service directory cache. |
| * |
| * This method sets the service directory cache state so that on the next request |
| * the service manager will issue a service directory query. |
| * |
| * @sa unresolve(WEAVE_ERROR) |
| * |
| */ |
| void WeaveServiceManager::unresolve(void) |
| { |
| WeaveLogProgress(ServiceDirectory, "unresolve()"); |
| |
| /* |
| * we should only do this if the cache state has advanced beyond |
| * "resolving". otherwise there's a chance of putting the service |
| * directory in an inconsistent state. |
| */ |
| |
| if (mCacheState > kServiceMgrState_Resolving) |
| { |
| cleanupExchangeContext(); |
| |
| mCacheState = kServiceMgrState_Resolving; |
| |
| finalizeConnectRequests(); |
| } |
| } |
| |
| /** |
| * @brief This method resets the service manager to its initial state. |
| * |
| * This method resets all service manager states including communications |
| * state, cache state, and the state of any pending connect requests. |
| * |
| * This version of the method - here for backwards compatibility - |
| * takes and logs an error then calls reset(void) . |
| * |
| * @param [in] aError The error that triggered this operation. |
| * |
| * @sa reset(void) |
| */ |
| void WeaveServiceManager::reset(WEAVE_ERROR aError) |
| { |
| WeaveLogProgress(ServiceDirectory, "reset: %s", ErrorStr(aError)); |
| |
| reset(); |
| } |
| |
| /** |
| * @brief This method resets the service manager to its initial state. |
| * |
| * This method resets all service manager states including communications |
| * state, cache state and the state of any pending connect requests. |
| * |
| * @sa reset(WEAVE_ERROR) |
| * |
| */ |
| void WeaveServiceManager::reset(void) |
| { |
| WeaveLogProgress(ServiceDirectory, "reset()"); |
| |
| cleanupExchangeContext(); |
| |
| clearWorkingState(); |
| clearCacheState(); |
| |
| finalizeConnectRequests(); |
| } |
| |
| /** |
| * @brief This method relocates the service directory cache. |
| * |
| * When a service endpoint returns a status report with status code |
| * kStatus_Relocated, the application could call unresolve() to clear |
| * up the cache and cancel connection requests. This method simplifies |
| * error handling by calling unresolve() in the first time, and reset() |
| * if the problem is not resolved yet. |
| * |
| * This version of the method - here for backwards compatibility - |
| * takes and logs an error then calls relocate(void) . |
| * |
| * @param [in] aError an error to log. |
| * |
| * @sa relocate(void) |
| */ |
| void WeaveServiceManager::relocate(WEAVE_ERROR aError) |
| { |
| WeaveLogProgress(ServiceDirectory, "relocate: %s", ErrorStr(aError)); |
| |
| relocate(); |
| } |
| |
| /** |
| * @brief This method relocates the service directory cache. |
| * |
| * When a service endpoint returns a status report with status code |
| * kStatus_Relocated, the application could call unresolve() to clear |
| * up the cache and cancel connection requests. This method simplifies |
| * error handling by calling unresolve() in the first time, and reset() |
| * if the problem is not resolved yet. |
| * |
| * @sa relocate(WEAVE_ERROR) |
| */ |
| void WeaveServiceManager::relocate(void) |
| { |
| WeaveLogProgress(ServiceDirectory, "relocate()"); |
| |
| if (mWasRelocated) |
| { |
| reset(); |
| } |
| else |
| { |
| mWasRelocated = !mWasRelocated; |
| |
| unresolve(); |
| } |
| } |
| |
| /** |
| * @brief |
| * This method handles the connect completed event for service endpoint |
| * query transaction. |
| * |
| * There are a couple of possibilities. First, the connection could |
| * have failed in which case we're done. Otherwise, the connection is |
| * actually complete and what we want to do is open an exchange |
| * context and send a directory query. |
| * |
| * @param [in] aError An Weave error if there is any error during the |
| * connection setup. |
| */ |
| void WeaveServiceManager::onConnectionComplete(WEAVE_ERROR aError) |
| { |
| WEAVE_ERROR err = aError; |
| PacketBuffer *buf = NULL; |
| |
| WeaveLogProgress(ServiceDirectory, "onConnectionComplete() <= %s", ErrorStr(aError)); |
| |
| SuccessOrExit(err); |
| |
| // get an exchange context from EM |
| |
| mExchangeContext = mExchangeManager->NewContext(mConnection, this); |
| VerifyOrExit(mExchangeContext, err = WEAVE_ERROR_NO_MEMORY); |
| |
| // get a buffer to send our message |
| |
| buf = PacketBuffer::NewWithAvailableSize(WEAVE_SYSTEM_CONFIG_HEADER_RESERVE_SIZE, 0); |
| VerifyOrExit(buf, err = WEAVE_ERROR_NO_MEMORY); |
| |
| if (mServiceEndpointQueryBegin) |
| mServiceEndpointQueryBegin(); |
| |
| /* |
| * put a "default" close callback in the connection in case it |
| * gets closed from the other end. |
| */ |
| |
| mConnection->OnConnectionClosed = handleConnectionClosed; |
| |
| mExchangeContext->AppState = this; |
| mExchangeContext->OnMessageReceived = handleResponseMsg; |
| mExchangeContext->OnConnectionClosed = ecHandleConnectionClosed; |
| mExchangeContext->OnResponseTimeout = handleResponseTimeout; |
| mExchangeContext->ResponseTimeout = kWeave_DefaultSendTimeout; |
| |
| err = mExchangeContext->SendMessage(kWeaveProfile_ServiceDirectory, |
| kMsgType_ServiceEndpointQuery, |
| buf, |
| ExchangeContext::kSendFlag_ExpectResponse); |
| |
| exit: |
| |
| if (err != WEAVE_NO_ERROR) |
| { |
| if (buf) |
| PacketBuffer::Free(buf); |
| |
| fail(err); |
| } |
| } |
| |
| /** |
| * This method handles the connection closed event reported by the associated Weave exchange context. |
| * |
| * @param [in] aError An Weave error indicating the reason for this connection to be closed. |
| */ |
| void WeaveServiceManager::onConnectionClosed(WEAVE_ERROR aError) |
| { |
| WeaveLogProgress(ServiceDirectory, "onConnectionClosed() <= %s", ErrorStr(aError)); |
| |
| fail(aError); |
| } |
| |
| /** |
| * @brief |
| * This method handles any response message in the conversation with |
| * directory service. |
| * |
| * @param [in] aProfileId The profile ID for this incoming message. |
| * @param [in] aMsgType The profile-specific type for this message. |
| * @param [in] aMsg The content of this message. |
| */ |
| void WeaveServiceManager::onResponseReceived(uint32_t aProfileId, uint8_t aMsgType, PacketBuffer *aMsg) |
| { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| StatusReport report; |
| bool redir = false; |
| |
| WeaveLogProgress(ServiceDirectory, "onResponseReceived(0x%x, %d)", aProfileId, aMsgType); |
| |
| // start by closing the old exchange context and connection to free up resources |
| |
| cleanupExchangeContext(); |
| |
| if (aProfileId == kWeaveProfile_StatusReport_Deprecated || aProfileId == kWeaveProfile_Common) |
| { |
| /* |
| * OK. so we got a status report rather than a reponse. at this |
| * point our handling for this case is pretty primitive. we need |
| * a more sophisticated way of doing errors like this. |
| */ |
| |
| StatusReport::parse(aMsg, report); |
| |
| WeaveLogProgress(ServiceDirectory, "status: %lx, %x", report.mProfileId, report.mStatusCode); |
| |
| clearWorkingState(); |
| |
| mCacheState = kServiceMgrState_Initial; |
| |
| transactionsReportStatus(report); |
| } |
| |
| else |
| { |
| VerifyOrExit(aProfileId == kWeaveProfile_ServiceDirectory, err = WEAVE_ERROR_INVALID_PROFILE_ID); |
| |
| WeaveLogProgress(ServiceDirectory, "kWeaveProfile_ServiceDirectory"); |
| |
| /* |
| * here, we've got an actual query response. what we do below |
| * depends on the state we're in, as follows. |
| */ |
| |
| VerifyOrExit(aMsgType == kMsgType_ServiceEndpointResponse, err = WEAVE_ERROR_INVALID_MESSAGE_TYPE); |
| VerifyOrExit(mCacheState == kServiceMgrState_Waiting, err = WEAVE_ERROR_INCORRECT_STATE); |
| |
| /* |
| * this block unpacks the service directory message. |
| */ |
| |
| { |
| MessageIterator i(aMsg); |
| |
| uint16_t msgLen = aMsg->DataLength(); |
| uint8_t dirCtrl; |
| uint8_t *writePtr; |
| uint8_t dirLen; |
| bool suffixesPresent; |
| uint8_t aLength; |
| bool timePresent = false; |
| |
| err = i.readByte(&dirCtrl); |
| SuccessOrExit(err); |
| |
| dirLen = dirCtrl & kMask_DirectoryLen; |
| redir = (dirCtrl & kMask_Redirect) != 0; |
| suffixesPresent = (dirCtrl & kMask_SuffixTablePresent) != 0; |
| timePresent = (dirCtrl & kMask_TimeFieldsPresent) != 0; |
| |
| if (((msgLen > mCache.length) && !timePresent) || (msgLen > (mCache.length + (sizeof(uint64_t) + sizeof(uint32_t))))) |
| { |
| WeaveLogProgress(ServiceDirectory, "message length error: %d m.len:%d", msgLen, mCache.length); |
| |
| err = WEAVE_ERROR_MESSAGE_TOO_LONG; |
| } |
| SuccessOrExit(err); |
| |
| /* |
| * here we have directory information beyond the root directory but |
| * we're not done yet. |
| */ |
| |
| mDirectory.length = dirLen; |
| writePtr = mDirectory.base = mCache.base; |
| |
| err = cacheDirectory(i, mDirectory.length, writePtr); |
| SuccessOrExit(err); |
| |
| if (suffixesPresent) |
| { |
| WeaveLogProgress(ServiceDirectory, "suffixesPresent"); |
| |
| err = i.readByte(&aLength); |
| SuccessOrExit(err); |
| |
| mSuffixTable.length = aLength; |
| writePtr += 1; |
| mSuffixTable.base = writePtr; |
| |
| mDirAndSuffTableSize++; |
| |
| err = cacheSuffixes(i, mSuffixTable.length, writePtr); |
| SuccessOrExit(err); |
| } |
| |
| else |
| { |
| mSuffixTable.length = 0; |
| mSuffixTable.base = NULL; |
| } |
| |
| if (timePresent) |
| { |
| WeaveLogProgress(ServiceDirectory, "timePresent"); |
| |
| err = handleTimeInfo(i); |
| SuccessOrExit(err); |
| } |
| } |
| |
| /* |
| * Release the received message buffer so that any code we |
| * call below can immediately re-use it. |
| */ |
| |
| PacketBuffer::Free(aMsg); |
| aMsg = NULL; |
| |
| if (redir) |
| { |
| // send out yet another query using this directory server. |
| |
| mConnection = mExchangeManager->MessageLayer->NewConnection(); |
| VerifyOrExit(mConnection, err = WEAVE_ERROR_NO_MEMORY); |
| |
| WeaveLogProgress(ServiceDirectory, "onResponseReceived(): redirecting"); |
| |
| err = lookupAndConnect(mConnection, |
| kServiceEndpoint_Directory, |
| mDirAuthMode, |
| this, |
| handleSDConnectionComplete, |
| WEAVE_CONFIG_SERVICE_DIR_CONNECT_TIMEOUT_MSECS); |
| } |
| |
| else |
| { |
| mCacheState = kServiceMgrState_Resolved; |
| |
| WeaveLogProgress(ServiceDirectory, "onResponseReceived(): ->resolved"); |
| |
| // now we gotta process all the pending transactions (see below) |
| |
| for (uint8_t j = 0; j < ARRAY_SIZE(mConnectRequestPool); j++) |
| { |
| |
| /* |
| * go through all the transactions here even if some |
| * of them err out and invoke a handler. this leaves |
| * openthe possiblity that hihger layer code can |
| * handle indiviual failures individually. |
| */ |
| |
| WEAVE_ERROR conErr; |
| ConnectRequest *req = &mConnectRequestPool[j]; |
| StatusHandler handler = req->mStatusHandler; |
| void *appState = req->mAppState; |
| |
| if (req->mServiceEp != 0) |
| { |
| WeaveLogProgress(ServiceDirectory, "onResponseReceived() txn = %llx", req->mServiceEp); |
| |
| conErr = lookupAndConnect(req->mConnection, |
| req->mServiceEp, |
| req->mAuthMode, |
| req, |
| handleAppConnectionComplete, |
| req->mConnectTimeoutMsecs, |
| req->mConnIntf); |
| |
| if (conErr != WEAVE_NO_ERROR) |
| { |
| req->finalize(); |
| |
| if (handler) |
| handler(appState, conErr, NULL); |
| } |
| } |
| } |
| } |
| } |
| |
| exit: |
| |
| // Free the received message buffer if it hasn't been done already. |
| |
| PacketBuffer::Free(aMsg); |
| |
| if (err != WEAVE_NO_ERROR) |
| { |
| WeaveLogProgress(ServiceDirectory, "onResponseReceived: %s", ErrorStr(err)); |
| |
| fail(err); |
| } |
| } |
| |
| /** |
| * @brief |
| * This method handles the timeout event, in which no response was received |
| * from directory service. |
| */ |
| void WeaveServiceManager::onResponseTimeout(void) |
| { |
| fail(WEAVE_ERROR_TIMEOUT); |
| } |
| |
| /** |
| * @brief |
| * This method initializes a ConnectRequest instance with the arguments passed in |
| * |
| * @param [in] aManager A pointer to the containing service manager. |
| * @param [in] aServiceEp An ID to the intended service endpoint for |
| * this connect request. |
| * @param [in] aAuthMode A descriptor for the authentication method |
| * which should be used for this connection. |
| * @param [in] aAppState An arbitrary pointer which would be passed |
| * back in callbacks. |
| * @param [in] aStatusHandler A pointer to callback function which handles |
| * a status report in response to service endpoint query. |
| * @param [in] aCompleteHandler A pointer to callback function which handles |
| * the connection complete event. |
| * @param [in] aConnectTimeoutMsecs The timeout for the Connect call to succeed |
| * or return an error. |
| * @param [in] aConnectIntf The interface over which the connection is |
| * to be established. |
| * |
| * @return #WEAVE_NO_ERROR on success; otherwise, a respective error code. |
| */ |
| WEAVE_ERROR WeaveServiceManager::ConnectRequest::init(WeaveServiceManager *aManager, |
| const uint64_t &aServiceEp, |
| WeaveAuthMode aAuthMode, |
| void *aAppState, |
| StatusHandler aStatusHandler, |
| WeaveConnection::ConnectionCompleteFunct aCompleteHandler, |
| const uint32_t aConnectTimeoutMsecs, |
| const InterfaceId aConnIntf) |
| { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| |
| VerifyOrExit(aServiceEp != 0 && aCompleteHandler, err = WEAVE_ERROR_INVALID_ARGUMENT); |
| |
| mConnection = aManager->mExchangeManager->MessageLayer->NewConnection(); |
| VerifyOrExit(mConnection, err = WEAVE_ERROR_NO_MEMORY); |
| |
| mServiceEp = aServiceEp; |
| mAuthMode = aAuthMode; |
| mAppState = aAppState; |
| mStatusHandler = aStatusHandler; |
| mConnectionCompleteHandler = aCompleteHandler; |
| mConnectTimeoutMsecs = aConnectTimeoutMsecs; |
| mConnIntf = aConnIntf; |
| |
| exit: |
| |
| return err; |
| } |
| |
| /** |
| * This method frees a connection request object, returning it to the pool |
| */ |
| void WeaveServiceManager::ConnectRequest::free(void) |
| { |
| if (!isFree()) |
| { |
| SYSTEM_STATS_DECREMENT(nl::Weave::System::Stats::kServiceMgr_NumRequests); |
| |
| memset(this, 0, sizeof(*this)); |
| } |
| } |
| |
| /** |
| * This method cleans up internal state, including connection closure |
| */ |
| void WeaveServiceManager::ConnectRequest::finalize(void) |
| { |
| WeaveConnection *con = mConnection; |
| |
| free(); |
| |
| if (con) |
| con->Close(); |
| } |
| |
| /** |
| * This method is a trampoline to application layer for the connection complete event. It calls the |
| * connection complete handler assigned at lookupAndConnect() . |
| */ |
| void WeaveServiceManager::ConnectRequest::onConnectionComplete(WEAVE_ERROR aError) |
| { |
| WeaveConnection *con = mConnection; |
| WeaveConnection::ConnectionCompleteFunct handler = mConnectionCompleteHandler; |
| |
| con->AppState = mAppState; |
| |
| free(); |
| |
| handler(con, aError); |
| } |
| |
| /** |
| * This method frees the entire connect request pool. |
| */ |
| void WeaveServiceManager::freeConnectRequests(void) |
| { |
| memset(mConnectRequestPool, 0, sizeof(mConnectRequestPool)); |
| |
| SYSTEM_STATS_RESET(nl::Weave::System::Stats::kServiceMgr_NumRequests); |
| } |
| |
| /** |
| * This method frees connect requests and close any hanging connections. |
| */ |
| void WeaveServiceManager::finalizeConnectRequests(void) |
| { |
| for (int i = 0; i < kConnectRequestPoolSize; i++) |
| { |
| ConnectRequest &r = mConnectRequestPool[i]; |
| |
| r.finalize(); |
| } |
| |
| SYSTEM_STATS_RESET(nl::Weave::System::Stats::kServiceMgr_NumRequests); |
| } |
| |
| /** |
| * @brief |
| * This method allocates and returns a new connect request instance or NULL. |
| * |
| * The returned ConnectRequest object is not initialized. A call to init() |
| * is necessary to properly initialize this object. |
| * |
| * @return A pointer to an uninitialized instance of ConnectRequest on success; |
| * NULL otherwise. |
| */ |
| WeaveServiceManager::ConnectRequest *WeaveServiceManager::getAvailableRequest(void) |
| { |
| WeaveServiceManager::ConnectRequest *retval = NULL; |
| |
| WEAVE_FAULT_INJECT(nl::Weave::FaultInjection::kFault_ServiceManager_ConnectRequestNew, return retval); |
| |
| for (uint8_t i = 0; i < ARRAY_SIZE(mConnectRequestPool); i++) |
| { |
| if (mConnectRequestPool[i].mServiceEp == 0) |
| { |
| retval = &(mConnectRequestPool[i]); |
| SYSTEM_STATS_INCREMENT(nl::Weave::System::Stats::kServiceMgr_NumRequests); |
| break; |
| } |
| } |
| |
| return retval; |
| } |
| |
| WEAVE_ERROR WeaveServiceManager::calculateEntryLength(uint8_t *entryStart, |
| uint8_t entryCtrlByte, |
| uint16_t *entryLen) |
| { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| uint8_t listLen = entryCtrlByte & kMask_HostPortListLen; |
| uint8_t entryType = entryCtrlByte & kMask_DirectoryEntryType; |
| uint8_t *p = entryStart; |
| |
| *entryLen = 0; |
| |
| switch (entryType) |
| { |
| case kDirectoryEntryType_SingleNode: |
| *entryLen += 8; |
| break; |
| case kDirectoryEntryType_HostPortList: |
| for (uint8_t j = 0; j < listLen; j++) |
| { |
| uint8_t itemCtrlByte = Read8(p); |
| (*entryLen)++; |
| // read the string length and skip the name string |
| |
| uint8_t itemLen = Read8(p); |
| (*entryLen)++; |
| |
| *entryLen += itemLen; |
| |
| // then the optional fields if any |
| |
| if (itemCtrlByte & kMask_SuffixIndexPresent) |
| (*entryLen)++; |
| |
| if (itemCtrlByte & kMask_PortIdPresent) |
| *entryLen += 2; |
| } |
| break; |
| default: |
| // don't know what to do about other entry types |
| err = WEAVE_ERROR_INVALID_DIRECTORY_ENTRY_TYPE; |
| break; |
| } |
| |
| return err; |
| } |
| |
| /** |
| * @brief |
| * This method looks up the given service endpoint in the cache and sets up an |
| * Weave connection with completion callback. |
| * |
| * @return #WEAVE_NO_ERROR on success; otherwise, a respective error code. |
| */ |
| WEAVE_ERROR WeaveServiceManager::lookupAndConnect(WeaveConnection *aConnection, |
| uint64_t aServiceEp, |
| WeaveAuthMode aAuthMode, |
| void *aAppState, |
| WeaveConnection::ConnectionCompleteFunct aHandler, |
| const uint32_t aConnectTimeoutMsecs, |
| const InterfaceId aConnectIntf) |
| { |
| WEAVE_ERROR err; |
| |
| uint8_t ctrlByte; |
| uint8_t *entry = NULL; |
| uint8_t itemCount; |
| |
| WeaveLogProgress(ServiceDirectory, "lookupAndConnect(%llx...)", aServiceEp); |
| |
| err = lookup(aServiceEp, &ctrlByte, &entry); |
| SuccessOrExit(err); |
| |
| VerifyOrExit((ctrlByte & kMask_DirectoryEntryType) == kDirectoryEntryType_HostPortList, err = WEAVE_ERROR_HOST_PORT_LIST_EMPTY); |
| |
| itemCount = ctrlByte & kMask_HostPortListLen; |
| |
| aConnection->AppState = aAppState; |
| aConnection->OnConnectionComplete = aHandler; |
| |
| aConnection->SetConnectTimeout(aConnectTimeoutMsecs); |
| |
| err = aConnection->Connect(aServiceEp, |
| aAuthMode, |
| HostPortList(entry, itemCount, mSuffixTable.base, mSuffixTable.length), |
| aConnectIntf); |
| |
| exit: |
| |
| if (err != WEAVE_NO_ERROR) |
| WeaveLogProgress(ServiceDirectory, "lookupAndConnect: %s", ErrorStr(err)); |
| |
| return err; |
| } |
| |
| |
| /** |
| * @brief |
| * This method updates the local directory cache with the response we |
| * receive from the directory service. |
| * |
| * @param [in] aIterator An iterator which is used to scan the input. |
| * @param [in] aLength The number of entries in the directory being |
| * cached. |
| * @param [inout] aWritePtr A reference to pointer to where we're writing |
| * in the cache which would be updated along the process. At return, the |
| * pointer should point to the byte right after the area which has been |
| * filled. |
| * |
| * @return #WEAVE_NO_ERROR on success; otherwise, a respective error code. |
| */ |
| WEAVE_ERROR WeaveServiceManager::cacheDirectory(MessageIterator &aIterator, uint8_t aLength, uint8_t *&aWritePtr) |
| { |
| WEAVE_ERROR retval = WEAVE_NO_ERROR; |
| uint8_t *startWritePtr = aWritePtr; |
| |
| for (int index = 0; index < aLength; index++) |
| { |
| // write the control byte |
| |
| retval = aIterator.readByte(aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| uint8_t listCtrl = *aWritePtr; |
| uint8_t listLen = listCtrl & kMask_HostPortListLen; |
| |
| aWritePtr++; |
| |
| // and the service EP |
| retval = aIterator.read64((uint64_t *)aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| aWritePtr += 8; |
| |
| if (0 == (listCtrl & ~kMask_HostPortListLen)) |
| { |
| // flags are zero |
| // this means we're looking at a single node ID |
| |
| retval = aIterator.read64((uint64_t *)aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| aWritePtr += 8; |
| } |
| else |
| { |
| // otherwise it's a host/port list |
| |
| for (int j = 0; j < listLen; j++) |
| { |
| // again, write the control byte |
| |
| retval = aIterator.readByte(aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| uint8_t itemCtrl = *aWritePtr; |
| |
| aWritePtr++; |
| |
| // now the string (with length) |
| |
| retval = aIterator.readByte(aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| uint8_t strLen = *aWritePtr; |
| |
| aWritePtr++; |
| |
| retval = aIterator.readBytes(strLen, aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| aWritePtr += strLen; |
| |
| // now the optional bits |
| |
| if ((itemCtrl & kMask_SuffixIndexPresent) != 0) |
| { |
| retval = aIterator.readByte(aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| aWritePtr++; |
| } |
| |
| if ((itemCtrl & kMask_PortIdPresent) != 0) |
| { |
| retval = aIterator.read16((uint16_t *)aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| aWritePtr += 2; |
| } |
| } |
| } |
| } |
| |
| // Store the size of the directory in bytes. |
| |
| mDirAndSuffTableSize += aWritePtr - startWritePtr; |
| |
| return retval; |
| } |
| |
| /** |
| * @brief |
| * This method updates suffix part of the local directory cache with the |
| * response we receive from the directory service. |
| * |
| * @param [in] aIterator An iterator which is used to scan the input. |
| * @param [in] aLength The number of entries in the directory being |
| * cached. |
| * @param [inout] aWritePtr A reference to pointer to where we're writing |
| * in the cache which would be updated along the process. At return, the |
| * pointer should point to the byte right after the area which has been |
| * filled. |
| * |
| * @return #WEAVE_NO_ERROR on success; otherwise, a respective error code. |
| */ |
| WEAVE_ERROR WeaveServiceManager::cacheSuffixes(MessageIterator &aIterator, uint8_t aLength, uint8_t *&aWritePtr) |
| { |
| WEAVE_ERROR retval = WEAVE_NO_ERROR; |
| uint8_t *startWritePtr = aWritePtr; |
| |
| for (uint8_t i = 0; i < aLength; i++) |
| { |
| // write the string (with length) |
| |
| retval = aIterator.readByte(aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| uint8_t strLen = *aWritePtr; |
| |
| aWritePtr++; |
| |
| retval = aIterator.readBytes(strLen, aWritePtr); |
| |
| if (retval != WEAVE_NO_ERROR) |
| break; |
| |
| aWritePtr += strLen; |
| } |
| |
| // Add the suffix table to the length |
| |
| mDirAndSuffTableSize += aWritePtr - startWritePtr; |
| |
| return retval; |
| } |
| |
| /** |
| * @brief |
| * This method cleans up after any failure by clearing the service |
| * manager's working state, calling all the appropriate handler methods and |
| * freeing any pending transactions. |
| * |
| * @param[in] anError An error code indicating the cause of failure. |
| */ |
| void WeaveServiceManager::fail(WEAVE_ERROR aError) |
| { |
| WeaveLogProgress(ServiceDirectory, "fail() <= %s", ErrorStr(aError)); |
| |
| cleanupExchangeContext(aError); |
| |
| clearWorkingState(); |
| clearCacheState(); |
| |
| transactionsErrorOut(aError); |
| } |
| |
| /** |
| * @brief |
| * This method finalizes all connection requests, and calls |
| * status handler to allocated connection requests, with the error code. |
| * |
| * @param[in] anError An error code indicating cause of failure. |
| */ |
| void WeaveServiceManager::transactionsErrorOut(WEAVE_ERROR aError) |
| { |
| for (size_t i = 0; i < ARRAY_SIZE(mConnectRequestPool); i++) |
| { |
| ConnectRequest *req = &mConnectRequestPool[i]; |
| |
| StatusHandler statusHndlr = req->mStatusHandler; |
| void *appState = req->mAppState; |
| |
| req->finalize(); |
| |
| if (statusHndlr && appState) |
| statusHndlr(appState, aError, NULL); |
| } |
| |
| SYSTEM_STATS_RESET(nl::Weave::System::Stats::kServiceMgr_NumRequests); |
| } |
| |
| /** |
| * @brief |
| * This method finalizes all connection requests, and calls |
| * status handler to allocated connection requests, with the status report. |
| * |
| * @param[in] aReport A reference to a status report received from a connection. |
| */ |
| void WeaveServiceManager::transactionsReportStatus(StatusReport &aReport) |
| { |
| for (size_t i = 0; i < ARRAY_SIZE(mConnectRequestPool); i++) |
| { |
| ConnectRequest *req = &mConnectRequestPool[i]; |
| |
| StatusHandler statusHndlr = req->mStatusHandler; |
| void *appState = req->mAppState; |
| |
| req->finalize(); |
| |
| if (statusHndlr && appState) |
| statusHndlr(appState, WEAVE_NO_ERROR, &aReport); |
| } |
| |
| SYSTEM_STATS_RESET(nl::Weave::System::Stats::kServiceMgr_NumRequests); |
| } |
| |
| /** |
| * @brief |
| * This method clears the current exchange context and its associated |
| * connection. |
| * |
| * @param[in] aErr WEAVE_NO_ERROR if this cleanup is not caused by |
| * any error. Otherwise, the connection would be aborted instead of |
| * gracefully closed. |
| * |
| * @sa cleanupExchangeContext(void) |
| */ |
| void WeaveServiceManager::cleanupExchangeContext(WEAVE_ERROR aErr) |
| { |
| if (mExchangeContext) |
| { |
| mExchangeContext->Close(); |
| mExchangeContext = NULL; |
| } |
| |
| if (mConnection) |
| { |
| if (WEAVE_NO_ERROR == aErr) |
| { |
| mConnection->Close(); |
| } |
| else |
| { |
| mConnection->Abort(); |
| } |
| mConnection = NULL; |
| } |
| } |
| |
| /** |
| * @brief |
| * This method clears the current exchange context and its associated |
| * connection. |
| * |
| * @sa cleanupExchangeContext(WEAVE_ERROR) |
| */ |
| void WeaveServiceManager::cleanupExchangeContext(void) |
| { |
| cleanupExchangeContext(WEAVE_NO_ERROR); |
| } |
| |
| /** |
| * @brief |
| * This method clears the working state of the manager leaving the cache state |
| * alone. |
| */ |
| void WeaveServiceManager::clearWorkingState(void) |
| { |
| mDirectory.length = 0; |
| mDirectory.base = NULL; |
| mSuffixTable.length = 0; |
| mSuffixTable.base = NULL; |
| mDirAndSuffTableSize = 0; |
| } |
| |
| /** |
| * @brief |
| * This private method parses the time related fields in response |
| * message. It calls mServiceEndpointQueryEndWithTimeInfo with the result |
| * if it's not NULL. |
| * |
| * @pararm[inout] itMsg A message iterator pointing to the beginning of time |
| * information. If no error is reported, the message iterator would be |
| * advanced after the time-related fields |
| * |
| * @retval #WEAVE_NO_ERROR on success; otherwise, a respective error code. |
| * @retval #WEAVE_ERROR_BUFFER_TOO_SMALL if the parsing fails because of |
| * buffer underrun |
| */ |
| WEAVE_ERROR WeaveServiceManager::handleTimeInfo(MessageIterator &itMsg) |
| { |
| WEAVE_ERROR err = WEAVE_NO_ERROR; |
| uint64_t timeQueryReceiptMsec; |
| uint32_t timeProcessMsec; |
| |
| SuccessOrExit(err = itMsg.read64(&timeQueryReceiptMsec)); |
| SuccessOrExit(err = itMsg.read32(&timeProcessMsec)); |
| |
| if (NULL != mServiceEndpointQueryEndWithTimeInfo) |
| { |
| mServiceEndpointQueryEndWithTimeInfo(timeQueryReceiptMsec, timeProcessMsec); |
| } |
| |
| exit: |
| WeaveLogFunctError(err); |
| return err; |
| } |
| |
| /** |
| * @brief |
| * This method clears the state and cache of the manager if the state is in |
| * the terminal kServiceMgrState_Resolved state, which means that response |
| * from Service Directory endpoint was received. |
| */ |
| void WeaveServiceManager::clearCache(void) |
| { |
| WeaveLogProgress(ServiceDirectory, "clearCache(), state is %d", mCacheState); |
| |
| if (mCacheState == kServiceMgrState_Resolved) |
| { |
| clearWorkingState(); |
| clearCacheState(); |
| } |
| } |
| #endif //WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY |