blob: 3aca0f8d5e5fcbeac21f08531e1fedac12dfb93a [file] [log] [blame]
/*
* 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/instance.hpp"
#include "common/locator_getters.hpp"
#include "net/ip6.hpp"
#include "net/udp6.hpp"
#include "thread/network_data_leader.hpp"
#include "thread/thread_netif.hpp"
using ot::Encoding::BigEndian::HostSwap16;
using ot::Encoding::BigEndian::ReadUint16;
using ot::Encoding::BigEndian::WriteUint16;
namespace ot {
namespace Lowpan {
Lowpan::Lowpan(Instance &aInstance)
: InstanceLocator(aInstance)
{
}
void Lowpan::CopyContext(const Context &aContext, Ip6::Address &aAddress)
{
aAddress.SetPrefix(aContext.mPrefix);
}
Error Lowpan::ComputeIid(const Mac::Address &aMacAddr, const Context &aContext, Ip6::Address &aIpAddress)
{
Error error = kErrorNone;
switch (aMacAddr.GetType())
{
case Mac::Address::kTypeShort:
aIpAddress.GetIid().SetToLocator(aMacAddr.GetShort());
break;
case Mac::Address::kTypeExtended:
aIpAddress.GetIid().SetFromExtAddress(aMacAddr.GetExtended());
break;
default:
ExitNow(error = kErrorParse);
}
if (aContext.mPrefix.GetLength() > 64)
{
for (int i = (aContext.mPrefix.GetLength() & ~7); i < aContext.mPrefix.GetLength(); i++)
{
aIpAddress.mFields.m8[i / CHAR_BIT] &= ~(0x80 >> (i % CHAR_BIT));
aIpAddress.mFields.m8[i / CHAR_BIT] |= aContext.mPrefix.GetBytes()[i / CHAR_BIT] & (0x80 >> (i % CHAR_BIT));
}
}
exit:
return error;
}
Error Lowpan::CompressSourceIid(const Mac::Address &aMacAddr,
const Ip6::Address &aIpAddr,
const Context & aContext,
uint16_t & aHcCtl,
BufferWriter & aBuf)
{
Error error = kErrorNone;
BufferWriter buf = aBuf;
Ip6::Address ipaddr;
Mac::Address tmp;
IgnoreError(ComputeIid(aMacAddr, aContext, ipaddr));
if (ipaddr.GetIid() == aIpAddr.GetIid())
{
aHcCtl |= kHcSrcAddrMode3;
}
else
{
tmp.SetShort(aIpAddr.GetIid().GetLocator());
IgnoreError(ComputeIid(tmp, aContext, ipaddr));
if (ipaddr.GetIid() == aIpAddr.GetIid())
{
aHcCtl |= kHcSrcAddrMode2;
SuccessOrExit(error = buf.Write(aIpAddr.mFields.m8 + 14, 2));
}
else
{
aHcCtl |= kHcSrcAddrMode1;
SuccessOrExit(error = buf.Write(aIpAddr.GetIid().GetBytes(), Ip6::InterfaceIdentifier::kSize));
}
}
exit:
if (error == kErrorNone)
{
aBuf = buf;
}
return error;
}
Error Lowpan::CompressDestinationIid(const Mac::Address &aMacAddr,
const Ip6::Address &aIpAddr,
const Context & aContext,
uint16_t & aHcCtl,
BufferWriter & aBuf)
{
Error error = kErrorNone;
BufferWriter buf = aBuf;
Ip6::Address ipaddr;
Mac::Address tmp;
IgnoreError(ComputeIid(aMacAddr, aContext, ipaddr));
if (ipaddr.GetIid() == aIpAddr.GetIid())
{
aHcCtl |= kHcDstAddrMode3;
}
else
{
tmp.SetShort(aIpAddr.GetIid().GetLocator());
IgnoreError(ComputeIid(tmp, aContext, ipaddr));
if (ipaddr.GetIid() == aIpAddr.GetIid())
{
aHcCtl |= kHcDstAddrMode2;
SuccessOrExit(error = buf.Write(aIpAddr.mFields.m8 + 14, 2));
}
else
{
aHcCtl |= kHcDstAddrMode1;
SuccessOrExit(error = buf.Write(aIpAddr.GetIid().GetBytes(), Ip6::InterfaceIdentifier::kSize));
}
}
exit:
if (error == kErrorNone)
{
aBuf = buf;
}
return error;
}
Error Lowpan::CompressMulticast(const Ip6::Address &aIpAddr, uint16_t &aHcCtl, BufferWriter &aBuf)
{
Error error = kErrorNone;
BufferWriter buf = aBuf;
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 = buf.Write(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 = buf.Write(aIpAddr.mFields.m8[1]));
SuccessOrExit(error = buf.Write(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 = buf.Write(aIpAddr.mFields.m8[1]));
SuccessOrExit(error = buf.Write(aIpAddr.mFields.m8 + 11, 5));
}
else
{
// Check if multicast address can be compressed using Context ID 0.
if (Get<NetworkData::Leader>().GetContext(0, multicastContext) == kErrorNone &&
multicastContext.mPrefix.GetLength() == aIpAddr.mFields.m8[3] &&
memcmp(multicastContext.mPrefix.GetBytes(), aIpAddr.mFields.m8 + 4, 8) == 0)
{
aHcCtl |= kHcDstAddrContext | kHcDstAddrMode0;
SuccessOrExit(error = buf.Write(aIpAddr.mFields.m8 + 1, 2));
SuccessOrExit(error = buf.Write(aIpAddr.mFields.m8 + 12, 4));
}
else
{
SuccessOrExit(error = buf.Write(aIpAddr.mFields.m8, sizeof(Ip6::Address)));
}
}
break;
}
}
exit:
if (error == kErrorNone)
{
aBuf = buf;
}
return error;
}
Error Lowpan::Compress(Message & aMessage,
const Mac::Address &aMacSource,
const Mac::Address &aMacDest,
BufferWriter & aBuf)
{
Error error;
uint8_t headerDepth = 0xff;
do
{
error = Compress(aMessage, aMacSource, aMacDest, aBuf, headerDepth);
} while ((error != kErrorNone) && (headerDepth > 0));
return error;
}
Error Lowpan::Compress(Message & aMessage,
const Mac::Address &aMacSource,
const Mac::Address &aMacDest,
BufferWriter & aBuf,
uint8_t & aHeaderDepth)
{
Error error = kErrorNone;
NetworkData::Leader &networkData = Get<NetworkData::Leader>();
uint16_t startOffset = aMessage.GetOffset();
BufferWriter buf = aBuf;
uint16_t hcCtl = kHcDispatch;
Ip6::Header ip6Header;
uint8_t * ip6HeaderBytes = reinterpret_cast<uint8_t *>(&ip6Header);
Context srcContext, dstContext;
bool srcContextValid, dstContextValid;
uint8_t nextHeader;
uint8_t ecn;
uint8_t dscp;
uint8_t headerDepth = 0;
uint8_t headerMaxDepth = aHeaderDepth;
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), ip6Header));
srcContextValid =
(networkData.GetContext(ip6Header.GetSource(), srcContext) == kErrorNone && srcContext.mCompressFlag);
if (!srcContextValid)
{
IgnoreError(networkData.GetContext(0, srcContext));
}
dstContextValid =
(networkData.GetContext(ip6Header.GetDestination(), dstContext) == kErrorNone && dstContext.mCompressFlag);
if (!dstContextValid)
{
IgnoreError(networkData.GetContext(0, dstContext));
}
// Lowpan HC Control Bits
SuccessOrExit(error = buf.Advance(sizeof(hcCtl)));
// Context Identifier
if (srcContext.mContextId != 0 || dstContext.mContextId != 0)
{
hcCtl |= kHcContextId;
SuccessOrExit(error = buf.Write(((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 = buf.Write(ecn | dscp));
}
}
else if (dscp == 0)
{
// Carry Flow Label and ECN only with 2-bit padding.
hcCtl |= kHcTrafficClass;
SuccessOrExit(error = buf.Write(ecn | (ip6HeaderBytes[1] & 0x0f)));
SuccessOrExit(error = buf.Write(ip6HeaderBytes + 2, 2));
}
else
{
// Carry Flow Label and Traffic Class in-line.
SuccessOrExit(error = buf.Write(ecn | dscp));
SuccessOrExit(error = buf.Write(ip6HeaderBytes[1] & 0x0f));
SuccessOrExit(error = buf.Write(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 = buf.Write(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 = buf.Write(ip6Header.GetHopLimit()));
break;
}
// Source Address
if (ip6Header.GetSource().IsUnspecified())
{
hcCtl |= kHcSrcAddrContext;
}
else if (ip6Header.GetSource().IsLinkLocal())
{
SuccessOrExit(error = CompressSourceIid(aMacSource, ip6Header.GetSource(), srcContext, hcCtl, buf));
}
else if (srcContextValid)
{
hcCtl |= kHcSrcAddrContext;
SuccessOrExit(error = CompressSourceIid(aMacSource, ip6Header.GetSource(), srcContext, hcCtl, buf));
}
else
{
SuccessOrExit(error = buf.Write(ip6Header.GetSource().mFields.m8, sizeof(ip6Header.GetSource())));
}
// Destination Address
if (ip6Header.GetDestination().IsMulticast())
{
SuccessOrExit(error = CompressMulticast(ip6Header.GetDestination(), hcCtl, buf));
}
else if (ip6Header.GetDestination().IsLinkLocal())
{
SuccessOrExit(error = CompressDestinationIid(aMacDest, ip6Header.GetDestination(), dstContext, hcCtl, buf));
}
else if (dstContextValid)
{
hcCtl |= kHcDstAddrContext;
SuccessOrExit(error = CompressDestinationIid(aMacDest, ip6Header.GetDestination(), dstContext, hcCtl, buf));
}
else
{
SuccessOrExit(error = buf.Write(&ip6Header.GetDestination(), sizeof(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, buf, nextHeader));
break;
case Ip6::kProtoUdp:
error = CompressUdp(aMessage, buf);
ExitNow();
case Ip6::kProtoIp6:
// For IP-in-IP the NH bit of the LOWPAN_NHC encoding MUST be set to zero.
SuccessOrExit(error = buf.Write(kExtHdrDispatch | kExtHdrEidIp6));
error = Compress(aMessage, aMacSource, aMacDest, buf);
OT_FALL_THROUGH;
default:
ExitNow();
}
headerDepth++;
}
exit:
aHeaderDepth = headerDepth;
if (error == kErrorNone)
{
IgnoreError(aBuf.Write(hcCtl >> 8));
IgnoreError(aBuf.Write(hcCtl & 0xff));
aBuf = buf;
}
else
{
aMessage.SetOffset(startOffset);
}
return error;
}
Error Lowpan::CompressExtensionHeader(Message &aMessage, BufferWriter &aBuf, uint8_t &aNextHeader)
{
Error error = kErrorNone;
BufferWriter buf = aBuf;
uint16_t startOffset = aMessage.GetOffset();
Ip6::ExtensionHeader extHeader;
uint16_t len;
uint8_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 = buf.Write(tmpByte));
tmpByte = static_cast<uint8_t>(extHeader.GetNextHeader());
break;
}
SuccessOrExit(error = buf.Write(tmpByte));
len = (extHeader.GetLength() + 1) * 8 - 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();
Ip6::OptionHeader optionHeader;
while ((offset - aMessage.GetOffset()) < len)
{
SuccessOrExit(error = aMessage.Read(offset, optionHeader));
if (optionHeader.GetType() == Ip6::OptionPad1::kType)
{
offset += sizeof(Ip6::OptionPad1);
}
else
{
offset += sizeof(optionHeader) + optionHeader.GetLength();
}
}
// Check if the last option can be compressed.
if (optionHeader.GetType() == Ip6::OptionPad1::kType)
{
padLength = sizeof(Ip6::OptionPad1);
}
else if (optionHeader.GetType() == Ip6::OptionPadN::kType)
{
padLength = sizeof(optionHeader) + optionHeader.GetLength();
}
len -= padLength;
}
VerifyOrExit(aMessage.GetOffset() + len + padLength <= aMessage.GetLength(), error = kErrorParse);
aNextHeader = static_cast<uint8_t>(extHeader.GetNextHeader());
SuccessOrExit(error = buf.Write(static_cast<uint8_t>(len)));
SuccessOrExit(error = buf.Write(aMessage, static_cast<uint8_t>(len)));
aMessage.MoveOffset(len + padLength);
exit:
if (error == kErrorNone)
{
aBuf = buf;
}
else
{
aMessage.SetOffset(startOffset);
}
return error;
}
Error Lowpan::CompressUdp(Message &aMessage, BufferWriter &aBuf)
{
Error error = kErrorNone;
BufferWriter buf = aBuf;
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 = buf.Write(kUdpDispatch | 3));
SuccessOrExit(error = buf.Write((((source & 0xf) << 4) | (destination & 0xf)) & 0xff));
}
else if ((source & 0xff00) == 0xf000)
{
SuccessOrExit(error = buf.Write(kUdpDispatch | 2));
SuccessOrExit(error = buf.Write(source & 0xff));
SuccessOrExit(error = buf.Write(destination >> 8));
SuccessOrExit(error = buf.Write(destination & 0xff));
}
else if ((destination & 0xff00) == 0xf000)
{
SuccessOrExit(error = buf.Write(kUdpDispatch | 1));
SuccessOrExit(error = buf.Write(source >> 8));
SuccessOrExit(error = buf.Write(source & 0xff));
SuccessOrExit(error = buf.Write(destination & 0xff));
}
else
{
SuccessOrExit(error = buf.Write(kUdpDispatch));
SuccessOrExit(error = buf.Write(&udpHeader, Ip6::Udp::Header::kLengthFieldOffset));
}
SuccessOrExit(error =
buf.Write(reinterpret_cast<uint8_t *>(&udpHeader) + Ip6::Udp::Header::kChecksumFieldOffset, 2));
aMessage.MoveOffset(sizeof(udpHeader));
exit:
if (error == kErrorNone)
{
aBuf = buf;
}
else
{
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;
}
int Lowpan::DecompressBaseHeader(Ip6::Header & aIp6Header,
bool & aCompressedNextHeader,
const Mac::Address &aMacSource,
const Mac::Address &aMacDest,
const uint8_t * aBuf,
uint16_t aBufLength)
{
NetworkData::Leader &networkData = Get<NetworkData::Leader>();
Error error = kErrorParse;
const uint8_t * cur = aBuf;
const uint8_t * end = aBuf + aBufLength;
uint16_t hcCtl;
Context srcContext, dstContext;
bool srcContextValid = true, dstContextValid = true;
uint8_t nextHeader;
uint8_t * bytes;
VerifyOrExit(cur + 2 <= end);
hcCtl = ReadUint16(cur);
cur += 2;
// check Dispatch bits
VerifyOrExit((hcCtl & kHcDispatchMask) == kHcDispatch);
// Context Identifier
srcContext.mPrefix.SetLength(0);
dstContext.mPrefix.SetLength(0);
if ((hcCtl & kHcContextId) != 0)
{
VerifyOrExit(cur < end);
if (networkData.GetContext(cur[0] >> 4, srcContext) != kErrorNone)
{
srcContextValid = false;
}
if (networkData.GetContext(cur[0] & 0xf, dstContext) != kErrorNone)
{
dstContextValid = false;
}
cur++;
}
else
{
IgnoreError(networkData.GetContext(0, srcContext));
IgnoreError(networkData.GetContext(0, dstContext));
}
aIp6Header.Clear();
aIp6Header.InitVersionTrafficClassFlow();
// Traffic Class and Flow Label
if ((hcCtl & kHcTrafficFlowMask) != kHcTrafficFlow)
{
VerifyOrExit(cur < end);
bytes = reinterpret_cast<uint8_t *>(&aIp6Header);
bytes[1] |= (cur[0] & 0xc0) >> 2;
if ((hcCtl & kHcTrafficClass) == 0)
{
bytes[0] |= (cur[0] >> 2) & 0x0f;
bytes[1] |= (cur[0] << 6) & 0xc0;
cur++;
}
if ((hcCtl & kHcFlowLabel) == 0)
{
VerifyOrExit(cur + 3 <= end);
bytes[1] |= cur[0] & 0x0f;
bytes[2] |= cur[1];
bytes[3] |= cur[2];
cur += 3;
}
}
// Next Header
if ((hcCtl & kHcNextHeader) == 0)
{
VerifyOrExit(cur < end);
aIp6Header.SetNextHeader(cur[0]);
cur++;
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:
VerifyOrExit(cur < end);
aIp6Header.SetHopLimit(cur[0]);
cur++;
break;
}
// Source Address
switch (hcCtl & kHcSrcAddrModeMask)
{
case kHcSrcAddrMode0:
if ((hcCtl & kHcSrcAddrContext) == 0)
{
VerifyOrExit(cur + sizeof(Ip6::Address) <= end);
memcpy(&aIp6Header.GetSource(), cur, sizeof(aIp6Header.GetSource()));
cur += sizeof(Ip6::Address);
}
break;
case kHcSrcAddrMode1:
VerifyOrExit(cur + Ip6::InterfaceIdentifier::kSize <= end);
aIp6Header.GetSource().GetIid().SetBytes(cur);
cur += Ip6::InterfaceIdentifier::kSize;
break;
case kHcSrcAddrMode2:
VerifyOrExit(cur + 2 <= end);
aIp6Header.GetSource().mFields.m8[11] = 0xff;
aIp6Header.GetSource().mFields.m8[12] = 0xfe;
memcpy(aIp6Header.GetSource().mFields.m8 + 14, cur, 2);
cur += 2;
break;
case kHcSrcAddrMode3:
IgnoreError(ComputeIid(aMacSource, srcContext, aIp6Header.GetSource()));
break;
}
if ((hcCtl & kHcSrcAddrModeMask) != kHcSrcAddrMode0)
{
if ((hcCtl & kHcSrcAddrContext) == 0)
{
aIp6Header.GetSource().mFields.m16[0] = HostSwap16(0xfe80);
}
else
{
VerifyOrExit(srcContextValid);
CopyContext(srcContext, aIp6Header.GetSource());
}
}
if ((hcCtl & kHcMulticast) == 0)
{
// Unicast Destination Address
switch (hcCtl & kHcDstAddrModeMask)
{
case kHcDstAddrMode0:
VerifyOrExit((hcCtl & kHcDstAddrContext) == 0);
VerifyOrExit(cur + sizeof(Ip6::Address) <= end);
memcpy(&aIp6Header.GetDestination(), cur, sizeof(aIp6Header.GetDestination()));
cur += sizeof(Ip6::Address);
break;
case kHcDstAddrMode1:
VerifyOrExit(cur + Ip6::InterfaceIdentifier::kSize <= end);
aIp6Header.GetDestination().GetIid().SetBytes(cur);
cur += Ip6::InterfaceIdentifier::kSize;
break;
case kHcDstAddrMode2:
VerifyOrExit(cur + 2 <= end);
aIp6Header.GetDestination().mFields.m8[11] = 0xff;
aIp6Header.GetDestination().mFields.m8[12] = 0xfe;
memcpy(aIp6Header.GetDestination().mFields.m8 + 14, cur, 2);
cur += 2;
break;
case kHcDstAddrMode3:
SuccessOrExit(ComputeIid(aMacDest, dstContext, aIp6Header.GetDestination()));
break;
}
if ((hcCtl & kHcDstAddrContext) == 0)
{
if ((hcCtl & kHcDstAddrModeMask) != 0)
{
aIp6Header.GetDestination().mFields.m16[0] = HostSwap16(0xfe80);
}
}
else
{
VerifyOrExit(dstContextValid);
CopyContext(dstContext, aIp6Header.GetDestination());
}
}
else
{
// Multicast Destination Address
aIp6Header.GetDestination().mFields.m8[0] = 0xff;
if ((hcCtl & kHcDstAddrContext) == 0)
{
switch (hcCtl & kHcDstAddrModeMask)
{
case kHcDstAddrMode0:
VerifyOrExit(cur + sizeof(Ip6::Address) <= end);
memcpy(aIp6Header.GetDestination().mFields.m8, cur, sizeof(Ip6::Address));
cur += sizeof(Ip6::Address);
break;
case kHcDstAddrMode1:
VerifyOrExit(cur + 6 <= end);
aIp6Header.GetDestination().mFields.m8[1] = cur[0];
memcpy(aIp6Header.GetDestination().mFields.m8 + 11, cur + 1, 5);
cur += 6;
break;
case kHcDstAddrMode2:
VerifyOrExit(cur + 4 <= end);
aIp6Header.GetDestination().mFields.m8[1] = cur[0];
memcpy(aIp6Header.GetDestination().mFields.m8 + 13, cur + 1, 3);
cur += 4;
break;
case kHcDstAddrMode3:
VerifyOrExit(cur < end);
aIp6Header.GetDestination().mFields.m8[1] = 0x02;
aIp6Header.GetDestination().mFields.m8[15] = cur[0];
cur++;
break;
}
}
else
{
switch (hcCtl & kHcDstAddrModeMask)
{
case 0:
VerifyOrExit(cur + 6 <= end);
VerifyOrExit(dstContextValid);
aIp6Header.GetDestination().mFields.m8[1] = cur[0];
aIp6Header.GetDestination().mFields.m8[2] = cur[1];
aIp6Header.GetDestination().mFields.m8[3] = dstContext.mPrefix.GetLength();
memcpy(aIp6Header.GetDestination().mFields.m8 + 4, dstContext.mPrefix.GetBytes(), 8);
memcpy(aIp6Header.GetDestination().mFields.m8 + 12, cur + 2, 4);
cur += 6;
break;
default:
ExitNow();
}
}
}
if ((hcCtl & kHcNextHeader) != 0)
{
VerifyOrExit(cur < end);
SuccessOrExit(DispatchToNextHeader(cur[0], nextHeader));
aIp6Header.SetNextHeader(nextHeader);
}
error = kErrorNone;
exit:
return (error == kErrorNone) ? static_cast<int>(cur - aBuf) : -1;
}
int Lowpan::DecompressExtensionHeader(Message &aMessage, const uint8_t *aBuf, uint16_t aBufLength)
{
Error error = kErrorParse;
const uint8_t * cur = aBuf;
const uint8_t * end = aBuf + aBufLength;
uint8_t hdr[2];
uint8_t len;
uint8_t nextHeader;
uint8_t ctl = cur[0];
uint8_t padLength;
Ip6::OptionPad1 optionPad1;
Ip6::OptionPadN optionPadN;
VerifyOrExit(cur < end);
cur++;
// next header
if (ctl & kExtHdrNextHeader)
{
VerifyOrExit(cur < end);
len = cur[0];
cur++;
VerifyOrExit(cur + len <= end);
SuccessOrExit(DispatchToNextHeader(cur[len], nextHeader));
hdr[0] = static_cast<uint8_t>(nextHeader);
}
else
{
VerifyOrExit(cur + 2 <= end);
hdr[0] = cur[0];
len = cur[1];
cur += 2;
VerifyOrExit(cur + len <= end);
}
// length
hdr[1] = BitVectorBytes(sizeof(hdr) + len) - 1;
SuccessOrExit(aMessage.AppendBytes(hdr, sizeof(hdr)));
aMessage.MoveOffset(sizeof(hdr));
// payload
SuccessOrExit(aMessage.AppendBytes(cur, len));
aMessage.MoveOffset(len);
cur += 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."
padLength = 8 - ((len + sizeof(hdr)) & 0x07);
if (padLength != 8)
{
if (padLength == 1)
{
optionPad1.Init();
SuccessOrExit(aMessage.AppendBytes(&optionPad1, padLength));
}
else
{
optionPadN.Init(padLength);
SuccessOrExit(aMessage.AppendBytes(&optionPadN, padLength));
}
aMessage.MoveOffset(padLength);
}
error = kErrorNone;
exit:
return (error == kErrorNone) ? static_cast<int>(cur - aBuf) : -1;
}
int Lowpan::DecompressUdpHeader(Ip6::Udp::Header &aUdpHeader, const uint8_t *aBuf, uint16_t aBufLength)
{
Error error = kErrorParse;
const uint8_t *cur = aBuf;
const uint8_t *end = aBuf + aBufLength;
uint8_t udpCtl;
VerifyOrExit(cur < end);
udpCtl = cur[0];
cur++;
VerifyOrExit((udpCtl & kUdpDispatchMask) == kUdpDispatch);
memset(&aUdpHeader, 0, sizeof(aUdpHeader));
// source and dest ports
switch (udpCtl & kUdpPortMask)
{
case 0:
VerifyOrExit(cur + 4 <= end);
aUdpHeader.SetSourcePort(ReadUint16(cur));
aUdpHeader.SetDestinationPort(ReadUint16(cur + 2));
cur += 4;
break;
case 1:
VerifyOrExit(cur + 3 <= end);
aUdpHeader.SetSourcePort(ReadUint16(cur));
aUdpHeader.SetDestinationPort(0xf000 | cur[2]);
cur += 3;
break;
case 2:
VerifyOrExit(cur + 3 <= end);
aUdpHeader.SetSourcePort(0xf000 | cur[0]);
aUdpHeader.SetDestinationPort(ReadUint16(cur + 1));
cur += 3;
break;
case 3:
VerifyOrExit(cur < end);
aUdpHeader.SetSourcePort(0xf0b0 | (cur[0] >> 4));
aUdpHeader.SetDestinationPort(0xf0b0 | (cur[0] & 0xf));
cur++;
break;
}
// checksum
if ((udpCtl & kUdpChecksum) != 0)
{
ExitNow();
}
else
{
VerifyOrExit(cur + 2 <= end);
aUdpHeader.SetChecksum(ReadUint16(cur));
cur += 2;
}
error = kErrorNone;
exit:
return (error == kErrorNone) ? static_cast<int>(cur - aBuf) : -1;
}
int Lowpan::DecompressUdpHeader(Message &aMessage, const uint8_t *aBuf, uint16_t aBufLength, uint16_t aDatagramLength)
{
Ip6::Udp::Header udpHeader;
int headerLen = -1;
headerLen = DecompressUdpHeader(udpHeader, aBuf, aBufLength);
VerifyOrExit(headerLen >= 0);
// length
if (aDatagramLength == 0)
{
udpHeader.SetLength(sizeof(udpHeader) + static_cast<uint16_t>(aBufLength - headerLen));
}
else
{
udpHeader.SetLength(aDatagramLength - aMessage.GetOffset());
}
VerifyOrExit(aMessage.Append(udpHeader) == kErrorNone, headerLen = -1);
aMessage.MoveOffset(sizeof(udpHeader));
exit:
return headerLen;
}
int Lowpan::Decompress(Message & aMessage,
const Mac::Address &aMacSource,
const Mac::Address &aMacDest,
const uint8_t * aBuf,
uint16_t aBufLength,
uint16_t aDatagramLength)
{
Error error = kErrorParse;
Ip6::Header ip6Header;
const uint8_t *cur = aBuf;
uint16_t remaining = aBufLength;
bool compressed;
int rval;
uint16_t ip6PayloadLength;
uint16_t compressedLength = 0;
uint16_t currentOffset = aMessage.GetOffset();
VerifyOrExit(remaining >= 2);
VerifyOrExit((rval = DecompressBaseHeader(ip6Header, compressed, aMacSource, aMacDest, cur, remaining)) >= 0);
cur += rval;
remaining -= rval;
SuccessOrExit(aMessage.Append(ip6Header));
aMessage.MoveOffset(sizeof(ip6Header));
while (compressed)
{
VerifyOrExit(remaining >= 1);
if ((cur[0] & kExtHdrDispatchMask) == kExtHdrDispatch)
{
if ((cur[0] & kExtHdrEidMask) == kExtHdrEidIp6)
{
compressed = false;
cur++;
remaining--;
VerifyOrExit((rval = Decompress(aMessage, aMacSource, aMacDest, cur, remaining, aDatagramLength)) >= 0);
}
else
{
compressed = (cur[0] & kExtHdrNextHeader) != 0;
VerifyOrExit((rval = DecompressExtensionHeader(aMessage, cur, remaining)) >= 0);
}
}
else if ((cur[0] & kUdpDispatchMask) == kUdpDispatch)
{
compressed = false;
VerifyOrExit((rval = DecompressUdpHeader(aMessage, cur, remaining, aDatagramLength)) >= 0);
}
else
{
ExitNow();
}
VerifyOrExit(remaining >= rval);
cur += rval;
remaining -= rval;
}
compressedLength = static_cast<uint16_t>(cur - aBuf);
if (aDatagramLength)
{
ip6PayloadLength = HostSwap16(aDatagramLength - currentOffset - sizeof(Ip6::Header));
}
else
{
ip6PayloadLength =
HostSwap16(aMessage.GetOffset() - currentOffset - sizeof(Ip6::Header) + aBufLength - compressedLength);
}
aMessage.Write(currentOffset + Ip6::Header::kPayloadLengthFieldOffset, ip6PayloadLength);
error = kErrorNone;
exit:
return (error == kErrorNone) ? static_cast<int>(compressedLength) : -1;
}
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 = 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 uint8_t *aFrame, uint16_t aFrameLength)
{
return (aFrameLength >= kMinHeaderLength) && ((*aFrame & kDispatchMask) == kDispatch);
}
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 = ReadUint16(aFrame);
mDestination = 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--;
}
}
uint16_t MeshHeader::WriteTo(uint8_t *aFrame) const
{
uint8_t *cur = aFrame;
uint8_t dispatch = (kDispatch | kSourceShort | kDestShort);
if (mHopsLeft < kDeepHopsLeft)
{
*cur++ = (dispatch | mHopsLeft);
}
else
{
*cur++ = (dispatch | kDeepHopsLeft);
*cur++ = mHopsLeft;
}
WriteUint16(mSource, cur);
cur += sizeof(uint16_t);
WriteUint16(mDestination, cur);
cur += sizeof(uint16_t);
return static_cast<uint16_t>(cur - aFrame);
}
uint16_t MeshHeader::WriteTo(Message &aMessage, uint16_t aOffset) const
{
uint8_t frame[kDeepHopsHeaderLength];
uint16_t headerLength;
headerLength = WriteTo(frame);
aMessage.WriteBytes(aOffset, frame, headerLength);
return headerLength;
}
//---------------------------------------------------------------------------------------------------------------------
// FragmentHeader
void FragmentHeader::Init(uint16_t aSize, uint16_t aTag, uint16_t aOffset)
{
mSize = (aSize & kSizeMask);
mTag = aTag;
mOffset = (aOffset & kOffsetMask);
}
bool FragmentHeader::IsFragmentHeader(const uint8_t *aFrame, uint16_t aFrameLength)
{
return (aFrameLength >= kFirstFragmentHeaderSize) && ((*aFrame & kDispatchMask) == kDispatch);
}
Error FragmentHeader::ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength)
{
Error error = kErrorParse;
VerifyOrExit(IsFragmentHeader(aFrame, aFrameLength));
mSize = ReadUint16(aFrame + kSizeIndex) & kSizeMask;
mTag = ReadUint16(aFrame + kTagIndex);
if ((*aFrame & kOffsetFlag) == kOffsetFlag)
{
VerifyOrExit(aFrameLength >= kSubsequentFragmentHeaderSize);
mOffset = aFrame[kOffsetIndex] * 8;
aHeaderLength = kSubsequentFragmentHeaderSize;
}
else
{
mOffset = 0;
aHeaderLength = kFirstFragmentHeaderSize;
}
error = kErrorNone;
exit:
return error;
}
Error FragmentHeader::ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t &aHeaderLength)
{
uint8_t frame[kSubsequentFragmentHeaderSize];
uint16_t frameLength;
frameLength = aMessage.ReadBytes(aOffset, frame, sizeof(frame));
return ParseFrom(frame, frameLength, aHeaderLength);
}
uint16_t FragmentHeader::WriteTo(uint8_t *aFrame) const
{
uint8_t *cur = aFrame;
WriteUint16((static_cast<uint16_t>(kDispatch) << 8) + mSize, cur);
cur += sizeof(uint16_t);
WriteUint16(mTag, cur);
cur += sizeof(uint16_t);
if (mOffset != 0)
{
*aFrame |= kOffsetFlag;
*cur++ = static_cast<uint8_t>(mOffset >> 3);
}
return static_cast<uint16_t>(cur - aFrame);
}
} // namespace Lowpan
} // namespace ot