blob: d349fabc025dce9857c7630b907a74ff011c7609 [file] [log] [blame]
/* -*- mode: c++; tab-width:4; c-basic-offset:4; indent-tabs-mode:nil -*- */
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
*
* This document is the property of Nest. It is considered
* confidential and proprietary information.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Nest.
*
*/
/**
* @file
* This file implements Weave Alarm Profile supporting
* interconnected alarm functionality.
*
*/
// __STDC_FORMAT_MACROS must be defined for PRIX64 to be defined for pre-C++11 clib
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif // __STDC_FORMAT_MACROS
// __STDC_LIMIT_MACROS must be defined for UINT8_MAX and INT32_MAX to be defined for pre-C++11 clib
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif // __STDC_LIMIT_MACROS
// __STDC_CONSTANT_MACROS must be defined for INT64_C and UINT64_C to be defined for pre-C++11 clib
#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif // __STDC_CONSTANT_MACROS
// it is important for this first inclusion of inttypes.h to have all the right switches turned ON
#include <inttypes.h>
#include <Weave/Core/WeaveCore.h>
#include <Weave/Core/WeaveEncoding.h>
#include <Weave/Core/WeaveMessageLayer.h>
#include <Weave/Core/WeaveServerBase.h>
#include <Weave/Profiles/ProfileCommon.h>
#include <Weave/Profiles/common/WeaveMessage.h>
#include <Weave/Profiles/vendor/nestlabs/alarm/WeaveAlarm.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Support/ProfileStringSupport.hpp>
#include <Weave/Support/WeaveFaultInjection.h>
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Support/logging/WeaveLogging.h>
namespace nl {
namespace Weave {
namespace Profiles {
using namespace ::nl::Weave;
using namespace ::nl::Weave::Profiles;
using namespace ::nl::Weave::Encoding;
#ifndef WEAVE_ALARM_DETAIL_LOGGING
#define WEAVE_ALARM_DETAIL_LOGGING 1
#endif // WEAVE_ALARM_DETAIL_LOGGING
#if WEAVE_ALARM_DETAIL_LOGGING && WEAVE_DETAIL_LOGGING
static void AlarmLogDetail(Alarm & aAlarm);
#else
#define AlarmLogDetail(ALARM)
#define WeaveLogDetail(MOD, MSG, ...)
#endif // WEAVE_ALARM_DETAIL_LOGGING && WEAVE_DETAIL_LOGGING
#define GetLeastSignificantByte(v) (uint8_t(v))
#define kWeave_VendorNameString_Nest "Nest"
#define kWeave_ProfileNameString_Alarm kWeave_VendorNameString_Nest ":Alarm"
// Forward Declarations
static const char *GetAlarmMessageName(uint32_t inProfileId, uint8_t inMsgType);
static const char *GetAlarmProfileName(uint32_t inProfileId);
#if !WEAVE_CONFIG_SHORT_ERROR_STR
static const char *GetAlarmStatusReportFormatString(uint32_t inProfileId, uint16_t inStatusCode);
#else
#define GetAlarmStatusReportFormatString NULL
#endif // #if !WEAVE_CONFIG_SHORT_ERROR_STR
static void _AlarmProfileStringInit(void) __attribute__((constructor));
static void _AlarmProfileStringDestroy(void) __attribute__((destructor));
// Globals
/**
* This structure provides storage for callbacks associated for
* returning human-readable support strings associated with the
* profile.
*/
static const Weave::Support::ProfileStringInfo sAlarmProfileStringInfo = {
kWeaveProfile_Alarm,
GetAlarmMessageName,
GetAlarmProfileName,
GetAlarmStatusReportFormatString
};
/**
* Context for registering and deregistering callbacks associated
* with for returning human-readable support strings associated with
* the profile.
*/
static Weave::Support::ProfileStringContext sAlarmProfileStringContext = {
NULL,
sAlarmProfileStringInfo
};
/**
* One time, yet reentrant, initializer for registering Weave Alarm
* profile callbacks for returning human-readable support strings
* associated with the profile.
*/
static void _AlarmProfileStringInit(void)
{
(void)Weave::Support::RegisterProfileStringInfo(sAlarmProfileStringContext);
}
/**
* One time, yet reentrant, deinitializer for unregistering Weave Alarm
* profile callbacks for returning human-readable support strings
* associated with the profile.
*/
static void _AlarmProfileStringDestroy(void)
{
(void)Weave::Support::UnregisterProfileStringInfo(sAlarmProfileStringContext);
}
/**
* @brief
* Callback function that returns a human-readable NULL-terminated
* C string describing the status code associated with this profile.
*
* This callback, when registered, is invoked when a human-readable
* NULL-terminated C string is needed to describe the status code
* associated with this profile.
*
* @param[in] inProfileId The profile identifier associated with the
* specified status code.
*
* @param[in] inStatusCode The status code for which a human-readable
* descriptive string is sought.
*
* @return a pointer to the NULL-terminated C string if a match is
* found; otherwise, NULL.
*
*/
#if !WEAVE_CONFIG_SHORT_ERROR_STR
static const char *GetAlarmStatusReportFormatString(uint32_t inProfileId, uint16_t inStatusCode)
{
const char *result = NULL;
switch (inProfileId)
{
case kWeaveProfile_Alarm:
switch (inStatusCode)
{
case kAlarmUpdateStatus_Success:
result = "[ " kWeave_ProfileNameString_Alarm "(%08" PRIX32 "):%" PRIu16 " ] Success";
break;
case kAlarmUpdateStatus_Rejected:
result = "[ " kWeave_ProfileNameString_Alarm "(%08" PRIX32 "):%" PRIu16 " ] Rejected";
break;
case kAlarmUpdateStatus_Invalid:
result = "[ " kWeave_ProfileNameString_Alarm "(%08" PRIX32 "):%" PRIu16 " ] Invalid";
break;
case kAlarmUpdateStatus_Timeout:
result = "[ " kWeave_ProfileNameString_Alarm "(%08" PRIX32 "):%" PRIu16 " ] Timeout";
break;
case kAlarmStatusReport_HushRejected_ProximityValidationFailure:
result = "[ " kWeave_ProfileNameString_Alarm "(%08" PRIX32 "):%" PRIu16 " ] Hush rejected because proximity verification failed";
break;
case kAlarmStatusReport_HushRejected_SignatureValidationFailure:
result = "[ " kWeave_ProfileNameString_Alarm "(%08" PRIX32 "):%" PRIu16 " ] Hush rejected because signature verification failed";
break;
default:
result = "[ " kWeave_ProfileNameString_Alarm "(%08" PRIX32 "):%" PRIu16 " ]";
break;
}
break;
}
return (result);
}
#endif // #if !WEAVE_CONFIG_SHORT_ERROR_STR
/**
* @brief
* Callback function that returns a human-readable NULL-terminated
* C string describing the message type associated with this
* profile.
*
* This callback, when registered, is invoked when a human-readable
* NULL-terminated C string is needed to describe the message type
* associated with this profile.
*
* @param[in] inProfileId The profile identifier associated with the
* specified message type.
*
* @param[in] inMsgType The message type for which a human-readable
* descriptive string is sought.
*
* @return a pointer to the NULL-terminated C string if a match is
* found; otherwise, NULL.
*
*/
static const char *GetAlarmMessageName(uint32_t inProfileId, uint8_t inMsgType)
{
const char *result = NULL;
switch (inProfileId) {
case kWeaveProfile_Alarm:
switch (inMsgType) {
case kAlarmMessageType_Alarm:
result = "Alarm";
break;
case kAlarmMessageType_AlarmUpdate:
result = "AlarmUpdate";
break;
case kAlarmMessageType_AlarmAck:
result = "AlarmAck";
break;
case kAlarmMessageType_AlarmHushRequest:
result = "AlarmHushRequest";
break;
case kAlarmMessageType_AlarmHushResponse:
result = "AlarmHushResponse";
break;
}
break;
}
return (result);
}
/**
* @brief
* Callback function that returns a human-readable NULL-terminated
* C string describing the profile with this profile.
*
* This callback, when registered, is invoked when a human-readable
* NULL-terminated C string is needed to describe this profile.
*
* @param[in] inProfileId The profile identifier for which a human-readable
* descriptive string is sought.
*
* @return a pointer to the NULL-terminated C string if a match is
* found; otherwise, NULL.
*
*/
static const char *GetAlarmProfileName(uint32_t inProfileId)
{
const char *result = NULL;
switch (inProfileId)
{
case kWeaveProfile_Alarm:
result = kWeave_ProfileNameString_Alarm;
break;
}
return (result);
}
/**
* @brief
* Default constructor for `Alarm` message. The message is ready for
* use immediately upon completion of the constructor.
*
*/
Alarm::Alarm(void)
{
init();
}
/**
* @brief
* Initialize the `Alarm` message object with default values.
*
* @return #WEAVE_NO_ERROR Unconditionally.
*/
WEAVE_ERROR Alarm::init(void)
{
mLength = 0;
mAlarmCtr = 0;
mWhere = 0;
mIsSessionIdAndExtEvtSnValid = false;
mSessionId = 0;
mExtEvtSeqNum = 0;
memset(mConditions, 0, sizeof(mConditions));
return WEAVE_NO_ERROR;
}
/**
* @brief
* Equality comparison between this `Alarm` message and another `Alarm`
* message
*
* @param[in] aAlarm A read-only reference to the other alarm to
* compare against.
*
* @return `true` if the alarms reflect the same alarm conditions
* `false` otherwise. The equality comparison considers the
* alarm "where" and the list of conditions only. The alarm
* counter is ignored for the purposes of this comparison.
*/
bool Alarm::operator ==(const Alarm &aAlarm) const
{
if (mWhere != aAlarm.mWhere)
{
return false;
}
for (int t = 0; t < kMaxNumAlarmConditions; t++)
{
if (mConditions[t] != aAlarm.mConditions[t])
return false;
}
return true;
}
/**
* @brief
* Retrieve a single alarm state from the list of alarms.
*
* @param[in] i The index of the condition to retrieve.
*
* @return The alarm state at the index `i` if the index points to a valid
* condition index; otherwise, 0.
*/
uint8_t Alarm::GetAlarmState(uint8_t i) const
{
if ((i >= mLength) || (i >= kMaxNumAlarmConditions))
return 0;
return mConditions[i] & 0x0f;
}
/**
* @brief
* Retrieve a single alarm condition (source) from the list of
* alarms.
*
* @param[in] i The index of the condition to retrieve.
*
* @return The alarm condition (source) at the index `i` if the index
* points to a valid condition index; otherwise, 0.
*/
uint8_t Alarm::GetAlarmCondition(uint8_t i) const
{
if ((i >= mLength) || (i >= kMaxNumAlarmConditions))
return 0;
return mConditions[i] & 0xf0;
}
/**
* @brief
* Retrieve a single alarm (source and state) from the list of
* alarms.
*
* @param[in] i The index of the condition to retrieve.
*
* @return The alarm (source and state) at the index `i` if the index
* points to a valid condition index; otherwise, 0.
*/
uint8_t Alarm::GetAlarm(uint8_t i) const
{
if ((i >= mLength) || (i >= kMaxNumAlarmConditions))
return 0;
return mConditions[i];
}
/**
* @brief
* Set the alarm state for an existing alarm in the list.
*
* @note Parameter `i` must be a valid index into the alarm list. If `i`
* is outside of the valid alarm list, no action is taken. The
* length of the list is unchanged by this operation.
*
* @param[in] i The index of the alarm to update.
* @param[in] val The alarm state to set.
*/
void Alarm::SetAlarmState(uint8_t i, uint8_t val)
{
if ((i >= mLength) || (i >= kMaxNumAlarmConditions))
return;
mConditions[i]= (mConditions[i] & 0xf0) | (val & 0x0f);
}
/**
* @brief
* Set the alarm condition (source) for an existing alarm in the
* list.
*
* @note Parameter `i` must be a valid index into the alarm list. If `i`
* is outside of the valid alarm list, no action is taken. The
* length of the list is unchanged by this operation.
*
* @param[in] i The index of the alarm to update.
* @param[in] val The alarm condition (source) to set.
*/
void Alarm::SetAlarmCondition(uint8_t i, uint8_t val)
{
if ((i >= mLength) || (i >= kMaxNumAlarmConditions))
return;
mConditions[i]= (mConditions[i] & 0x0f) | (val & 0xf0);
}
/**
* @brief
* Determine whether the severity of the alarm merits
* rebroadcasting.
*
* @return `true` if the alarm should be rebroadcast; otherwise, `false`.
*/
bool Alarm::KeepReBroadcasting(void) const
{
int i;
for (i = 0; i < mLength; i++) {
uint8_t state = GetAlarmState(i);
// For all states OTHER then the below, keep rebroadcasting
if (state != WEAVE_ALARM_STATE_STANDBY &&
state != WEAVE_ALARM_STATE_SELFTEST &&
state != WEAVE_ALARM_ANNOUNCE_HEADS_UP_1 &&
state != WEAVE_ALARM_ANNOUNCE_HEADS_UP_2)
return true;
}
return false;
}
/**
* @brief
* Replace an existing alarm in the list with a new alarm (both
* source and state).
*
* @note Parameter `i` must be a valid index into the alarm list. If
* `i` is outside of the valid alarm list, no action is taken.
* The length of the list is unchanged by this operation.
*
* @param[in] i The index of the alarm to update.
* @param[in] val The alarm value (state and source) to set.
*/
void Alarm::SetAlarm(uint8_t i, uint8_t val)
{
if ((i >= mLength) || (i >= kMaxNumAlarmConditions))
return;
mConditions[i] = val;
}
/**
* @brief
* Add a new alarm(source and state) to the existing list of the
* alarms.
*
* @note When the alarm list exceeds allocated storage, the method
* does not alter the existing list. No error code is returned
* at this time.
*
* @param[in] val The alarm value (source and state) to add to the
* alarm list.
*/
void Alarm::AddAlarm(uint8_t val)
{
if ((mLength + 1) < kMaxNumAlarmConditions)
mConditions[mLength++] = val;
}
/**
* @brief
* Serialize the current alarm message into the provided `PacketBuffer`.
*
* @param[out] aPacket A pointer to an `PacketBuffer` used to store the
* serialized message.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_BUFFER_TOO_SMALL If aPacket was NULL or
* contained insufficient space to serialize the message.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE if the alarm state is
* incorrect i.e. it contains invalid length, session ID or
* sequence number.
*/
WEAVE_ERROR Alarm::pack(PacketBuffer * aPacket)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint8_t *cursor = NULL;
// $mLength number of bytes for condition
const size_t necessaryDataLength = kPayloadSizeWithoutConditions_V2 + mLength;
VerifyOrExit(aPacket != NULL, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
cursor = aPacket->Start();
VerifyOrExit(aPacket->AvailableDataLength() >= necessaryDataLength, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
// this shall not happen, as we should always generate the session ID and
// extended sequence number when we compose any alarm messages (instead of just duplicating)
VerifyOrExit(mIsSessionIdAndExtEvtSnValid, err = WEAVE_ERROR_INCORRECT_STATE);
// this shall not happen, as the original parser implementation could go wild
// if received more than 8 alarm conditions, and the alarm class cannot hold more than that
VerifyOrExit(mLength <= kMaxNumAlarmConditions, err = WEAVE_ERROR_INCORRECT_STATE);
Write8(cursor, mAlarmCtr);
Write8(cursor, mLength);
for (int i = 0; i < mLength; ++i)
{
Write8(cursor, mConditions[i]);
}
Write8(cursor, mWhere);
LittleEndian::Write32(cursor, mSessionId);
LittleEndian::Write32(cursor, mExtEvtSeqNum);
// NOTE: if this packet is going to be extended, we need a new value for necessaryDataLength
// calculate the message length again
aPacket->SetDataLength(cursor - aPacket->Start());
exit:
return err;
}
/**
* @brief
* Deserialize the message in the packet into an `Alarm` message.
*
* @param[in] aPacket A read-only pointer to an `PacketBuffer`
* containing data to be deserialized
*
* @param[out] aResult A pointer to an `Alarm` message that will contain
* the deserialized alarm message. If the return
* code indicates any failure, the result is
* unchanged. Must not be NULL.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_MESSAGE_INCOMPLETE The message was NULL or did
* not contain enough data to deserialize the message.
*
* @retval #WEAVE_ERROR_INVALID_MESSAGE_LENGTH The message was too
* long to be deserialized on this device.
*
* @retval #WEAVE_ERROR_INVALID_ARGUMENT The pointer to store results
* was NULL.
*/
WEAVE_ERROR Alarm::parse(const PacketBuffer *aPacket, Alarm *aResult)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint8_t numAlarmConditions = 0;
const uint8_t *cursor = NULL;
// start with the assumption that we're dealing with a V1 alarm message
size_t expectedDataLength = kPayloadSizeWithoutConditions_V1;
VerifyOrExit(aPacket != NULL, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(aResult != NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
cursor = aPacket->Start();
VerifyOrExit(aPacket->DataLength() >= expectedDataLength, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
aResult->mAlarmCtr = Read8(cursor);
numAlarmConditions = Read8(cursor);
// $mLength number of bytes for condition
expectedDataLength += size_t(numAlarmConditions);
VerifyOrExit(aPacket->DataLength() >= expectedDataLength, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
// fail if the message contains more than 8 conditions
// the original implementation would parse incorrectly if there are more than 8, so
// we'd better fail here. (this is more like a design phase issue)
VerifyOrExit(numAlarmConditions <= kMaxNumAlarmConditions, err = WEAVE_ERROR_INVALID_MESSAGE_LENGTH);
aResult->mLength = numAlarmConditions;
for (uint8_t i = 0; i < numAlarmConditions; ++i)
{
aResult->mConditions[i] = Read8(cursor);
}
aResult->mWhere = Read8(cursor);
// 4 bytes for session ID
// 4 bytes for extended event sequence number
expectedDataLength += (kPayloadSizeWithoutConditions_V2 - kPayloadSizeWithoutConditions_V1);
if (aPacket->DataLength() < expectedDataLength)
{
//this is not an error, as maybe it's a V1
}
else
{
aResult->mSessionId = LittleEndian::Read32(cursor);
aResult->mExtEvtSeqNum = LittleEndian::Read32(cursor);
aResult->mIsSessionIdAndExtEvtSnValid = true;
if (aResult->mAlarmCtr != GetLeastSignificantByte(aResult->mExtEvtSeqNum))
{
WeaveLogError(Alarm, "ctr <> evtSN");
}
}
exit:
return err;
}
#if WEAVE_ALARM_DETAIL_LOGGING && WEAVE_DETAIL_LOGGING
static const char *AlarmSourceToString(uint8_t aCondition)
{
switch (aCondition & 0xf0)
{
case WEAVE_ALARM_SMOKE:
return "smoke";
case WEAVE_ALARM_TEMP:
return "temperature";
case WEAVE_ALARM_CO:
return "carbon monoxide";
case WEAVE_ALARM_CH4:
return "gas";
case WEAVE_ALARM_HUMIDITY:
return "humidity";
case WEAVE_ALARM_OTHER:
return "other";
default:
return "unknown";
}
}
static const char *AlarmStateToString(uint8_t aCondition)
{
switch (aCondition & 0x0f)
{
case WEAVE_ALARM_STATE_STANDBY:
return "standby";
case WEAVE_ALARM_STATE_HEADS_UP_1:
return "heads up 1";
case WEAVE_ALARM_STATE_HEADS_UP_2:
return "heads up 2";
case WEAVE_ALARM_STATE_HU_HUSH:
return "heads up hush";
case WEAVE_ALARM_STATE_ALARM_HUSHABLE:
return "ALARM, hushable";
case WEAVE_ALARM_STATE_ALARM_NONHUSHABLE:
return "ALARM, NONHUSHABLE";
case WEAVE_ALARM_STATE_ALARM_GLOBAL_HUSH:
return "global hush";
case WEAVE_ALARM_STATE_ALARM_REMOTE_HUSH:
return "remote hush";
case WEAVE_ALARM_STATE_SELFTEST:
return "selftest";
case WEAVE_ALARM_ANNOUNCE_HEADS_UP_1:
return "announce heads up 1";
case WEAVE_ALARM_ANNOUNCE_HEADS_UP_2:
return "announce heads up 2";
default:
return "unknown";
}
}
static void AlarmLogDetail(Alarm & aAlarm)
{
for (uint8_t i = 0; i < aAlarm.mLength; ++i)
{
WeaveLogDetail(Alarm, "Alarm %u [%2d] 0x%02X %s: %s",
aAlarm.mIsSessionIdAndExtEvtSnValid ? aAlarm.mExtEvtSeqNum : aAlarm.mAlarmCtr,
i, aAlarm.GetAlarm(i),
AlarmSourceToString(aAlarm.GetAlarm(i)), AlarmStateToString(aAlarm.GetAlarm(i)));
}
}
#endif // WEAVE_ALARM_DETAIL_LOGGING && WEAVE_DETAIL_LOGGING
/**
* @brief
* Default constructor for the `AlarmHushSignature`.
*/
AlarmHushSignature::AlarmHushSignature(void) :
mIsSigned(false)
{
}
/**
* @brief
* Equality operator for comparing two instances of `AlarmHushSignature`.
*
* @note The equality comparison is only meaningful for signed
* instances of `AlarmHushSignature`; in case where either of
* the operands is unsigned returns false. As a result, the
* operator does not strictly meet the requirements for equality
* relationship. For signed instances, function returns the
* results of comparison for both the key IDs and the
* signatures.
*
* param[in] rhs A read-only reference to the `AlarmHushSignature`
* instance to compare against.
*
* @return `false` if any of the signatures are not signed, the IDs
* don't match, or the content doesn't match
*/
bool AlarmHushSignature::operator == (const AlarmHushSignature & rhs) const
{
if (mIsSigned && rhs.mIsSigned && (mKeyId == rhs.mKeyId))
{
return (0 == memcmp(mKeyedHash, rhs.mKeyedHash, sizeof(mKeyedHash)));
}
// note that the comparison of two not signed signatures is always false
// this forces the application layer to be aware of if a request has been signed or not
return false;
}
/**
* @brief
* Not-equal operator for comparing two instances of `AlarmHushSignature`.
*
* @note The operator performs the equality testing and reverses the
* resulting value. The equality comparison is only meaningful
* for signed instances of `AlarmHushSignature`s; consequently
* any two unsigned Alarm Hush Signatures are unequal to one
* another. For signed instances, function returns true if
* either the key IDs or the signatures do not match.
*
* param[in] rhs A read-only reference to the `AlarmHushSignature`
* instance to compare against.
*
* @return negated result of operator ==
*/
bool AlarmHushSignature::operator != (const AlarmHushSignature & rhs) const
{
return !((*this) == rhs);
}
/**
* @brief
* Default constructor for `AlarmHushRequest`. Object must be
* initialized via `AlarmHushRequest::init()` prior to use.
*
*/
AlarmHushRequest::AlarmHushRequest(void)
{
}
/**
* @brief
* Initialize the `AlarmHushRequest`.
*
* @retval #WEAVE_NO_ERROR unconditionally.
*/
WEAVE_ERROR AlarmHushRequest::init(void)
{
mProximityVerificationCode = 0;
return WEAVE_NO_ERROR;
}
/**
* @brief
* Deserialize the message in the packet into an `AlarmHushRequest` message.
*
* @param[in] aPacket A read-only pointer to `PacketBuffer` containing
* data to be deserialized.
*
* @param[out] aResult A pointer to an `AlarmHushRequest` message that
* will contain the deserialized alarm hush
* request message. If the return code indicates
* any failure, the result is unchanged. Must not
* be NULL.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_MESSAGE_INCOMPLETE The message was NULL or did
* not contain enough data to deserialize the message.
*
* @retval #WEAVE_ERROR_INVALID_ARGUMENT The pointer to store results
* was NULL.
*/
WEAVE_ERROR AlarmHushRequest::parse(const PacketBuffer* aPacket, AlarmHushRequest *aResult)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// cannot use AlarmHushRequest::mProximityVerificationCode as clang compiler on OSX doesn't like it
const size_t expectedDataLength = sizeof(aResult->mProximityVerificationCode) + sizeof(aResult->mSignature.mKeyId) + sizeof(aResult->mSignature.mKeyedHash);
VerifyOrExit(aPacket != NULL, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(aResult != NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
VerifyOrExit(aPacket->DataLength() >= expectedDataLength, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
{
const uint8_t *cursor = aPacket->Start();
aResult->mProximityVerificationCode = LittleEndian::Read32(cursor);
aResult->mSignature.mKeyId = LittleEndian::Read16(cursor);
memcpy(aResult->mSignature.mKeyedHash, cursor, sizeof(aResult->mSignature.mKeyedHash));
// Note: cursor should be updated before further extension can be made into the parser
// cursor += sizeof(aResult->mSignature.mKeyedHash);
}
aResult->mSignature.mIsSigned = true;
exit:
return err;
}
/**
* @brief
* Serialize the current alarm hush request message into the
* provided `PacketBuffer`
*
* @param[out] aPacket A pointer to an `PacketBuffer` used to store the
* serialized message.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_BUFFER_TOO_SMALL If aPacket was NULL or
* contained insufficient space to serialize the message.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE If the alarm hush request
* message is incorrect, e.g. it is not signed.
*/
WEAVE_ERROR AlarmHushRequest::pack(PacketBuffer *aPacket)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
const size_t necessaryDataLength = sizeof(mProximityVerificationCode) + sizeof(mSignature.mKeyId) + sizeof(mSignature.mKeyedHash);
VerifyOrExit(aPacket != NULL, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
VerifyOrExit(aPacket->AvailableDataLength() >= necessaryDataLength, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
VerifyOrExit(mSignature.mIsSigned, err = WEAVE_ERROR_INCORRECT_STATE);
{
uint8_t *cursor = aPacket->Start();
LittleEndian::Write32(cursor, mProximityVerificationCode);
LittleEndian::Write16(cursor, mSignature.mKeyId);
memcpy(cursor, mSignature.mKeyedHash, sizeof(mSignature.mKeyedHash));
cursor += sizeof(mSignature.mKeyedHash);
// calculate the message length again
aPacket->SetDataLength(cursor - aPacket->Start());
}
exit:
return err;
}
/**
* @brief
* Sign the hush request with information provided here, and fill
* the request with signature
*
* @param[in] aChallenge A random 32-bit challenge delivered
* out-of-band from Weave.
*
* @param[in] aKeyId A 16-bit key ID of the key used to sign this
* request. The definition of this is TBD at
* this time; until further notice, users of
* this API should fill-in this value with
* #WEAVE_ALARM_HUSH_KEY_ID.
*
* @param[in] aKey The actual key used to sign the request.
*
* @param[in] aKeySize The number of bytes to be used from the
* key. The key must be at least
* #kAlarmHush_MinKeySize bytes long.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INVALID_ARGUMENT If the key size if
* insufficiently large
*/
WEAVE_ERROR AlarmHushRequest::sign(uint32_t aChallenge, uint16_t aKeyId, const uint8_t aKey[], uint16_t aKeySize)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// make sure the key used to sign the message is long enough
VerifyOrExit(aKeySize >= kAlarmHush_MinKeySize, err = WEAVE_ERROR_INVALID_ARGUMENT);
{
Crypto::HMACSHA1 hmac;
hmac.Begin(aKey, aKeySize);
hmac.AddData(reinterpret_cast<uint8_t *>(&aChallenge), sizeof(aChallenge));
hmac.AddData(reinterpret_cast<uint8_t *>(&mProximityVerificationCode), sizeof(mProximityVerificationCode));
hmac.Finish(mSignature.mKeyedHash);
}
mSignature.mIsSigned = true;
mSignature.mKeyId = aKeyId;
exit:
return err;
}
/**
* @brief
* Default constructor for `AlarmHushResponse`. Object must be
* initialized via `AlarmHushResponse::init()` prior to use.
*/
AlarmHushResponse::AlarmHushResponse(void)
{
mHushResult = kAlarmUpdateStatus_Success;
mLength = 0;
memset(mConditions, 0, sizeof(mConditions));
}
/**
* @brief
* Initialize `AlarmHushResponse` with the latest status stored in an
* `Alarm` object, with the given status code.
*
* @param[in] aResult The result status code to be returned in
* this `AlarmHushResponse` message (check the
* kAlarmHushResult_XXX family of
* constants)
*
* @param[in] aNumEntry The number of valid entries in `aAlarmCondition`
*
* @param[in] aAlarmCondition A read-only pointer to an array of alarm
* conditions. When `aNumEntry` is zero, this
* pointer could be NULL
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_BUFFER_TOO_SMALL When the number of conditions
* passed in exceeds the capacity of this `AlarmHushResponse`
* message.
*/
WEAVE_ERROR AlarmHushResponse::init(uint8_t aResult, uint8_t aNumEntry, const uint8_t * aAlarmCondition)
{
if (aNumEntry > sizeof(mConditions)/sizeof(mConditions[0]))
{
return WEAVE_ERROR_BUFFER_TOO_SMALL;
}
mHushResult = aResult;
mLength = aNumEntry;
if (mLength > 0)
{
memcpy(mConditions, aAlarmCondition, mLength * sizeof(mConditions[0]));
}
return WEAVE_NO_ERROR;
}
/**
* @brief
* Deserialize the message in the packet into an `AlarmHushResponse`
* message
*
* @param[in] aPacket A pointer to an `PacketBuffer` containing data to
* be deserialized
*
* @param[out] aResult A pointer to an `AlarmHushResponse` message
* that will contain the deserialized alarm hush
* response message. If the return code indicates
* any failure, the result is unchanged. Must not
* be NULL.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_MESSAGE_INCOMPLETE The message was NULL
* or did not contain enough data to deserialize the message.
*
* @retval #WEAVE_ERROR_INVALID_MESSAGE_LENGTH The message was too
* long to be deserialized on this device.
*
* @retval #WEAVE_ERROR_INVALID_ARGUMENT The pointer to store
* results was NULL.
*/
WEAVE_ERROR AlarmHushResponse::parse(const PacketBuffer *aPacket, AlarmHushResponse *aResult)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
const uint8_t *cursor;
size_t expectedDataLength = kPayloadSizeWithoutConditions;
VerifyOrExit(aPacket != NULL, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(aPacket->DataLength() >= expectedDataLength, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(aResult != NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
cursor = aPacket->Start();
aResult->mHushResult = Read8(cursor);
aResult->mLength = Read8(cursor);
// we cannot handle any extra alarm conditions
// since the original alarm implementation would go wild if we have more than 8 conditions,
// and alarm class can only hold 8 conditions, it doesn't make much sense to support beyond that
VerifyOrExit(aResult->mLength <= Alarm::kMaxNumAlarmConditions, err = WEAVE_ERROR_INVALID_MESSAGE_LENGTH);
// $mLength number of bytes for condition
expectedDataLength += aResult->mLength;
VerifyOrExit(aPacket->DataLength() >= expectedDataLength, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
for (uint8_t i = 0; i < aResult->mLength; ++i)
{
aResult->mConditions[i] = Read8(cursor);
}
exit:
return err;
}
/**
* @brief
* Serialize the current alarm message into the provided `PacketBuffer`.
*
* @param[out] aPacket A pointer to an `PacketBuffer` used to store the
* serialized message.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_BUFFER_TOO_SMALL If aPacket was NULL or
* contained insufficient space to serialize the message.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE If the alarm hush response is
* incorrect e.g. it contains invalid length
*/
WEAVE_ERROR AlarmHushResponse::pack(PacketBuffer *aPacket)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint8_t *cursor = NULL;
// $mLength number of bytes for condition
const size_t necessaryDataLength = kPayloadSizeWithoutConditions + mLength;
VerifyOrExit(aPacket != NULL, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
VerifyOrExit(aPacket->AvailableDataLength() >= necessaryDataLength, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
// we cannot handle any extra alarm conditions
// since the original alarm implementation would go wild if we have more than 8 conditions,
// and alarm class can only hold 8 conditions, it doesn't make much sense to support beyond that
VerifyOrExit(mLength <= Alarm::kMaxNumAlarmConditions, err = WEAVE_ERROR_INCORRECT_STATE);
cursor = aPacket->Start();
Write8(cursor, mHushResult);
Write8(cursor, mLength);
for (int i = 0; i < mLength; ++i)
{
Write8(cursor, mConditions[i]);
}
// calculate the message length again
aPacket->SetDataLength(cursor - aPacket->Start());
exit:
return err;
}
// Alarm client
/**
* @brief
* Converts the ClientState enum into a human readable string
*
* @param[in] aState ClientState enum
*
* @return Human-readable representation of the client state, suitable
* for printing. When the parameter does not correspond to
* any known state, string "UNKNOWN" is returned.
*/
const char * WeaveAlarmClient::GetClientStateName(const ClientState aState)
{
switch (aState)
{
case kAlarmClientState_Closed: return "CLOSED";
case kAlarmClientState_Active: return "ACTIVE";
case kAlarmClientState_GracePeriod: return "GRACE";
case kAlarmClientState_Linger: return "LINGER";
default: return "UNKNOWN";
}
}
/**
* @brief
* Default constructor for `WeaveAlarmClient`.
*
* @note Application code must not instantiate `WeaveAlarmClient`
* directly. Instances of `WeaveAlarmClient` must be obtained
* via calls to `WeaveAlarmServer::NewClient()` to ensure
* correct functionality.
*
*/
WeaveAlarmClient::WeaveAlarmClient(void)
{
}
/**
* @brief
* Send the alarm to all nodes in the network
*
* The main entry point to sending the `Alarm` messages to the network.
* The function expects that this object has been initialized and has
* an active `ExchangeContext`. The payload passed in is copied by
* value and becomes the current payload. The function allocates an
* `PacketBuffer` to serialize the `Alarm` into, and initiates a Trickle
* session to disseminate that alarm message throughout the network.
*
* @param[in] aPayload A read-only pointer to an alarm message to
* propagate to the network.
*
* @return #WEAVE_NO_ERROR on success, or an error code from
* `ExchangeContext::SendMessage()`
*
* @retval #WEAVE_ERROR_INCORRECT_STATE if the `WeaveAlarmClient`
* doesn't represent a local alarm or if it has not been
* initialized properly.
*
* @retval #WEAVE_ERROR_NO_MEMORY if the client was unable to allocate
* `PacketBuffer` for actually transmitting the message.
*/
WEAVE_ERROR WeaveAlarmClient::SendAlarm(const Alarm *aPayload)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint32_t preservedSessionId;
uint32_t preservedExtEvtSeqNum;
PacketBuffer *msgBuf = NULL;
VerifyOrExit(IsLocalAlarm(), err = WEAVE_ERROR_INCORRECT_STATE);
VerifyOrExit(NULL != mEc, err = WEAVE_ERROR_INCORRECT_STATE);
// this is local alarm, so we must have the session ID and extended event sequence number valid
VerifyOrExit(mCurrentAlarm.mIsSessionIdAndExtEvtSnValid, err = WEAVE_ERROR_INCORRECT_STATE);
if (mCurrentAlarm.mExtEvtSeqNum >= UINT32_MAX)
{
// this shall not happen in practice
WeaveLogError(Alarm, "[%d] WARNING evtSN wrap around", FindClientIndex());
RegenerateSessionId();
}
preservedSessionId = mCurrentAlarm.mSessionId;
preservedExtEvtSeqNum = mCurrentAlarm.mExtEvtSeqNum;
msgBuf = mServer->NewPacketBuffer();
WEAVE_FAULT_INJECT(FaultInjection::kFault_SendAlarm, if (msgBuf) { PacketBuffer::Free(msgBuf); msgBuf = NULL; });
VerifyOrExit(NULL != msgBuf, err = WEAVE_ERROR_NO_MEMORY);
// increase event sequence number
++preservedExtEvtSeqNum;
if (0 == GetLeastSignificantByte(preservedExtEvtSeqNum))
{
// make sure the lowest byte (the alarm counter) is never 0
// 0 is not anything bad, but normally alarm counter starts from 1
++preservedExtEvtSeqNum;
WeaveLogError(Alarm, "[%d] WARNING ctr wrap around", FindClientIndex());
}
mCurrentAlarm = *aPayload;
mCurrentAlarm.mIsSessionIdAndExtEvtSnValid = true;
mCurrentAlarm.mSessionId = preservedSessionId;
mCurrentAlarm.mExtEvtSeqNum = preservedExtEvtSeqNum;
// the alarm counter is now just the lowest byte of extended event sequence number
mCurrentAlarm.mAlarmCtr = GetLeastSignificantByte(preservedExtEvtSeqNum);
mCurrentAlarm.pack(msgBuf);
mEc-> PeerIntf = mServer->mInterfaceId;
// Note that InterfaceId is typdef to be a pointer with LwIP, but an unsigned int otherwise.
// Casting it to uint64_t should get us reasonable results on all platforms.
WeaveLogDetail(Alarm, "[%d] SendAlarm. session:0x%x, ctr:%u, evtSN:%u, i/f 0x%" PRIx64, FindClientIndex(),
mCurrentAlarm.mSessionId, mCurrentAlarm.mAlarmCtr, mCurrentAlarm.mExtEvtSeqNum, (uint64_t)mServer->mInterfaceId);
AlarmLogDetail(mCurrentAlarm);
// note that this setup has to preceed SendMessage, as the settings change made in this function affects the behavior of SendMessage
err = mEc->SetupTrickleRetransmit(
mServer->mAlarmRebroadcastPeriodMsec,
mServer->mAlarmRebroadcastThreshold,
mServer->mAlarmRefreshPeriodMsec);
SuccessOrExit(err);
// note that adding ExchangeContext::kSendFlag_DelaySen adds a random delay, depending on the trickle mechanism, before it's sent out
err = mEc->SendMessage(kWeaveProfile_Alarm, kAlarmMessageType_Alarm, msgBuf, ExchangeContext::kSendFlag_RetransmissionTrickle);
msgBuf = NULL;
SuccessOrExit(err);
SetClientState_Active();
exit:
WeaveLogFunctError(err);
if (msgBuf != NULL)
{
// This will never be executed with the current version of the code
PacketBuffer::Free(msgBuf);
}
// set client state to linger on error, which also cancels trickle timer and notifies app
if ((NULL != mEc) && (WEAVE_NO_ERROR != err))
{
// put local alarm client into linger
SetClientState_Linger();
}
return err;
}
/**
* @brief
* Handle the expiration of the timer dedicated to the refresh
* of the alarm freshness.
*
* For local alarms, we decide whether we should keep alarming. If
* so, the current alarm message is refreshed with a new counter and
* new session ID, as needed. If the alarm was not refreshed, the
* `WeaveAlarmClient` will transition into the linger state.
*
* @param[in] aEc A pointer to the `ExchangeContext` associated with
* this `WeaveAlarmClient`.
*/
void WeaveAlarmClient::HandleRetransmissionTimeout(ExchangeContext *aEc)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveAlarmClient *client = (WeaveAlarmClient *) aEc->AppState;
const int clientIndex = client->FindClientIndex();
static const char strRetransmissionTimeout[] = "Retransmission timeout.";
IgnoreUnusedVariable(clientIndex);
IgnoreUnusedVariable(strRetransmissionTimeout);
if (client->IsLocalAlarm())
{
if (client->mCurrentAlarm.KeepReBroadcasting())
{
WeaveLogDetail(Alarm, "[%d] %s Refresh", clientIndex, strRetransmissionTimeout);
err = client->SendAlarm(&(client->mCurrentAlarm));
if (WEAVE_NO_ERROR != err)
{
WeaveLogError(Alarm, "[%d] Refresh failed", clientIndex);
}
}
else
{
WeaveLogDetail(Alarm, "[%d] %s Nothing to report", clientIndex, strRetransmissionTimeout);
AlarmLogDetail(client->mCurrentAlarm);
// put local alarm client into linger
client->SetClientState_Linger();
}
}
else
{
WeaveLogDetail(Alarm, "[%d] %s Stop", clientIndex, strRetransmissionTimeout);
// put client into grace period
client->SetClientState_GracePeriod();
}
WeaveLogFunctError(err);
}
/**
* @brief
* Initialize the current `WeaveAlarmClient` to default values
*
* @param[in] aAlarmServer A read-only pointer to the alarm server
* object holding the pool from which this
* alarm client originated.
*/
void WeaveAlarmClient::initAlarmClient(WeaveAlarmServer * const aAlarmServer)
{
mEc = NULL;
mCurrentAlarm.init();
mClientState = kAlarmClientState_Closed;
mServer = aAlarmServer;
mIsLocalAlarm = false;
}
/**
* @brief
* Close this alarm client.
*
* The method attempts to set the client state to closed via
* `WeaveAlarmClient::SetClientState_Closed()` without issuing
* notifications to the higher layers.
*
* @note The object is considered terminated at this point. The
* object's storage is available for use by its associated
* `WeaveAlarmServer`. Consequently, this object must not be
* used by the application code past this call. The object may
* be reanimated (logically, a new object is created in the same
* storage) via `WeaveAlarmServer::NewClient()`.
*
*/
void WeaveAlarmClient::Close(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// there is no need to notify the app layer about state change, as we are closed by upper layer
err = SetClientState_Closed (false);
WeaveLogFunctError(err);
}
/**
* @brief
* C-to-C++ trampoline for handling linger timeout, actual
* handling done in `WeaveAlarmClient::HandleLingerTimeout()`
*
* @param[in] aSystemLayer A pointer to an `SystemLayer` object that
* generated the timeout (ignored)
* @param[in] aAppState A pointer to a context object associated with
* the timeout
* @param[in] aError An error code from the timer (ignored)
*/
void WeaveAlarmClient::_HandleLingerTimeout(System::Layer* aSystemLayer, void* aAppState, System::Error aError)
{
WeaveAlarmClient* client = reinterpret_cast<WeaveAlarmClient*>(aAppState);
client->HandleLingerTimeout();
}
/**
* @brief
* Handle the end of the linger state by closing the associated
* client and cleaning up the underlying resources.
*/
void WeaveAlarmClient::HandleLingerTimeout(void)
{
// as a precaution, for timers could fire at any time
if (NULL != mServer)
{
WeaveLogDetail(Alarm, "[%d] Linger ended. Closing", FindClientIndex());
}
SetClientState_Closed();
}
/**
* @brief
* C-to-C++ trampoline for handling grace period timeout, actual
* handling done in `WeaveAlarmClient::HandleGracePeriodTimeout()`.
*
* @param[in] inet A pointer to an `SystemLayer` object that
* generated the timeout (ignored)
* @param[in] aAppState A pointer to a context object associated with
* the timeout
* @param[in] err An error code from the timer (ignored)
*/
void WeaveAlarmClient::_HandleGracePeriodTimeout(System::Layer* aSystemLayer, void* aAppState, System::Error aError)
{
WeaveAlarmClient* client = reinterpret_cast<WeaveAlarmClient*>(aAppState);
client->HandleGracePeriodTimeout();
}
/**
* @brief
* Handle the end of grace period by transitioning into the linger
* state.
*
*/
void WeaveAlarmClient::HandleGracePeriodTimeout(void)
{
// as a precaution, for timers could fire at any time
if (NULL != mServer)
{
WeaveLogDetail(Alarm, "[%d] Grace period ended, moving to linger", FindClientIndex());
SetClientState_Linger();
}
}
/**
* @brief
* Find the index of this instance within the owning 'WeaveAlarmServer'
*
* @return -1 on failure; otherwise, the index of this instance in the pool
* of 'WeaveAlarmClient' instances
*/
int WeaveAlarmClient::FindClientIndex(void) const
{
int result = -1;
if (NULL != mServer)
{
result = mServer->GetClientIndex(this);
}
return result;
}
/**
* @brief
* Set the state of `WeaveAlarmClient`
*
* @note The method only acts as a setter, and does not implement the
* overall state machine. Applications should use `SetClientState_*`
* APIs whenever possible instead.
*
* @param[in] aNewState New state.
*/
void WeaveAlarmClient::ForceClientStateChangeNoError(ClientState aNewState)
{
WeaveLogDetail(Alarm, "[%d] ClientState: %s -> %s", FindClientIndex(), GetClientStateName(mClientState), GetClientStateName(aNewState));
mClientState = aNewState;
}
/**
* @brief
* Cancels the timers governing the timers related to the grace
* period and linger states.
*
*/
void WeaveAlarmClient::CancelAllTimersExceptForTrickle(void)
{
if (NULL != mServer)
{
// Note that the general practice in Weave code is to assume
// all pointers to singleton global objects have been properly initialized
mServer->ExchangeMgr->MessageLayer->SystemLayer->CancelTimer(_HandleLingerTimeout, this);
mServer->ExchangeMgr->MessageLayer->SystemLayer->CancelTimer(_HandleGracePeriodTimeout, this);
}
}
/**
* @brief
* Attempts to set the state of the `WeaveAlarmClient` to
* #kAlarmClientState_Active.
*
* The function examines the state of the current `WeaveAlarmClient`.
* If state transition is permitted (based the current state of the
* object), then object transitions into #kAlarmClientState_Active
* state. Appropriate actions are executed along the way. If
* transition is not permitted, the `WeaveAlarmClient` remains
* unchanged and the error code is returned.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE If the `WeaveAlarmClient` is
* in a state that does not permit the transition to
* #kAlarmClientState_Active.
*/
WEAVE_ERROR WeaveAlarmClient::SetClientState_Active(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
switch (mClientState)
{
case kAlarmClientState_Linger:
case kAlarmClientState_GracePeriod:
case kAlarmClientState_Active:
case kAlarmClientState_Closed:
break;
default:
ExitNow(err = WEAVE_ERROR_INCORRECT_STATE);
}
// cancel all timers so we have a cleaner slate
CancelAllTimersExceptForTrickle();
ForceClientStateChangeNoError(kAlarmClientState_Active);
exit:
WeaveLogFunctError(err);
// reclaim all resources on error, as this is a catastrophic error
if (WEAVE_NO_ERROR != err)
{
WeaveLogError(Alarm, "[%d] Error setting to ACTIVE", FindClientIndex());
if (IsLocalAlarm())
{
// do not close local alarm client, but at least we can try to cancel trickling
// Canceling retransmission at this point would prevent the trickle timer from firing.
mEc->CancelRetrans();
}
else
{
// close if this is a remote client
(void)SetClientState_Closed();
}
}
mServer->OnAlarmClientStateChange(this);
return err;
}
/**
* @brief
* Attempts to set the state of the `WeaveAlarmClient` to
* #kAlarmClientState_Closed.
*
* The function examines the state of the current `WeaveAlarmClient`.
* If state transition is permitted (based the current state of the
* object), then object transitions into #kAlarmClientState_Closed
* state. Appropriate actions are executed along the way. If
* transition is not permitted, the `WeaveAlarmClient` remains unchanged
* and the error code is returned.
*
* @param[in] aNotifyAppLayer a flag dictating whether the application
* should be notified about the state change via
* `WeaveAlarmServer::OnAlarmClientStateChange()` callback.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE If the `WeaveAlarmClient` is in
* a state that does not permit the transition to
* #kAlarmClientState_Closed.
*/
WEAVE_ERROR WeaveAlarmClient::SetClientState_Closed(bool aNotifyAppLayer)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
switch (mClientState)
{
case kAlarmClientState_Closed:
// do nothing if we're already closed
ExitNow();
break;
case kAlarmClientState_Active:
case kAlarmClientState_Linger:
case kAlarmClientState_GracePeriod:
break;
default:
ExitNow(err = WEAVE_ERROR_INCORRECT_STATE);
}
// reclaim all resources, be careful about partially initialized states
if (NULL != mServer)
{
// cancel all timers so we have a cleaner slate
CancelAllTimersExceptForTrickle();
}
ForceClientStateChangeNoError(kAlarmClientState_Closed);
exit:
WeaveLogFunctError(err);
if (WEAVE_NO_ERROR != err)
{
WeaveLogError(Alarm, "[%d] Error setting to CLOSED", FindClientIndex());
}
if (aNotifyAppLayer && (NULL != mServer))
{
// notify the application layer that state of some alarm client has changed
// note that the "IsLocalAlarm" status is not reset yet, so application layer has a chance to evaluate what happened
mServer->OnAlarmClientStateChange(this);
}
mIsLocalAlarm = false;
// close exchange context, which cancels the trickle implicitly
// we put it after OnAlarmClientStateChange callback so application layer can still access the source node id
if (mEc != NULL) {
mEc->Close();
mEc = NULL;
}
// we've tried our best reclaiming all resource, so no further error handling
return err;
}
/**
* @brief
* Attempts to set the state of the `WeaveAlarmClient` to
* #kAlarmClientState_GracePeriod.
*
* The function examines the state of the current `WeaveAlarmClient`.
* If state transition is permitted (based the current state of the
* object), then object transitions into #kAlarmClientState_GracePeriod
* state. Appropriate actions are executed along the way. If
* transition is not permitted, the `WeaveAlarmClient` remains unchanged
* and the error code is returned.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE If the `WeaveAlarmClient` is not
* in a #kAlarmClientState_Active state or if the
* `WeaveAlarmClient` does not pertain a local alarm.
*/
WEAVE_ERROR WeaveAlarmClient::SetClientState_GracePeriod(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// Usage error
// local alarm shall never enter this state
VerifyOrExit(!IsLocalAlarm(), err = WEAVE_ERROR_INCORRECT_STATE);
switch (mClientState)
{
case kAlarmClientState_Active:
break;
default:
ExitNow(err = WEAVE_ERROR_INCORRECT_STATE);
}
WeaveLogDetail(Alarm, "[%d] Stop trickle", FindClientIndex());
// make sure trickle is stopped
mEc->CancelRetrans();
// cancel all timers so we have a cleaner slate
CancelAllTimersExceptForTrickle ();
// start timer for grace period
SuccessOrExit(err = mServer->ExchangeMgr->MessageLayer->SystemLayer->StartTimer(mServer->mAlarmGracePeriodMsec, _HandleGracePeriodTimeout, this));
ForceClientStateChangeNoError(kAlarmClientState_GracePeriod);
exit:
WeaveLogFunctError(err);
if (WEAVE_NO_ERROR != err)
{
WeaveLogError(Alarm, "[%d] Error setting to GRACE PERIOD", FindClientIndex());
if (!IsLocalAlarm())
{
// timer failure for remote alarm is okay, just close it and notify the application layer on closing
(void)SetClientState_Closed();
}
}
mServer->OnAlarmClientStateChange(this);
return err;
}
/**
* @brief
* Attempts to set the state of the `WeaveAlarmClient` to
* #kAlarmClientState_Linger.
*
* The function examines the state of the current `WeaveAlarmClient`.
* If state transition is permitted (based the current state of the
* object), then object transitions into #kAlarmClientState_Linger
* state. Appropriate actions are executed along the way. If
* transition is not permitted, the `WeaveAlarmClient` remains unchanged
* and the error code is returned.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE If the `WeaveAlarmClient` is not
* in either a #kAlarmClientState_Active or
* #kAlarmClientState_GracePeriod state or if the
* `WeaveAlarmClient` does not pertain a local alarm.
*/
WEAVE_ERROR WeaveAlarmClient::SetClientState_Linger(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// stop trickling
mEc->CancelRetrans();
switch (mClientState)
{
case kAlarmClientState_Active:
if (!IsLocalAlarm())
{
// Usage error
// remote alarm shall not enter LINGER directly from ACTIVE
ExitNow(err = WEAVE_ERROR_INCORRECT_STATE);
}
break;
case kAlarmClientState_GracePeriod:
break;
default:
ExitNow(err = WEAVE_ERROR_INCORRECT_STATE);
}
// cancel all timers so we have a cleaner slate
CancelAllTimersExceptForTrickle ();
if (IsLocalAlarm())
{
// local alarms are never automatically closed. they linger until next SendAlarm
WeaveLogDetail(Alarm, "[%d] Initiator alarm timed out", FindClientIndex());
SuccessOrExit(err = TryAdvanceExtEvtSeqNum());
ForceClientStateChangeNoError(kAlarmClientState_Linger);
}
else
{
uint32_t lingerTimeoutMsec = mServer->mAlarmRefreshPeriodMsec * WEAVE_ALARM_MAX_NUM_HOPS;
if (lingerTimeoutMsec > mServer->mAlarmGracePeriodMsec)
{
lingerTimeoutMsec -= mServer->mAlarmGracePeriodMsec;
WeaveLogDetail(Alarm, "[%d] Setup linger timer: %u msec", FindClientIndex(), lingerTimeoutMsec);
// remote alarms will be closed when this timer fires
SuccessOrExit(err = mServer->ExchangeMgr->MessageLayer->SystemLayer->StartTimer(lingerTimeoutMsec, _HandleLingerTimeout, this));
ForceClientStateChangeNoError(kAlarmClientState_Linger);
}
else
{
// strange, but not impossible (if grace period is set to be equal or longer than linger time
WeaveLogDetail(Alarm, "[%d] No linger is needed", FindClientIndex());
// directly move to CLOSED, as we've already spent out time in grace period
SuccessOrExit(err = SetClientState_Closed());
}
}
exit:
WeaveLogFunctError(err);
if (WEAVE_NO_ERROR != err)
{
WeaveLogError(Alarm, "[%d] Error setting to LINGER", FindClientIndex());
if (!IsLocalAlarm())
{
// it's possible for us to call this twice in a row if there is any error from this function
// it should be fine
(void)SetClientState_Closed();
}
}
mServer->OnAlarmClientStateChange(this);
return err;
}
/**
* @brief
* Accessor function for retrieving the current state of the
* `WeaveAlarmClient`.
*
* @return Current state of the object.
*/
const WeaveAlarmClient::ClientState WeaveAlarmClient::GetClientState(void) const
{
return mClientState;
}
/**
* @brief
* Regenerate the ID of this Alarm Client.
*
* Session IDs are chosen at random. They discriminate between alarm
* messages across the alarm counter rollover or unexpected node
* reboot.
*
* @retval #WEAVE_NO_ERROR on success
*
* @retval #WEAVE_ERROR_RANDOM_DATA_UNAVAILABLE if the random number
* generator was unable to generate a random session IDs
*/
WEAVE_ERROR WeaveAlarmClient::RegenerateSessionId(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
mCurrentAlarm.mIsSessionIdAndExtEvtSnValid = false;
mCurrentAlarm.mSessionId = 0;
uint32_t sessionId = 0;
SuccessOrExit(err = Platform::Security::GetSecureRandomData((uint8_t *)&sessionId, sizeof(sessionId)));
mCurrentAlarm.mIsSessionIdAndExtEvtSnValid = true;
mCurrentAlarm.mSessionId = sessionId;
mCurrentAlarm.mExtEvtSeqNum = 0;
mCurrentAlarm.mAlarmCtr = GetLeastSignificantByte(mCurrentAlarm.mExtEvtSeqNum);
exit:
WeaveLogFunctError(err);
return err;
}
/**
* @brief
* Advance the sequence number and regenerate the session ID as
* needed.
*
* @return #WEAVE_NO_ERROR On success. Failures (and resultant error
* codes) are those of `WeaveAlarmClient::RegenerateSessionId()`
*/
WEAVE_ERROR WeaveAlarmClient::TryAdvanceExtEvtSeqNum(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
bool IsRegenerationNeeded = true;
if (mCurrentAlarm.mIsSessionIdAndExtEvtSnValid)
{
const uint32_t next256AlignedEvtSn = ((mCurrentAlarm.mExtEvtSeqNum + UINT8_MAX) >> 8) << 8;
if (next256AlignedEvtSn > mCurrentAlarm.mExtEvtSeqNum)
{
// no wrap around detected, so the new sequence number is a valid one
mCurrentAlarm.mExtEvtSeqNum = next256AlignedEvtSn;
mCurrentAlarm.mAlarmCtr = GetLeastSignificantByte(mCurrentAlarm.mExtEvtSeqNum);
IsRegenerationNeeded = false;
WeaveLogDetail(Alarm, "[%d] Advance ExtEvtSN: %u, Ctr: %u", FindClientIndex(), mCurrentAlarm.mExtEvtSeqNum, mCurrentAlarm.mAlarmCtr);
}
}
if (IsRegenerationNeeded)
{
SuccessOrExit(err = RegenerateSessionId());
WeaveLogDetail(Alarm, "[%d] Reset ExtEvtSN: %u, Ctr: %u", FindClientIndex(), mCurrentAlarm.mExtEvtSeqNum, mCurrentAlarm.mAlarmCtr);
}
exit:
WeaveLogFunctError(err);
return err;
}
/**
* @brief
* Determine if the current node generated the alarm associated with
* this `WeaveAlarmClient`.
*
* @return `true` if the current node is the originator of this alarm;
* otherwise `false`.
*/
bool WeaveAlarmClient::IsLocalAlarm(void) const
{
return mIsLocalAlarm;
}
/**
* @brief
* Return the ID of the alarm originator.
*
* @return The Weave node ID of the originator. If this
* `WeaveAlarmClient` is inactive or otherwise malformed, the
* function returns a node ID of 0. If the current node is
* the originator, the function returns the local node ID.
*/
uint64_t WeaveAlarmClient::GetOriginator(void)
{
if (NULL != mEc)
{
if (mEc->PeerNodeId == kAnyNodeId)
{
return mEc->ExchangeMgr->FabricState->LocalNodeId;
}
return mEc->PeerNodeId;
}
else
{
// this is an usage error
return uint64_t(0);
}
}
/**
* @brief
* Get the current alarm message handled by this `WeaveAlarmClient`.
*
* @return The alarm message handled by this `WeaveAlarmClient`.
*/
Alarm WeaveAlarmClient::GetCurrentAlarm(void)
{
return mCurrentAlarm;
}
/**
* @brief
* Get the spoken location (where) of the alarm originator.
*
* @return The ID of the spoken location (where) of the alarm originator
*/
uint8_t WeaveAlarmClient::GetOriginatorWhere(void)
{
return mCurrentAlarm.mWhere;
}
/**
* @brief
* Handle the incoming alarm messages.
*
* Primary entry point for handling the messages.
*
* @param[in] aPktInfo A read-only pointer to the packet info of the
* incoming packet
*
* @param[in] aMsgInfo A read-only pointer to the `WeaveMessageInfo`
* of the incoming packet
*
* @param[in] aPayload A pointer to an `PacketBuffer` containing the
* payload of the incoming message. The
* incoming message has been processed by both
* Weave Message layer and by Weave Exchange
* Layer, so at this point, the payload points
* to an application (profile)-level payload
*
* @param[in] aParsedAlarm A read-only reference to the parsed Alarm
* message.
*/
void WeaveAlarmClient::HandleAlarm(const IPPacketInfo *aPktInfo, const WeaveMessageInfo *aMsgInfo, PacketBuffer *aPayload, const Alarm & aParsedAlarm)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
PacketBuffer *msgBuf = NULL;
bool closeClientOnError = false;
bool isNewAlarm = false;
bool isCounterOrEvtSnEqual = false;
// if the server has an assigned interface but the alarm message
// came in on some unknown interface, drop it.
if (INET_NULL_INTERFACEID != mServer->mInterfaceId)
{
if (aPktInfo->Interface != mServer->mInterfaceId) {
WeaveLogDetail(Alarm, "[%d] unexpected i/f 0x%" PRIx64 ", expecting 0x%" PRIx64, FindClientIndex(),
(uint64_t)aPktInfo->Interface, (uint64_t)mServer->mInterfaceId);
ExitNow(err = WEAVE_ERROR_NO_ENDPOINT);
}
}
if (kAlarmClientState_Closed == mClientState)
{
// this is the first message to create this remote alarm client
WeaveLogDetail(Alarm, "[%d] New remote alarm", FindClientIndex());
mCurrentAlarm.init();
isNewAlarm = true;
}
// we're printing incoming alarm's session id, for they should be the same in most cases,
// and in the case of a new remote alarm, mCurrentAlarm is largely invalid, anyway
if (isNewAlarm)
{
if (aParsedAlarm.mIsSessionIdAndExtEvtSnValid)
{
WeaveLogDetail(Alarm, "[%d] Src:0x%" PRIx64 ", Session:0x%x, incoming evtSN:%u",
FindClientIndex(), aMsgInfo->SourceNodeId,
aParsedAlarm.mSessionId,
aParsedAlarm.mExtEvtSeqNum);
}
else
{
WeaveLogDetail(Alarm, "[%d] Src:0x%" PRIx64 ", incoming ctr:%u",
FindClientIndex(), aMsgInfo->SourceNodeId,
aParsedAlarm.mAlarmCtr);
}
}
else
{
if (aParsedAlarm.mIsSessionIdAndExtEvtSnValid)
{
WeaveLogDetail(Alarm, "[%d] Src:0x%" PRIx64 ", Session:0x%x, evtSN:%u, incoming:%u",
FindClientIndex(), aMsgInfo->SourceNodeId,
aParsedAlarm.mSessionId,
mCurrentAlarm.mExtEvtSeqNum,
aParsedAlarm.mExtEvtSeqNum);
}
else
{
WeaveLogDetail(Alarm, "[%d] Src:0x%" PRIx64 ", ctr:%u, incoming:%u",
FindClientIndex(), aMsgInfo->SourceNodeId,
mCurrentAlarm.mAlarmCtr,
aParsedAlarm.mAlarmCtr);
}
}
isCounterOrEvtSnEqual = ((!mCurrentAlarm.mIsSessionIdAndExtEvtSnValid) && (aParsedAlarm.mAlarmCtr == mCurrentAlarm.mAlarmCtr)) ||
(mCurrentAlarm.mIsSessionIdAndExtEvtSnValid && (aParsedAlarm.mExtEvtSeqNum == mCurrentAlarm.mExtEvtSeqNum));
if ((kAlarmClientState_Active == mClientState) && isCounterOrEvtSnEqual)
{
// only check for duplicates if we're active
// exchange context for a lingering client is not trickling, so there is no need to register duplicates
mEc->HandleTrickleMessage(aPktInfo, aMsgInfo);
}
else if (IsLocalAlarm())
{
// this is a local-originated alarm
// we only use incoming messages to count re-transmissions for trickle throttling, which is handled in previous case
// if this alarm message doesn't carry the same alarm counter, we should just discard it
WeaveLogDetail(Alarm, "[%d] Discard diff counter", FindClientIndex());
}
else if (((kAlarmClientState_Linger == mClientState) || (kAlarmClientState_GracePeriod == mClientState)) &&
isCounterOrEvtSnEqual)
{
// if we're in linger state, don't accept anything that carries the same alarm counter
WeaveLogDetail(Alarm, "[%d] Discard same counter", FindClientIndex());
}
else
{
// from now on, close the client on all errors
// many operations here leads to unrecoverable state, so the only choice is to close everything
closeClientOnError = true;
if (!isNewAlarm)
{
// only check the event sequence number or alarm counter if this is not a new alarm
if (mCurrentAlarm.mIsSessionIdAndExtEvtSnValid)
{
// both alarms are from newer version. We'd never have wrap around issue,
// because a new session id would be generated when that happens
if (aParsedAlarm.mExtEvtSeqNum < mCurrentAlarm.mExtEvtSeqNum)
{
WeaveLogDetail(Alarm, "[%d] stale: %u < %u",
FindClientIndex(),
aParsedAlarm.mExtEvtSeqNum,
mCurrentAlarm.mExtEvtSeqNum);
ExitNow();
}
}
else
{
// okay, both alarms are from older version
// check alarm counter, and allow some space for wrap around
const uint8_t distance = aParsedAlarm.mAlarmCtr - mCurrentAlarm.mAlarmCtr;
if (distance > WEAVE_ALARM_FORWARD_COUNTER_DISTANCE_LIMIT)
{
// this is considered as suspicious and rejected
WeaveLogDetail(Alarm, "[%d] ctr: %u - %u = %u > %u",
FindClientIndex(), aParsedAlarm.mAlarmCtr, mCurrentAlarm.mAlarmCtr,
distance, WEAVE_ALARM_FORWARD_COUNTER_DISTANCE_LIMIT);
ExitNow();
}
}
}
// make a copy of the incoming alarm
mCurrentAlarm = aParsedAlarm;
WeaveLogDetail(Alarm, "[%d] Overwrite", FindClientIndex());
AlarmLogDetail(mCurrentAlarm);
msgBuf = mServer->NewPacketBuffer();
WEAVE_FAULT_INJECT(FaultInjection::kFault_HandleAlarm, if (msgBuf) { PacketBuffer::Free(msgBuf); msgBuf = NULL; });
if (NULL == msgBuf)
{
WeaveLogError(Alarm, "[%d] no inetbuf", FindClientIndex());
ExitNow(err = WEAVE_ERROR_NO_MEMORY);
}
if (aPayload->DataLength() > WEAVE_ALARM_MAX_INCOMING_ALARM_MESSAGE_SIZE)
{
WeaveLogDetail(Alarm, "[%d] msg too long (%d, %d)", FindClientIndex(), aPayload->DataLength(), msgBuf->DataLength());
ExitNow(err = WEAVE_ERROR_MESSAGE_TOO_LONG);
}
memcpy(msgBuf->Start(), aPayload->Start(), aPayload->DataLength());
msgBuf->SetDataLength(aPayload->DataLength());
// make a copy of the Weave message information
WeaveMessageInfo msgInfoCopy(*aMsgInfo);
// note that this setup has to preceed SendMessage, as the settings change made in this function affects the behavior of SendMessage
// note the timing for remote alarms is a little bit different from local alarm
// this is not necessary, but kept so the behavior is as close to V1 as possible
err = mEc->SetupTrickleRetransmit(
mServer->mAlarmRebroadcastPeriodMsec,
mServer->mAlarmRebroadcastThreshold,
mServer->mAlarmRefreshPeriodMsec + mServer->mAlarmRebroadcastPeriodMsec);
SuccessOrExit(err);
// push copy of this incoming message into ec
// Note that the flag kSendFlag_FromInitiator is actually not necessary in this version, because all ExchangeContext instances are
// "initiators". This is left here to mark what it was in the original implementation.
// In the original implementation, the ExchangeContext instances for remote alarm clients are actually "responders", but they use
// this flag to fake their ExchangeHeader so they look like an "initiator" when a re-multicast is sent out.
// They are "responders" because they are generated from Unsolicited Message Handler, and this makes the ExchangeContext matching
// process in ExchangeMgr keep sending messages from other "initiators" into its message handler directly, skipping the UMH.
// In this version, all ExchangeContext instances are "initiators", so ExchangeMgr would always dispatch into UMH.
// This is true even when there are older implementations around, because they anyway pretend to be "initiators" when they send
// messages out.
err = mEc->SendMessage(kWeaveProfile_Alarm, kAlarmMessageType_Alarm, msgBuf,
ExchangeContext::kSendFlag_RetransmissionTrickle | ExchangeContext::kSendFlag_DelaySend |
ExchangeContext::kSendFlag_ReuseMessageId | ExchangeContext::kSendFlag_ReuseSourceId | ExchangeContext::kSendFlag_FromInitiator,
&msgInfoCopy);
msgBuf = NULL;
SuccessOrExit(err);
SetClientState_Active();
}
exit:
WeaveLogFunctError(err);
if (NULL != aPayload)
{
PacketBuffer::Free(aPayload);
aPayload = NULL;
}
if (NULL != msgBuf)
{
PacketBuffer::Free(msgBuf);
msgBuf = NULL;
}
// on error in critical stages, just close the (remote) alarm client
if (closeClientOnError && (WEAVE_NO_ERROR != err))
{
// note that handler for a local alarm client would never set this flag closeClientOnError
SetClientState_Closed();
}
}
/**
* @brief
* Generate 32-bit random number for the use in proximity
* verification code.
*
* @note The application layer is responsible for storing
* this result for later use.
*
* @param[out] aResult A pointer to the output 32-bit random number.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INVALID_ARGUMENT If a NULL pointer was
* passed in for the aResult.
*
* @retval #WEAVE_ERROR_RANDOM_DATA_UNAVAILABLE If the random number
* generator was unable to provide a randomized verification
* code.
*/
WEAVE_ERROR WeaveAlarmClient::GenerateProximityVerificationCode(uint32_t * const aResult)
{
if (aResult != NULL)
{
return Platform::Security::GetSecureRandomData((uint8_t *)aResult, sizeof(*aResult)/sizeof(uint8_t));
}
else
{
return WEAVE_ERROR_INVALID_ARGUMENT;
}
}
/**
* @brief
* Generate 32-bit random number for the use in proximity
* verification code.
*
* @note The application layer is responsible for storing
* this result for later use.
*
* @param[out] aResult A pointer to the output 32-bit random number.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INVALID_ARGUMENT If a NULL pointer was
* passed in for the aResult.
*
* @retval #WEAVE_ERROR_RANDOM_DATA_UNAVAILABLE If the random number
* generator was unable to provide a randomized verification
* code.
*/
WEAVE_ERROR WeaveAlarmClient::GenerateHushChallenge(uint32_t * const aResult)
{
return GenerateProximityVerificationCode(aResult);
}
// Alarm server
/**
* @brief
* Default constructor for `WeaveAlarmServer`.
*
* All parameters that govern the runtime of the alarm protocol are
* initialized to their default values. Pointers to
* `nl::Weave::ExchangeMgr and nl::Weave::FabricState` are initialized
* to NULL. The object is not usable until `WeaveAlarmServer::Init()`
* has been called.
*/
WeaveAlarmServer::WeaveAlarmServer(void)
{
ExchangeMgr = NULL;
FabricState = NULL;
mAlarmRefreshPeriodMsec = WEAVE_ALARM_DEFAULT_REBROADCAST_TIMEOUT_MSEC;
mAlarmRebroadcastPeriodMsec = WEAVE_ALARM_DEFAULT_REBROADCAST_PERIOD_MSEC;
mAlarmRebroadcastThreshold = WEAVE_ALARM_DEFAULT_REBROADCAST_THRESH;
mAlarmGracePeriodMsec = WEAVE_ALARM_DEFAULT_GRACE_PERIOD_MSEC;
}
/**
* @brief
* Given a pointer to the `nl::Weave::WeaveAlarmClient` from this
* server, return its index
*
* @param[in] aClient A read-only pointer to the client object. The
* client object must have been allocated from
* within the client pool of this server object
*
* @returns The index of the client in the client pool.
*/
int WeaveAlarmServer::GetClientIndex(const WeaveAlarmClient * aClient) const
{
return (aClient - mClientPool);
}
void WeaveAlarmServer::OnAlarmClientStateChange(WeaveAlarmClient * const aClient)
{
if (NULL != mAlarmDelegate)
{
mAlarmDelegate->OnAlarmClientStateChange(aClient);
}
}
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
// This gets called when we deallocate the buffer. In our case
// deallocation means two things: setting the outerRef to NULL to
// indicate that the buffer is ready for allocation and decrementing
// the refcount on the outer reference s.t. the reference can be freed
// as needed.
static void TinyPBufFreeFunction(struct pbuf * pbuf)
{
return;
}
WEAVE_ERROR WeaveAlarmServer::InitializeBackingStore(void)
{
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
memset(&mTinyPbufPool, 0, sizeof(mTinyPbufPool));
for (int i = 0; i< MAX_CONCURRENT_MESSAGES; i++)
{
mTinyPbufPool[i].pc.custom_free_function = TinyPBufFreeFunction;
}
#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
return WEAVE_NO_ERROR;
}
void WeaveAlarmServer::ShutdownBackingStore(void)
{
}
PacketBuffer* WeaveAlarmServer::NewPacketBuffer(void)
{
for (int i = 0; i< MAX_CONCURRENT_MESSAGES; i++)
{
// Note that these pbufs are not really accessed by LwIP through multiple threads, for they're only used
// to retain the messages. When these messages need to be sent out, another copy into another pbuf from, device-wide pool,
// is made in UDPEndPoint.
if (mTinyPbufPool[i].pc.pbuf.ref == 0)
{
// the current tiny pbuf is unallocated, initialize and return
struct tiny_custom_pbuf *tinypbuf = &(mTinyPbufPool[i]);
struct pbuf * pbuf = pbuf_alloced_custom(PBUF_RAW,
sizeof(struct tiny_custom_pbuf) - LWIP_MEM_ALIGN_SIZE(sizeof(tiny_custom_pbuf)),
PBUF_POOL,
&(tinypbuf->pc),
&(tinypbuf->payload[0]),
sizeof(tinypbuf->payload));
if (pbuf == NULL)
{
// Should never happen, but when it does fail fast
// rather than retry the other pbufs
goto error;
}
// reserve the space in the payload for Weave headers
tinypbuf->pc.pbuf.payload = ((uint8_t*) tinypbuf->pc.pbuf.payload) + WEAVE_HEADER_RESERVE_SIZE;
// make the pbufs follow the PacketBuffer protocol
tinypbuf->pc.pbuf.len = tinypbuf->pc.pbuf.tot_len = 0;
tinypbuf->pc.pbuf.next = NULL;
//return the value
return (PacketBuffer *) tinypbuf;
}
}
error:
WeaveLogError(Alarm, "alarm-specific pbuf pool exhausted");
return NULL;
}
#else // dummy implementation based on the existing PacketBuffers
PacketBuffer* WeaveAlarmServer::NewPacketBuffer(void)
{
return PacketBuffer::New();
}
WEAVE_ERROR WeaveAlarmServer::InitializeBackingStore(void)
{
return WEAVE_NO_ERROR;
}
void WeaveAlarmServer::ShutdownBackingStore(void)
{
return;
}
#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
/**
* @brief
* Fully initializes the `WeaveAlarmServer` object.
*
* @param[in] aExchangeMgr A pointer to the `WeaveExchangeManager` object
*
* @param[in] aAppState A pointer to the context object that will be
* used with callbacks
*/
WEAVE_ERROR WeaveAlarmServer::Init(WeaveExchangeManager *aExchangeMgr, void *aAppState)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
mAlarmDelegate = NULL;
mCurrentDelegateOp = NULL;
mAppState = aAppState;
if (ExchangeMgr != NULL)
{
return WEAVE_NO_ERROR;
}
err = InitializeBackingStore();
if (err == WEAVE_NO_ERROR)
{
ExchangeMgr = aExchangeMgr;
FabricState = aExchangeMgr->FabricState;
ExchangeMgr->RegisterUnsolicitedMessageHandler(kWeaveProfile_Alarm, UnsolicitedMessageHandler, true, this);
}
for (int i = 0; i< MAX_CONCURRENT_ALERTS; i++)
{
mClientPool[i].initAlarmClient(this);
}
return err;
}
/**
* @brief
* Shutdown the `WeaveAlarmServer`
*
* Closes all the existing `WeaveAlarmClient`s, shuts down any internal
* buffering structures, and informs the `WeaveExchangeManager` to stop
* accepting messages destined for Weave Alarm profile.
*
* @note This method logically terminates the object. After calling
* `Shutdown`, the only permissible calls on the object are:
* - the object destructor
* - `WeaveAlarmServer::Init()` that can be used to reanimate the
* object.
*
* @retval #WEAVE_NO_ERROR unconditionally.
*/
WEAVE_ERROR WeaveAlarmServer::Shutdown(void)
{
for (int i = 0; i < MAX_CONCURRENT_ALERTS; i++)
{
mClientPool[i].Close();
}
if (ExchangeMgr != NULL)
{
ExchangeMgr->UnregisterUnsolicitedMessageHandler(kWeaveProfile_Alarm, kAlarmMessageType_Alarm);
ExchangeMgr = NULL;
}
ShutdownBackingStore();
FabricState = NULL;
return WEAVE_NO_ERROR;
}
/**
* @brief
* A helper method that determines whether there are any
* available `WeaveAlarmClient`s.
*
* @return `true` if a call to allocate a `WeaveAlarmClient` for a remote
* alarm session can succeed immediately; otherwise `false`.
*/
bool WeaveAlarmServer::IsRemoteClientPoolFull(void) const
{
bool result = true;
for (int i = 0; i < MAX_CONCURRENT_ALERTS; i++)
{
if (mClientPool[i].GetClientState() == WeaveAlarmClient::kAlarmClientState_Closed)
{
if (!mClientPool[i].IsLocalAlarm())
{
// return false if there is a closed remote client
result = false;
break;
}
}
}
return result;
}
/**
* @brief
* Find and close an alarm of lesser importance than the given alarm.
*
* The method is used in cases of extreme resource pressure to close
* clients serving the alarms of lesser priority than the new,
* incoming alarm. It uses `AlarmDelegate::CompareSeverity()` to
* compare the alarms to find any alarm that is less important. If
* such an alarm is found, it is closed, and is available for
* subsequent allocation
*
* @param[in] aSrcNodeId Node ID of the new alarm originator.
*
* @param[in] aAlarm A read-only reference to the alarm message
* containing the new alarm.
*
* @retval #WEAVE_NO_ERROR If an alarm of smaller severity was found and closed.
*
* @retval #WEAVE_ERROR_NO_MEMORY If the new alarm is of smaller
* importance than anything currently serviced.
*/
WEAVE_ERROR WeaveAlarmServer::CloseLessSevereAlarmClient(uint64_t aSrcNodeId, const Alarm & aAlarm)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveAlarmClient * client = NULL;
VerifyOrExit(NULL != mAlarmDelegate, err = WEAVE_ERROR_INCORRECT_STATE);
// pass 1: find less severe client to close
for (int i = 0; i < MAX_CONCURRENT_ALERTS; i++)
{
if (mClientPool[i].IsLocalAlarm())
{
// skip all local alarms
continue;
}
if (mAlarmDelegate->CompareSeverity (mClientPool[i].mCurrentAlarm, aAlarm) < 0)
{
// we found one client which is less severe than the incoming one, close it
// application layer would be noticed before this function returns
WeaveLogProgress(Alarm, "[%d] Closing less severe alarm from 0x%" PRIx64, i, mClientPool[i].GetOriginator());
client = &mClientPool[i];
ExitNow();
}
}
// pass 2: find lowest src node id in all equally severe clients to close
for (int i = 0; i < MAX_CONCURRENT_ALERTS; i++)
{
if (mClientPool[i].IsLocalAlarm())
{
// skip all local alarms
continue;
}
if (mClientPool[i].GetOriginator() <= aSrcNodeId)
{
// skip all remote alarms that has equal or less src node id
continue;
}
if (0 == mAlarmDelegate->CompareSeverity (mClientPool[i].mCurrentAlarm, aAlarm))
{
// we're targeting at the remote client that is
// 1) equally severe, and
// 2) has the largest src node id
if (NULL == client)
{
client = &mClientPool[i];
}
else
{
if (client->GetOriginator() < mClientPool[i].GetOriginator())
{
client = &mClientPool[i];
}
}
}
}
if (NULL != client)
{
WeaveLogDetail(Alarm, "[%d] Closing equally severe alarm from 0x%" PRIx64, GetClientIndex(client), client->GetOriginator());
ExitNow();
}
else
{
// note that we've filtered out all less severe remote clients,
// and searched for all equally severe remote clients
// so if client is still NULL, it means the incoming remote alarm is
// 1) not very important, or
// 2) has a larger node id
ExitNow(err = WEAVE_ERROR_NO_MEMORY);
}
exit:
WeaveLogFunctError(err);
if (NULL != client)
{
client->SetClientState_Closed();
}
return err;
}
/**
* @brief
* Allocate and initialize a `WeaveAlarmClient` given an exchange
* context.
*
* The method finds an unused `WeaveAlarmClient` (client in a Closed
* state), and allocates it with the given `ExchangeContext`. If no
* unused clients can be found, the `ExchangeContext` is closed.
*
* @param[in] aEc A pointer to an `ExchangeContext` to
* associate with the `WeaveAlarmClient`.
*
* @param[in] aEncryptionType Encryption type to use on the
* `ExchangeContext`.
*
* @param[in] aKeyId ID of the security key to use with the
* `ExchangeContext`
*
* @param[in] aRemoteOnly Disallow the allocation algorithm to
* use `WeaveAlarmClient` that have been
* marked for local use.
*
* @return A pointer to the `WeaveAlarmClient` on success; otherwise,
* NULL on failure.
*/
WeaveAlarmClient *WeaveAlarmServer::AllocAlarmClientUsingExchangeContext(
ExchangeContext *aEc, uint8_t aEncryptionType, uint16_t aKeyId, bool aRemoteOnly)
{
WeaveAlarmClient * client = NULL;
for (int i = 0; i < MAX_CONCURRENT_ALERTS; i++)
{
if (mClientPool[i].GetClientState() == WeaveAlarmClient::kAlarmClientState_Closed)
{
client = &mClientPool[i];
if (aRemoteOnly)
{
// skip any client which claims it's a local alarm
if (client->IsLocalAlarm())
{
WeaveLogError(Alarm, "[%d] ERROR: Local alarm is in CLOSED state", i);
continue;
}
}
client->mEc = aEc;
aEc->EncryptionType = aEncryptionType;
aEc->KeyId = aKeyId;
// enforce multicast address irrespective of any address the incoming message may imply
aEc->PeerAddr = ALARM_MULTICAST_ADDR;
// enable duplicate messages on this exchange
aEc->AllowDuplicateMsgs = true;
aEc->OnRetransmissionTimeout = WeaveAlarmClient::HandleRetransmissionTimeout;
aEc->AppState = client;
break;
}
}
// close ec on allocation failure (otherwise it should already be held by the alarm client
if (NULL == client)
{
aEc->Close();
aEc = NULL;
}
return client;
}
/**
* @brief
* Allocates and initializes a `WeaveAlarmClient` from the pool.
*
* @param[in] aPeer Node ID of the peer that will receive
* messages in this exchange. In the
* typical alarm usage, where the alarm is
* disseminated to all nodes in the
* network, the peer will be #kAnyNodeId.
*
* @param[in] aEncryptionType Encryption type to use for this client.
*
* @param[in] aKeyId Key to use with this client.
*
* @return On success, a pointer to the allocated and initialized
* `WeaveAlarmClient`; otherwise, NULL if the allocation of
* either the `WeaveAlarmClient` or its `ExchangeContext`
* failed.
*/
WeaveAlarmClient * WeaveAlarmServer::NewClient(uint64_t aPeer, uint8_t aEncryptionType, uint16_t aKeyId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveAlarmClient *client = NULL;
ExchangeContext *ec = ExchangeMgr->NewContext(aPeer);
VerifyOrExit(NULL != ec, err = WEAVE_ERROR_NO_MEMORY);
// ec is either owned by the alarm client on success allocation, or closed on allocation failure
client = AllocAlarmClientUsingExchangeContext(ec, aEncryptionType, aKeyId, false);
VerifyOrExit(NULL != client, err = WEAVE_ERROR_NO_MEMORY);
client->mIsLocalAlarm = true;
// occupy this client slot (so it doesn't show as "closed" before application layer call "SendAlarm"
// also enforce session ID check when any incoming message got accidentally delivered into that exchange context
client->ForceClientStateChangeNoError(WeaveAlarmClient::kAlarmClientState_Linger);
// create a new session every time a local client is created, mostly at boot up
client->RegenerateSessionId();
exit:
WeaveLogFunctError(err);
return client;
}
/**
* @brief
* Dispatch the incoming alarm message to an appropriate
* `WeaveAlarmClient`.
*
* The function parses out the incoming `PacketBuffer` into an `Alarm`
* message. If the message was parsed successfully, and passed a
* number of other checks (such as coming in on the designated
* interface), the function proceeds to find an appropriate
* `WeaveAlarmClient`. First, the function checks whether the incoming
* alarm message already belongs to an existing `WeaveAlarmClient`,
* based on a session ID and source node ID. If no matching client
* has been found, the method attempts to allocate a new
* `WeaveAlarmClient`. If no such client has been found, the method
* proceeds to discard the least severe alarm from the list; if the
* incoming alarm has a smaller severity than the existing alarms, the
* processing stops, and the incoming alarm is discarded. Otherwise,
* the method has obtained a valid `WeaveAlarmClient`, and uses it to
* dispatch the incoming alarm message.
*
* @param[in] aPktInfo A read-only pointer to a `IPPacketInfo`
* structure containing the interface used to
* receive the packet.
*
* @param[in] aMsgInfo A read-only pointer to a `WeaveMessageInfo`
* containing the information about the incoming
* packet.
* @param[in] aPayload A pointer to a `PacketBuffer` containing the
* alarm payload.
*/
void WeaveAlarmServer::DispatchAlarmMessage(const IPPacketInfo *aPktInfo, const WeaveMessageInfo *aMsgInfo, PacketBuffer *aPayload)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
bool parseOk = false;
Alarm parsedAlarm;
SuccessOrExit(err = Alarm::parse(aPayload, &parsedAlarm));
parseOk = true;
if (parsedAlarm.mIsSessionIdAndExtEvtSnValid)
{
WeaveLogProgress(Alarm, "Incoming Src:0x%" PRIx64 ", session:0x%x, evtSN:%u",
aMsgInfo->SourceNodeId,
parsedAlarm.mSessionId,
parsedAlarm.mExtEvtSeqNum);
}
else
{
WeaveLogProgress(Alarm, "Incoming Src:0x%" PRIx64 ", ctr:%u",
aMsgInfo->SourceNodeId,
parsedAlarm.mAlarmCtr);
}
// if the server has an assigned interface but the alarm message
// came in on some unknown interface, drop it.
if (INET_NULL_INTERFACEID != mInterfaceId)
{
if (aPktInfo->Interface != mInterfaceId)
{
// Note that InterfaceId is typdef to be a pointer with LwIP, but an unsigned int otherwise.
// Casting it to uint64_t should get us reasonable results on all platforms.
WeaveLogError(Alarm, "Reject: unexpected i/f 0x%" PRIx64 ", expecting 0x%" PRIx64, (uint64_t)aPktInfo->Interface, (uint64_t)mInterfaceId);
ExitNow(err = WEAVE_ERROR_NO_ENDPOINT);
}
}
#if WEAVE_ALARM_DETAIL_LOGGING && WEAVE_DETAIL_LOGGING
for (int i = 0; i < MAX_CONCURRENT_ALERTS; i++)
{
WeaveAlarmClient &client = mClientPool[i];
if (client.GetClientState() == WeaveAlarmClient::kAlarmClientState_Closed)
{
WeaveLogDetail(Alarm, "[%d] [%6s]", i, WeaveAlarmClient::GetClientStateName(client.GetClientState()));
}
else
{
if (client.mCurrentAlarm.mIsSessionIdAndExtEvtSnValid)
{
WeaveLogDetail(Alarm, "[%d] [%6s] Src:0x%" PRIx64 ", Session:0x%" PRIx32 ", evtSN:%u",
i,
WeaveAlarmClient::GetClientStateName(client.GetClientState()),
client.GetOriginator(),
client.mCurrentAlarm.mSessionId,
client.mCurrentAlarm.mExtEvtSeqNum);
}
else
{
WeaveLogDetail(Alarm, "[%d] [%6s] Src:0x%" PRIx64 ", ctr:%u",
i,
WeaveAlarmClient::GetClientStateName(client.GetClientState()),
client.GetOriginator(),
client.mCurrentAlarm.mAlarmCtr);
}
}
}
#endif // WEAVE_DETAIL_LOGGING
// find if we already have a client handling alarm messages from
// this source and session ID. This occurs when we overhear one of our neighbors
// rebroadcasting our own message; the ExchangeContext matching
// logic in WeaveExchangeMgr will not pass the message into the
// ExchangeContext associated with ourselves, because the
// Initiator bit is set and that might indicate that someone else
// is initiating an exchange. We perform the check across the
// board to further enforce the notion that all the alarms
// originating at a single node and carry the same session ID should be handled within the same
// WeaveAlarmClient.
for (int i = 0; i < MAX_CONCURRENT_ALERTS; i++)
{
WeaveAlarmClient &client = mClientPool[i];
// skip entries that are already closed
// skip entries that have different node ID
// skip entries that have different session ID settings (this is pretty suspicious)
// (if I have one) skip any entry that doesn't have a matching session id with mine
if ((WeaveAlarmClient::kAlarmClientState_Closed != client.GetClientState()) &&
(client.GetOriginator() == aMsgInfo->SourceNodeId) &&
(parsedAlarm.mIsSessionIdAndExtEvtSnValid == client.mCurrentAlarm.mIsSessionIdAndExtEvtSnValid) &&
((!parsedAlarm.mIsSessionIdAndExtEvtSnValid) || (parsedAlarm.mIsSessionIdAndExtEvtSnValid && (client.mCurrentAlarm.mSessionId == parsedAlarm.mSessionId))))
{
// dispatch to an existing alarm cient, with all the information we have at hand.
// there is no need to pass profile id and message type, as we can handle one combination.
// we still need to pass payload, as it's possible the client needs to update its retransmission cache
client.HandleAlarm(aPktInfo, aMsgInfo, aPayload, parsedAlarm);
// payload should have been freed in handler
aPayload = NULL;
ExitNow();
}
}
// no match is found, is this one from our own past lives?
if (FabricState->LocalNodeId == aMsgInfo->SourceNodeId)
{
// so, we have an incoming message from ourselves, but couldn't find a match, probably because we have retired that session
// discard the message
WeaveLogDetail(Alarm, "Drop: no active client");
WeaveLogDetail(Alarm, "Src:0x%" PRIx64 ", Session:0x%x, ctr:%u, evtSN:%u",
aMsgInfo->SourceNodeId,
parsedAlarm.mIsSessionIdAndExtEvtSnValid ? parsedAlarm.mSessionId : 0,
parsedAlarm.mAlarmCtr,
parsedAlarm.mExtEvtSeqNum);
// payload will be freed when we exit
ExitNow ();
}
{
// We didn't find an existing client, and it's not an echo, so it's time to create a new client
WeaveLogProgress(Alarm, "Creating client");
// first step, make sure we have some space for this new comer
if (IsRemoteClientPoolFull())
{
// okay, the pool for remote clients is full
// let's try to make some room
// if we fail at here, we'd still close the payload and notify application layer when we exit
SuccessOrExit(err = CloseLessSevereAlarmClient(aMsgInfo->SourceNodeId, parsedAlarm));
}
// create an exchange context, note that appstate is NULL initially, as we haven't had the client yet
// note that a new exchange context has the "initiator" bit set by default, so no re-multicasts would be mapped into one by default
ExchangeContext *newExchangeContext = ExchangeMgr->NewContext(aMsgInfo->SourceNodeId, aPktInfo->SrcAddress, aPktInfo->SrcPort, aPktInfo->Interface);
VerifyOrExit (NULL != newExchangeContext, err = WEAVE_ERROR_NO_MEMORY);
// newExchangeContext is either owned by the alarm client on success allocation, or closed on allocation failure
WeaveAlarmClient *client = AllocAlarmClientUsingExchangeContext(newExchangeContext,
aMsgInfo->EncryptionType, aMsgInfo->KeyId, true);
VerifyOrExit(NULL != client, err = WEAVE_ERROR_NO_MEMORY);
client->HandleAlarm(aPktInfo, aMsgInfo, aPayload, parsedAlarm);
// payload should have been freed in handler
aPayload = NULL;
}
exit:
WeaveLogFunctError(err);
if (NULL != aPayload)
{
PacketBuffer::Free(aPayload);
aPayload = NULL;
}
if (WEAVE_NO_ERROR != err)
{
// oops! we couldn't handle this alarm message
WeaveLogError(Alarm, "Drop msg");
AlarmLogDetail(parsedAlarm);
if (parseOk && (NULL != mAlarmDelegate))
{
mAlarmDelegate->OnNewRemoteAlarmDropped(parsedAlarm);
}
}
}
/**
* @brief
* Assign the `AlarmDelegate` to the `WeaveAlarmServer`.
*
* @param[in] aDelegate Application-layer delegate for handling alarm
* semantics. If a NULL is passed, the existing
* delegate (if any) will be removed, and
* further forwarding of alarm state to the
* application layer will be suspended, but the
* `WeaveAlarmServer` will continue to receive
* and process messages according to the alarm
* protocol specifications.
*/
void WeaveAlarmServer::SetAlarmDelegate(AlarmDelegate * aDelegate)
{
mAlarmDelegate = aDelegate;
}
/**
* @brief
* Pack and send an `AlarmHushResponse` with the given
* information. Current operation is considered closed after this
* call
*
* @param[in] hushResult A status code to be returned in this
* `AlarmHushResponse` message (check the
* kAlarmHushResult_XXX family of constants)
*
* @param[in] length The number of valid entries in alarmCondition
*
* @param[in] conditions The pointer to the array of alarm conditions.
* When the length parameter is zero, this
* pointer may be NULL
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE If there is no active hush
* request.
*
* @retval #WEAVE_ERROR_NO_MEMORY If the system was unable to
* allocate memory for the hush response.
*
* @retval #WEAVE_ERROR Error codes returned by `SendMessage`.
*/
WEAVE_ERROR WeaveAlarmServer::SendHushResponse(uint8_t aHshResult, uint8_t aLength, uint8_t *aConditions)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
PacketBuffer *msgBuf = NULL;
AlarmHushResponse alarmHushResponse;
VerifyOrExit(mCurrentDelegateOp != NULL, err = WEAVE_ERROR_INCORRECT_STATE);
err = alarmHushResponse.init(aHshResult, aLength, aConditions);
SuccessOrExit(err);
// allocate buffer and then encode the response into it
msgBuf = PacketBuffer::New();
VerifyOrExit(msgBuf != NULL, err = WEAVE_ERROR_NO_MEMORY);
err = alarmHushResponse.pack(msgBuf);
SuccessOrExit(err);
// send out the response
err = mCurrentDelegateOp->SendMessage(kWeaveProfile_Alarm, kAlarmMessageType_AlarmHushResponse, msgBuf);
msgBuf = NULL;
SuccessOrExit(err);
exit:
if (msgBuf != NULL)
{
PacketBuffer::Free(msgBuf);
msgBuf = NULL;
}
if (mCurrentDelegateOp != NULL)
{
mCurrentDelegateOp->Close();
mCurrentDelegateOp = NULL;
}
return err;
}
/**
* @brief
* Pack and send a StatusUpdate with the given information. Current
* operation is considered closed after this call
*
* @param[in] statusProfileId A profile id for the status code.
*
* @param[in] statusCode A profile-specific status code.
*
* @param[in] sysError Extra error information, if needed.
*
* @retval #WEAVE_NO_ERROR On success.
*
* @retval #WEAVE_ERROR_INCORRECT_STATE If there is no active hush
* request.
*
* @retval #WEAVE_ERROR Error codes produced by `SendStatusReport()`
*/
WEAVE_ERROR WeaveAlarmServer::SendStatusReport(uint32_t aStatusProfileId, uint16_t aStatusCode, WEAVE_ERROR aSysError)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
VerifyOrExit(mCurrentDelegateOp != NULL, err = WEAVE_ERROR_INCORRECT_STATE);
err = nl::Weave::WeaveServerBase::SendStatusReport(mCurrentDelegateOp, aStatusProfileId, aStatusCode, aSysError);
SuccessOrExit(err);
exit:
if (mCurrentDelegateOp != NULL)
{
mCurrentDelegateOp->Close();
mCurrentDelegateOp = NULL;
}
return err;
}
/**
* @brief
* Handle unsolicited alarm messages
*
* Alarm messages are handled differently than other message types: we
* always close the incoming `ExchangeContext` and then create a new
* one for new clients. As a result, we receive all `Alarm` messages
* through this `UnsolicitedMessageHandler`. The primary reason for
* this design is the session ID mechanism: it can invalidate the
* dispatching decision made in `WeaveExchangeManager`, so it is
* better to do our own dispatch every time. The Alarm message
* processing flows from this method, through
* `WeaveAlarmServer::DispatchAlarmMessage()` to
* `WeaveAlarmClient::HandleAlarm()`.
*
* This method also handles Hush request processing; the handling of
* the Hush messages is much closer to the canonical Weave message
* dispatch.
*
* @param[in] aEc A pointer to an `ExchangeContext`
* instance created to handle the incoming
* message
*
* @param[in] aPktInfo A read-only pointer to a `IPPacketInfo`
* structure containing the interface used to
* receive the packet.
*
* @param[in] aMsgInfo A read-only pointer to a `WeaveMessageInfo`
* containing the information about the incoming
* packet.
*
* @param[in] aProfileId Profile ID extracted from the incoming
* message (WeaveExchange layer header)
*
* @param[in] aMsgType Message type extracted from the incoming
* message (WeaveExchange layer header)
*
* @param[in] aPayload A pointer to a `PacketBuffer` containing the
* payload of one of the messages from Weave Alarm.
*/
void WeaveAlarmServer::UnsolicitedMessageHandler(ExchangeContext *aEc, const IPPacketInfo *aPktInfo,
const WeaveMessageInfo *aMsgInfo, uint32_t aProfileId, uint8_t aMsgType, PacketBuffer *aPayload)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveAlarmServer *server = NULL;
bool newOpCreated = false;
VerifyOrExit(NULL != aEc, err = WEAVE_ERROR_INCORRECT_STATE);
VerifyOrExit(NULL != aEc->AppState, err = WEAVE_ERROR_INCORRECT_STATE);
VerifyOrExit(kWeaveProfile_Alarm == aProfileId, err = WEAVE_ERROR_INCORRECT_STATE);
server = (WeaveAlarmServer *) aEc->AppState;
// Alarm messages are handled differently than other message types
// because they do not need to invoke the alarm delegate, nor use the stored
// exchange context for delegated operations
if (kAlarmMessageType_Alarm == aMsgType)
{
// always close the incoming Exchange Context and then create a new one for new clients, so we always
// receive Alarm message through this UnsolicitedMessageHandler. The reason is the added session ID mechanism
// invalidates the dispatching decision made in Exchange Manager, so we're better off doing the dispatching on
// our own every time.
// potentially speaking we can just flip the initiator bit in this ec instead of closing it, but closing it simplifies
// the code paths for different situations and doesn't consume much more resource.
aEc->Close();
aEc = NULL;
server->DispatchAlarmMessage(aPktInfo, aMsgInfo, aPayload);
// payload should have been freed as part of alarm processing, so let's forget about it
aPayload = NULL;
ExitNow();
}
// Disallow simultaneous requests
if (NULL != server->mCurrentDelegateOp)
{
err = WeaveServerBase::SendStatusReport(aEc, kWeaveProfile_Common, Common::kStatus_Busy, WEAVE_NO_ERROR);
ExitNow();
}
// make sure we have a delegate to handle these requests
if (NULL == server->mAlarmDelegate)
{
err = WeaveServerBase::SendStatusReport(aEc, kWeaveProfile_Common, Common::kStatus_UnexpectedMessage, WEAVE_NO_ERROR);
ExitNow();
}
// transfer ownership of the ec to the current delegate operation
// any thing goes wrong from here, the (newly gained) operation would be closed
server->mCurrentDelegateOp = aEc;
aEc = NULL;
newOpCreated = true;
// Decode and dispatch the message.
switch (aMsgType)
{
case kAlarmMessageType_AlarmHushRequest:
err = server->HandleHushRequest(aPayload);
// payload should have been freed during processing, so let's forget about it
aPayload = NULL;
break;
default:
WeaveLogError(Alarm, "unsupported msg");
err = server->SendStatusReport(kWeaveProfile_Common, Common::kStatus_UnsupportedMessage);
}
exit:
WeaveLogFunctError(err);
if (NULL != aEc)
{
aEc->Close();
aEc = NULL;
}
if (aPayload != NULL)
{
PacketBuffer::Free(aPayload);
aPayload = NULL;
}
// close the current operation if there is any error
if ((NULL != server) && (WEAVE_NO_ERROR != err))
{
if (newOpCreated && (NULL != server->mCurrentDelegateOp))
{
server->mCurrentDelegateOp->Close();
server->mCurrentDelegateOp = NULL;
}
}
}
/**
* @brief
* Process Hush request.
*
* Parses the hush request out of the incoming `PacketBuffer`, and
* dispatches it to the `AlarmDelegate` for further processing. If the
* message could not be parsed, a status report is sent to the peer
* with a code #Common::kStatus_BadRequest.
*
* @param aPayload A pointer to an `PacketBuffer` containing the payload.
*
* @return #WEAVE_NO_ERROR on success; otherwise, on failure, return
* the error code from the application layer processing
*/
WEAVE_ERROR WeaveAlarmServer::HandleHushRequest(PacketBuffer *aPayload)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
AlarmHushRequest hushRequest;
err = AlarmHushRequest::parse(aPayload, &hushRequest);
VerifyOrExit(WEAVE_NO_ERROR == err, SendStatusReport(kWeaveProfile_Common, Common::kStatus_BadRequest));
err = mAlarmDelegate->OnHushRequest(mCurrentDelegateOp, hushRequest.mProximityVerificationCode, hushRequest.mSignature);
SuccessOrExit(err);
exit:
WeaveLogFunctError(err);
if (aPayload != NULL)
{
PacketBuffer::Free(aPayload);
aPayload = NULL;
}
return err;
}
/**
* @brief
* Default constructor for helper class for iterating through
* `WeaveAlarmClient`s.
*
* @param[in] aServer A read-only pointer to a `WeaveAlarmServer`
* object that owns the client pool that will be
* iterated over
*/
WeaveAlarmClientIterator::WeaveAlarmClientIterator(WeaveAlarmServer *aServer)
{
mServer = aServer;
// when we initialize, the "current" is undefined. User of this object must call "next" to get the head object
mIndex = -1;
}
/**
* @brief
* Iterator method, determine whether the iterator has a next element.
*
* @return `true` if there are more elements in the iterator; otherwise `false`.
*/
bool WeaveAlarmClientIterator::hasNext(void)
{
return ((mIndex + 1) < MAX_CONCURRENT_ALERTS);
}
/**
* @brief
* Fetch the next item in the iterator.
*
* @note This method performs no bounds checking. Instead, it assumes
* that the user of the method has already ascertained that the
* iterator will return a valid pointer by calling
* `WeaveAlarmClientIterator::hasNext()`.
*
* @return Next element in the iterator, unconditionally.
*/
WeaveAlarmClient * WeaveAlarmClientIterator::next(void)
{
mIndex++;
return &(mServer->mClientPool[mIndex]);
}
}
}
}