blob: bda532088ce474b574f73490213dad6c5167bf5f [file] [log] [blame]
/*
*
* 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 implements command handle for Weave
* Data Management (WDM) profile.
*
*/
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif // __STDC_FORMAT_MACROS
#include <Weave/Profiles/WeaveProfiles.h>
#include <Weave/Profiles/common/CommonProfile.h>
#include <Weave/Profiles/data-management/Current/WdmManagedNamespace.h>
#include <Weave/Profiles/data-management/DataManagement.h>
#include <Weave/Support/FlagUtils.hpp>
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <SystemLayer/SystemStats.h>
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING && WDM_PUBLISHER_ENABLE_CUSTOM_COMMANDS
namespace nl {
namespace Weave {
namespace Profiles {
namespace WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current) {
using namespace nl::Weave::TLV;
Command::Command(void)
{
(void) Init(NULL);
}
WEAVE_ERROR Command::Init(nl::Weave::ExchangeContext * aEC)
{
mEC = aEC;
mFlags = 0;
commandType = 0;
mustBeVersion = 0;
initiationTimeMicroSecond = 0;
actionTimeMicroSecond = 0;
expiryTimeMicroSecond = 0;
return WEAVE_NO_ERROR;
}
/**
* Determine whether the version in the command is valid.
*
* @return Returns 'true' if the version is valid, else 'false'.
*
*/
bool Command::IsMustBeVersionValid(void) const
{
return GetFlag(mFlags, kCommandFlag_MustBeVersionValid);
}
/**
* Determine whether the initiation time in the command is valid.
*
* @return Returns 'true' if the initiation time is valid, else 'false'.
*
*/
bool Command::IsInitiationTimeValid(void) const
{
return GetFlag(mFlags, kCommandFlag_InitiationTimeValid);
}
/**
* Determine whether the action time in the command is valid.
*
* @return Returns 'true' if the action time is valid, else 'false'.
*
*/
bool Command::IsActionTimeValid(void) const
{
return GetFlag(mFlags, kCommandFlag_ActionTimeValid);
}
/**
* Determine whether the expiry time in the command is valid.
*
* @return Returns 'true' if the expiry time is valid, else 'false'.
*
*/
bool Command::IsExpiryTimeValid(void) const
{
return GetFlag(mFlags, kCommandFlag_ExpiryTimeValid);
}
/**
* Determine whether the command is one-way.
*
* @return Returns 'true' if the command is one-way, else 'false'.
*
*/
bool Command::IsOneWay(void) const
{
return GetFlag(mFlags, kCommandFlag_IsOneWay);
}
/**
* Set the kCommandFlag_MustBeVersionValid flag bit.
*
* @param[in] inMustBeVersionValid A Boolean indicating whether (true) or not
* (false) the version field in the Command is
* valid.
*
*/
void Command::SetMustBeVersionValid(bool inMustBeVersionValid)
{
SetFlag(mFlags, kCommandFlag_MustBeVersionValid, inMustBeVersionValid);
}
/**
* Set the kCommandFlag_InitiationTimeValid flag bit.
*
* @param[in] inInitiationTimeValid A Boolean indicating whether (true) or not
* (false) the initiation time field in the
* Command is valid.
*
*/
void Command::SetInitiationTimeValid(bool inInitiationTimeValid)
{
SetFlag(mFlags, kCommandFlag_InitiationTimeValid, inInitiationTimeValid);
}
/**
* Set the kCommandFlag_ActionTimeValid flag bit.
*
* @param[in] inActionTimeValid A Boolean indicating whether (true) or not
* (false) the action time field in the
* Command is valid.
*
*/
void Command::SetActionTimeValid(bool inActionTimeValid)
{
SetFlag(mFlags, kCommandFlag_ActionTimeValid, inActionTimeValid);
}
/**
* Set the kCommandFlag_ExpiryTimeValid flag bit.
*
* @param[in] inExpiryTimeValid A Boolean indicating whether (true) or not
* (false) the expiry time field in the
* Command is valid.
*
*/
void Command::SetExpiryTimeValid(bool inExpiryTimeValid)
{
SetFlag(mFlags, kCommandFlag_ExpiryTimeValid, inExpiryTimeValid);
}
/**
* Set the kCommandFlag_IsOneWay flag bit.
*
* @param[in] inIsOneWay A Boolean indicating whether (true) or not
* (false) the Command is one-way.
*
*/
void Command::SetIsOneWay(bool inIsOneWay)
{
SetFlag(mFlags, kCommandFlag_IsOneWay, inIsOneWay);
}
/**
* @brief Send no message but free all resources associated, including closing the exchange context
*
* @return #WEAVE_NO_ERROR if the message has been pushed into message layer
*/
void Command::Close(void)
{
WeaveLogDetail(DataManagement, "Command[%d] [%04" PRIX16 "] %s", SubscriptionEngine::GetInstance()->GetCommandObjId(this),
(NULL != mEC) ? mEC->ExchangeId : 0xFFFF, __func__);
WeaveLogIfFalse(NULL != mEC);
if (NULL != mEC)
{
mEC->Close();
mEC = NULL;
}
SYSTEM_STATS_DECREMENT(nl::Weave::System::Stats::kWDM_NumCommands);
}
/**
* @brief Send a Status Report message to indicate the command has failed
*
* @param[in] aProfileId The profile for the specified status code.
*
* @param[in] aStatusCode The status code to send.
*
* @param[in] aWeaveError The Weave error associated with the status
* code.
*
* @note Application layer doesn’t have the choice to append custom data into this message.
* #Close is implicitly called at the end of this function in all conditions.
*
* @return #WEAVE_NO_ERROR if the message has been pushed into message layer
*/
WEAVE_ERROR Command::SendError(uint32_t aProfileId, uint16_t aStatusCode, WEAVE_ERROR aWeaveError)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveLogDetail(DataManagement, "Command[%d] [%04" PRIX16 "] %s profile: %" PRIu32 ", code: %" PRIu16 ", err %s",
SubscriptionEngine::GetInstance()->GetCommandObjId(this), (NULL != mEC) ? mEC->ExchangeId : 0xFFFF, __func__,
aProfileId, aStatusCode, nl::ErrorStr(aWeaveError));
VerifyOrExit(NULL != mEC, err = WEAVE_ERROR_INCORRECT_STATE);
// Drop the response if the Command was OneWay.
VerifyOrExit(!IsOneWay(), err = WEAVE_NO_ERROR);
err = nl::Weave::WeaveServerBase::SendStatusReport(mEC, aProfileId, aStatusCode, aWeaveError,
nl::Weave::ExchangeContext::kSendFlag_RequestAck);
exit:
WeaveLogFunctError(err);
Close();
return err;
}
/**
* @brief Send an In-Progress message to indicate the command is not yet completed yet
*
* @note The exact timing and meaning for this message is defined by each particular trait.
* #Close is implicitly called at the end of this function in all conditions.
*
* @return #WEAVE_NO_ERROR if the message is successfully pushed into message layer
*/
WEAVE_ERROR Command::SendInProgress(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// Drop the Send if the Command was OneWay.
if (IsOneWay())
{
Close();
ExitNow(err = WEAVE_NO_ERROR);
}
VerifyOrExit(NULL != mEC, err = WEAVE_ERROR_INCORRECT_STATE);
{
PacketBuffer * msgBuf = PacketBuffer::NewWithAvailableSize(0);
VerifyOrExit(NULL != msgBuf, err = WEAVE_ERROR_NO_MEMORY);
err = mEC->SendMessage(nl::Weave::Profiles::kWeaveProfile_WDM, kMsgType_InProgress, msgBuf,
nl::Weave::ExchangeContext::kSendFlag_RequestAck);
msgBuf = NULL;
}
exit:
WeaveLogDetail(DataManagement, "Command[%d] [%04" PRIX16 "] %s %s", SubscriptionEngine::GetInstance()->GetCommandObjId(this),
(NULL != mEC) ? mEC->ExchangeId : 0xFFFF,
IsOneWay() ? "OneWay: Dropping Response to Sender in": "", __func__);
WeaveLogFunctError(err);
return err;
}
/**
* Formulate and send a Custom Command Response message.
*
* @note
* If the application has any response data to send inside this message, it needs
* to pass that as an anonymous TLV structure encoded inside \p respBuf.
*
* @param[in] traitInstanceVersion the version of the trait instance.
*
* @param[in] respBuf pointer to the PacketBuffer that would have the CommandResponse encoded.
*
* @return WEAVE_ERROR the appropriate WEAVE_ERROR encountered while processing this call.
*/
WEAVE_ERROR Command::SendResponse(uint32_t traitInstanceVersion, nl::Weave::System::PacketBuffer * respBuf)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
const uint8_t * appRespData;
uint16_t appRespDataLen;
nl::Weave::TLV::TLVWriter respWriter;
TLVType containerType;
bool res;
enum
{
// The maximum number of bytes between the beginning of a WDM Command Response message
// and the point within the message at which the application response data begins.
kMaxCommandResponseHeaderSize = 1 + // Anonymous Structure
1 + 1 + 4 + // Version field (1 control byte + 1 context tag + 4 value bytes)
1 + 1, // App Response Data Structure (1 control byte + 1 context tag)
};
// Drop the response if the Command was OneWay.
VerifyOrExit(!IsOneWay(), err = WEAVE_NO_ERROR);
VerifyOrExit(NULL != mEC, err = WEAVE_ERROR_INCORRECT_STATE);
// If the application didn't supply a response buffer, allocate one for them.
if (respBuf == NULL)
{
respBuf = PacketBuffer::New(WEAVE_SYSTEM_CONFIG_HEADER_RESERVE_SIZE + kMaxCommandResponseHeaderSize);
VerifyOrExit(respBuf != NULL, err = WEAVE_ERROR_NO_MEMORY);
}
// Ensure that the buffer has enough space to accommodate the command response header and the lower layer packet headers.
res = respBuf->EnsureReservedSize(WEAVE_SYSTEM_CONFIG_HEADER_RESERVE_SIZE + kMaxCommandResponseHeaderSize);
VerifyOrExit(res, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
// Store the pointer to the application data that is to be inserted into the buffer later.
appRespData = respBuf->Start();
appRespDataLen = respBuf->DataLength();
// If the application supplied data to be sent with the response perform some sanity checks
if (appRespDataLen > 0)
{
// Verify that response data supplied by the application is encapsulated in an anonymous
// TLV structure. All anonymous TLV structures begin with an anonymous structure control
// byte (0x15) and end with an end-of-container control byte (0x18).
VerifyOrExit(appRespDataLen > 2, err = WEAVE_ERROR_INVALID_ARGUMENT);
VerifyOrExit(appRespData[0] == kTLVElementType_Structure, err = WEAVE_ERROR_INVALID_ARGUMENT);
VerifyOrExit(appRespData[appRespDataLen - 1] == kTLVElementType_EndOfContainer, err = WEAVE_ERROR_INVALID_ARGUMENT);
// Strip off the anonymous structure supplied by the application, leaving just the raw
// contents.
appRespData += 1;
appRespDataLen -= 1;
}
// Move the start pointer to prepare the buffer for encoding the Command Response message.
respBuf->SetStart(respBuf->Start() - kMaxCommandResponseHeaderSize);
// Set the datalength of the buffer to zero to let a TLVWriter write the entire message from the beginning.
respBuf->SetDataLength(0);
// Initialize a TLV writer to write the WDM command response message into the response buffer.
respWriter.Init(respBuf);
// Start the anonymous container that wraps the response.
err = respWriter.StartContainer(AnonymousTag, kTLVType_Structure, containerType);
SuccessOrExit(err);
// Encode the trait instance version field.
err = respWriter.Put(ContextTag(CustomCommandResponse::kCsTag_Version), traitInstanceVersion);
SuccessOrExit(err);
// If the application supplied data to be sent with the response, insert it in the Command Response.
if (appRespDataLen > 0)
{
// Copy the application response data into a new TLV structure field contained with the
// response structure. NOTE: The TLV writer will take care of moving the response data
// to the correct location within the buffer.
err = respWriter.PutPreEncodedContainer(ContextTag(CustomCommandResponse::kCsTag_Response), kTLVType_Structure, appRespData,
appRespDataLen);
SuccessOrExit(err);
}
// End the outer response structure.
err = respWriter.EndContainer(containerType);
SuccessOrExit(err);
// Finalize the response message encoding.
err = respWriter.Finalize();
SuccessOrExit(err);
// Call exchange context to send response
err = mEC->SendMessage(nl::Weave::Profiles::kWeaveProfile_WDM, kMsgType_CustomCommandResponse, respBuf,
nl::Weave::ExchangeContext::kSendFlag_NoAutoRequestAck);
// Don't free the buffer on exit.
respBuf = NULL;
exit:
WeaveLogDetail(DataManagement, "Command[%d] [%04" PRIX16 "] %s %s", SubscriptionEngine::GetInstance()->GetCommandObjId(this),
(NULL != mEC) ? mEC->ExchangeId : 0xFFFF,
IsOneWay() ? "OneWay: Dropping Response to Sender in": "", __func__);
WeaveLogFunctError(err);
Close();
if (NULL != respBuf)
{
PacketBuffer::Free(respBuf);
}
return err;
}
/**
* @brief Validate the authenticator that came with the command against specified condition
*
* @note We haven’t flushed out all the fields that have to go in here yet.
* This function has to be called BEFORE the request buffer is freed.
*
* @return #WEAVE_NO_ERROR if the command is valid
*/
WEAVE_ERROR Command::ValidateAuthenticator(nl::Weave::System::PacketBuffer * aRequestBuffer)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
IgnoreUnusedVariable(aRequestBuffer);
WeaveLogDetail(DataManagement, "Command[%d] [%04" PRIX16 "] %s", SubscriptionEngine::GetInstance()->GetCommandObjId(this),
(NULL != mEC) ? mEC->ExchangeId : 0xFFFF, __func__);
VerifyOrExit(NULL != mEC, err = WEAVE_ERROR_INCORRECT_STATE);
exit:
WeaveLogFunctError(err);
return err;
}
}; // namespace WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current)
}; // namespace Profiles
}; // namespace Weave
}; // namespace nl
#endif // WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING && WDM_PUBLISHER_ENABLE_CUSTOM_COMMANDS