| /* |
| * |
| * 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 <protocols/bdx/BdxMessages.h> |
| |
| #include <lib/support/BufferReader.h> |
| #include <lib/support/BufferWriter.h> |
| #include <lib/support/CodeUtils.h> |
| |
| #include <limits> |
| #include <utility> |
| |
| 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<TransferControlFlags> proposedTransferCtl(Version & kVersionMask, TransferCtlOptions); |
| const bool widerange = |
| (StartOffset > std::numeric_limits<uint32_t>::max()) || (MaxLength > std::numeric_limits<uint32_t>::max()); |
| |
| BitFlags<RangeControlFlags> 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<uint32_t>(StartOffset)); |
| } |
| } |
| |
| if (MaxLength > 0) |
| { |
| if (widerange) |
| { |
| aBuffer.Put64(MaxLength); |
| } |
| else |
| { |
| aBuffer.Put32(static_cast<uint32_t>(MaxLength)); |
| } |
| } |
| |
| aBuffer.Put16(FileDesLength); |
| if (FileDesignator != nullptr) |
| { |
| aBuffer.Put(FileDesignator, static_cast<size_t>(FileDesLength)); |
| } |
| |
| if (Metadata != nullptr) |
| { |
| aBuffer.Put(Metadata, static_cast<size_t>(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<uint8_t>(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<uint16_t>(bufReader.OctetsRead() + FileDesLength); |
| Metadata = &bufStart[metadataStartIndex]; |
| MetadataLength = static_cast<uint16_t>(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<int>(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<unsigned>(TransferCtlOptions.Raw() | Version)); |
| ChipLogAutomation(" Range Control: 0x%X", static_cast<unsigned>(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<TransferControlFlags> transferCtl(Version & kVersionMask, TransferCtlFlags); |
| |
| aBuffer.Put(transferCtl.Raw()); |
| aBuffer.Put16(MaxBlockSize); |
| |
| if (Metadata != nullptr) |
| { |
| aBuffer.Put(Metadata, static_cast<size_t>(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<uint8_t>(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<unsigned>(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<TransferControlFlags> transferCtlFlags(Version & kVersionMask, TransferCtlFlags); |
| const bool widerange = (StartOffset > std::numeric_limits<uint32_t>::max()) || (Length > std::numeric_limits<uint32_t>::max()); |
| |
| BitFlags<RangeControlFlags> 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<uint32_t>(StartOffset)); |
| } |
| } |
| |
| if (Length > 0) |
| { |
| if (widerange) |
| { |
| aBuffer.Put64(Length); |
| } |
| else |
| { |
| aBuffer.Put32(static_cast<uint32_t>(Length)); |
| } |
| } |
| |
| if (Metadata != nullptr) |
| { |
| aBuffer.Put(Metadata, static_cast<size_t>(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<uint8_t>(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<unsigned int>(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 |