blob: 7b73cd53fdc6d863bb322a2402d3cc65479bd6a9 [file] [log] [blame]
/*
*
* Copyright (c) 2016-2018 Nest Labs, Inc.
* Copyright (c) 2019-2020 Google, LLC.
* 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 forms the crux of the TDM layer (trait data management), providing
* various classes that manage and process data as it applies to traits and their
* associated schemas.
*
*/
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <Weave/Profiles/data-management/Current/WdmManagedNamespace.h>
#include <Weave/Profiles/data-management/DataManagement.h>
#include <Weave/Support/WeaveFaultInjection.h>
#include <Weave/Support/RandUtils.h>
using namespace ::nl::Weave;
using namespace ::nl::Weave::TLV;
using namespace ::nl::Weave::Profiles;
using namespace ::nl::Weave::Profiles::Common;
using namespace ::nl::Weave::Profiles::DataManagement;
using namespace ::nl::Weave::Profiles::DataManagement_Current;
UpdateDirtyPathFilter::UpdateDirtyPathFilter(SubscriptionClient * apSubClient, TraitDataHandle traitDataHandle,
const TraitSchemaEngine * aEngine)
{
mpSubClient = apSubClient;
mTraitDataHandle = traitDataHandle;
mSchemaEngine = aEngine;
}
bool UpdateDirtyPathFilter::FilterPath(PropertyPathHandle pathhandle)
{
bool retval = false;
#if WEAVE_CONFIG_ENABLE_WDM_UPDATE
if (mpSubClient)
{
// TODO: clean this up:
// mpSubClient is set to something only by UpdatableDataSink instances
retval = mpSubClient->FilterNotifiedPath(mTraitDataHandle, pathhandle, mSchemaEngine);
}
#endif // WEAVE_CONFIG_ENABLE_WDM_UPDATE
return retval;
}
UpdateDictionaryDirtyPathCut::UpdateDictionaryDirtyPathCut(TraitDataHandle aTraitDataHandle, UpdateEncoder * apEncoder)
{
mpUpdateEncoder = apEncoder;
mTraitDataHandle = aTraitDataHandle;
}
WEAVE_ERROR UpdateDictionaryDirtyPathCut::CutPath(PropertyPathHandle aPathhandle, const TraitSchemaEngine * apEngine)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// TODO: rename this struct, and pass apEngine to the constructor.
// Probably just replace the struct with a function; I don't see the point of it really.
#if WEAVE_CONFIG_ENABLE_WDM_UPDATE
err = mpUpdateEncoder->InsertInProgressUpdateItem(TraitPath(mTraitDataHandle, aPathhandle));
WeaveLogDetail(DataManagement, "Cut dictionary %u, %u; err %d", mTraitDataHandle, aPathhandle, err);
#endif // WEAVE_CONFIG_ENABLE_WDM_UPDATE
return err;
}
WEAVE_ERROR TraitSchemaEngine::ParseTagString(const char * apTagString, char ** apEndptr, uint8_t & aParseRes) const
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
VerifyOrExit(apTagString != NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
VerifyOrExit(*apTagString == '/', err = WEAVE_ERROR_INVALID_ARGUMENT);
apTagString++;
aParseRes = strtoul(apTagString, apEndptr, 0);
VerifyOrExit(!(*apEndptr == apTagString || (**apEndptr != '\0' && **apEndptr != '/')), err = WEAVE_ERROR_INVALID_ARGUMENT);
VerifyOrExit(aParseRes < kContextTagMaxNum, err = WEAVE_ERROR_INVALID_TLV_TAG);
exit:
return err;
}
WEAVE_ERROR TraitSchemaEngine::MapPathToHandle(const char * aPathString, PropertyPathHandle & aHandle) const
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
PropertyPathHandle childProperty, curProperty;
char * parseEnd;
uint8_t parseRes = 0;
uint64_t tag = 0;
VerifyOrExit(aPathString != NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
// initialize the out argument to NULL
aHandle = kNullPropertyPathHandle;
// Set our starting point for traversal to the root node.
curProperty = kRootPropertyPathHandle;
if (aPathString[0] == '/' && aPathString[1] == '\0')
{
ExitNow();
}
// Descend into our schema tree using the tags encountered to help navigate through the various branches.
while (*aPathString != '\0')
{
err = ParseTagString(aPathString, &parseEnd, parseRes);
SuccessOrExit(err);
// Todo: add dictionary support, not yet supported
tag = ContextTag(parseRes);
childProperty = GetChildHandle(curProperty, TagNumFromTag(tag));
if (IsNullPropertyPathHandle(childProperty))
{
err = WEAVE_ERROR_TLV_TAG_NOT_FOUND;
SuccessOrExit(err);
}
// Set the current node.
curProperty = childProperty;
aPathString = parseEnd;
}
exit:
if (err == WEAVE_NO_ERROR)
{
aHandle = curProperty;
}
return err;
}
WEAVE_ERROR TraitSchemaEngine::MapPathToHandle(TLVReader & aPathReader, PropertyPathHandle & aHandle) const
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
PropertyPathHandle childProperty, curProperty;
nl::Weave::TLV::TLVType dummyContainerType = kTLVType_Path;
// initialize the out argument to NULL
aHandle = kNullPropertyPathHandle;
// Set our starting point for traversal to the root node.
curProperty = kRootPropertyPathHandle;
// Descend into our schema tree using the tags encountered to help navigate through the various branches.
while ((err = aPathReader.Next()) == WEAVE_NO_ERROR)
{
const uint64_t tag = aPathReader.GetTag();
// If it's a profile tag, we know we're dealing with a dictionary item - get the appropriate dictionary item. Otherwise,
// treat it like a regular child node.
if (IsProfileTag(tag))
{
VerifyOrExit(ProfileIdFromTag(tag) == kWeaveProfile_DictionaryKey, err = WEAVE_ERROR_INVALID_TLV_TAG);
childProperty = GetDictionaryItemHandle(curProperty, TagNumFromTag(tag));
}
else
{
childProperty = GetChildHandle(curProperty, TagNumFromTag(tag));
}
if (IsNullPropertyPathHandle(childProperty))
{
err = WEAVE_ERROR_TLV_TAG_NOT_FOUND;
break;
}
// Set the current node.
curProperty = childProperty;
}
// End of TLV is the only expected error here and if so, correctly update the handle passed in by the caller.
if (err == WEAVE_END_OF_TLV)
{
err = aPathReader.ExitContainer(dummyContainerType);
SuccessOrExit(err);
aHandle = curProperty;
err = WEAVE_NO_ERROR;
}
exit:
return err;
}
WEAVE_ERROR TraitSchemaEngine::MapHandleToPath(PropertyPathHandle aHandle, TLVWriter & aPathWriter) const
{
// Use the tree depth specified by the schema to correctly size our path walk store
PropertyPathHandle pathWalkStore[mSchema.mTreeDepth];
uint32_t pathWalkDepth = 0;
WEAVE_ERROR err = WEAVE_NO_ERROR;
PropertyPathHandle curProperty = aHandle;
// Walk up the path till root and keep track of the handles encountered along the way.
while (curProperty != kRootPropertyPathHandle)
{
pathWalkStore[pathWalkDepth++] = curProperty;
curProperty = GetParent(curProperty);
}
// Write it into TLV by reverse walking over the encountered handles starting from root.
while (pathWalkDepth)
{
PropertyPathHandle curHandle = pathWalkStore[pathWalkDepth - 1];
err = aPathWriter.PutNull(GetTag(curHandle));
SuccessOrExit(err);
pathWalkDepth--;
}
exit:
return WEAVE_NO_ERROR;
}
uint64_t TraitSchemaEngine::GetTag(PropertyPathHandle aHandle) const
{
if (IsDictionary(GetParent(aHandle)))
{
return ProfileTag(kWeaveProfile_DictionaryKey, GetPropertyDictionaryKey(aHandle));
}
else
{
return ContextTag(GetMap(aHandle)->mContextTag);
}
}
WEAVE_ERROR TraitSchemaEngine::RetrieveData(PropertyPathHandle aHandle, uint64_t aTagToWrite, nl::Weave::TLV::TLVWriter & aWriter,
IGetDataDelegate * aDelegate, IDirtyPathCut * updateDirtyPathCut) const
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
if (IsLeaf(aHandle) || IsNullable(aHandle) || IsOptional(aHandle))
{
bool isPresent = true, isNull = false;
err = aDelegate->GetData(aHandle, aTagToWrite, aWriter, isNull, isPresent);
SuccessOrExit(err);
if (!isPresent && !(IsOptional(aHandle) || IsEphemeral(aHandle)))
{
err = WEAVE_ERROR_WDM_SCHEMA_MISMATCH;
}
VerifyOrExit(isPresent, /* no-op, either no error or set above */);
if (isNull)
{
if (!IsNullable(aHandle))
{
err = WEAVE_ERROR_WDM_SCHEMA_MISMATCH;
}
else
{
err = aWriter.PutNull(aTagToWrite);
SuccessOrExit(err);
}
}
VerifyOrExit(!isNull, /* no-op, either no error or set above */);
}
if (!IsLeaf(aHandle))
{
TLVType type;
err = aWriter.StartContainer(aTagToWrite, kTLVType_Structure, type);
SuccessOrExit(err);
#if TDM_ENABLE_PUBLISHER_DICTIONARY_SUPPORT
if (IsDictionary(aHandle))
{
PropertyDictionaryKey dictionaryItemKey;
uintptr_t context = 0;
if (NULL == updateDirtyPathCut)
{
// TODO: this looks like RetrieveUpdatableDictionaryData; must avoid the duplication
// if it's a dictionary, we need to iterate through the items in the container by asking our delegate.
while ((err = aDelegate->GetNextDictionaryItemKey(aHandle, context, dictionaryItemKey)) == WEAVE_NO_ERROR)
{
uint64_t tag = ProfileTag(kWeaveProfile_DictionaryKey, dictionaryItemKey);
PropertySchemaHandle itemHandle = GetFirstChild(aHandle);
VerifyOrExit(itemHandle != kNullPropertyPathHandle, err = WEAVE_ERROR_WDM_SCHEMA_MISMATCH);
err = RetrieveData(CreatePropertyPathHandle(itemHandle, dictionaryItemKey), tag, aWriter, aDelegate);
SuccessOrExit(err);
}
VerifyOrExit(err == WEAVE_END_OF_INPUT, );
err = WEAVE_NO_ERROR;
}
else
{
// Looks like we come down to a dictionary during a recursion started by TraitUpdatableDataSink::ReadData,
// because updateDirtyPathCut is not NULL.
// The dictionary is supposed to be replaced completely.
// The way this is implemented is:
// - an empty dictionary is encoded here;
// - the handle of the dictionary is put back in the queue.
// The empty dictionary is encoded here because if the dictionary is not a child of the
// current DataElement's path, it cannot be omitted.
// The dictionary will be encoded in a new DataElement as a "merge".
// The reason for that is that if the dictionary is too large to fit in the payload,
// it's easier to split it in more than one data element outside of a recursion.
if (aDelegate->GetNextDictionaryItemKey(aHandle, context, dictionaryItemKey) == WEAVE_NO_ERROR)
{
err = updateDirtyPathCut->CutPath(aHandle, this);
SuccessOrExit(err);
}
}
}
else
#endif // TDM_ENABLE_PUBLISHER_DICTIONARY_SUPPORT
{
PropertyPathHandle childProperty;
// Recursively iterate over all child nodes and call RetrieveData on them.
for (childProperty = GetFirstChild(aHandle); !IsNullPropertyPathHandle(childProperty);
childProperty = GetNextChild(aHandle, childProperty))
{
const PropertyInfo * childInfo = GetMap(childProperty);
err = RetrieveData(childProperty, ContextTag(childInfo->mContextTag), aWriter, aDelegate, updateDirtyPathCut);
SuccessOrExit(err);
}
}
err = aWriter.EndContainer(type);
SuccessOrExit(err);
} // if (!IsLeaf(aHandle))
exit:
return err;
}
#if WEAVE_CONFIG_ENABLE_WDM_UPDATE
WEAVE_ERROR TraitSchemaEngine::RetrieveUpdatableDictionaryData(PropertyPathHandle aHandle, uint64_t aTagToWrite,
TLVWriter & aWriter, IGetDataDelegate * aDelegate,
PropertyPathHandle & aPropertyPathHandleOfDictItemToStartFrom) const
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
#if TDM_ENABLE_PUBLISHER_DICTIONARY_SUPPORT
nl::Weave::TLV::TLVType dataContainerType;
PropertyDictionaryKey dictionaryItemKey;
uintptr_t context = 0;
uint32_t numKeysEncoded = 0;
PropertySchemaHandle dictionaryItemSchemaHandle = GetPropertySchemaHandle(GetFirstChild(aHandle));
PropertyPathHandle dictionaryItemPathHandle;
PropertyPathHandle itemToSkipTo = aPropertyPathHandleOfDictItemToStartFrom;
VerifyOrExit(IsDictionary(aHandle), err = WEAVE_ERROR_WDM_SCHEMA_MISMATCH);
// Clear the key to start from; it will be set again below if we can't encode all remaing
// items.
aPropertyPathHandleOfDictItemToStartFrom = kNullPropertyPathHandle;
err = aWriter.StartContainer(aTagToWrite, nl::Weave::TLV::kTLVType_Structure, dataContainerType);
SuccessOrExit(err);
while ((err = aDelegate->GetNextDictionaryItemKey(aHandle, context, dictionaryItemKey)) == WEAVE_NO_ERROR)
{
uint64_t tag = ProfileTag(kWeaveProfile_DictionaryKey, dictionaryItemKey);
dictionaryItemPathHandle = CreatePropertyPathHandle(dictionaryItemSchemaHandle, dictionaryItemKey);
if (dictionaryItemPathHandle < itemToSkipTo)
{
continue;
}
TLVWriter backupWriter = aWriter;
aPropertyPathHandleOfDictItemToStartFrom = kNullPropertyPathHandle;
err = RetrieveData(dictionaryItemPathHandle, tag, aWriter, aDelegate);
if (err != WEAVE_NO_ERROR)
{
WeaveLogDetail(DataManagement,
"Dictionary item whith path 0x%" PRIx32 ", tag 0x% " PRIx64 " failed with error % " PRIu32 "",
dictionaryItemPathHandle, tag, err);
}
if (numKeysEncoded > 0 && ((err == WEAVE_ERROR_BUFFER_TOO_SMALL) || (err == WEAVE_ERROR_NO_MEMORY)))
{
// BUFFER_TOO_SMALL means there is no more space in the current buffer.
// NO_MEMORY means the application is trying to build a chain of pBufs, but
// there are no more buffers.
aWriter = backupWriter;
aPropertyPathHandleOfDictItemToStartFrom = dictionaryItemPathHandle;
err = WEAVE_NO_ERROR;
break;
}
SuccessOrExit(err);
numKeysEncoded++;
}
if (err == WEAVE_END_OF_INPUT)
{
err = WEAVE_NO_ERROR;
}
SuccessOrExit(err);
err = aWriter.EndContainer(dataContainerType);
SuccessOrExit(err);
exit:
#endif // TDM_ENABLE_PUBLISHER_DICTIONARY_SUPPORT
return err;
}
WEAVE_ERROR TraitSchemaEngine::GetRelativePathTags(const PropertyPathHandle aCandidateHandle, uint64_t * aTags,
const uint32_t aTagsSize, uint32_t & aNumTags) const
{
PropertyPathHandle pathWalkStore[mSchema.mTreeDepth];
uint32_t pathWalkDepth = 0;
PropertyPathHandle curProperty;
WEAVE_ERROR err = WEAVE_NO_ERROR;
aNumTags = 0;
if (aCandidateHandle != kRootPropertyPathHandle)
{
curProperty = aCandidateHandle;
while (curProperty != kRootPropertyPathHandle)
{
pathWalkStore[pathWalkDepth++] = curProperty;
curProperty = GetParent(curProperty);
}
VerifyOrExit(aTagsSize >= pathWalkDepth, err = WEAVE_ERROR_NO_MEMORY);
// Write it into TLV by reverse walking over the encountered handles starting from root.
while (pathWalkDepth)
{
PropertyPathHandle curHandle = pathWalkStore[pathWalkDepth - 1];
aTags[aNumTags] = GetTag(curHandle);
pathWalkDepth--;
aNumTags++;
}
}
exit:
return err;
}
#endif // WEAVE_CONFIG_ENABLE_WDM_UPDATE
WEAVE_ERROR TraitSchemaEngine::StoreData(PropertyPathHandle aHandle, TLVReader & aReader, ISetDataDelegate * aDelegate,
IPathFilter * apPathFilter) const
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
TLVType type = kTLVType_NotSpecified;
PropertyPathHandle curHandle = aHandle;
PropertyPathHandle parentHandle = kNullPropertyPathHandle;
bool dictionaryEventSignalled = false;
PropertyPathHandle dictionaryItemHandle;
bool descending = true;
VerifyOrExit(!(apPathFilter != NULL && apPathFilter->FilterPath(curHandle)), );
// While the logic to actually parse out dictionaries is relatively easy, the logic to appropriately emit the
// OnReplace and OnItemModified events is not similarly so.
//
// This logic here deals with the case where this function was called with a path made to a dictionary *element* or deeper.
// The logic further below deals with the cases where this function was called on a path handle at the dictionary or higher.
if (IsInDictionary(curHandle, dictionaryItemHandle))
{
aDelegate->OnSetDataEvent(ISetDataDelegate::kSetDataEvent_DictionaryItemModifyBegin, dictionaryItemHandle);
dictionaryEventSignalled = true;
}
if (IsLeaf(curHandle))
{
err = aDelegate->SetData(curHandle, aReader, aReader.GetType() == kTLVType_Null);
SuccessOrExit(err);
}
else
{
// The crux of this loop is to iteratively parse out TLV and descend into the schema as necessary. The loop is bounded
// by the return of the iterator handle (curHandle) back to the original start point (aHandle).
//
// The loop also has a notion of ascension and descension. Descension occurs when you go deeper into the schema tree while
// ascension is returning back to a higher point in the tree.
do
{
if (!(apPathFilter != NULL && apPathFilter->FilterPath(curHandle)))
{
#if TDM_DISABLE_STRICT_SCHEMA_COMPLIANCE
if (!IsNullPropertyPathHandle(curHandle))
#endif
{
if (!IsLeaf(curHandle))
{
if (descending)
{
bool enterContainer = (aReader.GetType() != kTLVType_Null);
if (enterContainer)
{
err = aReader.EnterContainer(type);
SuccessOrExit(err);
parentHandle = curHandle;
}
else
{
if (IsNullable(curHandle))
{
err = aDelegate->SetData(curHandle, aReader, !enterContainer);
}
else
{
err = WEAVE_ERROR_WDM_SCHEMA_MISMATCH;
}
SuccessOrExit(err);
}
}
}
else
{
err = aDelegate->SetData(curHandle, aReader, aReader.GetType() == kTLVType_Null);
SuccessOrExit(err);
// Setting a leaf data can be interpreted as ascension since you are evaluating another node
// at the same level there-after by going back up to your parent and checking for more children.
descending = false;
}
}
if (!descending)
{
if (IsDictionary(curHandle))
{
// We can surmise this is a replace if we're ascending to a node that is a dictionary, and that node
// is lower than the target node this function was directed at (we can't get to this point in code if the
// two handles (target and current) are equivalent to each other).
aDelegate->OnSetDataEvent(ISetDataDelegate::kSetDataEvent_DictionaryReplaceEnd, curHandle);
}
else if (IsDictionary(parentHandle))
{
// We can surmise this is a modify/add if we're ascending to a node whose parent is a dictionary, and that
// node is lower than the target node this function was directed at (we can't get to this point in code if
// the two handles (target and current) are equivalent to each other). Those cases are handled by the two
// 'if' statements at the top and bottom of this function.
aDelegate->OnSetDataEvent(ISetDataDelegate::kSetDataEvent_DictionaryItemModifyEnd, curHandle);
}
}
}
// Get the next element in this container.
err = aReader.Next();
VerifyOrExit((err == WEAVE_NO_ERROR) || (err == WEAVE_END_OF_TLV), );
if (err == WEAVE_END_OF_TLV)
{
// We've hit the end of the container - exit out and point our current handle to its parent.
// In the process, restore the parentHandle as well.
err = aReader.ExitContainer(type);
SuccessOrExit(err);
curHandle = parentHandle;
parentHandle = GetParent(curHandle);
descending = false;
}
else
{
const uint64_t tag = aReader.GetTag();
descending = true;
if (IsProfileTag(tag))
{
VerifyOrExit(ProfileIdFromTag(tag) == kWeaveProfile_DictionaryKey, err = WEAVE_ERROR_INVALID_TLV_TAG);
curHandle = GetDictionaryItemHandle(parentHandle, TagNumFromTag(tag));
}
else
{
curHandle = GetChildHandle(parentHandle, TagNumFromTag(tag));
}
if (!(apPathFilter != NULL && apPathFilter->FilterPath(curHandle)))
{
if (IsDictionary(curHandle))
{
// If we're descending onto a node that is a dictionary, we know for certain that it is a replace operation
// since the target path handle for this function was higher in the tree than the node representing the
// dictionary itself.
aDelegate->OnSetDataEvent(ISetDataDelegate::kSetDataEvent_DictionaryReplaceBegin, curHandle);
}
else if (IsDictionary(parentHandle))
{
// Alternatively, if we're descending onto a node whose parent is a dictionary, we know that this node
// represents an element in the dictionary and as such, is an appropriate point in the traversal to notify
// the application of an upcoming dictionary item modification/insertion.
aDelegate->OnSetDataEvent(ISetDataDelegate::kSetDataEvent_DictionaryItemModifyBegin, curHandle);
}
}
#if !TDM_DISABLE_STRICT_SCHEMA_COMPLIANCE
if (IsNullPropertyPathHandle(curHandle))
{
err = WEAVE_ERROR_TLV_TAG_NOT_FOUND;
break;
}
#endif
}
} while (curHandle != aHandle);
}
if (dictionaryEventSignalled)
{
aDelegate->OnSetDataEvent(ISetDataDelegate::kSetDataEvent_DictionaryItemModifyEnd, dictionaryItemHandle);
}
exit:
return err;
}
PropertyPathHandle TraitSchemaEngine::GetFirstChild(PropertyPathHandle aParentHandle) const
{
return GetNextChild(aParentHandle, kRootPropertyPathHandle);
}
bool TraitSchemaEngine::IsParent(PropertyPathHandle aChildHandle, PropertyPathHandle aParentHandle) const
{
bool retval = false;
VerifyOrExit(aChildHandle != kNullPropertyPathHandle && aParentHandle != kNullPropertyPathHandle, );
do
{
aChildHandle = GetParent(aChildHandle);
if (aChildHandle == aParentHandle)
{
ExitNow(retval = true);
}
} while (aChildHandle != kNullPropertyPathHandle);
exit:
return retval;
}
PropertyPathHandle TraitSchemaEngine::GetParent(PropertyPathHandle aHandle) const
{
PropertySchemaHandle schemaHandle = GetPropertySchemaHandle(aHandle);
PropertyDictionaryKey dictionaryKey = GetPropertyDictionaryKey(aHandle);
const PropertyInfo * handleMap = GetMap(schemaHandle);
if (!handleMap)
{
return kNullPropertyPathHandle;
}
// update the schema handle to point to the parent handle.
schemaHandle = handleMap->mParentHandle;
// if the parent is a dictionary, just return the schema handle with the key cleared out since the key doesn't make sense
// anymore at this level or higher.
if (IsDictionary(schemaHandle))
{
return schemaHandle;
}
else
{
// Otherwise, preserve the dictionaroy key in the new path handle being created.
return CreatePropertyPathHandle(schemaHandle, dictionaryKey);
}
return WEAVE_NO_ERROR;
}
PropertyPathHandle TraitSchemaEngine::GetNextChild(PropertyPathHandle aParentHandle, PropertyPathHandle aChildHandle) const
{
unsigned int i;
PropertySchemaHandle parentSchemaHandle = GetPropertySchemaHandle(aParentHandle);
PropertySchemaHandle childSchemaHandle = GetPropertySchemaHandle(aChildHandle);
PropertyDictionaryKey parentDictionaryKey = GetPropertyDictionaryKey(aParentHandle);
// Starting from 1 node after the child node that's been passed in, iterate till we find the next child belonging to aParentId.
for (i = (childSchemaHandle - 1); i < mSchema.mNumSchemaHandleEntries; i++)
{
if (mSchema.mSchemaHandleTbl[i].mParentHandle == parentSchemaHandle)
{
break;
}
}
if (i == mSchema.mNumSchemaHandleEntries)
{
return kNullPropertyPathHandle;
}
else
{
return CreatePropertyPathHandle(i + kHandleTableOffset, parentDictionaryKey);
}
}
PropertyPathHandle TraitSchemaEngine::GetChildHandle(PropertyPathHandle aParentHandle, uint8_t aContextTag) const
{
if (IsDictionary(aParentHandle))
{
return kNullPropertyPathHandle;
}
return _GetChildHandle(aParentHandle, aContextTag);
}
PropertyPathHandle TraitSchemaEngine::_GetChildHandle(PropertyPathHandle aParentHandle, uint8_t aContextTag) const
{
for (PropertyPathHandle childProperty = GetFirstChild(aParentHandle); !IsNullPropertyPathHandle(childProperty);
childProperty = GetNextChild(aParentHandle, childProperty))
{
if (mSchema.mSchemaHandleTbl[GetPropertySchemaHandle(childProperty) - kHandleTableOffset].mContextTag == aContextTag)
{
return childProperty;
}
}
return kNullPropertyPathHandle;
}
PropertyPathHandle TraitSchemaEngine::GetDictionaryItemHandle(PropertyPathHandle aParentHandle, uint16_t aDictionaryKey) const
{
if (!IsDictionary(aParentHandle))
{
return kNullPropertyPathHandle;
}
return CreatePropertyPathHandle(_GetChildHandle(aParentHandle, 0), aDictionaryKey);
}
bool TraitSchemaEngine::IsLeaf(PropertyPathHandle aHandle) const
{
PropertySchemaHandle schemaHandle = GetPropertySchemaHandle(aHandle);
// Root is by definition not a leaf. This also conveniently handles the cases where we have traits that
// don't have any properties in them.
if (aHandle == kRootPropertyPathHandle)
{
return false;
}
else
{
for (unsigned int i = 0; i < mSchema.mNumSchemaHandleEntries; i++)
{
if (mSchema.mSchemaHandleTbl[i].mParentHandle == schemaHandle)
{
return false;
}
}
return true;
}
}
int32_t TraitSchemaEngine::GetDepth(PropertyPathHandle aHandle) const
{
int depth = 0;
PropertySchemaHandle schemaHandle = GetPropertySchemaHandle(aHandle);
if (schemaHandle > (mSchema.mNumSchemaHandleEntries + 1))
{
return -1;
}
while (schemaHandle != kRootPropertyPathHandle)
{
depth++;
schemaHandle = mSchema.mSchemaHandleTbl[schemaHandle - kHandleTableOffset].mParentHandle;
}
return depth;
}
PropertyPathHandle TraitSchemaEngine::FindLowestCommonAncestor(PropertyPathHandle aHandle1, PropertyPathHandle aHandle2,
PropertyPathHandle * aHandle1BranchChild,
PropertyPathHandle * aHandle2BranchChild) const
{
int32_t depth1 = GetDepth(aHandle1);
int32_t depth2 = GetDepth(aHandle2);
PropertyPathHandle laggingHandle1, laggingHandle2;
if (depth1 < 0 || depth2 < 0)
{
return kNullPropertyPathHandle;
}
laggingHandle1 = kNullPropertyPathHandle;
laggingHandle2 = kNullPropertyPathHandle;
while (depth1 != depth2)
{
if (depth1 > depth2)
{
laggingHandle1 = aHandle1;
aHandle1 = GetParent(aHandle1);
depth1--;
}
else
{
laggingHandle2 = aHandle2;
aHandle2 = GetParent(aHandle2);
depth2--;
}
}
while (aHandle1 != aHandle2)
{
laggingHandle1 = aHandle1;
laggingHandle2 = aHandle2;
aHandle1 = GetParent(aHandle1);
aHandle2 = GetParent(aHandle2);
}
if (aHandle1BranchChild)
{
*aHandle1BranchChild = laggingHandle1;
}
if (aHandle2BranchChild)
{
*aHandle2BranchChild = laggingHandle2;
}
return aHandle1;
}
const TraitSchemaEngine::PropertyInfo * TraitSchemaEngine::GetMap(PropertyPathHandle aHandle) const
{
PropertySchemaHandle schemaHandle = GetPropertySchemaHandle(aHandle);
if (schemaHandle < 2 || (schemaHandle >= (mSchema.mNumSchemaHandleEntries + kHandleTableOffset)))
{
return NULL;
}
return &mSchema.mSchemaHandleTbl[schemaHandle - kHandleTableOffset];
}
bool TraitSchemaEngine::IsDictionary(PropertyPathHandle aHandle) const
{
// The 'mIsDictionaryBitfield' is only populated by code-gen on traits that do have dictionaries. Otherwise, it defaults
// to NULL.
return GetBitFromPathHandleBitfield(mSchema.mIsDictionaryBitfield, aHandle);
}
bool TraitSchemaEngine::IsInDictionary(PropertyPathHandle aHandle, PropertyPathHandle & aDictionaryItemHandle) const
{
while (aHandle != kRootPropertyPathHandle)
{
PropertyPathHandle parentHandle = GetParent(aHandle);
if (IsDictionary(parentHandle))
{
aDictionaryItemHandle = aHandle;
return true;
}
aHandle = parentHandle;
}
return false;
}
bool TraitSchemaEngine::IsOptional(PropertyPathHandle aHandle) const
{
return GetBitFromPathHandleBitfield(mSchema.mIsOptionalBitfield, aHandle);
}
bool TraitSchemaEngine::IsNullable(PropertyPathHandle aHandle) const
{
return GetBitFromPathHandleBitfield(mSchema.mIsNullableBitfield, aHandle);
}
bool TraitSchemaEngine::IsEphemeral(PropertyPathHandle aHandle) const
{
return GetBitFromPathHandleBitfield(mSchema.mIsEphemeralBitfield, aHandle);
}
bool TraitSchemaEngine::GetBitFromPathHandleBitfield(uint8_t * aBitfield, PropertyPathHandle aPathHandle) const
{
bool retval = false;
if (aBitfield != NULL && !IsRootPropertyPathHandle(aPathHandle) && !IsNullPropertyPathHandle(aPathHandle))
{
PropertySchemaHandle adjustedSchemaHandle = GetPropertySchemaHandle(aPathHandle) - kHandleTableOffset;
retval = aBitfield[(adjustedSchemaHandle) / 8] & (1 << (adjustedSchemaHandle % 8));
}
return retval;
}
bool TraitSchemaEngine::MatchesProfileId(uint32_t aProfileId) const
{
return (aProfileId == mSchema.mProfileId);
}
uint32_t TraitSchemaEngine::GetProfileId(void) const
{
return mSchema.mProfileId;
}
bool TraitSchemaEngine::GetVersionIntersection(SchemaVersionRange & aVersion, SchemaVersionRange & aIntersection) const
{
SchemaVersion minCurrentVersion = 1;
SchemaVersion maxCurrentVersion = 1;
#if (TDM_VERSIONING_SUPPORT)
if (mSchema.mVersionRange != NULL)
{
minCurrentVersion = mSchema.mVersionRange->mMinVersion;
maxCurrentVersion = mSchema.mVersionRange->mMaxVersion;
}
#endif // TDM_VERSIONING_SUPPORT
aIntersection.mMinVersion = max(aVersion.mMinVersion, minCurrentVersion);
aIntersection.mMaxVersion = min(aVersion.mMaxVersion, maxCurrentVersion);
if (aIntersection.mMinVersion <= aIntersection.mMaxVersion)
{
return true;
}
else
{
return false;
}
}
SchemaVersion TraitSchemaEngine::GetHighestForwardVersion(SchemaVersion aVersion) const
{
SchemaVersion currentVersion = 1;
#if (TDM_VERSIONING_SUPPORT)
if (mSchema.mVersionRange != NULL)
{
currentVersion = mSchema.mVersionRange->mMaxVersion;
}
#endif // TDM_VERSIONING_SUPPORT
if (aVersion > currentVersion)
{
return 0;
}
else
{
return currentVersion;
}
}
SchemaVersion TraitSchemaEngine::GetLowestCompatibleVersion(SchemaVersion aVersion) const
{
SchemaVersion currentVersion = 1;
#if (TDM_VERSIONING_SUPPORT)
if (mSchema.mVersionRange != NULL)
{
currentVersion = mSchema.mVersionRange->mMinVersion;
}
#endif // TDM_VERSIONING_SUPPORT
// TODO: current assumption is that the trait is fully backwars compatible
return currentVersion;
}
SchemaVersion TraitSchemaEngine::GetMinVersion() const
{
SchemaVersion currentVersion = 1;
#if (TDM_VERSIONING_SUPPORT)
if (mSchema.mVersionRange != NULL)
{
currentVersion = mSchema.mVersionRange->mMinVersion;
}
#endif // TDM_VERSIONING_SUPPORT
return currentVersion;
}
SchemaVersion TraitSchemaEngine::GetMaxVersion() const
{
SchemaVersion currentVersion = 1;
#if (TDM_VERSIONING_SUPPORT)
if (mSchema.mVersionRange != NULL)
{
currentVersion = mSchema.mVersionRange->mMaxVersion;
}
#endif // TDM_VERSIONING_SUPPORT
return currentVersion;
}
void TraitDataSink::SetVersion(uint64_t aVersion)
{
if (mHasValidVersion)
{
if (aVersion != mVersion)
{
WeaveLogDetail(DataManagement, "Trait %08x version: 0x%" PRIx64 " -> 0x%" PRIx64 "", mSchemaEngine->GetProfileId(),
mVersion, aVersion);
}
}
else
{
WeaveLogDetail(DataManagement, "Trait %08x version: n/a -> 0x%" PRIx64 "", mSchemaEngine->GetProfileId(), aVersion);
}
mVersion = aVersion;
mHasValidVersion = true;
}
void TraitDataSink::ClearVersion(void)
{
WeaveLogDetail(DataManagement, "Trait %08x version: cleared", mSchemaEngine->GetProfileId());
mHasValidVersion = false;
}
void TraitDataSink::SetLastNotifyVersion(uint64_t aVersion)
{
WeaveLogDetail(DataManagement, "Trait %08x last notify version: 0x%" PRIx64 " -> 0x%" PRIx64 "", mSchemaEngine->GetProfileId(),
mLastNotifyVersion, aVersion);
mLastNotifyVersion = aVersion;
}
TraitDataSink::OnChangeRejection TraitDataSink::sChangeRejectionCb = NULL;
void * TraitDataSink::sChangeRejectionContext = NULL;
TraitDataSink::TraitDataSink(const TraitSchemaEngine * aEngine)
{
mSchemaEngine = aEngine;
mVersion = 0;
mLastNotifyVersion = 0;
mHasValidVersion = 0;
}
WEAVE_ERROR TraitDataSink::StoreDataElement(PropertyPathHandle aHandle, TLVReader & aReader, uint8_t aFlags,
OnChangeRejection aFunc, void * aContext, TraitDataHandle aDatahandle)
{
DataElement::Parser parser;
WEAVE_ERROR err = WEAVE_NO_ERROR;
DataVersion versionInDE;
bool dataPresent = false, deletePresent = false;
err = parser.Init(aReader);
SuccessOrExit(err);
err = parser.GetVersion(&versionInDE);
SuccessOrExit(err);
if (IsVersionNewer(versionInDE))
{
if (mHasValidVersion)
{
WeaveLogDetail(DataManagement, "<StoreDataElement> [Trait %08x] version: 0x%" PRIx64 " -> 0x%" PRIx64 "",
mSchemaEngine->GetProfileId(), mVersion, versionInDE);
}
else
{
WeaveLogDetail(DataManagement, "<StoreDataElement> [Trait %08x] version: n/a -> 0x%" PRIx64 "",
mSchemaEngine->GetProfileId(), versionInDE);
}
err = parser.CheckPresence(&dataPresent, &deletePresent);
SuccessOrExit(err);
if (aFlags & kFirstElementInChange)
{
OnEvent(kEventChangeBegin, NULL);
}
// Signal to the app we're about to process a data element.
OnEvent(kEventDataElementBegin, NULL);
// TODO: we need to check if there are pending updates for paths
// being deleted
if (deletePresent)
{
err = parser.GetDeletedDictionaryKeys(&aReader);
SuccessOrExit(err);
while ((err = aReader.Next()) == WEAVE_NO_ERROR)
{
PropertyDictionaryKey key;
PropertyPathHandle handle;
err = aReader.Get(key);
SuccessOrExit(err);
// In the case of a delete, the path is usually directed to the dictionary itself. We
// need to get the handle to the child dictionary element handle first before we can
// pass it up to the application.
handle = mSchemaEngine->GetFirstChild(aHandle);
VerifyOrExit(handle != kNullPropertyPathHandle, err = WEAVE_ERROR_INVALID_ARGUMENT);
handle = CreatePropertyPathHandle(GetPropertySchemaHandle(handle), key);
OnEvent(kEventDictionaryItemDelete, &handle);
}
VerifyOrExit(err == WEAVE_NO_ERROR || err == WEAVE_END_OF_TLV, );
err = WEAVE_NO_ERROR;
}
if (aHandle != kNullPropertyPathHandle && dataPresent)
{
err = parser.GetData(&aReader);
SuccessOrExit(err);
UpdateDirtyPathFilter pathFilter(GetSubscriptionClient(), aDatahandle, mSchemaEngine);
err = mSchemaEngine->StoreData(aHandle, aReader, this, &pathFilter);
}
OnEvent(kEventDataElementEnd, NULL);
// Only update the version number if the StoreData succeeded
if (err == WEAVE_NO_ERROR)
{
// Only update our internal version tracker if this is indeed the last element in the change.
if (aFlags & kLastElementInChange)
{
SetVersion(versionInDE);
OnEvent(kEventChangeEnd, NULL);
}
}
else
{
// We need to clear this since we don't have a good version of data anymore.
ClearVersion();
}
}
else
{
WeaveLogDetail(DataManagement, "<StoreData> [Trait %08x] version: 0x%" PRIx64 " (no-change)", mSchemaEngine->GetProfileId(),
mVersion);
}
if (aFlags & kLastElementInChange)
{
SetLastNotifyVersion(versionInDE);
}
exit:
return err;
}
void TraitDataSink::OnSetDataEvent(SetDataEventType aEventType, PropertyPathHandle aHandle)
{
EventType event;
switch (aEventType)
{
case kSetDataEvent_DictionaryReplaceBegin:
event = kEventDictionaryReplaceBegin;
break;
case kSetDataEvent_DictionaryReplaceEnd:
event = kEventDictionaryReplaceEnd;
break;
case kSetDataEvent_DictionaryItemModifyBegin:
event = kEventDictionaryItemModifyBegin;
break;
case kSetDataEvent_DictionaryItemModifyEnd:
event = kEventDictionaryItemModifyEnd;
break;
default:
return;
};
OnEvent(event, &aHandle);
}
void TraitDataSink::RejectChange(uint16_t aRejectionStatusCode)
{
if (sChangeRejectionCb)
{
sChangeRejectionCb(aRejectionStatusCode, mVersion, sChangeRejectionContext);
}
}
WEAVE_ERROR TraitDataSink::SetData(PropertyPathHandle aHandle, TLVReader & aReader, bool aIsNull)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// if a trait has no nullable handles, aIsNull will always be false
// and serves no purpose. this is true for the default implementation.
IgnoreUnusedVariable(aIsNull);
if (mSchemaEngine->IsLeaf(aHandle))
{
err = SetLeafData(aHandle, aReader);
if (err != WEAVE_NO_ERROR)
{
WeaveLogDetail(DataManagement, "ahandle %u err: %d", aHandle, err);
}
}
return err;
}
TraitDataSource::TraitDataSource(const TraitSchemaEngine * aEngine)
{
// Set the version to 0, indicating the lack of a valid version.
SetVersion(0);
mManagedVersion = true;
mSetDirtyCalled = false;
mSchemaEngine = aEngine;
#if (WEAVE_CONFIG_WDM_PUBLISHER_GRAPH_SOLVER == IntermediateGraphSolver)
ClearRootDirty();
#endif
}
uint64_t TraitDataSource::GetVersion(void)
{
// At the time of version retrieval, check to see if the version is still at the sentinel value of 0 (indicating 'no version')
// set at construction. If it is, it means that the data source has not over-ridden the version to something other than 0,
// indicating that it desires to use randomized data versions.
while (mVersion == 0)
{
mVersion = GetRandU64();
}
return mVersion;
}
void TraitDataSource::IncrementVersion()
{
// By invoking GetVersion within here, we get the benefit of checking if the version is currently 0 and if so, randomize it.
SetVersion(GetVersion() + 1);
}
/**
* @brief Handler for custom command
*
* This is a virtual method. If not overridden, the default behavior is to return a status report
* with status code Common::kStatus_UnsupportedMessage
*
*/
void TraitDataSource::OnCustomCommand(Command * aCommand, const nl::Weave::WeaveMessageInfo * aMsgInfo,
nl::Weave::PacketBuffer * aPayload, const uint64_t & aCommandType,
const bool aIsExpiryTimeValid, const int64_t & aExpiryTimeMicroSecond,
const bool aIsMustBeVersionValid, const uint64_t & aMustBeVersion,
nl::Weave::TLV::TLVReader & aArgumentReader)
{
IgnoreUnusedVariable(aCommandType);
IgnoreUnusedVariable(aIsExpiryTimeValid);
IgnoreUnusedVariable(aExpiryTimeMicroSecond);
IgnoreUnusedVariable(aIsMustBeVersionValid);
IgnoreUnusedVariable(aMustBeVersion);
return OnCustomCommand(aCommand, aMsgInfo, aPayload, aArgumentReader);
}
/**
* @brief Handler for custom command
*
* This is a virtual method. If not overridden, the default behavior is to return a status report
* with status code Common::kStatus_UnsupportedMessage
*
* @note All meta information for the Command are included in the Command
* object.
*/
void TraitDataSource::OnCustomCommand(Command * aCommand, const nl::Weave::WeaveMessageInfo * aMsgInfo,
nl::Weave::PacketBuffer * aPayload, nl::Weave::TLV::TLVReader & aArgumentReader)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
IgnoreUnusedVariable(aArgumentReader);
PacketBuffer::Free(aPayload);
aPayload = NULL;
err = aCommand->SendError(nl::Weave::Profiles::kWeaveProfile_Common, nl::Weave::Profiles::Common::kStatus_UnsupportedMessage,
WEAVE_NO_ERROR);
aCommand = NULL;
WeaveLogFunctError(err);
}
WEAVE_ERROR TraitDataSource::GetData(PropertyPathHandle aHandle, uint64_t aTagToWrite, nl::Weave::TLV::TLVWriter & aWriter,
bool & aIsNull, bool & aIsPresent)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
aIsNull = false;
aIsPresent = true;
if (mSchemaEngine->IsLeaf(aHandle))
{
err = GetLeafData(aHandle, aTagToWrite, aWriter);
}
return err;
}
WEAVE_ERROR TraitDataSource::ReadData(PropertyPathHandle aHandle, uint64_t aTagToWrite, TLVWriter & aWriter)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
Lock();
err = mSchemaEngine->RetrieveData(aHandle, aTagToWrite, aWriter, this);
Unlock();
return err;
}
void TraitDataSource::SetDirty(PropertyPathHandle aPropertyHandle)
{
if (aPropertyHandle != kNullPropertyPathHandle)
{
mSetDirtyCalled = true;
SubscriptionEngine::GetInstance()->GetNotificationEngine()->SetDirty(this, aPropertyHandle);
}
}
#if TDM_ENABLE_PUBLISHER_DICTIONARY_SUPPORT
void TraitDataSource::DeleteKey(PropertyPathHandle aPropertyHandle)
{
// Should only delete the dictionary key, which is a child of the dictionary handle. Only proceed if this is true.
if (mSchemaEngine->IsDictionary(mSchemaEngine->GetParent(aPropertyHandle)))
{
mSetDirtyCalled = true;
SubscriptionEngine::GetInstance()->GetNotificationEngine()->DeleteKey(this, aPropertyHandle);
}
}
#endif // TDM_ENABLE_PUBLISHER_DICTIONARY_SUPPORT
WEAVE_ERROR TraitDataSource::Lock()
{
mSetDirtyCalled = false;
VerifyOrDie(SubscriptionEngine::GetInstance());
return SubscriptionEngine::GetInstance()->Lock();
}
WEAVE_ERROR TraitDataSource::Unlock()
{
if (mManagedVersion && mSetDirtyCalled)
{
IncrementVersion();
}
VerifyOrDie(SubscriptionEngine::GetInstance());
return SubscriptionEngine::GetInstance()->Unlock();
}
#if WDM_ENABLE_PUBLISHER_UPDATE_SERVER_SUPPORT
WEAVE_ERROR TraitDataSource::Unlock(bool aSkipVersionIncrement)
{
if (mManagedVersion && mSetDirtyCalled && !aSkipVersionIncrement)
{
IncrementVersion();
}
VerifyOrDie(SubscriptionEngine::GetInstance());
return SubscriptionEngine::GetInstance()->Unlock();
}
#endif // WDM_ENABLE_PUBLISHER_UPDATE_SERVER_SUPPORT
#if WEAVE_CONFIG_ENABLE_WDM_UPDATE
TraitUpdatableDataSink::TraitUpdatableDataSink(const TraitSchemaEngine * aEngine) :
TraitDataSink(aEngine), mUpdateRequiredVersion(0), mUpdateStartVersion(0), mConditionalUpdate(false), mPotentialDataLoss(false),
mpSubClient(NULL), mpUpdateEncoder(NULL) { };
void TraitUpdatableDataSink::Lock(SubscriptionClient * apSubClient)
{
VerifyOrDie(apSubClient != NULL);
apSubClient->LockUpdateMutex();
}
void TraitUpdatableDataSink::Unlock(SubscriptionClient * apSubClient)
{
VerifyOrDie(apSubClient != NULL);
apSubClient->UnlockUpdateMutex();
}
WEAVE_ERROR TraitUpdatableDataSink::GetData(PropertyPathHandle aHandle, uint64_t aTagToWrite, nl::Weave::TLV::TLVWriter & aWriter,
bool & aIsNull, bool & aIsPresent)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
aIsNull = false;
aIsPresent = true;
if (mSchemaEngine->IsLeaf(aHandle))
{
err = GetLeafData(aHandle, aTagToWrite, aWriter);
}
return err;
}
void TraitUpdatableDataSink::SetUpdateRequiredVersion(const uint64_t & aUpdateRequiredVersion)
{
if (aUpdateRequiredVersion != mUpdateRequiredVersion)
{
WeaveLogDetail(DataManagement, "[Trait %08x] UpdateRequiredVersion: 0x%" PRIx64 " -> 0x%" PRIx64 "",
mSchemaEngine->GetProfileId(), mUpdateRequiredVersion, aUpdateRequiredVersion);
mUpdateRequiredVersion = aUpdateRequiredVersion;
// TODO: Ideally, this fault would be injected when the payload is encoded; but, all DataElements for the
// same trait instance need to have the same RequiredVersion, and it's easier to achieve that by
// changing the state here. The loop that does the encoding is being reworked, so this will
// probably change soon.
WEAVE_FAULT_INJECT(FaultInjection::kFault_WDM_SendUpdateBadVersion, mUpdateRequiredVersion -= 1);
}
return;
}
void TraitUpdatableDataSink::SetUpdateStartVersion(void)
{
if (GetVersion() != mUpdateStartVersion)
{
WeaveLogDetail(DataManagement, "[Trait %08x] UpdateStartVersion: 0x%" PRIx64 " -> 0x%" PRIx64 "",
mSchemaEngine->GetProfileId(), mUpdateStartVersion, GetVersion());
mUpdateStartVersion = GetVersion();
}
}
WEAVE_ERROR TraitUpdatableDataSink::ReadData(TraitDataHandle aTraitDataHandle, PropertyPathHandle aHandle, uint64_t aTagToWrite,
TLVWriter & aWriter, PropertyPathHandle & aPropertyPathHandleOfDictItemToStartFrom)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
if (mSchemaEngine->IsDictionary(aHandle))
{
WeaveLogDetail(DataManagement, "process dictionary in update");
err = mSchemaEngine->RetrieveUpdatableDictionaryData(aHandle, aTagToWrite, aWriter,
static_cast<TraitSchemaEngine::IGetDataDelegate *>(this),
aPropertyPathHandleOfDictItemToStartFrom);
}
else
{
UpdateDictionaryDirtyPathCut updateDirtyPathCut(aTraitDataHandle, GetUpdateEncoder());
err = mSchemaEngine->RetrieveData(aHandle, aTagToWrite, aWriter, static_cast<TraitSchemaEngine::IGetDataDelegate *>(this),
&updateDirtyPathCut);
}
SuccessOrExit(err);
exit:
return err;
}
/**
* Declares that the given PropertyPathHandle has local modifications.
* The NotificationEngine will not override the handle and its descendants
* until the update request has been processed.
* The application will receive kEvent_OnUpdateComplete callbacks for this
* handle or for one of its ancestors with the result of the update operation.
* The modification can be conditional or not. Conditional data updates will
* be lost if the local copy of the trait instance is not in sync anymore with
* publisher's because it was mutated by the publisher itself or by another client.
* Conditional and unconditional mutations are not supported at the same time in
* the same trait instance.
*
* @param[in] apSubClient A pointer to the SubscriptionClient managing this sink.
* @param[in] aPropertyHandle Any valid PropertyPathHandle for this Trait instance.
* @param[in] aIsConditional True for a conditional update; false otherwise.
*
* @retval WEAVE_NO_ERROR if the property handle was successfully added to the set of
* properties to be sent to the owner of the trait.
* @retval WEAVE_ERROR_INVALID_ARGUMENT if the handle or the SubscriptionClient
* pointer are invalid.
* @retval WEAVE_ERROR_WDM_INCONSISTENT_CONDITIONALITY if the trait instance is already being
* updated with the opposite conditionality.
* @retval WEAVE_ERROR_WDM_LOCAL_DATA_INCONSISTENT if aIsConditional is true but the
* trait instance does not have a valid version.
* @retval WEAVE_ERROR_WDM_PATH_STORE_FULL if there is no memory to store the path.
* @retval Other WEAVE_ERROR codes depending on the failure.
*/
WEAVE_ERROR TraitUpdatableDataSink::SetUpdated(SubscriptionClient * apSubClient, PropertyPathHandle aPropertyHandle,
bool aIsConditional)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
VerifyOrExit(aPropertyHandle != kNullPropertyPathHandle, err = WEAVE_ERROR_INVALID_ARGUMENT);
err = apSubClient->SetUpdated(this, aPropertyHandle, aIsConditional);
SuccessOrExit(err);
exit:
return err;
}
#endif // WEAVE_CONFIG_ENABLE_WDM_UPDATE
#if WDM_ENABLE_PUBLISHER_UPDATE_SERVER_SUPPORT
TraitUpdatableDataSource::OnChangeRejection TraitUpdatableDataSource::sChangeRejectionCb = NULL;
void * TraitUpdatableDataSource::sChangeRejectionContext = NULL;
TraitUpdatableDataSource::TraitUpdatableDataSource(const TraitSchemaEngine * aEngine) : TraitDataSource(aEngine) { }
WEAVE_ERROR TraitUpdatableDataSource::StoreDataElement(PropertyPathHandle aHandle, TLVReader & aReader, uint8_t aFlags,
OnChangeRejection aFunc, void * aContext)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
DataElement::Parser parser;
bool dataPresent = false, deletePresent = false;
err = parser.Init(aReader);
SuccessOrExit(err);
err = parser.CheckPresence(&dataPresent, &deletePresent);
SuccessOrExit(err);
if (deletePresent)
{
err = parser.GetDeletedDictionaryKeys(&aReader);
SuccessOrExit(err);
while ((err = aReader.Next()) == WEAVE_NO_ERROR)
{
PropertyDictionaryKey key;
PropertyPathHandle handle;
err = aReader.Get(key);
SuccessOrExit(err);
// In the case of a delete, the path is usually directed to the dictionary itself. We
// need to get the handle to the child dictionary element handle first before we can
// pass it up to the application.
handle = mSchemaEngine->GetFirstChild(aHandle);
VerifyOrExit(handle != kNullPropertyPathHandle, err = WEAVE_ERROR_INVALID_ARGUMENT);
handle = CreatePropertyPathHandle(GetPropertySchemaHandle(handle), key);
OnEvent(kEventDictionaryItemDelete, &handle);
}
VerifyOrExit(err == WEAVE_NO_ERROR || err == WEAVE_END_OF_TLV, );
err = WEAVE_NO_ERROR;
}
if (aHandle != kNullPropertyPathHandle && dataPresent)
{
err = parser.GetData(&aReader);
SuccessOrExit(err);
err = mSchemaEngine->StoreData(aHandle, aReader, this, NULL);
SuccessOrExit(err);
}
exit:
return err;
}
void TraitUpdatableDataSource::OnSetDataEvent(SetDataEventType aEventType, PropertyPathHandle aHandle)
{
EventType event;
switch (aEventType)
{
case kSetDataEvent_DictionaryReplaceBegin:
event = kEventDictionaryReplaceBegin;
break;
case kSetDataEvent_DictionaryReplaceEnd:
event = kEventDictionaryReplaceEnd;
break;
case kSetDataEvent_DictionaryItemModifyBegin:
event = kEventDictionaryItemModifyBegin;
break;
case kSetDataEvent_DictionaryItemModifyEnd:
event = kEventDictionaryItemModifyEnd;
break;
default:
return;
};
OnEvent(event, &aHandle);
}
void TraitUpdatableDataSource::RejectChange(uint16_t aRejectionStatusCode)
{
if (sChangeRejectionCb)
{
sChangeRejectionCb(aRejectionStatusCode, GetVersion(), sChangeRejectionContext);
}
}
WEAVE_ERROR TraitUpdatableDataSource::SetData(PropertyPathHandle aHandle, TLVReader & aReader, bool aIsNull)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// if a trait has no nullable handles, aIsNull will always be false
// and serves no purpose. this is true for the default implementation.
IgnoreUnusedVariable(aIsNull);
if (mSchemaEngine->IsLeaf(aHandle))
{
err = SetLeafData(aHandle, aReader);
if (err != WEAVE_NO_ERROR)
{
WeaveLogDetail(DataManagement, "ahandle %u err: %d", aHandle, err);
}
}
return err;
}
#endif // WDM_ENABLE_PUBLISHER_UPDATE_SERVER_SUPPORT