blob: 51088bbab90cc4a359686fa5b37721cdbfc1e545 [file] [log] [blame]
/*
*
* Copyright (c) 2022 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 "OTAImageHeader.h"
#include <lib/core/CHIPTLV.h>
#include <lib/support/BufferReader.h>
#include <lib/support/CodeUtils.h>
namespace chip {
namespace {
enum class Tag : uint8_t
{
kVendorId = 0,
kProductId = 1,
kSoftwareVersion = 2,
kSoftwareVersionString = 3,
kPayloadSize = 4,
kMinApplicableVersion = 5,
kMaxApplicableVersion = 6,
kReleaseNotesURL = 7,
kImageDigestType = 8,
kImageDigest = 9,
};
/// Length of the fixed portion of the Matter OTA image header: FileIdentifier (4B), TotalSize (8B) and HeaderSize (4B)
constexpr uint32_t kFixedHeaderSize = 16;
/// Maximum supported Matter OTA image header size
constexpr uint32_t kMaxHeaderSize = 1024;
/// Maximum size of the software version string
constexpr size_t kMaxSoftwareVersionStringSize = 64;
/// Maximum size of the release notes URL
constexpr size_t kMaxReleaseNotesURLSize = 256;
} // namespace
void OTAImageHeaderParser::Init()
{
mState = State::kInitialized;
mBufferOffset = 0;
mHeaderTlvSize = 0;
mBuffer.Alloc(kFixedHeaderSize);
}
void OTAImageHeaderParser::Clear()
{
mState = State::kNotInitialized;
mBufferOffset = 0;
mHeaderTlvSize = 0;
mBuffer.Free();
}
CHIP_ERROR OTAImageHeaderParser::AccumulateAndDecode(ByteSpan & buffer, OTAImageHeader & header)
{
CHIP_ERROR error = CHIP_NO_ERROR;
if (mState == State::kInitialized)
{
Append(buffer, kFixedHeaderSize - mBufferOffset);
error = DecodeFixed();
}
if (mState == State::kTlv)
{
Append(buffer, mHeaderTlvSize - mBufferOffset);
error = DecodeTlv(header);
}
if (error != CHIP_NO_ERROR && error != CHIP_ERROR_BUFFER_TOO_SMALL)
{
Clear();
}
return error;
}
void OTAImageHeaderParser::Append(ByteSpan & buffer, uint32_t numBytes)
{
numBytes = chip::min(numBytes, static_cast<uint32_t>(buffer.size()));
memcpy(&mBuffer[mBufferOffset], buffer.data(), numBytes);
mBufferOffset += numBytes;
buffer = buffer.SubSpan(numBytes);
}
CHIP_ERROR OTAImageHeaderParser::DecodeFixed()
{
ReturnErrorCodeIf(mBufferOffset < kFixedHeaderSize, CHIP_ERROR_BUFFER_TOO_SMALL);
Encoding::LittleEndian::Reader reader(mBuffer.Get(), mBufferOffset);
uint32_t fileIdentifier;
uint64_t totalSize;
ReturnErrorOnFailure(reader.Read32(&fileIdentifier).Read64(&totalSize).Read32(&mHeaderTlvSize).StatusCode());
ReturnErrorCodeIf(fileIdentifier != kOTAImageFileIdentifier, CHIP_ERROR_INVALID_FILE_IDENTIFIER);
// Safety check against malicious headers.
ReturnErrorCodeIf(mHeaderTlvSize > kMaxHeaderSize, CHIP_ERROR_NO_MEMORY);
ReturnErrorCodeIf(!mBuffer.Alloc(mHeaderTlvSize), CHIP_ERROR_NO_MEMORY);
mState = State::kTlv;
mBufferOffset = 0;
return CHIP_NO_ERROR;
}
CHIP_ERROR OTAImageHeaderParser::DecodeTlv(OTAImageHeader & header)
{
ReturnErrorCodeIf(mBufferOffset < mHeaderTlvSize, CHIP_ERROR_BUFFER_TOO_SMALL);
TLV::TLVReader tlvReader;
tlvReader.Init(mBuffer.Get(), mBufferOffset);
ReturnErrorOnFailure(tlvReader.Next(TLV::TLVType::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType outerType;
ReturnErrorOnFailure(tlvReader.EnterContainer(outerType));
ReturnErrorOnFailure(tlvReader.Next(TLV::ContextTag(to_underlying(Tag::kVendorId))));
ReturnErrorOnFailure(tlvReader.Get(header.mVendorId));
ReturnErrorOnFailure(tlvReader.Next(TLV::ContextTag(to_underlying(Tag::kProductId))));
ReturnErrorOnFailure(tlvReader.Get(header.mProductId));
ReturnErrorOnFailure(tlvReader.Next(TLV::ContextTag(to_underlying(Tag::kSoftwareVersion))));
ReturnErrorOnFailure(tlvReader.Get(header.mSoftwareVersion));
ReturnErrorOnFailure(tlvReader.Next(TLV::ContextTag(to_underlying(Tag::kSoftwareVersionString))));
ReturnErrorOnFailure(tlvReader.Get(header.mSoftwareVersionString));
ReturnErrorCodeIf(header.mSoftwareVersionString.size() > kMaxSoftwareVersionStringSize, CHIP_ERROR_INVALID_STRING_LENGTH);
ReturnErrorOnFailure(tlvReader.Next(TLV::ContextTag(to_underlying(Tag::kPayloadSize))));
ReturnErrorOnFailure(tlvReader.Get(header.mPayloadSize));
ReturnErrorOnFailure(tlvReader.Next());
if (tlvReader.GetTag() == TLV::ContextTag(to_underlying(Tag::kMinApplicableVersion)))
{
ReturnErrorOnFailure(tlvReader.Get(header.mMinApplicableVersion.Emplace()));
ReturnErrorOnFailure(tlvReader.Next());
}
if (tlvReader.GetTag() == TLV::ContextTag(to_underlying(Tag::kMaxApplicableVersion)))
{
ReturnErrorOnFailure(tlvReader.Get(header.mMaxApplicableVersion.Emplace()));
ReturnErrorOnFailure(tlvReader.Next());
}
if (tlvReader.GetTag() == TLV::ContextTag(to_underlying(Tag::kReleaseNotesURL)))
{
ReturnErrorOnFailure(tlvReader.Get(header.mReleaseNotesURL));
ReturnErrorCodeIf(header.mReleaseNotesURL.size() > kMaxReleaseNotesURLSize, CHIP_ERROR_INVALID_STRING_LENGTH);
ReturnErrorOnFailure(tlvReader.Next());
}
VerifyOrReturnError(tlvReader.GetTag() == TLV::ContextTag(to_underlying(Tag::kImageDigestType)),
CHIP_ERROR_UNEXPECTED_TLV_ELEMENT);
ReturnErrorOnFailure(tlvReader.Get(header.mImageDigestType));
ReturnErrorOnFailure(tlvReader.Next(TLV::ContextTag(to_underlying(Tag::kImageDigest))));
ReturnErrorOnFailure(tlvReader.Get(header.mImageDigest));
ReturnErrorOnFailure(tlvReader.ExitContainer(outerType));
return CHIP_NO_ERROR;
}
} // namespace chip