/* * * Copyright (c) 2020-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. */ /** * @file * This file defines CHIP binary header encode/decode. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace chip { namespace detail { // Figure out the max size of a packet we can allocate, including all headers. static constexpr size_t kMaxIPPacketSizeBytes = 1280; static constexpr size_t kMaxUDPAndIPHeaderSizeBytes = 48; static_assert(kMaxIPPacketSizeBytes >= kMaxUDPAndIPHeaderSizeBytes + CHIP_SYSTEM_HEADER_RESERVE_SIZE, "Matter headers and IP headers must fit in an MTU."); // Max space we have for our Application Payload and MIC, per spec. static constexpr size_t kMaxPerSpecApplicationPayloadAndMICSizeBytes = kMaxIPPacketSizeBytes - kMaxUDPAndIPHeaderSizeBytes - CHIP_SYSTEM_HEADER_RESERVE_SIZE; // Max space we have for our Application Payload and MIC in our actual packet // buffers. This is the size _excluding_ the header reserve. static constexpr size_t kMaxPacketBufferApplicationPayloadAndMICSizeBytes = System::PacketBuffer::kMaxSize; static constexpr size_t kMaxApplicationPayloadAndMICSizeBytes = std::min(kMaxPerSpecApplicationPayloadAndMICSizeBytes, kMaxPacketBufferApplicationPayloadAndMICSizeBytes); } // namespace detail static constexpr size_t kMaxTagLen = 16; static_assert(detail::kMaxApplicationPayloadAndMICSizeBytes > kMaxTagLen, "Need to be able to fit our tag in a message"); // This is somewhat of an under-estimate, because in practice any time we have a // tag we will not have source/destination node IDs, but above we are including // those in the header sizes. static constexpr size_t kMaxAppMessageLen = detail::kMaxApplicationPayloadAndMICSizeBytes - kMaxTagLen; static constexpr uint16_t kMsgUnicastSessionIdUnsecured = 0x0000; // Minimum header size of TCP + IPv6 without options. static constexpr size_t kMaxTCPAndIPHeaderSizeBytes = 60; // Max space for the Application Payload and MIC for large packet buffers // This is the size _excluding_ the header reserve. static constexpr size_t kMaxLargeApplicationPayloadAndMICSizeBytes = System::PacketBuffer::kLargeBufMaxSize - kMaxTCPAndIPHeaderSizeBytes; static constexpr size_t kMaxLargeAppMessageLen = kMaxLargeApplicationPayloadAndMICSizeBytes - kMaxTagLen; typedef int PacketHeaderFlags; namespace Header { enum class SessionType : uint8_t { kUnicastSession = 0, kGroupSession = 1, }; /** * @brief * The CHIP Exchange header flag bits. */ enum class ExFlagValues : uint8_t { /// Set when current message is sent by the initiator of an exchange. kExchangeFlag_Initiator = 0x01, /// Set when current message is an acknowledgment for a previously received message. kExchangeFlag_AckMsg = 0x02, /// Set when current message is requesting an acknowledgment from the recipient. kExchangeFlag_NeedsAck = 0x04, /// Secured Extension block is present. kExchangeFlag_SecuredExtension = 0x08, /// Set when a vendor id is prepended to the Message Protocol Id field. kExchangeFlag_VendorIdPresent = 0x10, }; // Message flags 8-bit value of the form // | 4 bits | 1 | 1 | 2 bits | // +---------+-------+--------| // | version | - | S | DSIZ // | | // | +---------------- Destination Id field // +-------------------- Source node Id present enum class MsgFlagValues : uint8_t { /// Header flag specifying that a source node id is included in the header. kSourceNodeIdPresent = 0b00000100, kDestinationNodeIdPresent = 0b00000001, kDestinationGroupIdPresent = 0b00000010, kDSIZReserved = 0b00000011, }; // Security flags 8-bit value of the form // | 1 | 1 | 1 | 3 | 2 bits | // +------------+---+--------| // | P | C | MX | - | SessionType // // With : // P = Privacy flag // C = Control Msg flag // MX = Message Extension enum class SecFlagValues : uint8_t { kPrivacyFlag = 0b10000000, kControlMsgFlag = 0b01000000, kMsgExtensionFlag = 0b00100000, }; enum SecFlagMask { kSessionTypeMask = 0b00000011, ///< Mask to extract sessionType }; using MsgFlags = BitFlags; using SecFlags = BitFlags; using ExFlags = BitFlags; } // namespace Header /** * Handles encoding/decoding of CHIP packet message headers. * * Packet headers are **UNENCRYPTED** and are placed at the start of * a message buffer. */ class PacketHeader { public: enum { kHeaderMinLength = 8, kPrivacyHeaderMinLength = 4, kPrivacyHeaderOffset = 4, }; /** * Gets the message counter set in the header. * * Message IDs are expecte to monotonically increase by one for each mesage * that has been sent. */ uint32_t GetMessageCounter() const { return mMessageCounter; } /** * Gets the source node id in the current message. * * NOTE: the source node id is optional and may be missing. */ const Optional & GetSourceNodeId() const { return mSourceNodeId; } /** * Gets the destination node id in the current message. * * NOTE: the destination node id is optional and may be missing. */ const Optional & GetDestinationNodeId() const { return mDestinationNodeId; } /** * Gets the destination group id in the current message. * * NOTE: the destination group id is optional and may be missing. */ const Optional & GetDestinationGroupId() const { return mDestinationGroupId; } uint16_t GetSessionId() const { return mSessionId; } Header::SessionType GetSessionType() const { return mSessionType; } uint8_t GetMessageFlags() const { return mMsgFlags.Raw(); } uint8_t GetSecurityFlags() const { return mSecFlags.Raw(); } bool HasPrivacyFlag() const { return mSecFlags.Has(Header::SecFlagValues::kPrivacyFlag); } bool HasSourceNodeId() const { return mMsgFlags.Has(Header::MsgFlagValues::kSourceNodeIdPresent); } bool HasDestinationNodeId() const { return mMsgFlags.Has(Header::MsgFlagValues::kDestinationNodeIdPresent); } bool HasDestinationGroupId() const { return mMsgFlags.Has(Header::MsgFlagValues::kDestinationGroupIdPresent); } void SetFlags(Header::SecFlagValues value) { mSecFlags.Set(value); } void SetFlags(Header::MsgFlagValues value) { mMsgFlags.Set(value); } void SetMessageFlags(uint8_t flags) { mMsgFlags.SetRaw(flags); } void SetSecurityFlags(uint8_t securityFlags) { mSecFlags.SetRaw(securityFlags); mSessionType = static_cast(securityFlags & Header::SecFlagMask::kSessionTypeMask); } bool IsGroupSession() const { return mSessionType == Header::SessionType::kGroupSession; } bool IsUnicastSession() const { return mSessionType == Header::SessionType::kUnicastSession; } bool IsSessionTypeValid() const { switch (mSessionType) { case Header::SessionType::kUnicastSession: return true; case Header::SessionType::kGroupSession: return true; default: return false; } } bool IsValidGroupMsg() const { // Check is based on spec 4.11.2 return (IsGroupSession() && HasSourceNodeId() && HasDestinationGroupId() && !IsSecureSessionControlMsg()); } bool IsValidMCSPMsg() const { // Check is based on spec 4.9.2.4 return (IsGroupSession() && HasSourceNodeId() && HasDestinationNodeId() && IsSecureSessionControlMsg()); } bool IsEncrypted() const { return !((mSessionId == kMsgUnicastSessionIdUnsecured) && IsUnicastSession()); } uint16_t MICTagLength() const { return (IsEncrypted()) ? chip::Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES : 0; } /** Check if it's a secure session control message. */ bool IsSecureSessionControlMsg() const { return mSecFlags.Has(Header::SecFlagValues::kControlMsgFlag); } PacketHeader & SetSecureSessionControlMsg(bool value) { mSecFlags.Set(Header::SecFlagValues::kControlMsgFlag, value); return *this; } PacketHeader & SetSourceNodeId(NodeId id) { mSourceNodeId.SetValue(id); mMsgFlags.Set(Header::MsgFlagValues::kSourceNodeIdPresent); return *this; } PacketHeader & SetSourceNodeId(Optional id) { mSourceNodeId = id; mMsgFlags.Set(Header::MsgFlagValues::kSourceNodeIdPresent, id.HasValue()); return *this; } PacketHeader & ClearSourceNodeId() { mSourceNodeId.ClearValue(); mMsgFlags.Clear(Header::MsgFlagValues::kSourceNodeIdPresent); return *this; } PacketHeader & SetDestinationNodeId(NodeId id) { mDestinationNodeId.SetValue(id); mMsgFlags.Set(Header::MsgFlagValues::kDestinationNodeIdPresent); return *this; } PacketHeader & SetDestinationNodeId(Optional id) { mDestinationNodeId = id; mMsgFlags.Set(Header::MsgFlagValues::kDestinationNodeIdPresent, id.HasValue()); return *this; } PacketHeader & ClearDestinationNodeId() { mDestinationNodeId.ClearValue(); mMsgFlags.Clear(Header::MsgFlagValues::kDestinationNodeIdPresent); return *this; } PacketHeader & SetDestinationGroupId(GroupId id) { mDestinationGroupId.SetValue(id); mMsgFlags.Set(Header::MsgFlagValues::kDestinationGroupIdPresent); return *this; } PacketHeader & SetDestinationGroupId(Optional id) { mDestinationGroupId = id; mMsgFlags.Set(Header::MsgFlagValues::kDestinationGroupIdPresent, id.HasValue()); return *this; } PacketHeader & ClearDestinationGroupId() { mDestinationGroupId.ClearValue(); mMsgFlags.Clear(Header::MsgFlagValues::kDestinationGroupIdPresent); return *this; } PacketHeader & SetSessionType(Header::SessionType type) { mSessionType = type; uint8_t typeMask = to_underlying(Header::kSessionTypeMask); mSecFlags.SetRaw(static_cast((mSecFlags.Raw() & ~typeMask) | (to_underlying(type) & typeMask))); return *this; } PacketHeader & SetSessionId(uint16_t id) { mSessionId = id; return *this; } PacketHeader & SetMessageCounter(uint32_t id) { mMessageCounter = id; return *this; } PacketHeader & SetUnsecured() { mSessionId = kMsgUnicastSessionIdUnsecured; mSessionType = Header::SessionType::kUnicastSession; return *this; } /** * Returns a pointer to the start of the privacy header * given a pointer to the start of the message. */ uint8_t * PrivacyHeader(uint8_t * msgBuf) const { return msgBuf + PacketHeader::kPrivacyHeaderOffset; } size_t PrivacyHeaderLength() const { size_t length = kPrivacyHeaderMinLength; if (mMsgFlags.Has(Header::MsgFlagValues::kSourceNodeIdPresent)) { length += sizeof(NodeId); } if (mMsgFlags.Has(Header::MsgFlagValues::kDestinationNodeIdPresent)) { length += sizeof(NodeId); } else if (mMsgFlags.Has(Header::MsgFlagValues::kDestinationGroupIdPresent)) { length += sizeof(GroupId); } return length; } size_t PayloadOffset() const { size_t offset = kPrivacyHeaderMinLength; offset += PrivacyHeaderLength(); return offset; } /** * A call to `Encode` will require at least this many bytes on the current * object to be successful. * * @return the number of bytes needed in a buffer to be able to Encode. */ uint16_t EncodeSizeBytes() const; /** * Decodes the fixed portion of the header fields from the given buffer. * The fixed header includes: message flags, session id, and security flags. * * @return CHIP_NO_ERROR on success. * * Possible failures: * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size * CHIP_ERROR_VERSION_MISMATCH if header version is not supported. */ CHIP_ERROR DecodeFixed(const System::PacketBufferHandle & buf); /** * Decodes a header from the given buffer. * * @param data - the buffer to read from * @param size - bytes available in the buffer * @param decode_size - number of bytes read from the buffer to decode the * object * * @return CHIP_NO_ERROR on success. * * Possible failures: * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size * CHIP_ERROR_VERSION_MISMATCH if header version is not supported. */ CHIP_ERROR Decode(const uint8_t * data, size_t size, uint16_t * decode_size); /** * A version of Decode that uses the type system to determine available * space. */ template inline CHIP_ERROR Decode(const uint8_t (&data)[N], uint16_t * decode_size) { return Decode(data, N, decode_size); } /** * A version of Decode that decodes from the start of a PacketBuffer and * consumes the bytes we decoded from. */ CHIP_ERROR DecodeAndConsume(const System::PacketBufferHandle & buf); /** * Encodes a header into the given buffer. * * @param data - the buffer to write to * @param size - space available in the buffer (in bytes) * @param encode_size - number of bytes written to the buffer. * * @return CHIP_NO_ERROR on success. * * Possible failures: * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size */ CHIP_ERROR Encode(uint8_t * data, size_t size, uint16_t * encode_size) const; /** * A version of Encode that uses the type system to determine available * space. */ template inline CHIP_ERROR Encode(uint8_t (&data)[N], uint16_t * encode_size) const { return Encode(data, N, encode_size); } /** * A version of Encode that encodes into a PacketBuffer before the * PacketBuffer's current data. */ CHIP_ERROR EncodeBeforeData(const System::PacketBufferHandle & buf) const; /** * A version of Encode that encodes into a PacketBuffer at the start of the * current data space. This assumes that someone has already preallocated * space for the header. */ inline CHIP_ERROR EncodeAtStart(const System::PacketBufferHandle & buf, uint16_t * encode_size) const { return Encode(buf->Start(), buf->DataLength(), encode_size); } private: /** * Decodes the fixed portion of the header fields from the stream reader. * The fixed header includes: message flags, session id, and security flags. * * @return CHIP_NO_ERROR on success. * * Possible failures: * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size * CHIP_ERROR_VERSION_MISMATCH if header version is not supported. */ CHIP_ERROR DecodeFixedCommon(Encoding::LittleEndian::Reader & reader); /// Represents the current encode/decode header version (4 bits) static constexpr uint8_t kMsgHeaderVersion = 0x00; /// Value expected to be incremented for each message sent. uint32_t mMessageCounter = 0; /// What node the message originated from Optional mSourceNodeId; /// Intended recipient of the message. Optional mDestinationNodeId; Optional mDestinationGroupId; /// Session ID uint16_t mSessionId = kMsgUnicastSessionIdUnsecured; Header::SessionType mSessionType = Header::SessionType::kUnicastSession; /// Flags read from the message. Header::MsgFlags mMsgFlags; Header::SecFlags mSecFlags; }; /** * Handles encoding/decoding of CHIP payload headers. * * Payload headers are **ENCRYPTED** and are placed at the start of * an encrypted message payload. */ class PayloadHeader { public: constexpr PayloadHeader() { SetProtocol(Protocols::NotSpecified); } constexpr PayloadHeader(const PayloadHeader &) = default; PayloadHeader & operator=(const PayloadHeader &) = default; /** Get the Session ID from this header. */ uint16_t GetExchangeID() const { return mExchangeID; } /** Get the Protocol ID from this header. */ Protocols::Id GetProtocolID() const { return mProtocolID; } /** Check whether the header has a given protocol */ bool HasProtocol(Protocols::Id protocol) const { return mProtocolID == protocol; } /** Get the secure msg type from this header. */ uint8_t GetMessageType() const { return mMessageType; } /** Get the raw exchange flags from this header. */ uint8_t GetExchangeFlags() const { return mExchangeFlags.Raw(); } /** Check whether the header has a given secure message type */ bool HasMessageType(uint8_t type) const { return mMessageType == type; } template ::value>> bool HasMessageType(MessageType type) const { return HasProtocol(Protocols::MessageTypeTraits::ProtocolId()) && HasMessageType(to_underlying(type)); } /** * Gets the Acknowledged Message Counter from this header. * * NOTE: the Acknowledged Message Counter is optional and may be missing. */ const Optional & GetAckMessageCounter() const { return mAckMessageCounter; } /** * Set the message type for this header. This requires setting the protocol * id as well, because the meaning of a message type is only relevant given * a specific protocol. * * This should only be used for cases when we don't have a strongly typed * message type and hence can't automatically determine the protocol from * the message type. */ PayloadHeader & SetMessageType(Protocols::Id protocol, uint8_t type) { SetProtocol(protocol); mMessageType = type; return *this; } /** Set the secure message type, with the protocol id derived from the message type. */ template ::value>> PayloadHeader & SetMessageType(MessageType type) { SetMessageType(Protocols::MessageTypeTraits::ProtocolId(), to_underlying(type)); return *this; } /** Set the security session ID for this header. */ PayloadHeader & SetExchangeID(uint16_t id) { mExchangeID = id; return *this; } /** Set the Initiator flag bit. */ PayloadHeader & SetInitiator(bool inInitiator) { mExchangeFlags.Set(Header::ExFlagValues::kExchangeFlag_Initiator, inInitiator); return *this; } PayloadHeader & SetAckMessageCounter(uint32_t id) { mAckMessageCounter.SetValue(id); mExchangeFlags.Set(Header::ExFlagValues::kExchangeFlag_AckMsg); return *this; } /** Set the AckMsg flag bit. */ PayloadHeader & SetAckMessageCounter(Optional id) { mAckMessageCounter = id; mExchangeFlags.Set(Header::ExFlagValues::kExchangeFlag_AckMsg, id.HasValue()); return *this; } /** Set the NeedsAck flag bit. */ PayloadHeader & SetNeedsAck(bool inNeedsAck) { mExchangeFlags.Set(Header::ExFlagValues::kExchangeFlag_NeedsAck, inNeedsAck); return *this; } /** * Determine whether the initiator of the exchange. * * @return Returns 'true' if it is the initiator, else 'false'. * */ bool IsInitiator() const { return mExchangeFlags.Has(Header::ExFlagValues::kExchangeFlag_Initiator); } /** * Determine whether the current message is an acknowledgment for a previously received message. * * @return Returns 'true' if current message is an acknowledgment, else 'false'. * */ bool IsAckMsg() const { return mExchangeFlags.Has(Header::ExFlagValues::kExchangeFlag_AckMsg); } /** * Determine whether current message is requesting an acknowledgment from the recipient. * * @return Returns 'true' if the current message is requesting an acknowledgment from the recipient, else 'false'. * */ bool NeedsAck() const { return mExchangeFlags.Has(Header::ExFlagValues::kExchangeFlag_NeedsAck); } /** * A call to `Encode` will require at least this many bytes on the current * object to be successful. * * @return the number of bytes needed in a buffer to be able to Encode. */ uint16_t EncodeSizeBytes() const; /** * Decodes the encrypted header fields from the given buffer. * * @param data - the buffer to read from * @param size - bytes available in the buffer * @param decode_size - number of bytes read from the buffer to decode the * object * * @return CHIP_NO_ERROR on success. * * Possible failures: * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size * CHIP_ERROR_VERSION_MISMATCH if header version is not supported. */ CHIP_ERROR Decode(const uint8_t * data, size_t size, uint16_t * decode_size); /** * A version of Decode that uses the type system to determine available * space. */ template inline CHIP_ERROR Decode(const uint8_t (&data)[N], uint16_t * decode_size) { return Decode(data, N, decode_size); } /** * A version of Decode that decodes from the start of a PacketBuffer and * consumes the bytes we decoded from. */ CHIP_ERROR DecodeAndConsume(const System::PacketBufferHandle & buf); /** * Encodes the encrypted part of the header into the given buffer. * * @param data - the buffer to write to * @param size - space available in the buffer (in bytes) * @param encode_size - number of bytes written to the buffer. * * @return CHIP_NO_ERROR on success. * * Possible failures: * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size */ CHIP_ERROR Encode(uint8_t * data, size_t size, uint16_t * encode_size) const; /** * A version of Encode that uses the type system to determine available * space. */ template inline CHIP_ERROR Encode(uint8_t (&data)[N], uint16_t * decode_size) const { return Encode(data, N, decode_size); } /** * A version of Encode that encodes into a PacketBuffer before the * PacketBuffer's current data. */ CHIP_ERROR EncodeBeforeData(const System::PacketBufferHandle & buf) const; /** * A version of Encode that encodes into a PacketBuffer at the start of the * current data space. This assumes that someone has already preallocated * space for the header. */ inline CHIP_ERROR EncodeAtStart(const System::PacketBufferHandle & buf, uint16_t * encode_size) const { return Encode(buf->Start(), buf->DataLength(), encode_size); } private: constexpr void SetProtocol(Protocols::Id protocol) { mExchangeFlags.Set(Header::ExFlagValues::kExchangeFlag_VendorIdPresent, protocol.GetVendorId() != VendorId::Common); mProtocolID = protocol; } constexpr bool HaveVendorId() const { return mExchangeFlags.Has(Header::ExFlagValues::kExchangeFlag_VendorIdPresent); } /// Packet type (application data, security control packets, e.g. pairing, /// configuration, rekey etc) uint8_t mMessageType = 0; /// Security session identifier uint16_t mExchangeID = 0; /// Protocol identifier Protocols::Id mProtocolID = Protocols::NotSpecified; /// Bit flag indicators for CHIP Exchange header Header::ExFlags mExchangeFlags; /// Message counter of a previous message that is being acknowledged by the current message Optional mAckMessageCounter; }; /** Handles encoding/decoding of CHIP message headers */ class MessageAuthenticationCode { public: const uint8_t * GetTag() const { return &mTag[0]; } /** Set the message auth tag for this header. */ MessageAuthenticationCode & SetTag(PacketHeader * header, const uint8_t * tag, size_t len) { const size_t tagLen = chip::Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES; if (tagLen > 0 && tagLen <= kMaxTagLen && len == tagLen) { memcpy(&mTag, tag, tagLen); } return *this; } /** * Decodes the Message Authentication Tag from the given buffer. * * @param packetHeader - header containing encryption information * @param data - the buffer to read from * @param size - bytes available in the buffer * @param decode_size - number of bytes read from the buffer to decode the * object * * @return CHIP_NO_ERROR on success. * * Possible failures: * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size * CHIP_ERROR_VERSION_MISMATCH if header version is not supported. */ CHIP_ERROR Decode(const PacketHeader & packetHeader, const uint8_t * data, size_t size, uint16_t * decode_size); /** * Encodes the Messae Authentication Tag into the given buffer. * * @param packetHeader - header containing encryption information * @param data - the buffer to write to * @param size - space available in the buffer (in bytes) * @param encode_size - number of bytes written to the buffer. * * @return CHIP_NO_ERROR on success. * * Possible failures: * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size */ CHIP_ERROR Encode(const PacketHeader & packetHeader, uint8_t * data, size_t size, uint16_t * encode_size) const; private: /// Message authentication tag generated at encryption of the message. uint8_t mTag[kMaxTagLen]; }; } // namespace chip