[dhcp6] introduce Option::Iterator class (#11574)

This commit adds the `Option::Iterator` class for searching for and
iterating over DHCPv6 options with a specific code within a message.
The iteration can cover the entire message or be constrained to a
given `OffsetRange`.

The `Option::Iterator` is used to simplify the `Dhcp6::Client` and
`Dhcp6.Server` implementations, particularly when iterating over
`IaAddressOption`s within an `IaNaOption`.
This commit is contained in:
Abtin Keshavarzian
2025-06-04 23:30:42 -07:00
committed by GitHub
parent 4b43f6de76
commit a1de12fd49
4 changed files with 149 additions and 39 deletions
+8 -19
View File
@@ -402,9 +402,10 @@ Error Client::ProcessClientIdOption(const Message &aMessage)
Error Client::ProcessIaNaOption(const Message &aMessage)
{
Error error = kErrorNone;
OffsetRange offsetRange;
IaNaOption option;
Error error = kErrorNone;
OffsetRange offsetRange;
IaNaOption option;
Option::Iterator iterator;
SuccessOrExit(error = Option::FindOption(aMessage, Option::kIaNa, offsetRange));
SuccessOrExit(error = aMessage.Read(offsetRange, option));
@@ -416,28 +417,16 @@ Error Client::ProcessIaNaOption(const Message &aMessage)
VerifyOrExit(StatusCodeOption::ReadStatusFrom(aMessage, offsetRange) == StatusCodeOption::kSuccess,
error = kErrorFailed);
while (!offsetRange.IsEmpty())
for (iterator.Init(aMessage, offsetRange, Option::kIaAddress); !iterator.IsDone(); iterator.Advance())
{
OffsetRange subOffsetRange;
IaAddressOption addressOption;
error = Option::FindOption(aMessage, offsetRange, Option::kIaAddress, subOffsetRange);
if (error == kErrorNotFound)
{
error = kErrorNone;
ExitNow();
}
SuccessOrExit(error);
SuccessOrExit(error = aMessage.Read(subOffsetRange, addressOption));
SuccessOrExit(error = aMessage.Read(iterator.GetOptionOffsetRange(), addressOption));
SuccessOrExit(error = ProcessIaAddressOption(addressOption));
// Update `offsetRange` to after the parsed `IaAddressOption`
offsetRange.InitFromRange(subOffsetRange.GetEndOffset(), offsetRange.GetEndOffset());
}
error = iterator.GetError();
exit:
return error;
}
+9 -20
View File
@@ -239,9 +239,10 @@ exit:
Error Server::ProcessIaNaOption(const Message &aMessage, uint32_t &aIaid)
{
Error error = kErrorNone;
OffsetRange offsetRange;
IaNaOption iaNaOption;
Error error = kErrorNone;
OffsetRange offsetRange;
IaNaOption iaNaOption;
Option::Iterator iterator;
SuccessOrExit(error = Option::FindOption(aMessage, Option::kIaNa, offsetRange));
SuccessOrExit(error = aMessage.Read(offsetRange, iaNaOption));
@@ -252,30 +253,18 @@ Error Server::ProcessIaNaOption(const Message &aMessage, uint32_t &aIaid)
mPrefixAgentsMask = 0;
// Iterate and parse sub-options within `IaNaOption`.
// Iterate and parse `kIaAddress` sub-options within `IaNaOption`.
while (!offsetRange.IsEmpty())
for (iterator.Init(aMessage, offsetRange, Option::kIaAddress); !iterator.IsDone(); iterator.Advance())
{
OffsetRange subOffsetRange;
IaAddressOption addressOption;
error = Option::FindOption(aMessage, offsetRange, Option::kIaAddress, subOffsetRange);
if (error == kErrorNotFound)
{
error = kErrorNone;
ExitNow();
}
SuccessOrExit(error);
SuccessOrExit(error = aMessage.Read(subOffsetRange, addressOption));
SuccessOrExit(error = aMessage.Read(iterator.GetOptionOffsetRange(), addressOption));
ProcessIaAddressOption(addressOption);
// Update `offsetRange` to after the parsed `IaAddressOption`
offsetRange.InitFromRange(subOffsetRange.GetEndOffset(), offsetRange.GetEndOffset());
}
error = iterator.GetError();
exit:
return error;
}
+52
View File
@@ -114,6 +114,58 @@ exit:
return error;
}
//---------------------------------------------------------------------------------------------------------------------
// Option::Iterator
void Option::Iterator::Init(const Message &aMessage, Code aCode)
{
OffsetRange msgOffsetRange;
msgOffsetRange.InitFromMessageOffsetToEnd(aMessage);
Init(aMessage, msgOffsetRange, aCode);
}
void Option::Iterator::Init(const Message &aMessage, const OffsetRange &aMsgOffsetRange, Code aCode)
{
mMessage = &aMessage;
mCode = aCode;
mMsgOffsetRange = aMsgOffsetRange;
mError = kErrorNone;
mIsDone = false;
Advance();
}
void Option::Iterator::Advance(void)
{
VerifyOrExit(!mIsDone);
if (mMessage == nullptr)
{
mError = kErrorInvalidState;
mIsDone = true;
ExitNow();
}
mError = Option::FindOption(*mMessage, mMsgOffsetRange, mCode, mOptionOffsetRange);
if (mError == kErrorNone)
{
// Update `mMsgOffsetRange` to start after the current option,
// preparing the iterator for the next call to `Advance()`.
mMsgOffsetRange.InitFromRange(mOptionOffsetRange.GetEndOffset(), mMsgOffsetRange.GetEndOffset());
ExitNow();
}
mOptionOffsetRange.Clear();
mError = (mError == kErrorNotFound) ? kErrorNone : mError;
mIsDone = true;
exit:
return;
}
//---------------------------------------------------------------------------------------------------------------------
// Eui64Duid
+80
View File
@@ -178,6 +178,86 @@ public:
kClientLastTransactionTime = 46, ///< Client Last Transaction Time Option.
};
/**
* Represents an iterator for searching for and iterating over DHCPv6 options with a specific code within a message.
*/
class Iterator : private Clearable<Iterator>
{
friend class Clearable<Iterator>;
public:
/**
* This is an iterator constructor that initializes the iterator to a cleared (invalid) state.
*
* An iterator in this state must be initialized using one of the `Init()` methods before use.
*/
Iterator(void) { Clear(); }
/**
* Initializes the iterator and finds the first matching option within an entire message.
*
* The search is performed from `aMessage.GetOffset()` to the end of the message.
*
* @param[in] aMessage The message to search in.
* @param[in] aCode The option code to search for.
*/
void Init(const Message &aMessage, Code aCode);
/**
* Initializes the iterator and finds the first matching option within a specific range of a message.
*
* @param[in] aMessage The message to search in.
* @param[in] aMsgOffsetRange The specific range within @p aMessage to search.
* @param[in] aCode The option code to search for.
*/
void Init(const Message &aMessage, const OffsetRange &aMsgOffsetRange, Code aCode);
/**
* Indicates whether the iteration is complete.
*
* The iteration is considered done when all matching options have been iterated through, or if an error
* occurred during iteration. The `GetError()` method can be used to get the error status.
*
* Particularly, `IsDone() && GetError() == kErrorNone` indicates a successful end of the iteration (i.e., no
* more matching options were found).
*
* @returns `true` if the iteration is complete, `false` otherwise.
*/
bool IsDone(void) const { return mIsDone; }
/**
* Advances the iterator to the next matching option.
*/
void Advance(void);
/**
* Gets the offset range of the current option matched by the iterator.
*
* The returned offset range refers to the matched option in the message when the iterator is not done
* (`IsDone()` is `false`). Otherwise, an empty offset range is returned.
*
* @returns The `OffsetRange` of the current option.
*/
const OffsetRange &GetOptionOffsetRange(void) const { return mOptionOffsetRange; }
/**
* Gets any error that occurred during the iteration.
*
* @retval kErrorNone Successfully iterated over options (so far).
* @retval kErrorParse The options in the message were malformed and failed to parse.
* @retval kErrorInvalidState The iterator was not initialized.
*/
Error GetError(void) const { return mError; }
private:
const Message *mMessage;
OffsetRange mMsgOffsetRange;
OffsetRange mOptionOffsetRange;
Code mCode;
Error mError;
bool mIsDone;
};
/**
* Returns the DHCPv6 option code.
*