blob: ea6105254c04f54f8346a4bfc053c05c0df45616 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* 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.
*/
#include <lib/support/ThreadOperationalDataset.h>
#include <lib/core/CHIPEncoding.h>
#include <cassert>
#include <cstring>
namespace chip {
namespace Thread {
/**
* The Thread specification defines two TLV element formats: a "base" format and an "extended" format.
* The base format is used for TLV elements with a length of 0-254 bytes and is laid out as follows:
*
* +---------+---------+-------------------------+
* | uint8_t | uint8_t | network byte order data |
* | 1 byte | 1 byte | n byte (0 <= n < 255) |
* +---------+---------+-------------------------+
* | Type | Length | Value |
* +---------+---------+-------------------------+
*
* A length values of 0xff (255) is an "escape" value and indicates that the element uses the "extended"
* format, where a two-byte length field follows the 0xff escape byte.
*
* Only the base format is allowed in Thread Operational Datasets, so only this format is supported here.
*/
class ThreadTLV final
{
public:
static constexpr uint8_t kLengthEscape = 0xff;
enum : uint8_t
{
kChannel = 0,
kPanId = 1,
kExtendedPanId = 2,
kNetworkName = 3,
kPSKc = 4,
kMasterKey = 5,
kMeshLocalPrefix = 7,
kSecurityPolicy = 12,
kActiveTimestamp = 14,
kDelayTimer = 52,
kChannelMask = 53,
};
size_t GetSize() const
{
static_assert(sizeof(*this) == 2, "ThreadTLV header should be 2 bytes (type, length)");
return sizeof(*this) + GetLength();
}
uint8_t GetType() const { return mType; }
void SetType(uint8_t aType) { mType = aType; }
uint8_t GetLength() const { return mLength; }
void SetLength(size_t aLength)
{
assert(aLength < kLengthEscape);
mLength = static_cast<uint8_t>(aLength);
}
const uint8_t * GetValue() const
{
assert(mLength != kLengthEscape);
return reinterpret_cast<const uint8_t *>(this) + sizeof(*this);
}
uint8_t * GetValue() { return const_cast<uint8_t *>(const_cast<const ThreadTLV *>(this)->GetValue()); }
ByteSpan GetValueAsSpan() const { return ByteSpan(GetValue(), GetLength()); }
void Get64(uint64_t & aValue) const
{
assert(GetLength() == sizeof(aValue));
aValue = Encoding::BigEndian::Get64(GetValue());
}
void Get32(uint32_t & aValue) const
{
assert(GetLength() == sizeof(aValue));
aValue = Encoding::BigEndian::Get32(GetValue());
}
void Get16(uint16_t & aValue) const
{
assert(GetLength() == sizeof(aValue));
aValue = Encoding::BigEndian::Get16(GetValue());
}
void Set64(uint64_t aValue)
{
assert(GetLength() == sizeof(aValue));
Encoding::BigEndian::Put64(GetValue(), aValue);
}
void Set32(uint32_t aValue)
{
assert(GetLength() == sizeof(aValue));
Encoding::BigEndian::Put32(GetValue(), aValue);
}
void Set16(uint16_t aValue)
{
assert(GetLength() == sizeof(aValue));
Encoding::BigEndian::Put16(GetValue(), aValue);
}
void SetValue(const void * aValue, size_t aLength)
{
assert(GetLength() == aLength);
memcpy(GetValue(), aValue, aLength);
}
void SetValue(const ByteSpan & aValue) { SetValue(aValue.data(), aValue.size()); }
const ThreadTLV * GetNext() const
{
static_assert(alignof(ThreadTLV) == 1, "Wrong alignment for ThreadTLV header");
return reinterpret_cast<const ThreadTLV *>(static_cast<const uint8_t *>(GetValue()) + GetLength());
}
ThreadTLV * GetNext() { return reinterpret_cast<ThreadTLV *>(static_cast<uint8_t *>(GetValue()) + GetLength()); }
private:
uint8_t mType;
uint8_t mLength;
};
/// OperationalDatasetView
bool OperationalDatasetView::IsValid(ByteSpan aData)
{
VerifyOrReturnValue(aData.size() <= kSizeOperationalDataset, false);
const ThreadTLV * tlv = reinterpret_cast<const ThreadTLV *>(aData.begin());
const ThreadTLV * end = reinterpret_cast<const ThreadTLV *>(aData.end());
while (tlv != end)
{
VerifyOrReturnValue(tlv + 1 <= end, false); // out of bounds
VerifyOrReturnValue(tlv->GetLength() != ThreadTLV::kLengthEscape, false); // not allowed in a dataset TLV
tlv = tlv->GetNext();
}
return true;
}
CHIP_ERROR OperationalDatasetView::Init(ByteSpan aData)
{
VerifyOrReturnError(IsValid(aData), CHIP_ERROR_INVALID_ARGUMENT);
mData = aData;
return CHIP_NO_ERROR;
}
const ThreadTLV * OperationalDatasetView::Locate(uint8_t aType) const
{
const ThreadTLV * tlv = reinterpret_cast<const ThreadTLV *>(mData.begin());
const ThreadTLV * end = reinterpret_cast<const ThreadTLV *>(mData.end());
while (tlv < end)
{
if (tlv->GetType() == aType)
{
return tlv;
}
tlv = tlv->GetNext();
}
return nullptr;
}
bool OperationalDatasetView::IsCommissioned() const
{
return Has(ThreadTLV::kPanId) && Has(ThreadTLV::kMasterKey) && Has(ThreadTLV::kExtendedPanId) && Has(ThreadTLV::kChannel);
}
CHIP_ERROR OperationalDatasetView::GetActiveTimestamp(uint64_t & aActiveTimestamp) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kActiveTimestamp);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == sizeof(aActiveTimestamp), CHIP_ERROR_INVALID_TLV_ELEMENT);
tlv->Get64(aActiveTimestamp);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetChannel(uint16_t & aChannel) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kChannel);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == 3, CHIP_ERROR_INVALID_TLV_ELEMENT);
const uint8_t * value = tlv->GetValue();
VerifyOrReturnError(value[0] == 0, CHIP_ERROR_INVALID_TLV_ELEMENT); // Channel Page must be 0
aChannel = Encoding::BigEndian::Get16(value + 1);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetExtendedPanId(uint8_t (&aExtendedPanId)[kSizeExtendedPanId]) const
{
ByteSpan extPanIdSpan;
ReturnErrorOnFailure(GetExtendedPanIdAsByteSpan(extPanIdSpan));
memcpy(aExtendedPanId, extPanIdSpan.data(), extPanIdSpan.size());
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetExtendedPanId(uint64_t & extendedPanId) const
{
ByteSpan extPanIdSpan;
ReturnErrorOnFailure(GetExtendedPanIdAsByteSpan(extPanIdSpan));
VerifyOrDie(extPanIdSpan.size() == sizeof(extendedPanId));
extendedPanId = Encoding::BigEndian::Get64(extPanIdSpan.data());
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetExtendedPanIdAsByteSpan(ByteSpan & span) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kExtendedPanId);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == kSizeExtendedPanId, CHIP_ERROR_INVALID_TLV_ELEMENT);
span = tlv->GetValueAsSpan();
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetMeshLocalPrefix(uint8_t (&aMeshLocalPrefix)[kSizeMeshLocalPrefix]) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kMeshLocalPrefix);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == sizeof(aMeshLocalPrefix), CHIP_ERROR_INVALID_TLV_ELEMENT);
memcpy(aMeshLocalPrefix, tlv->GetValue(), sizeof(aMeshLocalPrefix));
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetMasterKey(uint8_t (&aMasterKey)[kSizeMasterKey]) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kMasterKey);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == sizeof(aMasterKey), CHIP_ERROR_INVALID_TLV_ELEMENT);
memcpy(aMasterKey, tlv->GetValue(), sizeof(aMasterKey));
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetNetworkName(char (&aNetworkName)[kSizeNetworkName + 1]) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kNetworkName);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() <= kSizeNetworkName, CHIP_ERROR_INVALID_TLV_ELEMENT);
memcpy(aNetworkName, tlv->GetValue(), tlv->GetLength());
aNetworkName[tlv->GetLength()] = '\0';
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetPanId(uint16_t & aPanId) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kPanId);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == sizeof(aPanId), CHIP_ERROR_INVALID_TLV_ELEMENT);
tlv->Get16(aPanId);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetPSKc(uint8_t (&aPSKc)[kSizePSKc]) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kPSKc);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == sizeof(aPSKc), CHIP_ERROR_INVALID_TLV_ELEMENT);
memcpy(aPSKc, tlv->GetValue(), sizeof(aPSKc));
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetChannelMask(ByteSpan & aChannelMask) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kChannelMask);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() > 0, CHIP_ERROR_INVALID_TLV_ELEMENT);
aChannelMask = tlv->GetValueAsSpan();
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetSecurityPolicy(uint32_t & aSecurityPolicy) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kSecurityPolicy);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == sizeof(aSecurityPolicy), CHIP_ERROR_INVALID_TLV_ELEMENT);
tlv->Get32(aSecurityPolicy);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDatasetView::GetDelayTimer(uint32_t & aDelayMillis) const
{
const ThreadTLV * tlv = Locate(ThreadTLV::kDelayTimer);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
VerifyOrReturnError(tlv->GetLength() == sizeof(aDelayMillis), CHIP_ERROR_INVALID_TLV_ELEMENT);
tlv->Get32(aDelayMillis);
return CHIP_NO_ERROR;
}
/// OperationalDataset
inline constexpr size_t kMaxDatasetElementLength = kSizeOperationalDataset - sizeof(ThreadTLV);
CHIP_ERROR OperationalDataset::Init(ByteSpan aData)
{
VerifyOrReturnError(IsValid(aData), CHIP_ERROR_INVALID_ARGUMENT);
// Use memmove because aData could be a sub-span of AsByteSpan()
memmove(mBuffer, aData.data(), aData.size());
mData = ByteSpan(mBuffer, aData.size());
return CHIP_NO_ERROR;
}
void OperationalDataset::CopyDataIfNecessary()
{
// It's possible that mData points into an external buffer if someone has
// called OperationalDatasetView::Init() instead of our copying version.
if (mData.data() != mBuffer)
{
CopyData();
}
}
void OperationalDataset::CopyData()
{
memmove(mBuffer, mData.data(), mData.size());
mData = ByteSpan(mBuffer, mData.size());
}
void OperationalDataset::Remove(ThreadTLV * tlv)
{
size_t size = tlv->GetSize();
ThreadTLV * next = tlv->GetNext();
memmove(tlv, next, static_cast<size_t>(mData.end() - reinterpret_cast<uint8_t *>(next)));
mData = ByteSpan(mData.data(), mData.size() - size);
}
void OperationalDataset::Remove(uint8_t aType)
{
CopyDataIfNecessary();
ThreadTLV * tlv = const_cast<ThreadTLV *>(Locate(aType));
if (tlv != nullptr)
{
Remove(tlv);
}
}
// Inserts a TLV of the specified type and length into the dataset, replacing an existing TLV
// of the same type if one exists. Returns nullptr if there is not enough space.
ThreadTLV * OperationalDataset::InsertOrReplace(uint8_t aType, size_t aValueSize)
{
assert(aValueSize <= kMaxDatasetElementLength); // callers check this or a tighter limit
CopyDataIfNecessary();
ThreadTLV * tlv = const_cast<ThreadTLV *>(Locate(aType));
if (tlv != nullptr)
{
size_t tlvLength = tlv->GetLength();
VerifyOrReturnValue(aValueSize != tlvLength, tlv); // re-use in place if same size
VerifyOrReturnValue(aValueSize < tlvLength || mData.size() + aValueSize - tlvLength <= sizeof(mBuffer), nullptr);
Remove(tlv); // we could grow or shrink in place instead, but this is simpler
}
else
{
VerifyOrReturnValue(mData.size() + sizeof(ThreadTLV) + aValueSize <= sizeof(mBuffer), nullptr);
}
tlv = reinterpret_cast<ThreadTLV *>(mBuffer + mData.size());
tlv->SetType(aType);
tlv->SetLength(static_cast<uint8_t>(aValueSize));
mData = ByteSpan(mBuffer, mData.size() + tlv->GetSize());
return tlv;
}
CHIP_ERROR OperationalDataset::SetActiveTimestamp(uint64_t aActiveTimestamp)
{
static_assert(sizeof(aActiveTimestamp) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kActiveTimestamp, sizeof(aActiveTimestamp));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->Set64(aActiveTimestamp);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDataset::SetChannel(uint16_t aChannel)
{
uint8_t value[3] = { 0 }; // Channel Page is always 0
Encoding::BigEndian::Put16(value + 1, aChannel);
static_assert(sizeof(value) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kChannel, sizeof(value));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->SetValue(value, sizeof(value));
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDataset::SetExtendedPanId(const uint8_t (&aExtendedPanId)[kSizeExtendedPanId])
{
static_assert(sizeof(aExtendedPanId) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kExtendedPanId, sizeof(aExtendedPanId));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->SetValue(aExtendedPanId, sizeof(aExtendedPanId));
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDataset::SetMasterKey(const uint8_t (&aMasterKey)[kSizeMasterKey])
{
static_assert(sizeof(aMasterKey) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kMasterKey, sizeof(aMasterKey));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->SetValue(aMasterKey, sizeof(aMasterKey));
return CHIP_NO_ERROR;
}
void OperationalDataset::UnsetMasterKey()
{
Remove(ThreadTLV::kMasterKey);
}
CHIP_ERROR OperationalDataset::SetMeshLocalPrefix(const uint8_t (&aMeshLocalPrefix)[kSizeMeshLocalPrefix])
{
static_assert(sizeof(aMeshLocalPrefix) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kMeshLocalPrefix, sizeof(aMeshLocalPrefix));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->SetValue(aMeshLocalPrefix, sizeof(aMeshLocalPrefix));
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDataset::SetNetworkName(const char * aNetworkName)
{
VerifyOrReturnError(aNetworkName != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
size_t len = strlen(aNetworkName);
VerifyOrReturnError(0 < len && len <= kSizeNetworkName, CHIP_ERROR_INVALID_STRING_LENGTH);
static_assert(kSizeNetworkName <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kNetworkName, len);
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->SetValue(aNetworkName, len);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDataset::SetPanId(uint16_t aPanId)
{
static_assert(sizeof(aPanId) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kPanId, sizeof(aPanId));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->Set16(aPanId);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDataset::SetPSKc(const uint8_t (&aPSKc)[kSizePSKc])
{
static_assert(sizeof(aPSKc) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kPSKc, sizeof(aPSKc));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->SetValue(aPSKc, sizeof(aPSKc));
return CHIP_NO_ERROR;
}
void OperationalDataset::UnsetPSKc()
{
Remove(ThreadTLV::kPSKc);
}
CHIP_ERROR OperationalDataset::SetChannelMask(ByteSpan aChannelMask)
{
VerifyOrReturnError(0 < aChannelMask.size() && aChannelMask.size() <= kMaxDatasetElementLength, CHIP_ERROR_INVALID_ARGUMENT);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kChannelMask, aChannelMask.size());
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->SetValue(aChannelMask);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDataset::SetSecurityPolicy(uint32_t aSecurityPolicy)
{
static_assert(sizeof(aSecurityPolicy) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kSecurityPolicy, sizeof(aSecurityPolicy));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->Set32(aSecurityPolicy);
return CHIP_NO_ERROR;
}
CHIP_ERROR OperationalDataset::SetDelayTimer(uint32_t aDelayMillis)
{
static_assert(sizeof(aDelayMillis) <= kMaxDatasetElementLength);
ThreadTLV * tlv = InsertOrReplace(ThreadTLV::kDelayTimer, sizeof(aDelayMillis));
VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
tlv->Set32(aDelayMillis);
return CHIP_NO_ERROR;
}
} // namespace Thread
} // namespace chip