| /* |
| * |
| * Copyright (c) 2016-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 |
| * This file defines notification engine for Weave |
| * Data Management (WDM) profile. |
| * |
| */ |
| |
| |
| #ifndef _WEAVE_DATA_MANAGEMENT_NOTIFICATION_ENGINE_CURRENT_H |
| #define _WEAVE_DATA_MANAGEMENT_NOTIFICATION_ENGINE_CURRENT_H |
| |
| #include <Weave/Profiles/data-management/Current/WdmManagedNamespace.h> |
| #include <Weave/Core/WeaveCore.h> |
| #include <Weave/Profiles/data-management/SubscriptionHandler.h> |
| #include <Weave/Profiles/data-management/TraitData.h> |
| #include <Weave/Profiles/data-management/TraitCatalog.h> |
| |
| namespace nl { |
| namespace Weave { |
| namespace Profiles { |
| namespace WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current) { |
| |
| /* |
| * @class NotificationEngine |
| * |
| * @brief The notification engine is responsible for generating notifies to subscriber. It is able to find the intersection between |
| * the path interest set of each subscriber with what has changed in the publisher data store and generate tailored notifies for |
| * each subscriber. |
| * |
| * To achieve this, the engine tracks data-changes (i.e data dirtiness) at a couple of different levels: |
| * - Per subscriber, per trait instance dirtiness: Every subscriber tracks trait-changes at a per-instance granularity. |
| * Anytime a data source makes known that a property handle within has changed, the NE will iterate over every subscriber that has subscribed |
| * to that trait instance and mark the fact that that instance is now dirty. |
| * |
| * - Granular per trait instance, per property handle dirtiness: If selected through compile-time options by the user, the engine will |
| * mark dirtiness down to the property handle. This allows it to generate compact notifies that convey as succinctly as possible the data |
| * that has changed. This will be described in more detail in the solvers section. |
| * |
| * At its core, it iterates over every subscription, then every dirty instance within that subscription and tries to gather and pack as much relevant data |
| * as possible into a notify message before sending that to the subscriber. It continues to do so until it has no more work to do. This could be due to a couple of reasons: |
| * - Notifies are in flight to the subscriber(s) |
| * - We have exceeded the maximum number of notifies that can be flight across all subscribers. |
| * - We have no more space in the packet to stuff in more data. |
| * - We have no more dirty data to process for a particular set of subscriptions. |
| * |
| * Once it surmises there is no more work to be done, it returns. If all work for a subscription has been completed, it will invoke a method in the SubscriptionHandler to finish |
| * processing that subscription (which might involve sending out subscription responses). |
| * |
| * During subscription establishment, the NE works slightly differently than at other times - it will retrieve *all* the data for a particular trait. There-after, it will only retrieve |
| * new, changed data. |
| * |
| * Some notable features: |
| * - Subscription fairness: The engine round-robins over all subscriptions and will always resume its work loop at the last subscription it was |
| * trying to process to ensure all subscriptions are handled with equal priority. |
| * |
| * - Trait instance fairness: Within a subscription, the engine also rounds robins over all trait instances and will resume its work loop at the last trait instance that was being |
| * processed *for that subscription*. This ensures trait instances that have a high rate of change don't starve out others. |
| * |
| * - Inter-trait chunking across multiple notifies: The engine supports splitting trait data over multiple notifies. It will however only do this split at the trait instance granularity. |
| * It cannot chunk up data within a trait. |
| * |
| * - Graceful degradation due to resource shortages: If it runs out space in the dirty stores, the engine will degrade gracefully by generating sub-optimal notify messages that have more |
| * data in them while still being protocol correct. |
| * |
| */ |
| class NotificationEngine |
| { |
| public: |
| /** |
| * Initializes the engine. Should only be called once. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval other Was unable to retrieve data and write it into the writer. |
| */ |
| WEAVE_ERROR Init(void); |
| |
| /** |
| * Main work-horse function that executes the run-loop. |
| * |
| * @retval void |
| */ |
| void Run(void); |
| |
| /** |
| * Marks a handle associated with a data source as being dirty. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval other Was unable to retrieve data and write it into the writer. |
| */ |
| WEAVE_ERROR SetDirty(TraitDataSource *aDataSource, PropertyPathHandle aPropertyHandle); |
| |
| WEAVE_ERROR DeleteKey(TraitDataSource *aDataSource, PropertyPathHandle aPropertyHandle); |
| |
| enum NotifyRequestBuilderState |
| { |
| kNotifyRequestBuilder_Idle = 0, //< The request has not been opened or has been closed and finalized |
| kNotifyRequestBuilder_Ready, //< The request has been initialized and is ready for any optional toplevel elements |
| kNotifyRequestBuilder_BuildDataList, //< The request is building the DataList portion of the structure |
| kNotifyRequestBuilder_BuildEventList //< The request is building the EventList portion of the structure |
| }; |
| |
| /** |
| * @class NotifyRequestBuilder |
| * |
| * @brief This provides a helper class to compose notifies and abstract away the construction and structure of the message from its consumers. This is a more compact |
| * version of a similar class provided in MessageDef.cpp that aims to be sensitive to the flash and ram needs of the device. |
| */ |
| class NotifyRequestBuilder |
| { |
| public: |
| /** |
| * Initializes the builder. Should only be called once. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval other Was unable to initialize the builder. |
| */ |
| WEAVE_ERROR Init(PacketBuffer**aBuf, TLV::TLVWriter *aWriter, SubscriptionHandler *aSubHandler); |
| |
| /** |
| * Start the construction of the notify. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval #WEAVE_ERROR_INCORRECT_STATE If the request is not at the toplevel of the buffer. |
| * @retval other Unable to construct the end of the notify. |
| */ |
| |
| WEAVE_ERROR StartNotifyRequest(); |
| |
| /** |
| * End the construction of the notify. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval #WEAVE_ERROR_INCORRECT_STATE If the request is not at Notify container. |
| * @retval other Unable to construct the end of the notify. |
| */ |
| WEAVE_ERROR EndNotifyRequest(); |
| |
| /** |
| * Starts the construction of the data list array. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval #WEAVE_ERROR_INCORRECT_STATE If the request is not at the Notify container. |
| * @retval other Unable to construct the beginning of the data list. |
| */ |
| WEAVE_ERROR StartDataList(void); |
| |
| /** |
| * End the construction of the data list array. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval #WEAVE_ERROR_INCORRECT_STATE If the request is not at the DataList container. |
| * @retval other Unable to construct the end of the data list. |
| */ |
| WEAVE_ERROR EndDataList(); |
| |
| /** |
| * Starts the construction of the event list. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval #WEAVE_ERROR_INCORRECT_STATE If the request is not at the Notify container. |
| * @retval other Unable to construct the beginning of the data list. |
| */ |
| WEAVE_ERROR StartEventList(); |
| |
| /** |
| * End the construction of the event list. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval #WEAVE_ERROR_INCORRECT_STATE If the request is not at the EventList container. |
| * @retval other Unable to construct the end of the data list. |
| */ |
| WEAVE_ERROR EndEventList(); |
| |
| /** |
| * Given a trait path, write out the data element associated |
| * with that path. The caller can also optionally pass in a |
| * handle set allows for leveraging the merge operation with a |
| * narrower set of immediate child nodes of the parent |
| * property path handle. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval other Unable to retrieve and write the data element. |
| */ |
| WEAVE_ERROR WriteDataElement(TraitDataHandle aTraitDataHandle, PropertyPathHandle aPropertyPathHandle, SchemaVersion aSchemaVersion, PropertyPathHandle *aMergeDataHandleSet, uint32_t aNumMergeDataHandles, PropertyPathHandle *aDeleteHandleSet, uint32_t aNumDeleteHandles); |
| |
| /** |
| * Checkpoint the request state into a TLVWriter |
| * |
| * @param aWriter[out] A writer to checkpoint the state of the TLV writer into. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| */ |
| WEAVE_ERROR Checkpoint(TLV::TLVWriter &aPoint); |
| |
| /** |
| * Rollback the request state into the checkpointed TLVWriter |
| * |
| * @param aWriter[in] A writer to that captured the state at some point in the past |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| */ |
| WEAVE_ERROR Rollback(TLV::TLVWriter &aPoint); |
| |
| |
| TLV::TLVWriter *GetWriter(void) { return mWriter; } |
| |
| /** |
| * The main state transition function. The function takes the |
| * desired state (i.e., the phase of the notify request builder |
| * that we would like to reach), and transitions the request |
| * into that state. If the desired state is the same as the |
| * current state, the function does nothing. Otherwise, an |
| * PacketBuffer is allocated (if needed); the function first |
| * transitions the request into the toplevel notify request |
| * (either opening the notify request TLV structure, or |
| * closing the current TLV data container as needed), and then |
| * transitions the Notify request either by opening the |
| * appropriate TLV data container or by closing the |
| * overarching Notify request. |
| * |
| * @param aDesiredState The desired state the request should transition into |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval #WEAVE_ERROR_NO_MEMORY Could not transition into the state because of insufficient memory. |
| * @retval #WEAVE_ERROR_INCORRECT_STATE Corruption of the internal state machine. |
| * @retval other When the state machine could not record the state in its buffer, likely indicates a design flaw rather than a runtime issue. |
| */ |
| WEAVE_ERROR MoveToState(NotifyRequestBuilderState aDesiredState); |
| |
| private: |
| TLV::TLVWriter *mWriter; |
| NotifyRequestBuilderState mState; |
| PacketBuffer **mBuf; |
| SubscriptionHandler *mSub; |
| }; |
| |
| /* |
| * GRAPH SOLVERS |
| * |
| * To generate notifies, NE institutes a notion of a solver to help crunch the tree-based representation of the schema along with the dirty handle information. |
| * This allows for consolidation of the logic to processing the schema for notify generation to a localized body of functions. |
| * |
| * To accommodate the varying capabilities of Weave-enabled devices, a number of different solvers have been provided that range in their capabilities and their |
| * constraints. |
| * |
| * These solvers can be chosen at compile time to reduce flash usage. |
| * |
| * ---- To facilitate this, all the public methods in the solvers have to have the same signature! If you decide to add a new solver, please ensure this criteria is met! --- |
| * |
| */ |
| |
| /* |
| * @class BasicGraphSolver |
| * |
| * @brief This is a coarse, basic solver that will retrieve the entire contents of a trait instance from root. The solver trades of computational complexity and |
| * reduced storage requirements with inefficiency in the data transmitted over the wire. This is rarely useful for most applications given the sheer in-efficiency of data transmitted |
| * over the wire, especially for traits with lots of key/value pairs. It is however useful for bring-up or for debugging issues with the other solvers. |
| * |
| * Constraints: It only supports subscriptions to root and nothing deeper. |
| */ |
| class BasicGraphSolver |
| { |
| public: |
| static bool IsPropertyPathSupported(PropertyPathHandle aHandle); |
| WEAVE_ERROR RetrieveTraitInstanceData(NotifyRequestBuilder *aBuilder, TraitDataHandle aTraitDataHandle, SchemaVersion aSchemaVersion, bool aRetrieveAll); |
| static WEAVE_ERROR SetDirty(TraitDataHandle aTraitDataHandle, PropertyPathHandle aPropertyHandle); |
| WEAVE_ERROR ClearDirty(void); |
| }; |
| |
| /* |
| * @class IntermediateGraphSolver |
| * |
| * @brief This solver is able to generate compact notifies that try to only contain the modified bits of data. This leverages a finitely sized, global dirty store |
| * that houses granular dirty information per property handle per trait instance. When a notify is to be generated, the solver attempts to find the LCA (lowest-common-ancestor) |
| * of all the dirty nodes in the tree and generates a data-element against that path. In addition, it exploits the merge semantics of WDM to only include child trees of that LCA |
| * that contain dirty elements. This is pretty efficient given the reasonably flat, shallow structure of our IDLs. |
| * |
| * If it is unable to store anymore dirty items in the granular store, it will degrade to marking the entire trait instance as dirty. |
| * In addition, if it runs out of space in the merge handle set, it will degrade to including all child trees of the LCA'ed node. |
| * |
| */ |
| class IntermediateGraphSolver |
| { |
| public: |
| static bool IsPropertyPathSupported(PropertyPathHandle aHandle); |
| WEAVE_ERROR RetrieveTraitInstanceData(NotifyRequestBuilder *aBuilder, TraitDataHandle aTraitDataHandle, SchemaVersion aSchemaVersion, bool aRetrieveAll); |
| WEAVE_ERROR SetDirty(TraitDataHandle aTraitDataHandle, PropertyPathHandle aPropertyHandle); |
| |
| #if TDM_ENABLE_PUBLISHER_DICTIONARY_SUPPORT |
| WEAVE_ERROR DeleteKey(TraitDataHandle aTraitDataHandle, PropertyPathHandle aPropertyHandle); |
| #endif |
| |
| WEAVE_ERROR ClearDirty(void); |
| |
| struct Store |
| { |
| public: |
| Store(); |
| bool AddItem(TraitPath aItem); |
| void RemoveItem(TraitDataHandle aDataHandle); |
| void RemoveItemAt(uint32_t aIndex); |
| bool IsPresent(TraitPath aItem); |
| bool IsFull() { return mNumItems >= WDM_PUBLISHER_MAX_ITEMS_IN_TRAIT_DIRTY_STORE; } |
| uint32_t GetNumItems() { return mNumItems; } |
| uint32_t GetStoreSize() { return WDM_PUBLISHER_MAX_ITEMS_IN_TRAIT_DIRTY_STORE; } |
| void Clear(); |
| |
| TraitPath mStore[WDM_PUBLISHER_MAX_ITEMS_IN_TRAIT_DIRTY_STORE]; |
| bool mValidFlags[WDM_PUBLISHER_MAX_ITEMS_IN_TRAIT_DIRTY_STORE]; |
| uint32_t mNumItems; |
| }; |
| |
| private: |
| static void ClearTraitInstanceDirty(void *aDataSource, TraitDataHandle aDataHandle, void *aContext); |
| PropertyPathHandle GetNextCandidateHandle(uint32_t &aChangeStoreCursor, TraitDataHandle aTargetDataHandle, bool &aCandidateHandleIsDelete); |
| |
| Store mDirtyStore; |
| |
| #if TDM_ENABLE_PUBLISHER_DICTIONARY_SUPPORT |
| Store mDeleteStore; |
| #endif |
| }; |
| |
| private: |
| friend class SubscriptionHandler; |
| friend class TestTdm; |
| friend class TestWdm; |
| |
| /** |
| * Should be invoked when the device receives a NotifyConfirm, or when the Notify request times out. |
| * This allows the engine to do some clean-up. |
| * |
| * @retval #WEAVE_NO_ERROR On success. |
| * @retval other Was unable to retrieve data and write it into the writer. |
| */ |
| void OnNotifyConfirm(SubscriptionHandler *aSubHandler, bool aNotifyDelivered); |
| |
| WEAVE_ERROR BuildSingleNotifyRequestDataList(SubscriptionHandler *aSubHandler, NotifyRequestBuilder &aNotifyRequest, bool &isSubscriptionClean, bool &aNeWriteInProgress); |
| WEAVE_ERROR BuildSingleNotifyRequestEventList(SubscriptionHandler *aSubHandler, NotifyRequestBuilder &aNotifyRequest, bool &isSubscriptionClean, bool &aNeWriteInProgress); |
| WEAVE_ERROR BuildSingleNotifyRequest(SubscriptionHandler *aSubHandler, bool &aSubscriptionHandled, bool &isSubscriptionClean); |
| |
| WEAVE_ERROR RetrieveTraitInstanceData(SubscriptionHandler *aSubHandler, SubscriptionHandler::TraitInstanceInfo *aTraitInfo, NotifyRequestBuilder *aBuilder, bool *aPacketFull); |
| WEAVE_ERROR SendNotify(PacketBuffer *aBuf, SubscriptionHandler *aSubHandler); |
| |
| WEAVE_ERROR SendNotifyRequest(); |
| |
| uint32_t mCurSubscriptionHandlerIdx; |
| uint32_t mCurTraitInstanceIdx; |
| uint32_t mNumNotifiesInFlight; |
| nl::Weave::TLV::TLVType mOuterContainerType; |
| WEAVE_CONFIG_WDM_PUBLISHER_GRAPH_SOLVER mGraphSolver; |
| }; |
| |
| }; // WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current) |
| }; // Profiles |
| }; // Weave |
| }; // nl |
| |
| #endif // _WEAVE_DATA_MANAGEMENT_NOTIFICATION_ENGINE_CURRENT_H |