| /* |
| * |
| * 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 <assert.h> |
| #include <string.h> |
| |
| #include <lib/support/ThreadOperationalDataset.h> |
| |
| namespace chip { |
| namespace Thread { |
| |
| /** |
| * Thread Operational Dataset TLV is defined in Thread Specification as the following format: |
| * |
| * +---------+---------+-------------------------+ |
| * | uint8_t | uint8_t | network byte order data | |
| * | 1 byte | 1 byte | n byte (0 <= n < 255) | |
| * +---------+---------+-------------------------+ |
| * | Type | Length | Value | |
| * +---------+---------+-------------------------+ |
| * |
| */ |
| class ThreadTLV final |
| { |
| static constexpr uint8_t kLengthEscape = 0xff; ///< This length value indicates the actual length is of two-bytes length, which |
| ///< not allowed in Thread Operational Dataset TLVs. |
| |
| public: |
| enum : uint8_t |
| { |
| kChannel = 0, |
| kPanId = 1, |
| kExtendedPanId = 2, |
| kNetworkName = 3, |
| kPSKc = 4, |
| kMasterKey = 5, |
| kMeshLocalPrefix = 7, |
| kActiveTimestamp = 14, |
| }; |
| |
| uint8_t GetSize() const { return static_cast<uint8_t>(sizeof(*this) + GetLength()); } |
| |
| uint8_t GetType() const { return mType; } |
| |
| void SetType(uint8_t aType) { mType = aType; } |
| |
| uint8_t GetLength() const |
| { |
| assert(mLength != kLengthEscape); |
| return mLength; |
| } |
| |
| void SetLength(uint8_t aLength) |
| { |
| assert(mLength != kLengthEscape); |
| mLength = aLength; |
| } |
| |
| const void * GetValue() const |
| { |
| assert(mLength != kLengthEscape); |
| |
| static_assert(sizeof(*this) == sizeof(ThreadTLV::mType) + sizeof(ThreadTLV::mLength), "Wrong size for ThreadTLV header"); |
| |
| return reinterpret_cast<const uint8_t *>(this) + sizeof(*this); |
| } |
| |
| void * GetValue() { return const_cast<void *>(const_cast<const ThreadTLV *>(this)->GetValue()); } |
| |
| void Get64(uint64_t & aValue) const |
| { |
| assert(GetLength() >= sizeof(aValue)); |
| |
| const uint8_t * p = reinterpret_cast<const uint8_t *>(GetValue()); |
| aValue = // |
| (static_cast<uint64_t>(p[0]) << 56) | // |
| (static_cast<uint64_t>(p[1]) << 48) | // |
| (static_cast<uint64_t>(p[2]) << 40) | // |
| (static_cast<uint64_t>(p[3]) << 32) | // |
| (static_cast<uint64_t>(p[4]) << 24) | // |
| (static_cast<uint64_t>(p[5]) << 16) | // |
| (static_cast<uint64_t>(p[6]) << 8) | // |
| (static_cast<uint64_t>(p[7])); |
| } |
| |
| void Get16(uint16_t & aValue) const |
| { |
| assert(GetLength() >= sizeof(aValue)); |
| |
| const uint8_t * p = static_cast<const uint8_t *>(GetValue()); |
| |
| aValue = static_cast<uint16_t>(p[0] << 8 | p[1]); |
| } |
| |
| void Get8(uint8_t & aValue) const |
| { |
| assert(GetLength() >= sizeof(aValue)); |
| aValue = *static_cast<const uint8_t *>(GetValue()); |
| } |
| |
| void Set64(uint64_t aValue) |
| { |
| uint8_t * value = static_cast<uint8_t *>(GetValue()); |
| |
| SetLength(sizeof(aValue)); |
| |
| value[0] = static_cast<uint8_t>((aValue >> 56) & 0xff); |
| value[1] = static_cast<uint8_t>((aValue >> 48) & 0xff); |
| value[2] = static_cast<uint8_t>((aValue >> 40) & 0xff); |
| value[3] = static_cast<uint8_t>((aValue >> 32) & 0xff); |
| value[4] = static_cast<uint8_t>((aValue >> 24) & 0xff); |
| value[5] = static_cast<uint8_t>((aValue >> 16) & 0xff); |
| value[6] = static_cast<uint8_t>((aValue >> 8) & 0xff); |
| value[7] = static_cast<uint8_t>(aValue & 0xff); |
| } |
| |
| void Set16(uint16_t aValue) |
| { |
| uint8_t * value = static_cast<uint8_t *>(GetValue()); |
| |
| SetLength(sizeof(aValue)); |
| |
| value[0] = static_cast<uint8_t>(aValue >> 8); |
| value[1] = static_cast<uint8_t>(aValue & 0xff); |
| } |
| |
| void Set8(uint8_t aValue) |
| { |
| SetLength(sizeof(aValue)); |
| *static_cast<uint8_t *>(GetValue()) = aValue; |
| } |
| |
| void Set8(int8_t aValue) |
| { |
| SetLength(sizeof(aValue)); |
| *static_cast<int8_t *>(GetValue()) = aValue; |
| } |
| |
| void SetValue(const void * aValue, uint8_t aLength) |
| { |
| SetLength(aLength); |
| memcpy(GetValue(), aValue, aLength); |
| } |
| |
| 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()); } |
| |
| static bool IsValid(ByteSpan aData) |
| { |
| const uint8_t * const end = aData.data() + aData.size(); |
| const uint8_t * curr = aData.data(); |
| |
| while (curr + sizeof(ThreadTLV) < end) |
| { |
| const ThreadTLV * tlv = reinterpret_cast<const ThreadTLV *>(curr); |
| |
| if (tlv->GetLength() == kLengthEscape) |
| { |
| break; |
| } |
| |
| curr = reinterpret_cast<const uint8_t *>(tlv->GetNext()); |
| } |
| |
| return curr == end; |
| } |
| |
| private: |
| uint8_t mType; |
| uint8_t mLength; |
| }; |
| |
| bool OperationalDataset::IsValid(ByteSpan aData) |
| { |
| return ThreadTLV::IsValid(aData); |
| } |
| |
| CHIP_ERROR OperationalDataset::Init(ByteSpan aData) |
| { |
| if (aData.size() > sizeof(mData)) |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| if (aData.size() > 0) |
| { |
| if (!ThreadTLV::IsValid(aData)) |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| memcpy(mData, aData.data(), aData.size()); |
| } |
| |
| mLength = static_cast<uint8_t>(aData.size()); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetActiveTimestamp(uint64_t & aActiveTimestamp) const |
| { |
| const ThreadTLV * tlv = Locate(ThreadTLV::kActiveTimestamp); |
| |
| if (tlv != nullptr) |
| { |
| tlv->Get64(aActiveTimestamp); |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_ERROR_TLV_TAG_NOT_FOUND; |
| } |
| |
| CHIP_ERROR OperationalDataset::SetActiveTimestamp(uint64_t aActiveTimestamp) |
| { |
| ThreadTLV * tlv = MakeRoom(ThreadTLV::kActiveTimestamp, sizeof(*tlv) + sizeof(aActiveTimestamp)); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| tlv->Set64(aActiveTimestamp); |
| |
| mLength = static_cast<uint8_t>(mLength + tlv->GetSize()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetChannel(uint16_t & aChannel) const |
| { |
| const ThreadTLV * tlv = Locate(ThreadTLV::kChannel); |
| |
| if (tlv != nullptr) |
| { |
| const uint8_t * value = reinterpret_cast<const uint8_t *>(tlv->GetValue()); |
| aChannel = static_cast<uint16_t>((value[1] << 8) | value[2]); |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_ERROR_TLV_TAG_NOT_FOUND; |
| } |
| |
| CHIP_ERROR OperationalDataset::SetChannel(uint16_t aChannel) |
| { |
| uint8_t value[] = { 0, static_cast<uint8_t>(aChannel >> 8), static_cast<uint8_t>(aChannel & 0xff) }; |
| ThreadTLV * tlv = MakeRoom(ThreadTLV::kChannel, sizeof(*tlv) + sizeof(value)); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| tlv->SetValue(value, sizeof(value)); |
| |
| mLength = static_cast<uint8_t>(mLength + tlv->GetSize()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetExtendedPanId(uint8_t (&aExtendedPanId)[kSizeExtendedPanId]) const |
| { |
| ByteSpan extPanIdSpan; |
| CHIP_ERROR error = GetExtendedPanIdAsByteSpan(extPanIdSpan); |
| |
| if (error != CHIP_NO_ERROR) |
| { |
| return error; |
| } |
| |
| memcpy(aExtendedPanId, extPanIdSpan.data(), extPanIdSpan.size()); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetExtendedPanIdAsByteSpan(ByteSpan & span) const |
| { |
| const ThreadTLV * tlv = Locate(ThreadTLV::kExtendedPanId); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_TLV_TAG_NOT_FOUND; |
| } |
| |
| if (tlv->GetLength() != kSizeExtendedPanId) |
| { |
| return CHIP_ERROR_INVALID_TLV_ELEMENT; |
| } |
| |
| span = ByteSpan(static_cast<const uint8_t *>(tlv->GetValue()), tlv->GetLength()); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::SetExtendedPanId(const uint8_t (&aExtendedPanId)[kSizeExtendedPanId]) |
| { |
| ThreadTLV * tlv = MakeRoom(ThreadTLV::kExtendedPanId, sizeof(*tlv) + sizeof(aExtendedPanId)); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| tlv->SetValue(aExtendedPanId, sizeof(aExtendedPanId)); |
| |
| assert(mLength + tlv->GetSize() <= sizeof(mData)); |
| |
| mLength = static_cast<uint8_t>(mLength + tlv->GetSize()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetMasterKey(uint8_t (&aMasterKey)[kSizeMasterKey]) const |
| { |
| const ThreadTLV * tlv = Locate(ThreadTLV::kMasterKey); |
| |
| if (tlv != nullptr) |
| { |
| memcpy(aMasterKey, tlv->GetValue(), sizeof(aMasterKey)); |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_ERROR_TLV_TAG_NOT_FOUND; |
| } |
| |
| CHIP_ERROR OperationalDataset::SetMasterKey(const uint8_t (&aMasterKey)[kSizeMasterKey]) |
| { |
| ThreadTLV * tlv = MakeRoom(ThreadTLV::kMasterKey, sizeof(*tlv) + sizeof(aMasterKey)); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| tlv->SetValue(aMasterKey, sizeof(aMasterKey)); |
| |
| assert(mLength + tlv->GetSize() <= sizeof(mData)); |
| |
| mLength = static_cast<uint8_t>(mLength + tlv->GetSize()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetMeshLocalPrefix(uint8_t (&aMeshLocalPrefix)[kSizeMeshLocalPrefix]) const |
| { |
| const ThreadTLV * tlv = Locate(ThreadTLV::kMeshLocalPrefix); |
| |
| if (tlv != nullptr) |
| { |
| memcpy(aMeshLocalPrefix, tlv->GetValue(), sizeof(aMeshLocalPrefix)); |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_ERROR_TLV_TAG_NOT_FOUND; |
| } |
| |
| CHIP_ERROR OperationalDataset::SetMeshLocalPrefix(const uint8_t (&aMeshLocalPrefix)[kSizeMeshLocalPrefix]) |
| { |
| ThreadTLV * tlv = MakeRoom(ThreadTLV::kMeshLocalPrefix, sizeof(*tlv) + sizeof(aMeshLocalPrefix)); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| tlv->SetValue(aMeshLocalPrefix, sizeof(aMeshLocalPrefix)); |
| |
| mLength = static_cast<uint8_t>(mLength + tlv->GetSize()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetNetworkName(char (&aNetworkName)[kSizeNetworkName + 1]) const |
| { |
| const ThreadTLV * tlv = Locate(ThreadTLV::kNetworkName); |
| |
| if (tlv != nullptr) |
| { |
| memcpy(aNetworkName, tlv->GetValue(), tlv->GetLength()); |
| aNetworkName[tlv->GetLength()] = '\0'; |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_ERROR_TLV_TAG_NOT_FOUND; |
| } |
| |
| CHIP_ERROR OperationalDataset::SetNetworkName(const char * aNetworkName) |
| { |
| size_t len = strlen(aNetworkName); |
| |
| if (len > kSizeNetworkName || len == 0) |
| { |
| return CHIP_ERROR_INVALID_STRING_LENGTH; |
| } |
| |
| ThreadTLV * tlv = MakeRoom(ThreadTLV::kNetworkName, static_cast<uint8_t>(sizeof(*tlv) + static_cast<uint8_t>(len))); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| tlv->SetValue(aNetworkName, static_cast<uint8_t>(len)); |
| |
| mLength = static_cast<uint8_t>(mLength + tlv->GetSize()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetPanId(uint16_t & aPanId) const |
| { |
| const ThreadTLV * tlv = Locate(ThreadTLV::kPanId); |
| |
| if (tlv != nullptr) |
| { |
| tlv->Get16(aPanId); |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_ERROR_TLV_TAG_NOT_FOUND; |
| } |
| |
| CHIP_ERROR OperationalDataset::SetPanId(uint16_t aPanId) |
| { |
| ThreadTLV * tlv = MakeRoom(ThreadTLV::kPanId, sizeof(*tlv) + sizeof(aPanId)); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| tlv->Set16(aPanId); |
| |
| mLength = static_cast<uint8_t>(mLength + tlv->GetSize()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR OperationalDataset::GetPSKc(uint8_t (&aPSKc)[kSizePSKc]) const |
| { |
| const ThreadTLV * tlv = Locate(ThreadTLV::kPSKc); |
| |
| if (tlv != nullptr) |
| { |
| memcpy(aPSKc, tlv->GetValue(), sizeof(aPSKc)); |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_ERROR_TLV_TAG_NOT_FOUND; |
| } |
| |
| CHIP_ERROR OperationalDataset::SetPSKc(const uint8_t (&aPSKc)[kSizePSKc]) |
| { |
| ThreadTLV * tlv = MakeRoom(ThreadTLV::kPSKc, sizeof(*tlv) + sizeof(aPSKc)); |
| |
| if (tlv == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| tlv->SetValue(aPSKc, sizeof(aPSKc)); |
| |
| mLength = static_cast<uint8_t>(mLength + tlv->GetSize()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void OperationalDataset::UnsetMasterKey() |
| { |
| Remove(ThreadTLV::kMasterKey); |
| } |
| |
| void OperationalDataset::UnsetPSKc() |
| { |
| Remove(ThreadTLV::kPSKc); |
| } |
| |
| bool OperationalDataset::IsCommissioned() const |
| { |
| return Has(ThreadTLV::kPanId) && Has(ThreadTLV::kMasterKey) && Has(ThreadTLV::kExtendedPanId) && Has(ThreadTLV::kChannel); |
| } |
| |
| const ThreadTLV * OperationalDataset::Locate(uint8_t aType) const |
| { |
| const ThreadTLV * tlv = &Begin(); |
| const ThreadTLV * end = &End(); |
| |
| while (tlv < end) |
| { |
| if (tlv->GetType() == aType) |
| break; |
| tlv = tlv->GetNext(); |
| } |
| |
| assert(tlv < reinterpret_cast<const ThreadTLV *>(&mData[sizeof(mData)])); |
| |
| return tlv != end ? tlv : nullptr; |
| } |
| |
| void OperationalDataset::Remove(ThreadTLV & aThreadTLV) |
| { |
| uint8_t offset = static_cast<uint8_t>(reinterpret_cast<uint8_t *>(&aThreadTLV) - mData); |
| |
| if (offset < mLength && mLength >= (offset + aThreadTLV.GetSize())) |
| { |
| mLength = static_cast<uint8_t>(mLength - aThreadTLV.GetSize()); |
| memmove(&aThreadTLV, aThreadTLV.GetNext(), mLength - offset); |
| } |
| } |
| |
| void OperationalDataset::Remove(uint8_t aType) |
| { |
| ThreadTLV * tlv = Locate(aType); |
| |
| if (tlv != nullptr) |
| { |
| Remove(*tlv); |
| } |
| } |
| |
| ThreadTLV * OperationalDataset::MakeRoom(uint8_t aType, uint8_t aSize) |
| { |
| ThreadTLV * tlv = Locate(aType); |
| |
| size_t freeSpace = sizeof(mData) - mLength; |
| |
| if (tlv != nullptr) |
| { |
| if (freeSpace + tlv->GetSize() < aSize) |
| { |
| return nullptr; |
| } |
| |
| Remove(*tlv); |
| } |
| else if (freeSpace < aSize) |
| { |
| return nullptr; |
| } |
| |
| End().SetType(aType); |
| |
| return &End(); |
| } |
| |
| } // namespace Thread |
| } // namespace chip |