[CoAP Server] Message deduplication (#1500)

* Message deduplication in CoAP server.

* Limit number of responses in CoAP server deduplication queue.
This commit is contained in:
Hubert Miś
2017-03-23 02:01:58 -07:00
committed by Jonathan Hui
parent 4fae0c248a
commit 68de8f669a
14 changed files with 444 additions and 45 deletions
+2
View File
@@ -924,6 +924,8 @@ typedef struct otBufferInfo
uint16_t mArpBuffers; ///< The number of buffers in the ARP send queue.
uint16_t mCoapClientMessages; ///< The number of messages in the CoAP client send queue.
uint16_t mCoapClientBuffers; ///< The number of buffers in the CoAP client send queue.
uint16_t mCoapServerMessages; ///< The number of messages in the CoAP server responses queue.
uint16_t mCoapServerBuffers; ///< The number of buffers in the CoAP server responses queue.
} otBufferInfo;
/**
+2 -1
View File
@@ -433,7 +433,8 @@ void Interpreter::ProcessBufferInfo(int argc, char *argv[])
sServer->OutputFormat("mpl: %d %d\r\n", bufferInfo.mMplMessages, bufferInfo.mMplBuffers);
sServer->OutputFormat("mle: %d %d\r\n", bufferInfo.mMleMessages, bufferInfo.mMleBuffers);
sServer->OutputFormat("arp: %d %d\r\n", bufferInfo.mArpMessages, bufferInfo.mArpBuffers);
sServer->OutputFormat("coap: %d %d\r\n", bufferInfo.mCoapClientMessages, bufferInfo.mCoapClientBuffers);
sServer->OutputFormat("coap client: %d %d\r\n", bufferInfo.mCoapClientMessages, bufferInfo.mCoapClientBuffers);
sServer->OutputFormat("coap server: %d %d\r\n", bufferInfo.mCoapServerMessages, bufferInfo.mCoapServerBuffers);
AppendResult(kThreadError_None);
}
+1 -1
View File
@@ -65,7 +65,7 @@ otInstance::otInstance(void) :
, mLinkRaw(*this)
#endif // OPENTHREAD_ENABLE_RAW_LINK_API
#if OPENTHREAD_ENABLE_APPLICATION_COAP
, mApplicationCoapServer(mIp6.mUdp, OT_DEFAULT_COAP_PORT)
, mApplicationCoapServer(mThreadNetif, OT_DEFAULT_COAP_PORT)
#endif // OPENTHREAD_ENABLE_APPLICATION_COAP
#if OPENTHREAD_CONFIG_ENABLE_DYNAMIC_LOG_LEVEL
, mLogLevel(static_cast<otLogLevel>(OPENTHREAD_CONFIG_LOG_LEVEL))
+3
View File
@@ -173,6 +173,9 @@ void otMessageGetBufferInfo(otInstance *aInstance, otBufferInfo *aBufferInfo)
aInstance->mThreadNetif.GetCoapClient().GetRequestMessages().GetInfo(aBufferInfo->mCoapClientMessages,
aBufferInfo->mCoapClientBuffers);
aInstance->mThreadNetif.GetCoapServer().GetCachedResponses().GetInfo(aBufferInfo->mCoapServerMessages,
aBufferInfo->mCoapServerBuffers);
}
#ifdef __cplusplus
+27
View File
@@ -51,6 +51,33 @@ namespace Coap {
*
*/
/**
* Protocol Constants (RFC 7252).
*
*/
enum
{
kAckTimeout = OPENTHREAD_CONFIG_COAP_ACK_TIMEOUT,
kAckRandomFactorNumerator = OPENTHREAD_CONFIG_COAP_ACK_RANDOM_FACTOR_NUMERATOR,
kAckRandomFactorDenominator = OPENTHREAD_CONFIG_COAP_ACK_RANDOM_FACTOR_DENOMINATOR,
kMaxRetransmit = OPENTHREAD_CONFIG_COAP_MAX_RETRANSMIT,
kNStart = 1,
kDefaultLeisure = 5,
kProbingRate = 1,
// Note that 2 << (kMaxRetransmit - 1) is equal to kMaxRetransmit power of 2
kMaxTransmitSpan = kAckTimeout * ((2 << (kMaxRetransmit - 1)) - 1) *
kAckRandomFactorNumerator / kAckRandomFactorDenominator,
kMaxTransmitWait = kAckTimeout * ((2 << kMaxRetransmit) - 1) *
kAckRandomFactorNumerator / kAckRandomFactorDenominator,
kMaxLatency = 100,
kProcessingDelay = kAckTimeout,
kMaxRtt = 2 * kMaxLatency + kProcessingDelay,
kExchangeLifetime = kMaxTransmitSpan + 2 * (kMaxLatency) + kProcessingDelay,
kNonLifetime = kMaxTransmitSpan + kMaxLatency
};
enum
{
kMeshCoPMessagePriority = Message::kPriorityHigh, // The priority for MeshCoP message
+3 -3
View File
@@ -87,7 +87,7 @@ ThreadError Client::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMess
Message *storedCopy = NULL;
uint16_t copyLength = 0;
SuccessOrExit(error = header.FromMessage(aMessage, false));
SuccessOrExit(error = header.FromMessage(aMessage, 0));
// Set Message Id if it was not already set.
if (header.GetMessageId() == 0)
@@ -335,7 +335,7 @@ Message *Client::FindRelatedRequest(const Header &aResponseHeader, const Ip6::Me
aRequestMetadata.mDestinationAddress.IsAnycastRoutingLocator()) &&
(aRequestMetadata.mDestinationPort == aMessageInfo.GetPeerPort()))
{
assert(aRequestHeader.FromMessage(*message, true) == kThreadError_None);
assert(aRequestHeader.FromMessage(*message, sizeof(RequestMetadata)) == kThreadError_None);
switch (aResponseHeader.GetType())
{
@@ -387,7 +387,7 @@ void Client::ProcessReceivedMessage(Message &aMessage, const Ip6::MessageInfo &a
Message *message = NULL;
ThreadError error;
SuccessOrExit(error = responseHeader.FromMessage(aMessage, false));
SuccessOrExit(error = responseHeader.FromMessage(aMessage, 0));
aMessage.MoveOffset(responseHeader.GetLength());
message = FindRelatedRequest(responseHeader, aMessageInfo, requestHeader, requestMetadata);
-26
View File
@@ -48,32 +48,6 @@ namespace Coap {
class Client;
/**
* Protocol Constants (RFC 7252).
*
*/
enum
{
kAckTimeout = OPENTHREAD_CONFIG_COAP_ACK_TIMEOUT,
kAckRandomFactorNumerator = OPENTHREAD_CONFIG_COAP_ACK_RANDOM_FACTOR_NUMERATOR,
kAckRandomFactorDenominator = OPENTHREAD_CONFIG_COAP_ACK_RANDOM_FACTOR_DENOMINATOR,
kMaxRetransmit = OPENTHREAD_CONFIG_COAP_MAX_RETRANSMIT,
kNStart = 1,
kDefaultLeisure = 5,
kProbingRate = 1,
// Note that 2 << (kMaxRetransmit - 1) is equal to kMaxRetransmit power of 2
kMaxTransmitSpan = kAckTimeout * ((2 << (kMaxRetransmit - 1)) - 1) *
kAckRandomFactorNumerator / kAckRandomFactorDenominator,
kMaxTransmitWait = kAckTimeout * ((2 << kMaxRetransmit) - 1) *
kAckRandomFactorNumerator / kAckRandomFactorDenominator,
kMaxLatency = 100,
kProcessingDelay = kAckTimeout,
kMaxRtt = 2 * kMaxLatency + kProcessingDelay,
kExchangeLifetime = kMaxTransmitSpan + 2 * (kMaxLatency) + kProcessingDelay,
kNonLifetime = kMaxTransmitSpan + kMaxLatency
};
/**
* This class implements metadata required for CoAP retransmission.
*
+2 -5
View File
@@ -59,7 +59,7 @@ void Header::Init(Type aType, Code aCode)
SetCode(aCode);
}
ThreadError Header::FromMessage(const Message &aMessage, bool aCopiedMessage)
ThreadError Header::FromMessage(const Message &aMessage, uint16_t aMetadataSize)
{
ThreadError error = kThreadError_Parse;
uint16_t offset = aMessage.GetOffset();
@@ -69,10 +69,7 @@ ThreadError Header::FromMessage(const Message &aMessage, bool aCopiedMessage)
uint16_t optionDelta;
uint16_t optionLength;
if (aCopiedMessage)
{
length -= sizeof(RequestMetadata);
}
length -= aMetadataSize;
VerifyOrExit(length >= kTokenOffset, error = kThreadError_Parse);
aMessage.Read(offset, kTokenOffset, mHeader.mBytes);
+2 -2
View File
@@ -109,13 +109,13 @@ public:
* This method parses the CoAP header from a message.
*
* @param[in] aMessage A reference to the message.
* @param[in] aCopiedMessage To indicate if this message is a copied one or not
* @param[in] aMetadataSize A size of metadata appended to the message.
*
* @retval kThreadError_None Successfully parsed the message.
* @retval kThreadError_Parse Failed to parse the message.
*
*/
ThreadError FromMessage(const Message &aMessage, bool aCopiedMessage);
ThreadError FromMessage(const Message &aMessage, uint16_t aMetadataSize);
/**
* This method returns the Version value.
+194 -3
View File
@@ -33,12 +33,14 @@
#include <coap/coap_server.hpp>
#include <common/code_utils.hpp>
#include <net/ip6.hpp>
namespace Thread {
namespace Coap {
Server::Server(Ip6::Udp &aUdp, uint16_t aPort, SenderFunction aSender, ReceiverFunction aReceiver):
CoapBase(aUdp, aSender, aReceiver)
Server::Server(Ip6::Netif &aNetif, uint16_t aPort, SenderFunction aSender, ReceiverFunction aReceiver):
CoapBase(aNetif.GetIp6().mUdp, aSender, aReceiver),
mResponsesQueue(aNetif)
{
mPort = aPort;
mResources = NULL;
@@ -54,6 +56,8 @@ ThreadError Server::Start(void)
ThreadError Server::Stop(void)
{
mResponsesQueue.DequeueAllResponses();
return CoapBase::Stop();
}
@@ -114,6 +118,8 @@ exit:
ThreadError Server::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
mResponsesQueue.EnqueueResponse(aMessage, aMessageInfo);
return mSender(this, aMessage, aMessageInfo);
}
@@ -147,10 +153,26 @@ void Server::ProcessReceivedMessage(Message &aMessage, const Ip6::MessageInfo &a
char uriPath[Resource::kMaxReceivedUriPath] = "";
char *curUriPath = uriPath;
const Header::Option *coapOption;
Message *response;
SuccessOrExit(header.FromMessage(aMessage, false));
SuccessOrExit(header.FromMessage(aMessage, 0));
aMessage.MoveOffset(header.GetLength());
switch (mResponsesQueue.GetMatchedResponseCopy(header, aMessageInfo, &response))
{
case kThreadError_None:
SendMessage(*response, aMessageInfo);
// fall through
case kThreadError_NoBufs:
ExitNow();
case kThreadError_NotFound:
default:
break;
}
coapOption = header.GetCurrentOption();
while (coapOption != NULL)
@@ -204,5 +226,174 @@ ThreadError Server::SetPort(uint16_t aPort)
return mSocket.Bind(sockaddr);
}
ThreadError ResponsesQueue::GetMatchedResponseCopy(const Header &aHeader,
const Ip6::MessageInfo &aMessageInfo,
Message **aResponse)
{
ThreadError error = kThreadError_NotFound;
Message *message;
EnqueuedResponseHeader enqueuedResponseHeader;
Ip6::MessageInfo messageInfo;
Header header;
for (message = mQueue.GetHead(); message != NULL; message = message->GetNext())
{
enqueuedResponseHeader.ReadFrom(*message);
messageInfo = enqueuedResponseHeader.GetMessageInfo();
// Check source endpoint
if (messageInfo.GetPeerPort() != aMessageInfo.GetPeerPort())
{
continue;
}
if (messageInfo.GetPeerAddr() != aMessageInfo.GetPeerAddr())
{
continue;
}
// Check Message Id
if (header.FromMessage(*message, sizeof(EnqueuedResponseHeader)) != kThreadError_None)
{
continue;
}
if (header.GetMessageId() != aHeader.GetMessageId())
{
continue;
}
*aResponse = message->Clone();
VerifyOrExit(*aResponse != NULL, error = kThreadError_NoBufs);
EnqueuedResponseHeader::RemoveFrom(**aResponse);
error = kThreadError_None;
break;
}
exit:
return error;
}
ThreadError ResponsesQueue::GetMatchedResponseCopy(const Message &aRequest,
const Ip6::MessageInfo &aMessageInfo,
Message **aResponse)
{
ThreadError error = kThreadError_None;
Header header;
SuccessOrExit(error = header.FromMessage(aRequest, 0));
error = GetMatchedResponseCopy(header, aMessageInfo, aResponse);
exit:
return error;
}
void ResponsesQueue::EnqueueResponse(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Header header;
Message *copy;
EnqueuedResponseHeader enqueuedResponseHeader(aMessageInfo);
uint16_t messageCount;
uint16_t bufferCount;
SuccessOrExit(header.FromMessage(aMessage, 0));
VerifyOrExit(header.GetType() == kCoapTypeAcknowledgment ||
header.GetType() == kCoapTypeReset,);
switch (GetMatchedResponseCopy(aMessage, aMessageInfo, &copy))
{
case kThreadError_NotFound:
break;
case kThreadError_None:
copy->Free();
// fall through
case kThreadError_NoBufs:
default:
ExitNow();
}
mQueue.GetInfo(messageCount, bufferCount);
if (messageCount >= kMaxCachedResponses)
{
DequeueOldestResponse();
}
copy = aMessage.Clone();
VerifyOrExit(copy != NULL,);
enqueuedResponseHeader.AppendTo(*copy);
mQueue.Enqueue(*copy);
if (!mTimer.IsRunning())
{
mTimer.Start(Timer::SecToMsec(kExchangeLifetime));
}
exit:
return;
}
void ResponsesQueue::DequeueOldestResponse(void)
{
Message *message;
VerifyOrExit((message = mQueue.GetHead()) != NULL,);
DequeueResponse(*message);
exit:
return;
}
void ResponsesQueue::DequeueAllResponses(void)
{
Message *message;
while ((message = mQueue.GetHead()) != NULL)
{
DequeueResponse(*message);
}
}
void ResponsesQueue::HandleTimer(void *aContext)
{
static_cast<ResponsesQueue *>(aContext)->HandleTimer();
}
void ResponsesQueue::HandleTimer(void)
{
Message *message;
EnqueuedResponseHeader enqueuedResponseHeader;
while ((message = mQueue.GetHead()) != NULL)
{
enqueuedResponseHeader.ReadFrom(*message);
if (enqueuedResponseHeader.IsEarlier(Timer::GetNow()))
{
DequeueResponse(*message);
}
else
{
mTimer.Start(enqueuedResponseHeader.GetRemainingTime());
break;
}
}
}
uint32_t EnqueuedResponseHeader::GetRemainingTime(void) const
{
int32_t remainingTime = static_cast<int32_t>(mDequeueTime - Timer::GetNow());
return remainingTime >= 0 ? static_cast<uint32_t>(remainingTime) : 0;
}
} // namespace Coap
} // namespace Thread
+194 -2
View File
@@ -38,7 +38,10 @@
#include <coap/coap_base.hpp>
#include <coap/coap_header.hpp>
#include <common/debug.hpp>
#include <common/message.hpp>
#include <common/timer.hpp>
#include <net/ip6.hpp>
#include <net/udp6.hpp>
namespace Thread {
@@ -93,6 +96,190 @@ private:
}
};
/**
* This class implements metadata required for caching CoAP responses.
*
*/
class EnqueuedResponseHeader
{
public:
/**
* Default constructor creating empty object.
*
*/
EnqueuedResponseHeader(void): mDequeueTime(0), mMessageInfo() {}
/**
* Constructor creating object with valid dequeue time and message info.
*
* @param[in] aMessageInfo The message info containing source endpoint identification.
*
*/
EnqueuedResponseHeader(const Ip6::MessageInfo &aMessageInfo):
mDequeueTime(Timer::GetNow() + Timer::SecToMsec(kExchangeLifetime)),
mMessageInfo(aMessageInfo) {}
/**
* This method append metadata to the message.
*
* @param[in] aMessage A reference to the message.
*
* @retval kThreadError_None Successfully appended the bytes.
* @retval kThreadError_NoBufs Insufficient available buffers to grow the message.
*/
ThreadError AppendTo(Message &aMessage) const { return aMessage.Append(this, sizeof(*this)); }
/**
* This method reads request data from the message.
*
* @param[in] aMessage A reference to the message.
*
* @returns The number of bytes read.
*
*/
uint16_t ReadFrom(const Message &aMessage) {
return aMessage.Read(aMessage.GetLength() - sizeof(*this), sizeof(*this), this);
}
/**
* This method removes metadata from the message.
*
* @param[in] aMessage A reference to the message.
*
*/
static void RemoveFrom(Message &aMessage) {
assert(aMessage.SetLength(aMessage.GetLength() - sizeof(EnqueuedResponseHeader)) == kThreadError_None);
}
/**
* This method checks if the message shall be sent before the given time.
*
* @param[in] aTime A time to compare.
*
* @retval TRUE If the message shall be sent before the given time.
* @retval FALSE Otherwise.
*
*/
bool IsEarlier(uint32_t aTime) const { return (static_cast<int32_t>(aTime - mDequeueTime) > 0); }
/**
* This method returns number of milliseconds in which the message should be sent.
*
* @returns The number of milliseconds in which the message should be sent.
*
*/
uint32_t GetRemainingTime(void) const;
/**
* This method returns the message info of cached CoAP response.
*
* @returns The message info of the cached CoAP response.
*
*/
const Ip6::MessageInfo &GetMessageInfo(void) const { return mMessageInfo; }
private:
uint32_t mDequeueTime;
const Ip6::MessageInfo mMessageInfo;
};
/**
* This class caches CoAP responses to implement message deduplication.
*
*/
class ResponsesQueue
{
public:
/**
* Default class constructor.
*
* @param[in] aNetif A reference to the network interface that CoAP server should be assigned to.
*
*/
ResponsesQueue(Ip6::Netif &aNetif):
mTimer(aNetif.GetIp6().mTimerScheduler, &ResponsesQueue::HandleTimer, this) {}
/**
* Add given response to the cache.
*
* If matching response (the same Message ID, source endpoint address and port) exists in the cache given
* response is not added.
* The CoAP response is copied before it is added to the cache.
*
* @param[in] aMessage The CoAP response to add to the cache.
* @param[in] aMessageInfo The message info corresponding to @p aMessage.
*
*/
void EnqueueResponse(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
/**
* Remove the oldest response from the cache.
*
*/
void DequeueOldestResponse(void);
/**
* Remove all responses from the cache.
*
*/
void DequeueAllResponses(void);
/**
* Get a copy of CoAP response from the cache that matches given Message ID and source endpoint.
*
* @param[in] aHeader The CoAP message header containing Message ID.
* @param[in] aMessageInfo The message info containing source endpoint address and port.
* @param[out] aResponse A pointer to a copy of a cached CoAP response matching given arguments.
*
* @retval kThreadError_None Matching response found and successfully created a copy.
* @retval kThreadError_NoBufs Matching response found but there is not sufficient buffer to create a copy.
* @retval kThreadError_NotFound Matching response not found.
*
*/
ThreadError GetMatchedResponseCopy(const Header &aHeader,
const Ip6::MessageInfo &aMessageInfo,
Message **aResponse);
/**
* Get a copy of CoAP response from the cache that matches given Message ID and source endpoint.
*
* @param[in] aRequest The CoAP message containing Message ID.
* @param[in] aMessageInfo The message info containing source endpoint address and port.
* @param[out] aResponse A pointer to a copy of a cached CoAP response matching given arguments.
*
* @retval kThreadError_None Matching response found and successfully created a copy.
* @retval kThreadError_NoBufs Matching response found but there is not sufficient buffer to create a copy.
* @retval kThreadError_NotFound Matching response not found.
* @retval kThreadError_Parse Could not parse CoAP header in the request message.
*
*/
ThreadError GetMatchedResponseCopy(const Message &aRequest,
const Ip6::MessageInfo &aMessageInfo,
Message **aResponse);
/**
* Get a reference to the cached CoAP responses queue.
*
* @returns A reference to the cached CoAP responses queue.
*
*/
const MessageQueue &GetResponses(void) const { return mQueue; }
private:
enum
{
kMaxCachedResponses = OPENTHREAD_CONFIG_COAP_SERVER_MAX_CACHED_RESPONSES,
};
void DequeueResponse(Message &aMessage) { mQueue.Dequeue(aMessage); aMessage.Free(); }
MessageQueue mQueue;
Timer mTimer;
static void HandleTimer(void *aContext);
void HandleTimer(void);
};
/**
* This class implements the CoAP server.
*
@@ -103,13 +290,13 @@ public:
/**
* This constructor initializes the object.
*
* @param[in] aUdp A reference to the UDP object.
* @param[in] aNetif A reference to the network interface that CoAP server should be assigned to.
* @param[in] aPort The port to listen on.
* @param[in] aSender A pointer to a function for sending messages.
* @param[in] aReceiver A pointer to a function for handling received messages.
*
*/
Server(Ip6::Udp &aUdp, uint16_t aPort, SenderFunction aSender = &Server::Send,
Server(Ip6::Netif &aNetif, uint16_t aPort, SenderFunction aSender = &Server::Send,
ReceiverFunction aReceiver = &Server::Receive);
/**
@@ -222,6 +409,8 @@ public:
*/
ThreadError SetPort(uint16_t aPort);
const MessageQueue &GetCachedResponses(void) const { return mResponsesQueue.GetResponses(); }
protected:
void ProcessReceivedMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
@@ -236,8 +425,11 @@ private:
uint16_t mPort;
Resource *mResources;
ResponsesQueue mResponsesQueue;
};
/**
* @}
*
+1 -1
View File
@@ -42,7 +42,7 @@ namespace Thread {
namespace Coap {
SecureServer::SecureServer(ThreadNetif &aNetif, uint16_t aPort):
Server(aNetif.GetIp6().mUdp, aPort, &SecureServer::Send, &SecureServer::Receive),
Server(aNetif, aPort, &SecureServer::Send, &SecureServer::Receive),
mTransmitCallback(NULL),
mContext(NULL),
mNetif(aNetif),
+12
View File
@@ -321,6 +321,18 @@
#define OPENTHREAD_CONFIG_COAP_MAX_RETRANSMIT 4
#endif // OPENTHREAD_CONFIG_COAP_MAX_RETRANSMIT
/**
* @def OPENTHREAD_CONFIG_COAP_SERVER_MAX_CACHED_RESPONSES
*
* Maximum number of cached responses for CoAP Confirmable messages.
*
* Cached responses are used for message deduplication.
*
*/
#ifndef OPENTHREAD_CONFIG_COAP_SERVER_MAX_CACHED_RESPONSES
#define OPENTHREAD_CONFIG_COAP_SERVER_MAX_CACHED_RESPONSES 10
#endif // OPENTHREAD_CONFIG_COAP_SERVER_MAX_CACHED_RESPONSES
/**
* @def OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT
*
+1 -1
View File
@@ -55,7 +55,7 @@ static const uint8_t kThreadMasterKey[] =
ThreadNetif::ThreadNetif(Ip6::Ip6 &aIp6):
Netif(aIp6, OT_NETIF_INTERFACE_ID_THREAD),
mCoapServer(aIp6.mUdp, kCoapUdpPort),
mCoapServer(*this, kCoapUdpPort),
mCoapClient(*this),
mAddressResolver(*this),
#if OPENTHREAD_ENABLE_DHCP6_CLIENT