Files
openthread/tests/unit/test_coap_message.cpp
Abtin Keshavarzian 5cae26e22b [coap] simplify Coap::Message implementation (#12313)
This change simplifies the `Coap::Message` implementation and removes
the fragile `HelpData` struct which was used to cache header
information within reserved portion of message.

The `Coap::Msg` class is updated to hold the parsed CoAP header
information (type, code, message ID, token). It now inherits from a
new `HeaderInfo` class which contains the parsed fields. This change
helps to simplify many of the call sites which previously had to parse
the header information themselves.

The key changes are:
- The `HelpData` struct is removed from `Coap::Message`.
- `Coap::Msg` is updated to track parsed header info in `HeaderInfo`.
- `otCoapMessageInit()` and `otCoapMessageInitResponse()` now return an
  `otError`.
- Methods are renamed to harmonize their names.
- A new unit test `test_coap_message.cpp` is added to verify the
  `Coap::Message` implementation.
2026-01-23 10:52:42 -08:00

430 lines
16 KiB
C++

/*
* Copyright (c) 2026, 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.
*/
#include "instance/instance.hpp"
#include "test_platform.h"
#include "test_util.hpp"
namespace ot {
class UnitTester
{
public:
static void TestCoapMessage(void)
{
Instance *instance;
Coap::Message *message;
Coap::HeaderInfo headerInfo;
Coap::Token readToken;
Coap::Token token;
Coap::Option::Iterator iterator;
uint8_t tokenLength;
uint16_t length;
Ip6::MessageInfo messageInfo;
Coap::Message::UriPathStringBuffer uriBuffer;
printf("TestCoapMessage()\n");
instance = static_cast<Instance *>(testInitInstance());
VerifyOrQuit(instance != nullptr);
message = AsCoapMessagePtr(instance->Get<MessagePool>().Allocate(Message::kTypeOther));
VerifyOrQuit(message != nullptr);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SuccessOrQuit(message->Init(Coap::kTypeNonConfirmable, Coap::kCodePut));
SuccessOrQuit(message->ParseHeaderInfo(headerInfo));
VerifyOrQuit(headerInfo.GetType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(headerInfo.GetCode() == Coap::kCodePut);
VerifyOrQuit(headerInfo.GetMessageId() == 0);
VerifyOrQuit(headerInfo.GetToken().GetLength() == 0);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePut);
VerifyOrQuit(message->ReadMessageId() == 0);
SuccessOrQuit(message->ReadTokenLength(tokenLength));
VerifyOrQuit(tokenLength == 0);
SuccessOrQuit(message->ReadToken(readToken));
VerifyOrQuit(readToken.GetLength() == 0);
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(!iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == message->GetLength());
{
Coap::Msg msg(*message, messageInfo);
SuccessOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRemovePayloadMarkerIfNoPayload));
VerifyOrQuit(msg.GetType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePut);
VerifyOrQuit(msg.GetMessageId() == 0);
VerifyOrQuit(msg.GetToken().GetLength() == 0);
VerifyOrQuit(msg.mMessage.GetOffset() == msg.mMessage.GetLength());
}
// AppendPayloadMaker
SuccessOrQuit(message->AppendPayloadMarker());
VerifyOrQuit(message->GetOffset() == message->GetLength());
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == message->GetLength());
{
Coap::Msg msg(*message, messageInfo);
VerifyOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRejectIfNoPayloadWithPayloadMarker) != kErrorNone);
}
// AppendPayloadMaker again should not make any changes
length = message->GetLength();
SuccessOrQuit(message->AppendPayloadMarker());
VerifyOrQuit(message->GetLength() == length);
VerifyOrQuit(message->GetOffset() == length);
// Ensure payload marker is removed since we have no payload
{
Coap::Msg msg(*message, messageInfo);
SuccessOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRemovePayloadMarkerIfNoPayload));
VerifyOrQuit(msg.GetType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePut);
VerifyOrQuit(msg.GetMessageId() == 0);
VerifyOrQuit(msg.GetToken().GetLength() == 0);
VerifyOrQuit(msg.mMessage.GetOffset() == msg.mMessage.GetLength());
}
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(!iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == message->GetLength());
SuccessOrQuit(message->Append<uint8_t>(0xaa));
VerifyOrQuit(iterator.Init(*message) != kErrorNone);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SuccessOrQuit(message->Init(Coap::kTypeConfirmable, Coap::kCodePost, 0x1234));
SuccessOrQuit(message->ParseHeaderInfo(headerInfo));
VerifyOrQuit(headerInfo.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(headerInfo.GetCode() == Coap::kCodePost);
VerifyOrQuit(headerInfo.GetMessageId() == 0x1234);
VerifyOrQuit(headerInfo.GetToken().GetLength() == 0);
VerifyOrQuit(message->ReadType() == Coap::kTypeConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
VerifyOrQuit(message->ReadMessageId() == 0x1234);
SuccessOrQuit(message->ReadTokenLength(tokenLength));
VerifyOrQuit(tokenLength == 0);
SuccessOrQuit(message->ReadToken(readToken));
VerifyOrQuit(readToken.GetLength() == 0);
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(!iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == message->GetLength());
{
Coap::Msg msg(*message, messageInfo);
SuccessOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRemovePayloadMarkerIfNoPayload));
VerifyOrQuit(msg.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePost);
VerifyOrQuit(msg.GetMessageId() == 0x1234);
VerifyOrQuit(msg.GetToken().GetLength() == 0);
VerifyOrQuit(msg.mMessage.GetOffset() == msg.mMessage.GetLength());
}
// Write Token
token.mLength = 2;
token.m8[0] = 0x11;
token.m8[1] = 0x22;
SuccessOrQuit(message->WriteToken(token));
SuccessOrQuit(message->ParseHeaderInfo(headerInfo));
VerifyOrQuit(headerInfo.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(headerInfo.GetCode() == Coap::kCodePost);
VerifyOrQuit(headerInfo.GetMessageId() == 0x1234);
VerifyOrQuit(headerInfo.GetToken().GetLength() == 2);
SuccessOrQuit(message->ReadToken(readToken));
VerifyOrQuit(readToken.GetLength() == 2);
VerifyOrQuit(readToken == token);
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(!iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == message->GetLength());
{
Coap::Msg msg(*message, messageInfo);
SuccessOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRemovePayloadMarkerIfNoPayload));
VerifyOrQuit(msg.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePost);
VerifyOrQuit(msg.GetMessageId() == 0x1234);
VerifyOrQuit(msg.GetToken().GetLength() == 2);
VerifyOrQuit(msg.GetToken() == token);
VerifyOrQuit(msg.mMessage.GetOffset() == msg.mMessage.GetLength());
}
// Append URI-Path Option
SuccessOrQuit(message->AppendUriPathOptions("uri"));
SuccessOrQuit(message->ParseHeaderInfo(headerInfo));
VerifyOrQuit(headerInfo.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(headerInfo.GetCode() == Coap::kCodePost);
VerifyOrQuit(headerInfo.GetMessageId() == 0x1234);
VerifyOrQuit(headerInfo.GetToken().GetLength() == 2);
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(!iterator.IsDone());
VerifyOrQuit(iterator.GetOption() != nullptr);
VerifyOrQuit(iterator.GetOption()->GetNumber() == Coap::kOptionUriPath);
VerifyOrQuit(iterator.GetOption()->GetLength() == 3);
SuccessOrQuit(iterator.Advance());
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(!iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == message->GetLength());
SuccessOrQuit(message->AppendPayloadMarker());
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(!iterator.IsDone());
SuccessOrQuit(iterator.Advance());
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == message->GetLength());
// Append some payload after marker
length = message->GetLength();
SuccessOrQuit(message->Append<uint8_t>(0xef));
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(!iterator.IsDone());
SuccessOrQuit(iterator.Advance());
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == length);
{
Coap::Msg msg(*message, messageInfo);
SuccessOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRemovePayloadMarkerIfNoPayload));
VerifyOrQuit(msg.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePost);
VerifyOrQuit(msg.GetMessageId() == 0x1234);
VerifyOrQuit(msg.GetToken().GetLength() == 2);
VerifyOrQuit(msg.GetToken() == token);
VerifyOrQuit(msg.mMessage.GetOffset() == length);
}
// Re-write Token
token.mLength = 2;
token.m8[0] = 0x33;
token.m8[1] = 0x44;
SuccessOrQuit(message->WriteToken(token));
SuccessOrQuit(message->ParseHeaderInfo(headerInfo));
VerifyOrQuit(headerInfo.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(headerInfo.GetCode() == Coap::kCodePost);
VerifyOrQuit(headerInfo.GetMessageId() == 0x1234);
VerifyOrQuit(headerInfo.GetToken().GetLength() == 2);
SuccessOrQuit(message->ReadToken(readToken));
VerifyOrQuit(readToken.GetLength() == 2);
VerifyOrQuit(readToken == token);
{
Coap::Msg msg(*message, messageInfo);
SuccessOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRemovePayloadMarkerIfNoPayload));
VerifyOrQuit(msg.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePost);
VerifyOrQuit(msg.GetMessageId() == 0x1234);
VerifyOrQuit(msg.GetToken().GetLength() == 2);
VerifyOrQuit(msg.GetToken() == token);
VerifyOrQuit(msg.mMessage.GetOffset() == length);
}
// Re-write token - changing token length afterwards is not allowed
token.mLength = 3;
token.m8[2] = 0x55;
VerifyOrQuit(message->WriteToken(token) != kErrorNone);
SuccessOrQuit(message->ParseHeaderInfo(headerInfo));
VerifyOrQuit(headerInfo.GetToken().GetLength() == 2);
token.mLength = 2;
SuccessOrQuit(message->ReadToken(readToken));
VerifyOrQuit(readToken.GetLength() == 2);
VerifyOrQuit(readToken == token);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SuccessOrQuit(message->Init(Coap::kTypeConfirmable, Coap::kCodeGet, kUriCommissionerSet));
SuccessOrQuit(message->ParseHeaderInfo(headerInfo));
VerifyOrQuit(headerInfo.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(headerInfo.GetCode() == Coap::kCodeGet);
VerifyOrQuit(headerInfo.GetMessageId() == 0);
VerifyOrQuit(headerInfo.GetToken().GetLength() == Coap::Token::kDefaultLength);
VerifyOrQuit(message->ReadType() == Coap::kTypeConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodeGet);
VerifyOrQuit(message->ReadMessageId() == 0);
SuccessOrQuit(message->ReadTokenLength(tokenLength));
VerifyOrQuit(tokenLength == Coap::Token::kDefaultLength);
SuccessOrQuit(iterator.Init(*message));
VerifyOrQuit(!iterator.IsDone());
VerifyOrQuit(iterator.GetOption() != nullptr);
VerifyOrQuit(iterator.GetOption()->GetNumber() == Coap::kOptionUriPath);
SuccessOrQuit(iterator.Advance());
VerifyOrQuit(!iterator.IsDone());
VerifyOrQuit(iterator.GetOption() != nullptr);
VerifyOrQuit(iterator.GetOption()->GetNumber() == Coap::kOptionUriPath);
SuccessOrQuit(iterator.Advance());
VerifyOrQuit(iterator.IsDone());
VerifyOrQuit(!iterator.HasPayloadMarker());
VerifyOrQuit(iterator.GetPayloadMessageOffset() == message->GetLength());
SuccessOrQuit(message->ReadUriPathOptions(uriBuffer));
VerifyOrQuit(StringMatch(uriBuffer, PathForUri(kUriCommissionerSet)));
{
Coap::Msg msg(*message, messageInfo);
SuccessOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRemovePayloadMarkerIfNoPayload));
VerifyOrQuit(msg.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodeGet);
VerifyOrQuit(msg.GetMessageId() == 0);
VerifyOrQuit(msg.GetToken().GetLength() == 2);
VerifyOrQuit(msg.mMessage.GetOffset() == msg.mMessage.GetLength());
}
// Re-write code, type, and message ID
message->WriteType(Coap::kTypeNonConfirmable);
message->WriteCode(Coap::kCodePost);
message->WriteMessageId(0x9876);
SuccessOrQuit(message->ParseHeaderInfo(headerInfo));
VerifyOrQuit(headerInfo.GetType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(headerInfo.GetCode() == Coap::kCodePost);
VerifyOrQuit(headerInfo.GetMessageId() == 0x9876);
{
Coap::Msg msg(*message, messageInfo);
SuccessOrQuit(msg.ParseHeaderAndOptions(Coap::Msg::kRemovePayloadMarkerIfNoPayload));
VerifyOrQuit(msg.GetType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePost);
VerifyOrQuit(msg.GetMessageId() == 0x9876);
VerifyOrQuit(msg.GetToken().GetLength() == 2);
VerifyOrQuit(msg.mMessage.GetOffset() == msg.mMessage.GetLength());
// Msg::UpdateType()
msg.UpdateType(Coap::kTypeConfirmable);
VerifyOrQuit(msg.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePost);
VerifyOrQuit(msg.GetMessageId() == 0x9876);
VerifyOrQuit(message->ReadType() == Coap::kTypeConfirmable);
// Msg::UpdateMessageId()
msg.UpdateMessageId(0xabcd);
VerifyOrQuit(msg.GetType() == Coap::kTypeConfirmable);
VerifyOrQuit(msg.GetCode() == Coap::kCodePost);
VerifyOrQuit(msg.GetMessageId() == 0xabcd);
VerifyOrQuit(message->ReadMessageId() == 0xabcd);
}
message->Free();
testFreeInstance(instance);
}
};
} // namespace ot
int main(void)
{
ot::UnitTester::TestCoapMessage();
printf("All tests passed\n");
return 0;
}