blob: 067a1e2ffd2dda7ef6c861e3f9f26bb47910a8b1 [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file implements the Device Description profile, containing
* data semantics and methods to specify and query device specific
* characteristics that pertain to Weave.
*
* The Device Description profile is used to communicate device
* specific characteristics between Weave nodes. This information
* is communicated via the IdentifyRequest and IdentifyResponse
* message types, the former used to query for devices matching a
* filter, and the latter used to respond with a payload detailing
* some or all of the device specific characteristics. Such
* characteristics include the device vendor, make and model, as
* well as network information including MAC addresses and connections.
*/
#include <string.h>
#include <ctype.h>
#include <Weave/Core/WeaveCore.h>
#include "DeviceDescription.h"
#include <Weave/Core/WeaveEncoding.h>
#include <Weave/Core/WeaveTLV.h>
#include <Weave/Core/WeaveVendorIdentifiers.hpp>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Support/SerialNumberUtils.h>
using namespace nl::Weave::TLV;
using namespace nl::Weave::Profiles;
using namespace nl::Weave::Encoding;
namespace nl {
namespace Weave {
namespace Profiles {
namespace DeviceDescription {
enum
{
kTextKey_VendorId = 'V', // [ 1-4 hex digits ] Code identifying product vendor.
kTextKey_ProductId = 'P', // [ 1-4 hex digits ] Code identifying product.
kTextKey_ProductRevision = 'R', // [ 1-4 hex digits ] Code identifying product revision.
kTextKey_ManufacturingDate = 'D', // [ 4 or 6 decimal digits ] Calendar date of manufacture in YYMM or YYMMDD form.
kTextKey_SerialNumber = 'S', // [ 1-32 char string ] Device serial number.
kTextKey_DeviceId = 'E', // [ 8 hex digits ] Weave Device Id / device unique id.
kTextKey_Primary802154MACAddress = 'L', // [ 8 hex digits ] MAC address for device's primary 802.15.4 interface.
kTextKey_PrimaryWiFiMACAddress = 'W', // [ 6 hex digits ] MAC address for device's primary WiFi interface.
kTextKey_RendezvousWiFiESSID = 'I', // [ 1-32 char string ] ESSID for device's WiFi rendezvous network.
kTextKey_PairingCode = 'C', // [ 1-16 char string ] The pairing code for the device.
kTextKey_PairingCompatibilityVersionMajor = 'J', // [ 1-4 hex digits ] Pairing software compatibility major version.
kTextKey_PairingCompatibilityVersionMinor = 'N', // [ 1-4 hex digits ] Pairing software compatibility minor version.
kEncodingVersion = '1',
kKeySeparator = ':',
kValueTerminator = '$'
};
class TextDescriptorWriter
{
public:
TextDescriptorWriter(char *buf, uint32_t bufSize)
{
mBuf = mWritePoint = buf;
mBufEnd = buf + bufSize;
}
WEAVE_ERROR WriteHex(char fieldId, uint16_t val)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint16_t len;
if (val < 0x0010)
len = 1;
else if (val < 0x0100)
len = 2;
else if (val < 0x1000)
len = 3;
else
len = 4;
VerifyOrExit(mWritePoint + len + 3 < mBufEnd, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
*mWritePoint++ = fieldId;
*mWritePoint++ = kKeySeparator;
if (len == 4)
*mWritePoint++ = HexDigit((val >> 12) & 0xF);
if (len >= 3)
*mWritePoint++ = HexDigit((val >> 8) & 0xF);
if (len >= 2)
*mWritePoint++ = HexDigit((val >> 4) & 0xF);
*mWritePoint++ = HexDigit(val & 0xF);
*mWritePoint++ = kValueTerminator;
exit:
return err;
}
WEAVE_ERROR WriteHex(char fieldId, const uint8_t *val, uint32_t valLen)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
VerifyOrExit(mWritePoint + valLen + 3 < mBufEnd, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
*mWritePoint++ = fieldId;
*mWritePoint++ = kKeySeparator;
for (; valLen > 0; val++, valLen--)
{
*mWritePoint++ = HexDigit(*val >> 4);
*mWritePoint++ = HexDigit(*val & 0xF);
}
*mWritePoint++ = kValueTerminator;
exit:
return err;
}
WEAVE_ERROR WriteString(char fieldId, const char *val)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint32_t len;
len = strlen(val);
VerifyOrExit(mWritePoint + len + 3 < mBufEnd, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
VerifyOrExit(strchr(val, '$') == NULL, err = WEAVE_ERROR_INVALID_ARGUMENT);
*mWritePoint++ = fieldId;
*mWritePoint++ = kKeySeparator;
memcpy(mWritePoint, val, len);
mWritePoint += len;
*mWritePoint++ = kValueTerminator;
exit:
return err;
}
WEAVE_ERROR WriteDate(char fieldId, uint16_t year, uint8_t month, uint8_t day)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint16_t len;
len = (day != 0) ? 9 : 7;
VerifyOrExit(mWritePoint + len < mBufEnd, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
*mWritePoint++ = fieldId;
*mWritePoint++ = kKeySeparator;
year -= 2000;
VerifyOrExit(year < 100, err = WEAVE_ERROR_INVALID_ARGUMENT);
VerifyOrExit(month >= 1 && month <= 12, err = WEAVE_ERROR_INVALID_ARGUMENT);
VerifyOrExit(day <= 31, err = WEAVE_ERROR_INVALID_ARGUMENT);
*mWritePoint++ = '0' + (year / 10);
*mWritePoint++ = '0' + (year % 10);
*mWritePoint++ = '0' + (month / 10);
*mWritePoint++ = '0' + (month % 10);
if (day != 0)
{
*mWritePoint++ = '0' + (day / 10);
*mWritePoint++ = '0' + (day % 10);
}
*mWritePoint++ = kValueTerminator;
exit:
return err;
}
WEAVE_ERROR WriteVersion()
{
if (mWritePoint + 1 > mBufEnd)
return WEAVE_ERROR_INVALID_ARGUMENT;
*mWritePoint++ = kEncodingVersion;
return WEAVE_NO_ERROR;
}
WEAVE_ERROR Finalize()
{
if (mWritePoint + 1 > mBufEnd)
return WEAVE_ERROR_INVALID_ARGUMENT;
*mWritePoint = 0;
return WEAVE_NO_ERROR;
}
uint32_t GetLengthWritten()
{
return mWritePoint - mBuf;
}
private:
char *mBuf;
char *mWritePoint;
char *mBufEnd;
char HexDigit(uint8_t val)
{
return (val < 10) ? '0' + val : 'A' + (val - 10);
}
};
class TextDescriptorReader
{
public:
TextDescriptorReader(const char *val, uint32_t valLen)
{
mVal = val;
mValEnd = val + valLen;
mReadPoint = val;
while (mReadPoint < mValEnd && isspace(*mReadPoint))
mReadPoint++;
mFieldEnd = val;
Version = (mReadPoint < mValEnd) ? *mReadPoint : 0;
Key = 0;
}
char Version;
char Key;
WEAVE_ERROR Next()
{
mReadPoint = mFieldEnd + 1;
while (mReadPoint < mValEnd && isspace(*mReadPoint))
mReadPoint++;
if (mReadPoint >= mValEnd)
{
Key = 0;
return WEAVE_END_OF_INPUT;
}
mFieldEnd = (const char *)memchr(mReadPoint, '$', mValEnd - mReadPoint);
if (mFieldEnd == NULL)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
Key = *mReadPoint;
return WEAVE_NO_ERROR;
}
WEAVE_ERROR ReadString(char *buf, uint32_t bufSize)
{
if (Key == 0)
return WEAVE_ERROR_INCORRECT_STATE;
uint32_t len = mFieldEnd - mReadPoint - 2;
if (len > bufSize - 1)
return WEAVE_ERROR_BUFFER_TOO_SMALL;
memcpy(buf, mReadPoint + 2, len);
buf[len] = 0;
return WEAVE_NO_ERROR;
}
WEAVE_ERROR ReadHex(uint16_t& val)
{
val = 0;
if (Key == 0)
return WEAVE_ERROR_INCORRECT_STATE;
const char *p = mReadPoint + 2;
if (p >= mFieldEnd)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
for (; p < mFieldEnd; p++)
{
int8_t digitVal = HexDigitValue(*p);
if (digitVal < 0)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
val = (val << 4) | digitVal;
}
return WEAVE_NO_ERROR;
}
WEAVE_ERROR ReadHex(uint8_t *buf, uint32_t bufLen)
{
if (Key == 0)
return WEAVE_ERROR_INCORRECT_STATE;
uint32_t len = mFieldEnd - mReadPoint - 2;
if ((len & 1) != 0 || len / 2 != bufLen)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
for (const char *p = mReadPoint + 2; p < mFieldEnd; p += 2, buf++)
{
int8_t highDigitVal = HexDigitValue(p[0]);
int8_t lowDigitVal = HexDigitValue(p[1]);
if (highDigitVal < 0 || lowDigitVal < 0)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
*buf = (uint8_t)((highDigitVal << 4) | lowDigitVal);
}
return WEAVE_NO_ERROR;
}
WEAVE_ERROR ReadDate(uint16_t& year, uint8_t& month, uint8_t& day)
{
if (Key == 0)
return WEAVE_ERROR_INCORRECT_STATE;
uint32_t len = mFieldEnd - mReadPoint - 2;
if (len != 4 && len != 6)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
int8_t v = DecimalDigitPairValue(mReadPoint[2], mReadPoint[3]);
if (v < 0)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
year = 2000 + v;
v = DecimalDigitPairValue(mReadPoint[4], mReadPoint[5]);
if (v < 1 || v > 12)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
month = v;
if (len == 6)
{
v = DecimalDigitPairValue(mReadPoint[6], mReadPoint[7]);
if (v < 1 || v > 31)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
day = v;
}
else
day = 0;
return WEAVE_NO_ERROR;
}
private:
const char *mVal;
const char *mValEnd;
const char *mReadPoint;
const char *mFieldEnd;
inline int8_t HexDigitValue(char digit)
{
if (digit >= '0' && digit <= '9')
return digit - '0';
if (digit >= 'A' && digit <= 'F')
return (digit - 'A') + 10;
if (digit >= 'a' && digit <= 'f')
return (digit - 'a') + 10;
return -1;
}
inline int8_t DecimalDigitPairValue(char digit1, char digit2)
{
if (digit1 < '0' || digit1 > '9' || digit2 < '0' || digit2 > '9')
return -1;
return (digit1 - '0') * 10 + (digit2 - '0');
}
};
WeaveDeviceDescriptor::WeaveDeviceDescriptor()
{
memset(this, 0, sizeof(*this));
}
/**
* Clears the device description
*/
void WeaveDeviceDescriptor::Clear()
{
memset(this, 0, sizeof(*this));
}
/**
* Encodes the provided device descriptor as text written to the supplied buffer.
*
* @param[in] desc A reference to the Weave Device Descriptor to encode.
* @param[out buf A pointer to a buffer where the encoded text will be written.
* @param[in] bufLen The length of the supplied buffer.
* @param[out] outEncodedLen A reference to the length variable that will be overwritten
* with the number of characters written to the buffer.
*
* @retval #WEAVE_ERROR_BUFFER_TOO_SMALL If the supplied buffer is too small for the generated
* text description.
* @retval #WEAVE_ERROR_INVALID_ARGUMENT If a descriptor field is invalid.
* @retval #WEAVE_NO_ERROR On success.
*/
WEAVE_ERROR WeaveDeviceDescriptor::EncodeText(const WeaveDeviceDescriptor& desc, char *buf, uint32_t bufLen, uint32_t& outEncodedLen)
{
WEAVE_ERROR err;
TextDescriptorWriter writer(buf, bufLen);
err = writer.WriteVersion();
SuccessOrExit(err);
if (desc.VendorId != 0)
{
err = writer.WriteHex(kTextKey_VendorId, desc.VendorId);
SuccessOrExit(err);
}
if (desc.ProductId != 0)
{
err = writer.WriteHex(kTextKey_ProductId, desc.ProductId);
SuccessOrExit(err);
}
if (desc.ProductRevision != 0)
{
err = writer.WriteHex(kTextKey_ProductRevision, desc.ProductRevision);
SuccessOrExit(err);
}
if (desc.ManufacturingDate.Year != 0 && desc.ManufacturingDate.Month != 0)
{
err = writer.WriteDate(kTextKey_ManufacturingDate, desc.ManufacturingDate.Year, desc.ManufacturingDate.Month, desc.ManufacturingDate.Day);
SuccessOrExit(err);
}
if (desc.SerialNumber[0] != 0)
{
err = writer.WriteString(kTextKey_SerialNumber, desc.SerialNumber);
SuccessOrExit(err);
}
if (desc.DeviceId != 0)
{
uint64_t val = BigEndian::HostSwap64(desc.DeviceId);
err = writer.WriteHex(kTextKey_DeviceId, (const uint8_t *)&val, sizeof(val));
SuccessOrExit(err);
}
if (!IsZeroBytes(desc.Primary802154MACAddress, sizeof(desc.Primary802154MACAddress)))
{
err = writer.WriteHex(kTextKey_Primary802154MACAddress, desc.Primary802154MACAddress, sizeof(desc.Primary802154MACAddress));
SuccessOrExit(err);
}
if (!IsZeroBytes(desc.PrimaryWiFiMACAddress, sizeof(desc.PrimaryWiFiMACAddress)))
{
err = writer.WriteHex(kTextKey_PrimaryWiFiMACAddress, desc.PrimaryWiFiMACAddress, sizeof(desc.PrimaryWiFiMACAddress));
SuccessOrExit(err);
}
if (desc.RendezvousWiFiESSID[0] != 0)
{
err = writer.WriteString(kTextKey_RendezvousWiFiESSID, desc.RendezvousWiFiESSID);
SuccessOrExit(err);
}
if (desc.PairingCode[0] != 0)
{
err = writer.WriteString(kTextKey_PairingCode, desc.PairingCode);
SuccessOrExit(err);
}
if (desc.PairingCompatibilityVersionMajor != 0)
{
err = writer.WriteHex(kTextKey_PairingCompatibilityVersionMajor, desc.PairingCompatibilityVersionMajor);
SuccessOrExit(err);
}
if (desc.PairingCompatibilityVersionMinor != 0)
{
err = writer.WriteHex(kTextKey_PairingCompatibilityVersionMinor, desc.PairingCompatibilityVersionMinor);
SuccessOrExit(err);
}
err = writer.Finalize();
SuccessOrExit(err);
outEncodedLen = writer.GetLengthWritten();
exit:
return err;
}
/**
* Encodes the provided device descriptor as Weave TLV written to the supplied buffer.
*
* @param[in] desc A reference to the Weave Device Descriptor to encode.
* @param[out] buf A pointer to a buffer where the encoded text will be written.
* @param[in] bufLen The length of the supplied buffer.
* @param[out] outEncodedLen A reference to the length variable that will be overwritten
* with the number of characters written to the buffer.
*
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred preventing the encoding of the TLV.
*/
WEAVE_ERROR WeaveDeviceDescriptor::EncodeTLV(const WeaveDeviceDescriptor& desc, uint8_t *buf, uint32_t bufLen, uint32_t& outEncodedLen)
{
WEAVE_ERROR err;
TLVWriter writer;
writer.Init(buf, bufLen);
err = EncodeTLV(desc, writer);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
outEncodedLen = writer.GetLengthWritten();
exit:
return err;
}
/**
* Encodes the provided device descriptor as Weave TLV written using the provided
* pre-initialized TLVWriter object. This is used to add the device description to
* larger TLV output.
*
* @param[in] desc A reference to the Weave Device Descriptor to encode.
* @param[in] writer A reference to the pre-initialized TLVWriter object to be used.
*
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred preventing the encoding of the TLV.
*/
WEAVE_ERROR WeaveDeviceDescriptor::EncodeTLV(const WeaveDeviceDescriptor& desc, nl::Weave::TLV::TLVWriter& writer)
{
WEAVE_ERROR err;
TLVType outerContainer;
err = writer.StartContainer(ProfileTag(kWeaveProfile_DeviceDescription, kTag_WeaveDeviceDescriptor), kTLVType_Structure, outerContainer);
SuccessOrExit(err);
if (desc.VendorId != 0)
{
err = writer.Put(ContextTag(kTag_VendorId), desc.VendorId);
SuccessOrExit(err);
}
if (desc.ProductId != 0)
{
err = writer.Put(ContextTag(kTag_ProductId), desc.ProductId);
SuccessOrExit(err);
}
if (desc.ProductRevision != 0)
{
err = writer.Put(ContextTag(kTag_ProductRevision), desc.ProductRevision);
SuccessOrExit(err);
}
if (desc.ManufacturingDate.Year != 0 && desc.ManufacturingDate.Month != 0)
{
uint16_t encodedDate;
err = EncodeManufacturingDate(desc.ManufacturingDate.Year, desc.ManufacturingDate.Month, desc.ManufacturingDate.Day, encodedDate);
SuccessOrExit(err);
err = writer.Put(ContextTag(kTag_ManufacturingDate), encodedDate);
SuccessOrExit(err);
}
if (desc.SerialNumber[0] != 0)
{
err = writer.PutString(ContextTag(kTag_SerialNumber), desc.SerialNumber);
SuccessOrExit(err);
}
if (!IsZeroBytes(desc.Primary802154MACAddress, sizeof(desc.Primary802154MACAddress)))
{
err = writer.PutBytes(ContextTag(kTag_Primary802154MACAddress), desc.Primary802154MACAddress, sizeof(desc.Primary802154MACAddress));
SuccessOrExit(err);
}
if (!IsZeroBytes(desc.PrimaryWiFiMACAddress, sizeof(desc.PrimaryWiFiMACAddress)))
{
err = writer.PutBytes(ContextTag(kTag_PrimaryWiFiMACAddress), desc.PrimaryWiFiMACAddress, sizeof(desc.PrimaryWiFiMACAddress));
SuccessOrExit(err);
}
if (desc.RendezvousWiFiESSID[0] != 0)
{
err = writer.PutString(ContextTag(kTag_RendezvousWiFiESSID), desc.RendezvousWiFiESSID);
SuccessOrExit(err);
}
if (desc.PairingCode[0] != 0)
{
err = writer.PutString(ContextTag(kTag_PairingCode), desc.PairingCode);
SuccessOrExit(err);
}
if (desc.DeviceId != 0)
{
err = writer.Put(ContextTag(kTag_DeviceId), desc.DeviceId);
SuccessOrExit(err);
}
if (desc.FabricId != 0)
{
err = writer.Put(ContextTag(kTag_FabricId), desc.FabricId);
SuccessOrExit(err);
}
if (desc.SoftwareVersion[0] != 0)
{
err = writer.PutString(ContextTag(kTag_SoftwareVersion), desc.SoftwareVersion);
SuccessOrExit(err);
}
if (desc.PairingCompatibilityVersionMajor != 0)
{
err = writer.Put(ContextTag(kTag_PairingCompatibilityVersionMajor), desc.PairingCompatibilityVersionMajor);
SuccessOrExit(err);
}
if (desc.PairingCompatibilityVersionMinor != 0)
{
err = writer.Put(ContextTag(kTag_PairingCompatibilityVersionMinor), desc.PairingCompatibilityVersionMinor);
SuccessOrExit(err);
}
if ((desc.DeviceFeatures & kFeature_HomeAlarmLinkCapable) != 0)
{
err = writer.PutBoolean(ContextTag(kTag_DeviceFeature_HomeAlarmLinkCapable), true);
SuccessOrExit(err);
}
if ((desc.DeviceFeatures & kFeature_LinePowered) != 0)
{
err = writer.PutBoolean(ContextTag(kTag_DeviceFeature_LinePowered), true);
SuccessOrExit(err);
}
err = writer.EndContainer(outerContainer);
SuccessOrExit(err);
exit:
return err;
}
/**
* Decodes the contents of the provided data buffer into a Weave Device Descriptor object.
*
* @param[in] data A pointer to a buffer containing text or TLV encoded Weave Device
* Descriptor data.
* @param[in] dataLen The length of the provided buffer.
* @param[out] outDesc A reference to the Device Descriptor object to be populated.
*
* @retval #WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR If the provided buffer is invalid.
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred preventing the decoding of the TLV.
*/
WEAVE_ERROR WeaveDeviceDescriptor::Decode(const uint8_t *data, uint32_t dataLen, WeaveDeviceDescriptor& outDesc)
{
if (dataLen == 0)
return WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR;
// Automatically detect a TLV-encoded descriptor by looking for the TLV structure encoding. A proper TLV-encoded
// device descriptor will begin with a structure having either a fully-qualified or implicit profile-specific tag
// of 0000000E:1. E.g.:
//
// Fully-qualified: D5 00 00 0E 00 01 00
// Implicit: 95 01 00
//
if ((dataLen > 3 && data[0] == 0x95 && data[1] == 0x01 && data[2] == 0x00) ||
(dataLen > 7 && data[0] == 0xD5 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x0E && data[4] == 0x00 && data[5] == 0x01 && data[6] == 0x00))
return DecodeTLV(data, dataLen, outDesc);
// If the descriptor is not TLV-encoded, assume its in text format.
return DecodeText((const char *)data, dataLen, outDesc);
}
/**
* Decodes the contents of the provided text data buffer into a Weave Device Descriptor object.
*
* @param[in] data A pointer to a buffer containing text encoded Weave Device
* Descriptor data.
* @param[in] dataLen The length of the provided buffer.
* @param[out] outDesc A reference to the Device Descriptor object to be populated.
*
* @retval #WEAVE_ERROR_UNSUPPORTED_DEVICE_DESCRIPTOR_VERSION If the encoded data version is
* unsupported.
* @retval #WEAVE_ERROR_INVALID_DEVICE_DESCRIPTOR If the encoded data is not formated correctly.
* @retval #WEAVE_ERROR_INCORRECT_STATE If an inconsistent state is encountered by the
* decoder.
* @retval #WEAVE_ERROR_BUFFER_TOO_SMALL If the end of the buffer is reached during
* decoding.
* @retval #WEAVE_NO_ERROR On success.
*/
WEAVE_ERROR WeaveDeviceDescriptor::DecodeText(const char *data, uint32_t dataLen, WeaveDeviceDescriptor& outDesc)
{
WEAVE_ERROR err;
TextDescriptorReader reader(data, dataLen);
bool vendorIdIncluded = false;
bool mfgDateIncluded = false;
bool serialNumIncluded = false;
if (reader.Version != kEncodingVersion)
return WEAVE_ERROR_UNSUPPORTED_DEVICE_DESCRIPTOR_VERSION;
while ((err = reader.Next()) == WEAVE_NO_ERROR)
{
switch (reader.Key)
{
case kTextKey_VendorId:
err = reader.ReadHex(outDesc.VendorId);
SuccessOrExit(err);
vendorIdIncluded = true;
break;
case kTextKey_ProductId:
err = reader.ReadHex(outDesc.ProductId);
SuccessOrExit(err);
break;
case kTextKey_ProductRevision:
err = reader.ReadHex(outDesc.ProductRevision);
SuccessOrExit(err);
break;
case kTextKey_ManufacturingDate:
err = reader.ReadDate(outDesc.ManufacturingDate.Year, outDesc.ManufacturingDate.Month, outDesc.ManufacturingDate.Day);
SuccessOrExit(err);
mfgDateIncluded = true;
break;
case kTextKey_SerialNumber:
err = reader.ReadString(outDesc.SerialNumber, sizeof(outDesc.SerialNumber));
SuccessOrExit(err);
serialNumIncluded = true;
break;
case kTextKey_DeviceId:
{
uint64_t val;
err = reader.ReadHex((uint8_t *)&val, sizeof(val));
SuccessOrExit(err);
outDesc.DeviceId = BigEndian::HostSwap64(val);
break;
}
case kTextKey_Primary802154MACAddress:
err = reader.ReadHex(outDesc.Primary802154MACAddress, sizeof(outDesc.Primary802154MACAddress));
SuccessOrExit(err);
break;
case kTextKey_PrimaryWiFiMACAddress:
err = reader.ReadHex(outDesc.PrimaryWiFiMACAddress, sizeof(outDesc.PrimaryWiFiMACAddress));
SuccessOrExit(err);
break;
case kTextKey_RendezvousWiFiESSID:
err = reader.ReadString(outDesc.RendezvousWiFiESSID, sizeof(outDesc.RendezvousWiFiESSID));
SuccessOrExit(err);
break;
case kTextKey_PairingCode:
err = reader.ReadString(outDesc.PairingCode, sizeof(outDesc.PairingCode));
SuccessOrExit(err);
break;
case kTextKey_PairingCompatibilityVersionMajor:
err = reader.ReadHex(outDesc.PairingCompatibilityVersionMajor);
SuccessOrExit(err);
break;
case kTextKey_PairingCompatibilityVersionMinor:
err = reader.ReadHex(outDesc.PairingCompatibilityVersionMinor);
SuccessOrExit(err);
break;
default:
// ignore unknown keys
break;
}
}
if (err == WEAVE_END_OF_INPUT)
err = WEAVE_NO_ERROR;
// Absence of a vendor id in a *text* device descriptor implies Nest.
if (!vendorIdIncluded)
outDesc.VendorId = nl::Weave::kWeaveVendor_NestLabs;
// If the device was manufactured by Nest and the manufacturing date was not included in the descriptor,
// then extract the date from the serial number if included. Since the serial number only includes the
// week of manufacture, this date will correspond to the first day (Sunday) of the corresponding week.
//
// Note: we ignore any errors and just leave the manufacturing date field empty if the serial number
// can't be parsed.
//
if (outDesc.VendorId == nl::Weave::kWeaveVendor_NestLabs && !mfgDateIncluded && serialNumIncluded)
ExtractManufacturingDateFromSerialNumber(outDesc.SerialNumber, outDesc.ManufacturingDate.Year,
outDesc.ManufacturingDate.Month, outDesc.ManufacturingDate.Day);
exit:
return err;
}
/**
* Decodes the contents of the provided TLV data buffer into a Weave Device Descriptor object.
*
* @param[in] data A pointer to a buffer containing text encoded Weave Device
* Descriptor data.
* @param[in] dataLen The length of the provided buffer.
* @param[out] outDesc A reference to the Device Descriptor object to be populated.
*
* @retval #WEAVE_ERROR_WRONG_TLV_TYPE If this is not Device Description TLV.
* @retval #WEAVE_ERROR_UNEXPECTED_TLV_ELEMENT If more TLV data is encountered after the
* Device Description.
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred preventing the encoding of the TLV.
*/
WEAVE_ERROR WeaveDeviceDescriptor::DecodeTLV(const uint8_t *data, uint32_t dataLen, WeaveDeviceDescriptor& outDesc)
{
WEAVE_ERROR err;
TLVReader reader;
reader.Init(data, dataLen);
// Treat an implicit profile tag as specifying the Device Description profile.
reader.ImplicitProfileId = kWeaveProfile_DeviceDescription;
err = reader.Next();
SuccessOrExit(err);
VerifyOrExit(reader.GetTag() == ProfileTag(kWeaveProfile_DeviceDescription, kTag_WeaveDeviceDescriptor), err = WEAVE_ERROR_WRONG_TLV_TYPE);
err = DecodeTLV(reader, outDesc);
SuccessOrExit(err);
err = reader.Next();
VerifyOrExit(err == WEAVE_END_OF_TLV, err = WEAVE_ERROR_UNEXPECTED_TLV_ELEMENT);
err = WEAVE_NO_ERROR;
exit:
return err;
}
/**
* Decodes the Device Description using the provided pre-initialized TLVReader.
*
* @param[in] reader A reference to the pre-initialized TLVReader.
* @param[out] outDesc A reference to the Device Descriptor object to be populated.
*
* @retval #WEAVE_ERROR_INVALID_TLV_ELEMENT If invalid Device Description information is found
* in the TLV data.
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred that prevented the decoding of the TLV.
*/
WEAVE_ERROR WeaveDeviceDescriptor::DecodeTLV(nl::Weave::TLV::TLVReader& reader, WeaveDeviceDescriptor& outDesc)
{
WEAVE_ERROR err;
TLVType outerContainer;
outDesc.Clear();
err = reader.EnterContainer(outerContainer);
SuccessOrExit(err);
while ((err = reader.Next()) == WEAVE_NO_ERROR)
{
uint64_t tag = reader.GetTag();
if (tag == ContextTag(kTag_VendorId))
{
err = reader.Get(outDesc.VendorId);
SuccessOrExit(err);
VerifyOrExit(outDesc.VendorId != 0, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
}
else if (tag == ContextTag(kTag_ProductId))
{
err = reader.Get(outDesc.ProductId);
SuccessOrExit(err);
VerifyOrExit(outDesc.ProductId != 0, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
}
else if (tag == ContextTag(kTag_ProductRevision))
{
err = reader.Get(outDesc.ProductRevision);
SuccessOrExit(err);
VerifyOrExit(outDesc.ProductRevision != 0, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
}
else if (tag == ContextTag(kTag_ManufacturingDate))
{
uint16_t encodedDate;
err = reader.Get(encodedDate);
SuccessOrExit(err);
err = DecodeManufacturingDate(encodedDate, outDesc.ManufacturingDate.Year, outDesc.ManufacturingDate.Month, outDesc.ManufacturingDate.Day);
SuccessOrExit(err);
}
else if (tag == ContextTag(kTag_SerialNumber))
{
err = reader.GetString(outDesc.SerialNumber, sizeof(outDesc.SerialNumber));
SuccessOrExit(err);
VerifyOrExit(outDesc.SerialNumber[0] != 0, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
}
else if (tag == ContextTag(kTag_Primary802154MACAddress))
{
VerifyOrExit(reader.GetLength() == 8, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
err = reader.GetBytes(outDesc.Primary802154MACAddress, sizeof(outDesc.Primary802154MACAddress));
SuccessOrExit(err);
}
else if (tag == ContextTag(kTag_PrimaryWiFiMACAddress))
{
VerifyOrExit(reader.GetLength() == 6, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
err = reader.GetBytes(outDesc.PrimaryWiFiMACAddress, sizeof(outDesc.PrimaryWiFiMACAddress));
SuccessOrExit(err);
}
else if (tag == ContextTag(kTag_RendezvousWiFiESSID))
{
err = reader.GetString(outDesc.RendezvousWiFiESSID, sizeof(outDesc.RendezvousWiFiESSID));
SuccessOrExit(err);
VerifyOrExit(outDesc.RendezvousWiFiESSID[0] != 0, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
}
else if (tag == ContextTag(kTag_PairingCode))
{
err = reader.GetString(outDesc.PairingCode, sizeof(outDesc.PairingCode));
SuccessOrExit(err);
VerifyOrExit(outDesc.PairingCode[0] != 0, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
}
else if (tag == ContextTag(kTag_SoftwareVersion))
{
const uint8_t *swVer;
uint32_t swVerLen = reader.GetLength();
err = reader.GetDataPtr(swVer);
SuccessOrExit(err);
if (swVerLen > kMaxSoftwareVersionLength)
swVerLen = kMaxSoftwareVersionLength;
memcpy(outDesc.SoftwareVersion, swVer, swVerLen);
outDesc.SoftwareVersion[swVerLen] = 0;
}
else if (tag == ContextTag(kTag_DeviceId))
{
err = reader.Get(outDesc.DeviceId);
SuccessOrExit(err);
}
else if (tag == ContextTag(kTag_FabricId))
{
err = reader.Get(outDesc.FabricId);
SuccessOrExit(err);
}
else if (tag == ContextTag(kTag_PairingCompatibilityVersionMajor))
{
err = reader.Get(outDesc.PairingCompatibilityVersionMajor);
SuccessOrExit(err);
VerifyOrExit(outDesc.PairingCompatibilityVersionMajor != 0, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
}
else if (tag == ContextTag(kTag_PairingCompatibilityVersionMinor))
{
err = reader.Get(outDesc.PairingCompatibilityVersionMinor);
SuccessOrExit(err);
VerifyOrExit(outDesc.PairingCompatibilityVersionMinor != 0, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
}
else {
uint32_t flag;
if (tag == ContextTag(kTag_DeviceFeature_HomeAlarmLinkCapable))
flag = kFeature_HomeAlarmLinkCapable;
else if (tag == ContextTag(kTag_DeviceFeature_LinePowered))
flag = kFeature_LinePowered;
else
flag = 0;
if (flag)
{
bool val;
err = reader.Get(val);
SuccessOrExit(err);
if (val)
outDesc.DeviceFeatures |= flag;
}
}
// Ignore unknown tags.
}
VerifyOrExit(err == WEAVE_END_OF_TLV, );
err = reader.ExitContainer(outerContainer);
SuccessOrExit(err);
exit:
return err;
}
WEAVE_ERROR WeaveDeviceDescriptor::EncodeManufacturingDate(uint16_t year, uint8_t month, uint8_t day, uint16_t& outEncodedDate)
{
if (year < 2001 || year > 2099 || month < 1 || month > 12 || day > 31)
return WEAVE_ERROR_INVALID_ARGUMENT;
outEncodedDate = (year - 2000)
+ ((month - 1) * 100)
+ (day * 12 * 100);
return WEAVE_NO_ERROR;
}
WEAVE_ERROR WeaveDeviceDescriptor::DecodeManufacturingDate(uint16_t encodedDate, uint16_t& outYear, uint8_t& outMonth, uint8_t& outDay)
{
outYear = (encodedDate % 100) + 2000;
outMonth = ((encodedDate / 100) % 12) + 1;
outDay = (encodedDate / 1200);
if (outDay > 31)
return WEAVE_ERROR_INVALID_ARGUMENT;
return WEAVE_NO_ERROR;
}
/**
* Check if the specified buffer contains only zeros.
*
* @param[in] buf A pointer to a buffer.
* @param[in] len The length of the buffer.
*
* @retval TRUE if the buffer contains only zeros.
* @retval FALSE if the buffer contains any non-zero values.
*/
bool WeaveDeviceDescriptor::IsZeroBytes(const uint8_t *buf, uint32_t len)
{
for (; len > 0; len--, buf++)
if (*buf != 0)
return false;
return true;
}
/**
* Encodes this IdentifyRequestMessage object into the provided Inet buffer.
*
* @param[inout] msgBuf A pointer to the Inet buffer to write the Identify
* Request message to.
*
* @retval #WEAVE_NO_ERROR unconditionally.
*/
WEAVE_ERROR IdentifyRequestMessage::Encode(PacketBuffer *msgBuf) const
{
uint8_t *p;
p = msgBuf->Start();
LittleEndian::Write64(p, TargetFabricId);
LittleEndian::Write32(p, TargetModes);
LittleEndian::Write16(p, TargetVendorId);
LittleEndian::Write16(p, TargetProductId);
msgBuf->SetDataLength(p - msgBuf->Start());
return WEAVE_NO_ERROR;
}
/**
* Decodes an Identify Request message from an Inet buffer into the provided
* IdentifyRequestMessage object.
*
* @param[in] msgBuf A pointer to the Inet buffer to decode the Identify Request
* message from.
* @param[in] msgDestNodeId The destination node ID of the message being decoded.
* @param[inout] msg A reference to the IdentifyRequestMessage to populate.
*
* @retval #WEAVE_ERROR_INVALID_MESSAGE_LENGTH If the provided buffer is an invalid length.
* @retval #WEAVE_NO_ERROR On success.
*/
WEAVE_ERROR IdentifyRequestMessage::Decode(PacketBuffer *msgBuf, uint64_t msgDestNodeId, IdentifyRequestMessage& msg)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
const uint8_t *p;
VerifyOrExit(msgBuf->DataLength() == 16, err = WEAVE_ERROR_INVALID_MESSAGE_LENGTH);
memset(&msg, 0, sizeof(msg));
p = msgBuf->Start();
msg.TargetFabricId = LittleEndian::Read64(p);
msg.TargetModes = LittleEndian::Read32(p);
msg.TargetVendorId = LittleEndian::Read16(p);
msg.TargetProductId = LittleEndian::Read16(p);
msg.TargetDeviceId = msgDestNodeId;
exit:
return err;
}
/**
* Encodes this IdentifyResponseMessage object into the provided message buffer.
*
* @param[inout] msgBuf A pointer to the Inet buffer to write the Identify
* Response message to.
*
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred preventing the encoding of the IdentifyResponseMessage.
*/
WEAVE_ERROR IdentifyResponseMessage::Encode(PacketBuffer *msgBuf)
{
WEAVE_ERROR err;
uint32_t msgLen;
err = WeaveDeviceDescriptor::EncodeTLV(DeviceDesc, msgBuf->Start(), msgBuf->AvailableDataLength(), msgLen);
if (err == WEAVE_NO_ERROR)
msgBuf->SetDataLength(msgLen);
return err;
}
/**
* Decodes an Identify Response message from an Inet buffer into the provided
* IdentifyResponseMessage object.
*
* @param[in] msgBuf A pointer to the Inet buffer to decode the Identify Request
* message from.
* @param[in] msgDestNodeId The destination node ID of the message being decoded.
* @param[out] msg A reference to the IdentifyRequestMessage to populate.
*
* @retval #WEAVE_ERROR_WRONG_TLV_TYPE If this is not Device Description TLV.
* @retval #WEAVE_ERROR_UNEXPECTED_TLV_ELEMENT If more TLV data is encountered after the
* Device Description.
* @retval #WEAVE_NO_ERROR On success.
* @retval other Other Weave or platform-specific error codes indicating that an error
* occurred preventing the decoding of the IdentifyResponseMessage.
*/
WEAVE_ERROR IdentifyResponseMessage::Decode(PacketBuffer *msgBuf, IdentifyResponseMessage& msg)
{
return WeaveDeviceDescriptor::DecodeTLV(msgBuf->Start(), msgBuf->DataLength(), msg.DeviceDesc);
}
IdentifyDeviceCriteria::IdentifyDeviceCriteria()
{
Reset();
}
/**
* Resets this Identify Device Criteria object to be least restrictive,
* that is, matching any.
*/
void IdentifyDeviceCriteria::Reset()
{
TargetFabricId = kTargetFabricId_Any;
TargetModes = kTargetDeviceMode_Any;
TargetVendorId = 0xFFFF; // Any vendor
TargetProductId = 0xFFFF; // Any product
TargetDeviceId = kAnyNodeId;
}
/**
* Compare two fabric IDs to determine if they match (considering wildcard values).
*
* @param[in] fabricId The fabric ID to test.
* @param[in] targetFabricId The fabric ID to test against.
*
* @retval TRUE if the fabric ids match.
* @retval FALSE if the fabric ids do not match.
*/
NL_DLL_EXPORT bool MatchTargetFabricId(uint64_t fabricId, uint64_t targetFabricId)
{
if (targetFabricId == kTargetFabricId_Any)
return true;
if (targetFabricId == kTargetFabricId_NotInFabric)
return (fabricId == kFabricIdNotSpecified);
if (targetFabricId == kTargetFabricId_AnyFabric)
return (fabricId != kFabricIdNotSpecified);
return (targetFabricId == fabricId);
}
} // namespace DeviceDescription
} // namespace Profiles
} // namespace Weave
} // namespace nl