| /* |
| * Copyright (c) 2016, The OpenThread Authors. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the copyright holder nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @file |
| * This file implements 6LoWPAN header compression. |
| */ |
| |
| #include "lowpan.hpp" |
| |
| #include "common/code_utils.hpp" |
| #include "common/debug.hpp" |
| #include "common/encoding.hpp" |
| #include "common/locator_getters.hpp" |
| #include "common/numeric_limits.hpp" |
| #include "instance/instance.hpp" |
| #include "net/ip6.hpp" |
| #include "net/udp6.hpp" |
| #include "thread/network_data_leader.hpp" |
| #include "thread/thread_netif.hpp" |
| |
| namespace ot { |
| namespace Lowpan { |
| |
| Lowpan::Lowpan(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| { |
| } |
| |
| void Lowpan::FindContextForId(uint8_t aContextId, Context &aContext) const |
| { |
| if (Get<NetworkData::Leader>().GetContext(aContextId, aContext) != kErrorNone) |
| { |
| aContext.Clear(); |
| } |
| } |
| |
| void Lowpan::FindContextToCompressAddress(const Ip6::Address &aIp6Address, Context &aContext) const |
| { |
| Error error = Get<NetworkData::Leader>().GetContext(aIp6Address, aContext); |
| |
| if ((error != kErrorNone) || !aContext.mCompressFlag) |
| { |
| aContext.Clear(); |
| } |
| } |
| |
| Error Lowpan::ComputeIid(const Mac::Address &aMacAddr, const Context &aContext, Ip6::InterfaceIdentifier &aIid) |
| { |
| Error error = kErrorNone; |
| |
| switch (aMacAddr.GetType()) |
| { |
| case Mac::Address::kTypeShort: |
| aIid.SetToLocator(aMacAddr.GetShort()); |
| break; |
| |
| case Mac::Address::kTypeExtended: |
| aIid.SetFromExtAddress(aMacAddr.GetExtended()); |
| break; |
| |
| default: |
| ExitNow(error = kErrorParse); |
| } |
| |
| aIid.ApplyPrefix(aContext.mPrefix); |
| |
| exit: |
| return error; |
| } |
| |
| Error Lowpan::CompressSourceIid(const Mac::Address &aMacAddr, |
| const Ip6::Address &aIpAddr, |
| const Context &aContext, |
| uint16_t &aHcCtl, |
| FrameBuilder &aFrameBuilder) |
| { |
| Error error = kErrorNone; |
| Ip6::InterfaceIdentifier iid; |
| |
| IgnoreError(ComputeIid(aMacAddr, aContext, iid)); |
| |
| if (iid == aIpAddr.GetIid()) |
| { |
| aHcCtl |= kHcSrcAddrMode3; |
| } |
| else if (aIpAddr.GetIid().IsLocator()) |
| { |
| aHcCtl |= kHcSrcAddrMode2; |
| error = aFrameBuilder.AppendBigEndianUint16(aIpAddr.GetIid().GetLocator()); |
| } |
| else |
| { |
| aHcCtl |= kHcSrcAddrMode1; |
| error = aFrameBuilder.Append(aIpAddr.GetIid()); |
| } |
| |
| return error; |
| } |
| |
| Error Lowpan::CompressDestinationIid(const Mac::Address &aMacAddr, |
| const Ip6::Address &aIpAddr, |
| const Context &aContext, |
| uint16_t &aHcCtl, |
| FrameBuilder &aFrameBuilder) |
| { |
| Error error = kErrorNone; |
| Ip6::InterfaceIdentifier iid; |
| |
| IgnoreError(ComputeIid(aMacAddr, aContext, iid)); |
| |
| if (iid == aIpAddr.GetIid()) |
| { |
| aHcCtl |= kHcDstAddrMode3; |
| } |
| else if (aIpAddr.GetIid().IsLocator()) |
| { |
| aHcCtl |= kHcDstAddrMode2; |
| error = aFrameBuilder.AppendBigEndianUint16(aIpAddr.GetIid().GetLocator()); |
| } |
| else |
| { |
| aHcCtl |= kHcDstAddrMode1; |
| error = aFrameBuilder.Append(aIpAddr.GetIid()); |
| } |
| |
| return error; |
| } |
| |
| Error Lowpan::CompressMulticast(const Ip6::Address &aIpAddr, uint16_t &aHcCtl, FrameBuilder &aFrameBuilder) |
| { |
| Error error = kErrorNone; |
| Context multicastContext; |
| |
| aHcCtl |= kHcMulticast; |
| |
| for (unsigned int i = 2; i < sizeof(Ip6::Address); i++) |
| { |
| if (aIpAddr.mFields.m8[i]) |
| { |
| // Check if multicast address can be compressed to 8-bits (ff02::00xx) |
| if (aIpAddr.mFields.m8[1] == 0x02 && i >= 15) |
| { |
| aHcCtl |= kHcDstAddrMode3; |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(aIpAddr.mFields.m8[15])); |
| } |
| // Check if multicast address can be compressed to 32-bits (ffxx::00xx:xxxx) |
| else if (i >= 13) |
| { |
| aHcCtl |= kHcDstAddrMode2; |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(aIpAddr.mFields.m8[1])); |
| SuccessOrExit(error = aFrameBuilder.AppendBytes(aIpAddr.mFields.m8 + 13, 3)); |
| } |
| // Check if multicast address can be compressed to 48-bits (ffxx::00xx:xxxx:xxxx) |
| else if (i >= 11) |
| { |
| aHcCtl |= kHcDstAddrMode1; |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(aIpAddr.mFields.m8[1])); |
| SuccessOrExit(error = aFrameBuilder.AppendBytes(aIpAddr.mFields.m8 + 11, 5)); |
| } |
| else |
| { |
| // Check if multicast address can be compressed using Context ID 0 (mesh local prefix) |
| FindContextForId(0, multicastContext); |
| |
| if (multicastContext.mPrefix.GetLength() == aIpAddr.mFields.m8[3] && |
| memcmp(multicastContext.mPrefix.GetBytes(), aIpAddr.mFields.m8 + 4, 8) == 0) |
| { |
| aHcCtl |= kHcDstAddrContext | kHcDstAddrMode0; |
| SuccessOrExit(error = aFrameBuilder.AppendBytes(aIpAddr.mFields.m8 + 1, 2)); |
| SuccessOrExit(error = aFrameBuilder.AppendBytes(aIpAddr.mFields.m8 + 12, 4)); |
| } |
| else |
| { |
| SuccessOrExit(error = aFrameBuilder.Append(aIpAddr)); |
| } |
| } |
| |
| break; |
| } |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error Lowpan::Compress(Message &aMessage, const Mac::Addresses &aMacAddrs, FrameBuilder &aFrameBuilder) |
| { |
| Error error = kErrorNone; |
| uint8_t headerDepth = 0xff; |
| |
| while (headerDepth > 0) |
| { |
| FrameBuilder frameBuilder = aFrameBuilder; |
| |
| error = Compress(aMessage, aMacAddrs, aFrameBuilder, headerDepth); |
| |
| // We exit if `Compress()` is successful. Otherwise we reset |
| // the `aFrameBuidler` to its earlier state (remove all |
| // appended content from the failed `Compress()` call) and |
| // try again with a different `headerDepth`. |
| |
| VerifyOrExit(error != kErrorNone); |
| aFrameBuilder = frameBuilder; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error Lowpan::Compress(Message &aMessage, |
| const Mac::Addresses &aMacAddrs, |
| FrameBuilder &aFrameBuilder, |
| uint8_t &aHeaderDepth) |
| { |
| Error error = kErrorNone; |
| uint16_t startOffset = aMessage.GetOffset(); |
| uint16_t hcCtl = kHcDispatch; |
| uint16_t hcCtlOffset = 0; |
| Ip6::Header ip6Header; |
| uint8_t *ip6HeaderBytes = reinterpret_cast<uint8_t *>(&ip6Header); |
| Context srcContext, dstContext; |
| uint8_t nextHeader; |
| uint8_t ecn; |
| uint8_t dscp; |
| uint8_t headerDepth = 0; |
| uint8_t headerMaxDepth = aHeaderDepth; |
| |
| SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), ip6Header)); |
| |
| FindContextToCompressAddress(ip6Header.GetSource(), srcContext); |
| FindContextToCompressAddress(ip6Header.GetDestination(), dstContext); |
| |
| // Lowpan HC Control Bits |
| hcCtlOffset = aFrameBuilder.GetLength(); |
| SuccessOrExit(error = aFrameBuilder.AppendBigEndianUint16(hcCtl)); |
| |
| // Context Identifier |
| if (srcContext.mContextId != 0 || dstContext.mContextId != 0) |
| { |
| hcCtl |= kHcContextId; |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(((srcContext.mContextId << 4) | dstContext.mContextId) & 0xff)); |
| } |
| |
| dscp = ((ip6HeaderBytes[0] << 2) & 0x3c) | (ip6HeaderBytes[1] >> 6); |
| ecn = (ip6HeaderBytes[1] << 2) & 0xc0; |
| |
| // Flow Label |
| if (((ip6HeaderBytes[1] & 0x0f) == 0) && ((ip6HeaderBytes[2]) == 0) && ((ip6HeaderBytes[3]) == 0)) |
| { |
| if (dscp == 0 && ecn == 0) |
| { |
| // Elide Flow Label and Traffic Class. |
| hcCtl |= kHcTrafficClass | kHcFlowLabel; |
| } |
| else |
| { |
| // Elide Flow Label and carry Traffic Class in-line. |
| hcCtl |= kHcFlowLabel; |
| |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(ecn | dscp)); |
| } |
| } |
| else if (dscp == 0) |
| { |
| // Carry Flow Label and ECN only with 2-bit padding. |
| hcCtl |= kHcTrafficClass; |
| |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(ecn | (ip6HeaderBytes[1] & 0x0f))); |
| SuccessOrExit(error = aFrameBuilder.AppendBytes(ip6HeaderBytes + 2, 2)); |
| } |
| else |
| { |
| // Carry Flow Label and Traffic Class in-line. |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(ecn | dscp)); |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(ip6HeaderBytes[1] & 0x0f)); |
| SuccessOrExit(error = aFrameBuilder.AppendBytes(ip6HeaderBytes + 2, 2)); |
| } |
| |
| // Next Header |
| switch (ip6Header.GetNextHeader()) |
| { |
| case Ip6::kProtoHopOpts: |
| case Ip6::kProtoUdp: |
| case Ip6::kProtoIp6: |
| if (headerDepth + 1 < headerMaxDepth) |
| { |
| hcCtl |= kHcNextHeader; |
| break; |
| } |
| OT_FALL_THROUGH; |
| |
| default: |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(static_cast<uint8_t>(ip6Header.GetNextHeader()))); |
| break; |
| } |
| |
| // Hop Limit |
| switch (ip6Header.GetHopLimit()) |
| { |
| case 1: |
| hcCtl |= kHcHopLimit1; |
| break; |
| |
| case 64: |
| hcCtl |= kHcHopLimit64; |
| break; |
| |
| case 255: |
| hcCtl |= kHcHopLimit255; |
| break; |
| |
| default: |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(ip6Header.GetHopLimit())); |
| break; |
| } |
| |
| // Source Address |
| if (ip6Header.GetSource().IsUnspecified()) |
| { |
| hcCtl |= kHcSrcAddrContext; |
| } |
| else if (ip6Header.GetSource().IsLinkLocal()) |
| { |
| SuccessOrExit( |
| error = CompressSourceIid(aMacAddrs.mSource, ip6Header.GetSource(), srcContext, hcCtl, aFrameBuilder)); |
| } |
| else if (srcContext.mIsValid) |
| { |
| hcCtl |= kHcSrcAddrContext; |
| SuccessOrExit( |
| error = CompressSourceIid(aMacAddrs.mSource, ip6Header.GetSource(), srcContext, hcCtl, aFrameBuilder)); |
| } |
| else |
| { |
| SuccessOrExit(error = aFrameBuilder.Append(ip6Header.GetSource())); |
| } |
| |
| // Destination Address |
| if (ip6Header.GetDestination().IsMulticast()) |
| { |
| SuccessOrExit(error = CompressMulticast(ip6Header.GetDestination(), hcCtl, aFrameBuilder)); |
| } |
| else if (ip6Header.GetDestination().IsLinkLocal()) |
| { |
| SuccessOrExit(error = CompressDestinationIid(aMacAddrs.mDestination, ip6Header.GetDestination(), dstContext, |
| hcCtl, aFrameBuilder)); |
| } |
| else if (dstContext.mIsValid) |
| { |
| hcCtl |= kHcDstAddrContext; |
| SuccessOrExit(error = CompressDestinationIid(aMacAddrs.mDestination, ip6Header.GetDestination(), dstContext, |
| hcCtl, aFrameBuilder)); |
| } |
| else |
| { |
| SuccessOrExit(error = aFrameBuilder.Append(ip6Header.GetDestination())); |
| } |
| |
| headerDepth++; |
| |
| aMessage.MoveOffset(sizeof(ip6Header)); |
| |
| nextHeader = static_cast<uint8_t>(ip6Header.GetNextHeader()); |
| |
| while (headerDepth < headerMaxDepth) |
| { |
| switch (nextHeader) |
| { |
| case Ip6::kProtoHopOpts: |
| SuccessOrExit(error = CompressExtensionHeader(aMessage, aFrameBuilder, nextHeader)); |
| break; |
| |
| case Ip6::kProtoUdp: |
| error = CompressUdp(aMessage, aFrameBuilder); |
| ExitNow(); |
| |
| case Ip6::kProtoIp6: |
| // For IP-in-IP the NH bit of the LOWPAN_NHC encoding MUST be set to zero. |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(kExtHdrDispatch | kExtHdrEidIp6)); |
| |
| error = Compress(aMessage, aMacAddrs, aFrameBuilder); |
| |
| OT_FALL_THROUGH; |
| |
| default: |
| ExitNow(); |
| } |
| |
| headerDepth++; |
| } |
| |
| exit: |
| aHeaderDepth = headerDepth; |
| |
| if (error == kErrorNone) |
| { |
| aFrameBuilder.Write<uint16_t>(hcCtlOffset, BigEndian::HostSwap16(hcCtl)); |
| } |
| else |
| { |
| aMessage.SetOffset(startOffset); |
| } |
| |
| return error; |
| } |
| |
| Error Lowpan::CompressExtensionHeader(Message &aMessage, FrameBuilder &aFrameBuilder, uint8_t &aNextHeader) |
| { |
| Error error = kErrorNone; |
| uint16_t startOffset = aMessage.GetOffset(); |
| Ip6::ExtensionHeader extHeader; |
| uint16_t len; |
| uint16_t padLength = 0; |
| uint8_t tmpByte; |
| |
| SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), extHeader)); |
| aMessage.MoveOffset(sizeof(extHeader)); |
| |
| tmpByte = kExtHdrDispatch | kExtHdrEidHbh; |
| |
| switch (extHeader.GetNextHeader()) |
| { |
| case Ip6::kProtoUdp: |
| case Ip6::kProtoIp6: |
| tmpByte |= kExtHdrNextHeader; |
| break; |
| |
| default: |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(tmpByte)); |
| tmpByte = static_cast<uint8_t>(extHeader.GetNextHeader()); |
| break; |
| } |
| |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(tmpByte)); |
| |
| len = extHeader.GetSize() - sizeof(extHeader); |
| |
| // RFC 6282 does not support compressing large extension headers |
| VerifyOrExit(len <= kExtHdrMaxLength, error = kErrorFailed); |
| |
| // RFC 6282 says: "IPv6 Hop-by-Hop and Destination Options Headers may use a trailing |
| // Pad1 or PadN to achieve 8-octet alignment. When there is a single trailing Pad1 or PadN |
| // option of 7 octets or less and the containing header is a multiple of 8 octets, the trailing |
| // Pad1 or PadN option MAY be elided by the compressor." |
| if (aNextHeader == Ip6::kProtoHopOpts || aNextHeader == Ip6::kProtoDstOpts) |
| { |
| uint16_t offset = aMessage.GetOffset(); |
| uint16_t endOffset = offset + len; |
| bool hasOption = false; |
| Ip6::Option option; |
| |
| for (; offset < endOffset; offset += option.GetSize()) |
| { |
| SuccessOrExit(error = option.ParseFrom(aMessage, offset, endOffset)); |
| hasOption = true; |
| } |
| |
| // Check if the last option can be compressed. |
| if (hasOption && option.IsPadding()) |
| { |
| padLength = option.GetSize(); |
| len -= padLength; |
| } |
| } |
| |
| VerifyOrExit(aMessage.GetOffset() + len + padLength <= aMessage.GetLength(), error = kErrorParse); |
| |
| aNextHeader = static_cast<uint8_t>(extHeader.GetNextHeader()); |
| |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(static_cast<uint8_t>(len))); |
| SuccessOrExit(error = aFrameBuilder.AppendBytesFromMessage(aMessage, aMessage.GetOffset(), len)); |
| aMessage.MoveOffset(len + padLength); |
| |
| exit: |
| if (error != kErrorNone) |
| { |
| aMessage.SetOffset(startOffset); |
| } |
| |
| return error; |
| } |
| |
| Error Lowpan::CompressUdp(Message &aMessage, FrameBuilder &aFrameBuilder) |
| { |
| Error error = kErrorNone; |
| uint16_t startOffset = aMessage.GetOffset(); |
| Ip6::Udp::Header udpHeader; |
| uint16_t source; |
| uint16_t destination; |
| |
| SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), udpHeader)); |
| |
| source = udpHeader.GetSourcePort(); |
| destination = udpHeader.GetDestinationPort(); |
| |
| if ((source & 0xfff0) == 0xf0b0 && (destination & 0xfff0) == 0xf0b0) |
| { |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(kUdpDispatch | 3)); |
| SuccessOrExit(error = aFrameBuilder.AppendUint8((((source & 0xf) << 4) | (destination & 0xf)) & 0xff)); |
| } |
| else if ((source & 0xff00) == 0xf000) |
| { |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(kUdpDispatch | 2)); |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(source & 0xff)); |
| SuccessOrExit(error = aFrameBuilder.AppendBigEndianUint16(destination)); |
| } |
| else if ((destination & 0xff00) == 0xf000) |
| { |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(kUdpDispatch | 1)); |
| SuccessOrExit(error = aFrameBuilder.AppendBigEndianUint16(source)); |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(destination & 0xff)); |
| } |
| else |
| { |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(kUdpDispatch)); |
| SuccessOrExit(error = aFrameBuilder.AppendBytes(&udpHeader, Ip6::Udp::Header::kLengthFieldOffset)); |
| } |
| |
| SuccessOrExit(error = aFrameBuilder.AppendBigEndianUint16(udpHeader.GetChecksum())); |
| |
| aMessage.MoveOffset(sizeof(udpHeader)); |
| |
| exit: |
| if (error != kErrorNone) |
| { |
| aMessage.SetOffset(startOffset); |
| } |
| |
| return error; |
| } |
| |
| Error Lowpan::DispatchToNextHeader(uint8_t aDispatch, uint8_t &aNextHeader) |
| { |
| Error error = kErrorNone; |
| |
| if ((aDispatch & kExtHdrDispatchMask) == kExtHdrDispatch) |
| { |
| switch (aDispatch & kExtHdrEidMask) |
| { |
| case kExtHdrEidHbh: |
| aNextHeader = Ip6::kProtoHopOpts; |
| ExitNow(); |
| |
| case kExtHdrEidRouting: |
| aNextHeader = Ip6::kProtoRouting; |
| ExitNow(); |
| |
| case kExtHdrEidFragment: |
| aNextHeader = Ip6::kProtoFragment; |
| ExitNow(); |
| |
| case kExtHdrEidDst: |
| aNextHeader = Ip6::kProtoDstOpts; |
| ExitNow(); |
| |
| case kExtHdrEidIp6: |
| aNextHeader = Ip6::kProtoIp6; |
| ExitNow(); |
| } |
| } |
| else if ((aDispatch & kUdpDispatchMask) == kUdpDispatch) |
| { |
| aNextHeader = Ip6::kProtoUdp; |
| ExitNow(); |
| } |
| |
| error = kErrorParse; |
| |
| exit: |
| return error; |
| } |
| |
| Error Lowpan::DecompressBaseHeader(Ip6::Header &aIp6Header, |
| bool &aCompressedNextHeader, |
| const Mac::Addresses &aMacAddrs, |
| FrameData &aFrameData) |
| { |
| Error error = kErrorParse; |
| uint16_t hcCtl; |
| uint8_t byte; |
| uint8_t srcContextId = 0; |
| uint8_t dstContextId = 0; |
| Context srcContext; |
| Context dstContext; |
| uint8_t nextHeader; |
| |
| SuccessOrExit(aFrameData.ReadBigEndianUint16(hcCtl)); |
| |
| // check Dispatch bits |
| VerifyOrExit((hcCtl & kHcDispatchMask) == kHcDispatch); |
| |
| // Context Identifier |
| if ((hcCtl & kHcContextId) != 0) |
| { |
| SuccessOrExit(aFrameData.ReadUint8(byte)); |
| |
| srcContextId = (byte >> 4); |
| dstContextId = (byte & 0xf); |
| } |
| |
| FindContextForId(srcContextId, srcContext); |
| FindContextForId(dstContextId, dstContext); |
| |
| aIp6Header.Clear(); |
| aIp6Header.InitVersionTrafficClassFlow(); |
| |
| // Traffic Class and Flow Label |
| if ((hcCtl & kHcTrafficFlowMask) != kHcTrafficFlow) |
| { |
| uint8_t *ip6HeaderBytes = reinterpret_cast<uint8_t *>(&aIp6Header); |
| |
| VerifyOrExit(aFrameData.GetLength() > 0); |
| |
| ip6HeaderBytes[1] |= (aFrameData.GetBytes()[0] & 0xc0) >> 2; |
| |
| if ((hcCtl & kHcTrafficClass) == 0) |
| { |
| IgnoreError(aFrameData.ReadUint8(byte)); |
| ip6HeaderBytes[0] |= (byte >> 2) & 0x0f; |
| ip6HeaderBytes[1] |= (byte << 6) & 0xc0; |
| } |
| |
| if ((hcCtl & kHcFlowLabel) == 0) |
| { |
| VerifyOrExit(aFrameData.GetLength() >= 3); |
| ip6HeaderBytes[1] |= aFrameData.GetBytes()[0] & 0x0f; |
| ip6HeaderBytes[2] |= aFrameData.GetBytes()[1]; |
| ip6HeaderBytes[3] |= aFrameData.GetBytes()[2]; |
| aFrameData.SkipOver(3); |
| } |
| } |
| |
| // Next Header |
| if ((hcCtl & kHcNextHeader) == 0) |
| { |
| SuccessOrExit(aFrameData.ReadUint8(byte)); |
| |
| aIp6Header.SetNextHeader(byte); |
| aCompressedNextHeader = false; |
| } |
| else |
| { |
| aCompressedNextHeader = true; |
| } |
| |
| // Hop Limit |
| switch (hcCtl & kHcHopLimitMask) |
| { |
| case kHcHopLimit1: |
| aIp6Header.SetHopLimit(1); |
| break; |
| |
| case kHcHopLimit64: |
| aIp6Header.SetHopLimit(64); |
| break; |
| |
| case kHcHopLimit255: |
| aIp6Header.SetHopLimit(255); |
| break; |
| |
| default: |
| SuccessOrExit(aFrameData.ReadUint8(byte)); |
| aIp6Header.SetHopLimit(byte); |
| break; |
| } |
| |
| // Source Address |
| switch (hcCtl & kHcSrcAddrModeMask) |
| { |
| case kHcSrcAddrMode0: |
| if ((hcCtl & kHcSrcAddrContext) == 0) |
| { |
| SuccessOrExit(aFrameData.Read(aIp6Header.GetSource())); |
| } |
| |
| break; |
| |
| case kHcSrcAddrMode1: |
| SuccessOrExit(aFrameData.Read(aIp6Header.GetSource().GetIid())); |
| break; |
| |
| case kHcSrcAddrMode2: |
| aIp6Header.GetSource().mFields.m8[11] = 0xff; |
| aIp6Header.GetSource().mFields.m8[12] = 0xfe; |
| SuccessOrExit(aFrameData.ReadBytes(aIp6Header.GetSource().mFields.m8 + 14, 2)); |
| break; |
| |
| case kHcSrcAddrMode3: |
| IgnoreError(ComputeIid(aMacAddrs.mSource, srcContext, aIp6Header.GetSource().GetIid())); |
| break; |
| } |
| |
| if ((hcCtl & kHcSrcAddrModeMask) != kHcSrcAddrMode0) |
| { |
| if ((hcCtl & kHcSrcAddrContext) == 0) |
| { |
| aIp6Header.GetSource().mFields.m16[0] = BigEndian::HostSwap16(0xfe80); |
| } |
| else |
| { |
| VerifyOrExit(srcContext.mIsValid); |
| aIp6Header.GetSource().SetPrefix(srcContext.mPrefix); |
| } |
| } |
| |
| if ((hcCtl & kHcMulticast) == 0) |
| { |
| // Unicast Destination Address |
| |
| switch (hcCtl & kHcDstAddrModeMask) |
| { |
| case kHcDstAddrMode0: |
| VerifyOrExit((hcCtl & kHcDstAddrContext) == 0); |
| SuccessOrExit(aFrameData.Read(aIp6Header.GetDestination())); |
| break; |
| |
| case kHcDstAddrMode1: |
| SuccessOrExit(aFrameData.Read(aIp6Header.GetDestination().GetIid())); |
| break; |
| |
| case kHcDstAddrMode2: |
| aIp6Header.GetDestination().mFields.m8[11] = 0xff; |
| aIp6Header.GetDestination().mFields.m8[12] = 0xfe; |
| SuccessOrExit(aFrameData.ReadBytes(aIp6Header.GetDestination().mFields.m8 + 14, 2)); |
| break; |
| |
| case kHcDstAddrMode3: |
| SuccessOrExit(ComputeIid(aMacAddrs.mDestination, dstContext, aIp6Header.GetDestination().GetIid())); |
| break; |
| } |
| |
| if ((hcCtl & kHcDstAddrContext) == 0) |
| { |
| if ((hcCtl & kHcDstAddrModeMask) != 0) |
| { |
| aIp6Header.GetDestination().mFields.m16[0] = BigEndian::HostSwap16(0xfe80); |
| } |
| } |
| else |
| { |
| VerifyOrExit(dstContext.mIsValid); |
| aIp6Header.GetDestination().SetPrefix(dstContext.mPrefix); |
| } |
| } |
| else |
| { |
| // Multicast Destination Address |
| |
| aIp6Header.GetDestination().mFields.m8[0] = 0xff; |
| |
| if ((hcCtl & kHcDstAddrContext) == 0) |
| { |
| switch (hcCtl & kHcDstAddrModeMask) |
| { |
| case kHcDstAddrMode0: |
| SuccessOrExit(aFrameData.Read(aIp6Header.GetDestination())); |
| break; |
| |
| case kHcDstAddrMode1: |
| SuccessOrExit(aFrameData.ReadUint8(aIp6Header.GetDestination().mFields.m8[1])); |
| SuccessOrExit(aFrameData.ReadBytes(aIp6Header.GetDestination().mFields.m8 + 11, 5)); |
| break; |
| |
| case kHcDstAddrMode2: |
| SuccessOrExit(aFrameData.ReadUint8(aIp6Header.GetDestination().mFields.m8[1])); |
| SuccessOrExit(aFrameData.ReadBytes(aIp6Header.GetDestination().mFields.m8 + 13, 3)); |
| break; |
| |
| case kHcDstAddrMode3: |
| aIp6Header.GetDestination().mFields.m8[1] = 0x02; |
| SuccessOrExit(aFrameData.ReadUint8(aIp6Header.GetDestination().mFields.m8[15])); |
| break; |
| } |
| } |
| else |
| { |
| switch (hcCtl & kHcDstAddrModeMask) |
| { |
| case 0: |
| VerifyOrExit(dstContext.mIsValid); |
| SuccessOrExit(aFrameData.ReadBytes(aIp6Header.GetDestination().mFields.m8 + 1, 2)); |
| aIp6Header.GetDestination().mFields.m8[3] = dstContext.mPrefix.GetLength(); |
| memcpy(aIp6Header.GetDestination().mFields.m8 + 4, dstContext.mPrefix.GetBytes(), 8); |
| SuccessOrExit(aFrameData.ReadBytes(aIp6Header.GetDestination().mFields.m8 + 12, 4)); |
| break; |
| |
| default: |
| ExitNow(); |
| } |
| } |
| } |
| |
| if ((hcCtl & kHcNextHeader) != 0) |
| { |
| VerifyOrExit(aFrameData.GetLength() > 0); |
| SuccessOrExit(DispatchToNextHeader(*aFrameData.GetBytes(), nextHeader)); |
| aIp6Header.SetNextHeader(nextHeader); |
| } |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| Error Lowpan::DecompressExtensionHeader(Message &aMessage, FrameData &aFrameData) |
| { |
| Error error = kErrorParse; |
| uint8_t hdr[2]; |
| uint8_t len; |
| uint8_t ctl; |
| Ip6::PadOption padOption; |
| |
| SuccessOrExit(aFrameData.ReadUint8(ctl)); |
| |
| // next header |
| if (ctl & kExtHdrNextHeader) |
| { |
| SuccessOrExit(aFrameData.ReadUint8(len)); |
| |
| VerifyOrExit(aFrameData.CanRead(len + 1)); |
| SuccessOrExit(DispatchToNextHeader(aFrameData.GetBytes()[len], hdr[0])); |
| } |
| else |
| { |
| SuccessOrExit(aFrameData.ReadUint8(hdr[0])); |
| SuccessOrExit(aFrameData.ReadUint8(len)); |
| |
| VerifyOrExit(aFrameData.CanRead(len)); |
| } |
| |
| // length |
| hdr[1] = BytesForBitSize(sizeof(hdr) + len) - 1; |
| |
| SuccessOrExit(aMessage.AppendBytes(hdr, sizeof(hdr))); |
| aMessage.MoveOffset(sizeof(hdr)); |
| |
| // payload |
| SuccessOrExit(aMessage.AppendBytes(aFrameData.GetBytes(), len)); |
| aMessage.MoveOffset(len); |
| aFrameData.SkipOver(len); |
| |
| // The RFC6282 says: "The trailing Pad1 or PadN option MAY be elided by the compressor. |
| // A decompressor MUST ensure that the containing header is padded out to a multiple of 8 octets |
| // in length, using a Pad1 or PadN option if necessary." |
| |
| if (padOption.InitToPadHeaderWithSize(len + sizeof(hdr)) == kErrorNone) |
| { |
| SuccessOrExit(aMessage.AppendBytes(&padOption, padOption.GetSize())); |
| aMessage.MoveOffset(padOption.GetSize()); |
| } |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| Error Lowpan::DecompressUdpHeader(Ip6::Udp::Header &aUdpHeader, FrameData &aFrameData) |
| { |
| Error error = kErrorParse; |
| uint8_t udpCtl; |
| uint8_t byte; |
| uint16_t srcPort = 0; |
| uint16_t dstPort = 0; |
| |
| SuccessOrExit(aFrameData.ReadUint8(udpCtl)); |
| |
| VerifyOrExit((udpCtl & kUdpDispatchMask) == kUdpDispatch); |
| |
| aUdpHeader.Clear(); |
| |
| switch (udpCtl & kUdpPortMask) |
| { |
| case 0: |
| SuccessOrExit(aFrameData.ReadBigEndianUint16(srcPort)); |
| SuccessOrExit(aFrameData.ReadBigEndianUint16(dstPort)); |
| break; |
| |
| case 1: |
| SuccessOrExit(aFrameData.ReadBigEndianUint16(srcPort)); |
| SuccessOrExit(aFrameData.ReadUint8(byte)); |
| dstPort = (0xf000 | byte); |
| break; |
| |
| case 2: |
| SuccessOrExit(aFrameData.ReadUint8(byte)); |
| srcPort = (0xf000 | byte); |
| SuccessOrExit(aFrameData.ReadBigEndianUint16(dstPort)); |
| break; |
| |
| case 3: |
| SuccessOrExit(aFrameData.ReadUint8(byte)); |
| srcPort = (0xf0b0 | (byte >> 4)); |
| dstPort = (0xf0b0 | (byte & 0xf)); |
| break; |
| } |
| |
| aUdpHeader.SetSourcePort(srcPort); |
| aUdpHeader.SetDestinationPort(dstPort); |
| |
| if ((udpCtl & kUdpChecksum) != 0) |
| { |
| ExitNow(); |
| } |
| else |
| { |
| uint16_t checksum; |
| |
| SuccessOrExit(aFrameData.ReadBigEndianUint16(checksum)); |
| aUdpHeader.SetChecksum(checksum); |
| } |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| Error Lowpan::DecompressUdpHeader(Message &aMessage, FrameData &aFrameData, uint16_t aDatagramLength) |
| { |
| Error error; |
| Ip6::Udp::Header udpHeader; |
| |
| SuccessOrExit(error = DecompressUdpHeader(udpHeader, aFrameData)); |
| |
| // length |
| if (aDatagramLength == 0) |
| { |
| udpHeader.SetLength(sizeof(udpHeader) + aFrameData.GetLength()); |
| } |
| else |
| { |
| udpHeader.SetLength(aDatagramLength - aMessage.GetOffset()); |
| } |
| |
| SuccessOrExit(error = aMessage.Append(udpHeader)); |
| aMessage.MoveOffset(sizeof(udpHeader)); |
| |
| exit: |
| return error; |
| } |
| |
| Error Lowpan::Decompress(Message &aMessage, |
| const Mac::Addresses &aMacAddrs, |
| FrameData &aFrameData, |
| uint16_t aDatagramLength) |
| { |
| Error error = kErrorParse; |
| Ip6::Header ip6Header; |
| bool compressed; |
| uint16_t ip6PayloadLength; |
| uint16_t currentOffset = aMessage.GetOffset(); |
| |
| SuccessOrExit(DecompressBaseHeader(ip6Header, compressed, aMacAddrs, aFrameData)); |
| |
| SuccessOrExit(aMessage.Append(ip6Header)); |
| aMessage.MoveOffset(sizeof(ip6Header)); |
| |
| while (compressed) |
| { |
| uint8_t byte; |
| |
| VerifyOrExit(aFrameData.GetLength() > 0); |
| byte = *aFrameData.GetBytes(); |
| |
| if ((byte & kExtHdrDispatchMask) == kExtHdrDispatch) |
| { |
| if ((byte & kExtHdrEidMask) == kExtHdrEidIp6) |
| { |
| compressed = false; |
| |
| aFrameData.SkipOver(sizeof(uint8_t)); |
| |
| SuccessOrExit(Decompress(aMessage, aMacAddrs, aFrameData, aDatagramLength)); |
| } |
| else |
| { |
| compressed = (byte & kExtHdrNextHeader) != 0; |
| SuccessOrExit(DecompressExtensionHeader(aMessage, aFrameData)); |
| } |
| } |
| else if ((byte & kUdpDispatchMask) == kUdpDispatch) |
| { |
| compressed = false; |
| SuccessOrExit(DecompressUdpHeader(aMessage, aFrameData, aDatagramLength)); |
| } |
| else |
| { |
| ExitNow(); |
| } |
| } |
| |
| if (aDatagramLength) |
| { |
| ip6PayloadLength = BigEndian::HostSwap16(aDatagramLength - currentOffset - sizeof(Ip6::Header)); |
| } |
| else |
| { |
| ip6PayloadLength = |
| BigEndian::HostSwap16(aMessage.GetOffset() - currentOffset - sizeof(Ip6::Header) + aFrameData.GetLength()); |
| } |
| |
| aMessage.Write(currentOffset + Ip6::Header::kPayloadLengthFieldOffset, ip6PayloadLength); |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| Ip6::Ecn Lowpan::DecompressEcn(const Message &aMessage, uint16_t aOffset) const |
| { |
| Ip6::Ecn ecn = Ip6::kEcnNotCapable; |
| uint16_t hcCtl; |
| uint8_t byte; |
| |
| SuccessOrExit(aMessage.Read(aOffset, hcCtl)); |
| hcCtl = BigEndian::HostSwap16(hcCtl); |
| |
| VerifyOrExit((hcCtl & kHcDispatchMask) == kHcDispatch); |
| aOffset += sizeof(uint16_t); |
| |
| if ((hcCtl & kHcTrafficFlowMask) == kHcTrafficFlow) |
| { |
| // ECN is elided and is zero (`kEcnNotCapable`). |
| ExitNow(); |
| } |
| |
| // When ECN is not elided, it is always included as the |
| // first two bits of the next byte. |
| SuccessOrExit(aMessage.Read(aOffset, byte)); |
| ecn = static_cast<Ip6::Ecn>((byte & kEcnMask) >> kEcnOffset); |
| |
| exit: |
| return ecn; |
| } |
| |
| void Lowpan::MarkCompressedEcn(Message &aMessage, uint16_t aOffset) |
| { |
| uint8_t byte; |
| |
| aOffset += sizeof(uint16_t); |
| IgnoreError(aMessage.Read(aOffset, byte)); |
| |
| byte &= ~kEcnMask; |
| byte |= static_cast<uint8_t>(Ip6::kEcnMarked << kEcnOffset); |
| |
| aMessage.Write(aOffset, byte); |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // MeshHeader |
| |
| void MeshHeader::Init(uint16_t aSource, uint16_t aDestination, uint8_t aHopsLeft) |
| { |
| mSource = aSource; |
| mDestination = aDestination; |
| mHopsLeft = aHopsLeft; |
| } |
| |
| bool MeshHeader::IsMeshHeader(const FrameData &aFrameData) |
| { |
| return (aFrameData.GetLength() >= kMinHeaderLength) && ((*aFrameData.GetBytes() & kDispatchMask) == kDispatch); |
| } |
| |
| Error MeshHeader::ParseFrom(FrameData &aFrameData) |
| { |
| Error error; |
| uint16_t headerLength; |
| |
| SuccessOrExit(error = ParseFrom(aFrameData.GetBytes(), aFrameData.GetLength(), headerLength)); |
| aFrameData.SkipOver(headerLength); |
| |
| exit: |
| return error; |
| } |
| |
| Error MeshHeader::ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength) |
| { |
| Error error = kErrorParse; |
| uint8_t dispatch; |
| |
| VerifyOrExit(aFrameLength >= kMinHeaderLength); |
| dispatch = *aFrame++; |
| |
| VerifyOrExit((dispatch & (kDispatchMask | kSourceShort | kDestShort)) == (kDispatch | kSourceShort | kDestShort)); |
| |
| mHopsLeft = (dispatch & kHopsLeftMask); |
| |
| if (mHopsLeft == kDeepHopsLeft) |
| { |
| VerifyOrExit(aFrameLength >= kDeepHopsHeaderLength); |
| mHopsLeft = *aFrame++; |
| aHeaderLength = kDeepHopsHeaderLength; |
| } |
| else |
| { |
| aHeaderLength = kMinHeaderLength; |
| } |
| |
| mSource = BigEndian::ReadUint16(aFrame); |
| mDestination = BigEndian::ReadUint16(aFrame + 2); |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| Error MeshHeader::ParseFrom(const Message &aMessage) |
| { |
| uint16_t headerLength; |
| |
| return ParseFrom(aMessage, headerLength); |
| } |
| |
| Error MeshHeader::ParseFrom(const Message &aMessage, uint16_t &aHeaderLength) |
| { |
| uint8_t frame[kDeepHopsHeaderLength]; |
| uint16_t frameLength; |
| |
| frameLength = aMessage.ReadBytes(/* aOffset */ 0, frame, sizeof(frame)); |
| |
| return ParseFrom(frame, frameLength, aHeaderLength); |
| } |
| |
| uint16_t MeshHeader::GetHeaderLength(void) const |
| { |
| return (mHopsLeft >= kDeepHopsLeft) ? kDeepHopsHeaderLength : kMinHeaderLength; |
| } |
| |
| void MeshHeader::DecrementHopsLeft(void) |
| { |
| if (mHopsLeft > 0) |
| { |
| mHopsLeft--; |
| } |
| } |
| |
| Error MeshHeader::AppendTo(FrameBuilder &aFrameBuilder) const |
| { |
| Error error; |
| uint8_t dispatch = (kDispatch | kSourceShort | kDestShort); |
| |
| if (mHopsLeft < kDeepHopsLeft) |
| { |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(dispatch | mHopsLeft)); |
| } |
| else |
| { |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(dispatch | kDeepHopsLeft)); |
| SuccessOrExit(error = aFrameBuilder.AppendUint8(mHopsLeft)); |
| } |
| |
| SuccessOrExit(error = aFrameBuilder.AppendBigEndianUint16(mSource)); |
| SuccessOrExit(error = aFrameBuilder.AppendBigEndianUint16(mDestination)); |
| |
| exit: |
| return error; |
| } |
| |
| Error MeshHeader::AppendTo(Message &aMessage) const |
| { |
| uint8_t frame[kDeepHopsHeaderLength]; |
| FrameBuilder frameBuilder; |
| |
| frameBuilder.Init(frame, sizeof(frame)); |
| |
| IgnoreError(AppendTo(frameBuilder)); |
| |
| return aMessage.AppendBytes(frameBuilder.GetBytes(), frameBuilder.GetLength()); |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // FragmentHeader |
| |
| bool FragmentHeader::IsFragmentHeader(const FrameData &aFrameData) |
| { |
| return IsFragmentHeader(aFrameData.GetBytes(), aFrameData.GetLength()); |
| } |
| |
| bool FragmentHeader::IsFragmentHeader(const uint8_t *aFrame, uint16_t aFrameLength) |
| { |
| return (aFrameLength >= sizeof(FirstFrag)) && ((*aFrame & kDispatchMask) == kDispatch); |
| } |
| |
| Error FragmentHeader::ParseFrom(FrameData &aFrameData) |
| { |
| Error error; |
| uint16_t headerLength; |
| |
| SuccessOrExit(error = ParseFrom(aFrameData.GetBytes(), aFrameData.GetLength(), headerLength)); |
| aFrameData.SkipOver(headerLength); |
| |
| exit: |
| return error; |
| } |
| |
| Error FragmentHeader::ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength) |
| { |
| Error error = kErrorParse; |
| |
| VerifyOrExit(IsFragmentHeader(aFrame, aFrameLength)); |
| |
| mSize = BigEndian::ReadUint16(aFrame + kSizeIndex) & kSizeMask; |
| mTag = BigEndian::ReadUint16(aFrame + kTagIndex); |
| |
| if ((*aFrame & kOffsetFlag) == kOffsetFlag) |
| { |
| VerifyOrExit(aFrameLength >= sizeof(NextFrag)); |
| mOffset = aFrame[kOffsetIndex] * 8; |
| aHeaderLength = sizeof(NextFrag); |
| } |
| else |
| { |
| mOffset = 0; |
| aHeaderLength = sizeof(FirstFrag); |
| } |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| Error FragmentHeader::ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t &aHeaderLength) |
| { |
| uint8_t frame[sizeof(NextFrag)]; |
| uint16_t frameLength; |
| |
| frameLength = aMessage.ReadBytes(aOffset, frame, sizeof(frame)); |
| |
| return ParseFrom(frame, frameLength, aHeaderLength); |
| } |
| |
| } // namespace Lowpan |
| } // namespace ot |