/* * * Copyright (c) 2020-2021 Project CHIP Authors * 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 * Implements utility methods for working with some complex BDX messages. */ #include #include #include #include #include #include namespace { constexpr uint8_t kVersionMask = 0x0F; } // namespace using namespace chip; using namespace chip::bdx; using namespace chip::Encoding::LittleEndian; // WARNING: this function should never return early, since MessageSize() relies on it to calculate // the size of the message (even if the message is incomplete or filled out incorrectly). BufferWriter & TransferInit::WriteToBuffer(BufferWriter & aBuffer) const { const BitFlags proposedTransferCtl(Version & kVersionMask, TransferCtlOptions); const bool widerange = (StartOffset > std::numeric_limits::max()) || (MaxLength > std::numeric_limits::max()); BitFlags rangeCtlFlags; rangeCtlFlags.Set(RangeControlFlags::kDefLen, MaxLength > 0); rangeCtlFlags.Set(RangeControlFlags::kStartOffset, StartOffset > 0); rangeCtlFlags.Set(RangeControlFlags::kWiderange, widerange); aBuffer.Put(proposedTransferCtl.Raw()); aBuffer.Put(rangeCtlFlags.Raw()); aBuffer.Put16(MaxBlockSize); if (StartOffset > 0) { if (widerange) { aBuffer.Put64(StartOffset); } else { aBuffer.Put32(static_cast(StartOffset)); } } if (MaxLength > 0) { if (widerange) { aBuffer.Put64(MaxLength); } else { aBuffer.Put32(static_cast(MaxLength)); } } aBuffer.Put16(FileDesLength); if (FileDesignator != nullptr) { aBuffer.Put(FileDesignator, static_cast(FileDesLength)); } if (Metadata != nullptr) { aBuffer.Put(Metadata, static_cast(MetadataLength)); } return aBuffer; } CHIP_ERROR TransferInit::Parse(System::PacketBufferHandle aBuffer) { uint8_t proposedTransferCtl; uint32_t tmpUint32Value = 0; // Used for reading non-wide length and offset fields uint8_t * bufStart = aBuffer->Start(); Reader bufReader(bufStart, aBuffer->DataLength()); ReturnErrorOnFailure( bufReader.Read8(&proposedTransferCtl).Read8(mRangeCtlFlags.RawStorage()).Read16(&MaxBlockSize).StatusCode()); Version = proposedTransferCtl & kVersionMask; TransferCtlOptions.SetRaw(static_cast(proposedTransferCtl & ~kVersionMask)); StartOffset = 0; if (mRangeCtlFlags.Has(RangeControlFlags::kStartOffset)) { if (mRangeCtlFlags.Has(RangeControlFlags::kWiderange)) { ReturnErrorOnFailure(bufReader.Read64(&StartOffset).StatusCode()); } else { ReturnErrorOnFailure(bufReader.Read32(&tmpUint32Value).StatusCode()); StartOffset = tmpUint32Value; } } MaxLength = 0; if (mRangeCtlFlags.Has(RangeControlFlags::kDefLen)) { if (mRangeCtlFlags.Has(RangeControlFlags::kWiderange)) { ReturnErrorOnFailure(bufReader.Read64(&MaxLength).StatusCode()); } else { ReturnErrorOnFailure(bufReader.Read32(&tmpUint32Value).StatusCode()); MaxLength = tmpUint32Value; } } ReturnErrorOnFailure(bufReader.Read16(&FileDesLength).StatusCode()); VerifyOrReturnError(bufReader.HasAtLeast(FileDesLength), CHIP_ERROR_MESSAGE_INCOMPLETE); FileDesignator = &bufStart[bufReader.OctetsRead()]; // Rest of message is metadata (could be empty) Metadata = nullptr; MetadataLength = 0; if (bufReader.Remaining() > FileDesLength) { uint16_t metadataStartIndex = static_cast(bufReader.OctetsRead() + FileDesLength); Metadata = &bufStart[metadataStartIndex]; MetadataLength = static_cast(aBuffer->DataLength() - metadataStartIndex); } // Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid. Buffer = std::move(aBuffer); return CHIP_NO_ERROR; } size_t TransferInit::MessageSize() const { BufferWriter emptyBuf(nullptr, 0); return WriteToBuffer(emptyBuf).Needed(); } #if CHIP_AUTOMATION_LOGGING void TransferInit::LogMessage(bdx::MessageType messageType) const { char fd[kMaxFileDesignatorLen]; snprintf(fd, sizeof(fd), "%.*s", static_cast(FileDesLength), FileDesignator); switch (messageType) { case MessageType::SendInit: ChipLogAutomation("SendInit"); break; case MessageType::ReceiveInit: ChipLogAutomation("ReceiveInit"); break; default: break; } ChipLogAutomation(" Proposed Transfer Control: 0x%X", static_cast(TransferCtlOptions.Raw() | Version)); ChipLogAutomation(" Range Control: 0x%X", static_cast(mRangeCtlFlags.Raw())); ChipLogAutomation(" Proposed Max Block Size: %u", MaxBlockSize); ChipLogAutomation(" Start Offset: 0x" ChipLogFormatX64, ChipLogValueX64(StartOffset)); ChipLogAutomation(" Proposed Max Length: 0x" ChipLogFormatX64, ChipLogValueX64(MaxLength)); ChipLogAutomation(" File Designator Length: %u", FileDesLength); ChipLogAutomation(" File Designator: %s", fd); } #endif // CHIP_AUTOMATION_LOGGING bool TransferInit::operator==(const TransferInit & another) const { if ((MetadataLength != another.MetadataLength) || (FileDesLength != another.FileDesLength)) { return false; } bool fileDesMatches = true; if (FileDesLength > 0) { fileDesMatches = (memcmp(FileDesignator, another.FileDesignator, FileDesLength) == 0); } bool metadataMatches = true; if (MetadataLength > 0) { metadataMatches = (memcmp(Metadata, another.Metadata, MetadataLength) == 0); } return ((Version == another.Version) && (TransferCtlOptions == another.TransferCtlOptions) && (StartOffset == another.StartOffset) && (MaxLength == another.MaxLength) && (MaxBlockSize == another.MaxBlockSize) && fileDesMatches && metadataMatches); } // WARNING: this function should never return early, since MessageSize() relies on it to calculate // the size of the message (even if the message is incomplete or filled out incorrectly). Encoding::LittleEndian::BufferWriter & SendAccept::WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const { const BitFlags transferCtl(Version & kVersionMask, TransferCtlFlags); aBuffer.Put(transferCtl.Raw()); aBuffer.Put16(MaxBlockSize); if (Metadata != nullptr) { aBuffer.Put(Metadata, static_cast(MetadataLength)); } return aBuffer; } CHIP_ERROR SendAccept::Parse(System::PacketBufferHandle aBuffer) { uint8_t transferCtl = 0; uint8_t * bufStart = aBuffer->Start(); Reader bufReader(bufStart, aBuffer->DataLength()); ReturnErrorOnFailure(bufReader.Read8(&transferCtl).Read16(&MaxBlockSize).StatusCode()); Version = transferCtl & kVersionMask; // Only one of these values should be set. It is up to the caller to verify this. TransferCtlFlags.SetRaw(static_cast(transferCtl & ~kVersionMask)); // Rest of message is metadata (could be empty) Metadata = nullptr; MetadataLength = 0; if (bufReader.Remaining() > 0) { Metadata = &bufStart[bufReader.OctetsRead()]; MetadataLength = bufReader.Remaining(); } // Retain ownership of the packet buffer so that the Metadata pointer remains valid. Buffer = std::move(aBuffer); return CHIP_NO_ERROR; } size_t SendAccept::MessageSize() const { BufferWriter emptyBuf(nullptr, 0); return WriteToBuffer(emptyBuf).Needed(); } #if CHIP_AUTOMATION_LOGGING void SendAccept::LogMessage(bdx::MessageType messageType) const { (void) messageType; ChipLogAutomation("SendAccept"); ChipLogAutomation(" Transfer Control: 0x%X", static_cast(TransferCtlFlags.Raw() | Version)); ChipLogAutomation(" Max Block Size: %u", MaxBlockSize); } #endif // CHIP_AUTOMATION_LOGGING bool SendAccept::operator==(const SendAccept & another) const { if (MetadataLength != another.MetadataLength) { return false; } bool metadataMatches = true; if (MetadataLength > 0) { metadataMatches = (memcmp(Metadata, another.Metadata, MetadataLength) == 0); } return ((Version == another.Version) && (TransferCtlFlags == another.TransferCtlFlags) && (MaxBlockSize == another.MaxBlockSize) && metadataMatches); } // WARNING: this function should never return early, since MessageSize() relies on it to calculate // the size of the message (even if the message is incomplete or filled out incorrectly). Encoding::LittleEndian::BufferWriter & ReceiveAccept::WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const { const BitFlags transferCtlFlags(Version & kVersionMask, TransferCtlFlags); const bool widerange = (StartOffset > std::numeric_limits::max()) || (Length > std::numeric_limits::max()); BitFlags rangeCtlFlags; rangeCtlFlags.Set(RangeControlFlags::kDefLen, Length > 0); rangeCtlFlags.Set(RangeControlFlags::kStartOffset, StartOffset > 0); rangeCtlFlags.Set(RangeControlFlags::kWiderange, widerange); aBuffer.Put(transferCtlFlags.Raw()); aBuffer.Put(rangeCtlFlags.Raw()); aBuffer.Put16(MaxBlockSize); if (StartOffset > 0) { if (widerange) { aBuffer.Put64(StartOffset); } else { aBuffer.Put32(static_cast(StartOffset)); } } if (Length > 0) { if (widerange) { aBuffer.Put64(Length); } else { aBuffer.Put32(static_cast(Length)); } } if (Metadata != nullptr) { aBuffer.Put(Metadata, static_cast(MetadataLength)); } return aBuffer; } CHIP_ERROR ReceiveAccept::Parse(System::PacketBufferHandle aBuffer) { uint8_t transferCtl = 0; uint32_t tmpUint32Value = 0; // Used for reading non-wide length and offset fields uint8_t * bufStart = aBuffer->Start(); Reader bufReader(bufStart, aBuffer->DataLength()); ReturnErrorOnFailure(bufReader.Read8(&transferCtl).Read8(mRangeCtlFlags.RawStorage()).Read16(&MaxBlockSize).StatusCode()); Version = transferCtl & kVersionMask; // Only one of these values should be set. It is up to the caller to verify this. TransferCtlFlags.SetRaw(static_cast(transferCtl & ~kVersionMask)); StartOffset = 0; if (mRangeCtlFlags.Has(RangeControlFlags::kStartOffset)) { if (mRangeCtlFlags.Has(RangeControlFlags::kWiderange)) { ReturnErrorOnFailure(bufReader.Read64(&StartOffset).StatusCode()); } else { ReturnErrorOnFailure(bufReader.Read32(&tmpUint32Value).StatusCode()); StartOffset = tmpUint32Value; } } Length = 0; if (mRangeCtlFlags.Has(RangeControlFlags::kDefLen)) { if (mRangeCtlFlags.Has(RangeControlFlags::kWiderange)) { ReturnErrorOnFailure(bufReader.Read64(&Length).StatusCode()); } else { ReturnErrorOnFailure(bufReader.Read32(&tmpUint32Value).StatusCode()); Length = tmpUint32Value; } } // Rest of message is metadata (could be empty) Metadata = nullptr; MetadataLength = 0; if (bufReader.Remaining() > 0) { Metadata = &bufStart[bufReader.OctetsRead()]; MetadataLength = bufReader.Remaining(); } // Retain ownership of the packet buffer so that the Metadata pointer remains valid. Buffer = std::move(aBuffer); return CHIP_NO_ERROR; } size_t ReceiveAccept::MessageSize() const { BufferWriter emptyBuf(nullptr, 0); return WriteToBuffer(emptyBuf).Needed(); } #if CHIP_AUTOMATION_LOGGING void ReceiveAccept::LogMessage(bdx::MessageType messageType) const { (void) messageType; ChipLogAutomation("ReceiveAccept"); ChipLogAutomation(" Transfer Control: 0x%X", TransferCtlFlags.Raw() | Version); ChipLogAutomation(" Range Control: 0x%X", mRangeCtlFlags.Raw()); ChipLogAutomation(" Max Block Size: %u", MaxBlockSize); ChipLogAutomation(" Length: 0x" ChipLogFormatX64, ChipLogValueX64(Length)); } #endif // CHIP_AUTOMATION_LOGGING bool ReceiveAccept::operator==(const ReceiveAccept & another) const { if (MetadataLength != another.MetadataLength) { return false; } bool metadataMatches = true; if (MetadataLength > 0) { metadataMatches = (memcmp(Metadata, another.Metadata, MetadataLength) == 0); } return ((Version == another.Version) && (TransferCtlFlags == another.TransferCtlFlags) && (StartOffset == another.StartOffset) && (MaxBlockSize == another.MaxBlockSize) && (Length == another.Length) && metadataMatches); } // WARNING: this function should never return early, since MessageSize() relies on it to calculate // the size of the message (even if the message is incomplete or filled out incorrectly). Encoding::LittleEndian::BufferWriter & CounterMessage::WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const { return aBuffer.Put32(BlockCounter); } CHIP_ERROR CounterMessage::Parse(System::PacketBufferHandle aBuffer) { uint8_t * bufStart = aBuffer->Start(); Reader bufReader(bufStart, aBuffer->DataLength()); return bufReader.Read32(&BlockCounter).StatusCode(); } size_t CounterMessage::MessageSize() const { BufferWriter emptyBuf(nullptr, 0); return WriteToBuffer(emptyBuf).Needed(); } bool CounterMessage::operator==(const CounterMessage & another) const { return (BlockCounter == another.BlockCounter); } #if CHIP_AUTOMATION_LOGGING void CounterMessage::LogMessage(bdx::MessageType messageType) const { switch (messageType) { case MessageType::BlockQuery: ChipLogAutomation("BlockQuery"); break; case MessageType::BlockAck: ChipLogAutomation("BlockAck"); break; case MessageType::BlockAckEOF: ChipLogAutomation("BlockAckEOF"); break; default: break; } ChipLogAutomation(" Block Counter: %" PRIu32, BlockCounter); } #endif // CHIP_AUTOMATION_LOGGING // WARNING: this function should never return early, since MessageSize() relies on it to calculate // the size of the message (even if the message is incomplete or filled out incorrectly). Encoding::LittleEndian::BufferWriter & DataBlock::WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const { aBuffer.Put32(BlockCounter); if (Data != nullptr) { aBuffer.Put(Data, DataLength); } return aBuffer; } CHIP_ERROR DataBlock::Parse(System::PacketBufferHandle aBuffer) { uint8_t * bufStart = aBuffer->Start(); Reader bufReader(bufStart, aBuffer->DataLength()); ReturnErrorOnFailure(bufReader.Read32(&BlockCounter).StatusCode()); // Rest of message is data Data = nullptr; DataLength = 0; if (bufReader.Remaining() > 0) { Data = &bufStart[bufReader.OctetsRead()]; DataLength = bufReader.Remaining(); } // Retain ownership of the packet buffer so that the Data pointer remains valid. Buffer = std::move(aBuffer); return CHIP_NO_ERROR; } size_t DataBlock::MessageSize() const { BufferWriter emptyBuf(nullptr, 0); return WriteToBuffer(emptyBuf).Needed(); } #if CHIP_AUTOMATION_LOGGING void DataBlock::LogMessage(bdx::MessageType messageType) const { switch (messageType) { case MessageType::Block: ChipLogAutomation("Block"); break; case MessageType::BlockEOF: ChipLogAutomation("BlockEOF"); break; default: break; } ChipLogAutomation(" Block Counter: %" PRIu32, BlockCounter); ChipLogAutomation(" Data Length: %u", static_cast(DataLength)); } #endif // CHIP_AUTOMATION_LOGGING bool DataBlock::operator==(const DataBlock & another) const { if (DataLength != another.DataLength) { return false; } bool dataMatches = true; if (DataLength > 0) { dataMatches = memcmp(Data, another.Data, DataLength) == 0; } return ((BlockCounter == another.BlockCounter) && dataMatches); } // WARNING: this function should never return early, since MessageSize() relies on it to calculate // the size of the message (even if the message is incomplete or filled out incorrectly). Encoding::LittleEndian::BufferWriter & BlockQueryWithSkip::WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const { aBuffer.Put32(BlockCounter); aBuffer.Put64(BytesToSkip); return aBuffer; } CHIP_ERROR BlockQueryWithSkip::Parse(System::PacketBufferHandle aBuffer) { uint8_t * bufStart = aBuffer->Start(); Reader bufReader(bufStart, aBuffer->DataLength()); return bufReader.Read32(&BlockCounter).Read64(&BytesToSkip).StatusCode(); } size_t BlockQueryWithSkip::MessageSize() const { BufferWriter emptyBuf(nullptr, 0); return WriteToBuffer(emptyBuf).Needed(); } bool BlockQueryWithSkip::operator==(const BlockQueryWithSkip & another) const { return (BlockCounter == another.BlockCounter && BytesToSkip == another.BytesToSkip); } #if CHIP_AUTOMATION_LOGGING void BlockQueryWithSkip::LogMessage(bdx::MessageType messageType) const { ChipLogAutomation("BlockQueryWithSkip"); ChipLogAutomation(" Block Counter: %" PRIu32, BlockCounter); ChipLogAutomation(" Bytes To Skip: 0x" ChipLogFormatX64, ChipLogValueX64(BytesToSkip)); } #endif // CHIP_AUTOMATION_LOGGING