Skip to content

Added sized overloads for buffer construction of IPv6Address and MacAddress. #1847

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Jul 12, 2025

Conversation

Dimi1010
Copy link
Collaborator

@Dimi1010 Dimi1010 commented Jun 7, 2025

  • Added new constructors: for when the buffer size isn't guaranteed.
    • IPv6Address(const uint8_t* buf, size_t size)
    • MacAddress(const uint8_t* buf, size_t size)
  • Added sized overloads of copyTo
    • size_t IPv6Address::copyTo(uint8_t* buf, size_t size)
    • size_t MacAddress::copyTo(uint8_t* buf, size_t size)
  • Replaced allocating copyTo(uint8_t** buf) and copyTo(uint8_t** buf, size_t& size) with copyToNewBuffer.

@Dimi1010 Dimi1010 added refactoring API deprecation Pull requests that deprecate parts of the public interface. labels Jun 7, 2025
@Dimi1010 Dimi1010 marked this pull request as ready for review June 7, 2025 20:12
@Dimi1010 Dimi1010 requested a review from seladb as a code owner June 7, 2025 20:12
/// @brief Allocates a byte array and copies address value into it.
/// @param[out] size Returns the size in bytes of the allocated array. Usually 16.
/// @return A unique pointer to the allocated byte array containing the address value
std::unique_ptr<uint8_t[]> copyToNewBuffer(size_t& size) const;
Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debating if its better to return unique_ptr with size as an out param, or return pair<unique_ptr<...>, size_t>

PS: Kinda starting to lean to pair as it will keep the buffer and size logically grouped? 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can consider to use std::array here as well?

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is toByteArray that returns an std::array type.

This one mostly covers existing functionality that was in copyTo that allocates heap buffer. Which I suppose can be cheaper if it will be changing ownership around a lot?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am okay with having std::unique_ptr API, but I wonder if people need it. IMO, most of the time, people may only need the reference to the bytes directly from the IpAddress object. In rare cases, they can just copy the bytes. Then, we can keep the interface as simple as possible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true. I can't find a lot of usages where a new buffer is required. Most of the time it is copied to an already existing buffer, if the copyTo api is used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up deprecating the allocating copyTo API altogether. It is not commonly used and it is clunky IMO.
The use case can be handled by the new size_t copyTo(uint8_t* buffer, size_t size) function that returns the written bytes or the required size. It also supports querying the function.

Example usage:

MacAddress mac;
size_t addrLen = mac.copyTo(nullptr, 0); // Query the function for a required buffer.
uint8_t* addr = new uint8_t[addrLen];
if(mac.copyTo(addr, addrLen) != addrLen) { /* Error check */ }
// Use addr...

Copy link

codecov bot commented Jun 7, 2025

Codecov Report

Attention: Patch coverage is 94.24460% with 8 lines in your changes missing coverage. Please review.

Project coverage is 83.24%. Comparing base (ed08e08) to head (f04b721).
Report is 1 commits behind head on dev.

Files with missing lines Patch % Lines
Common++/src/IpAddress.cpp 90.24% 4 Missing ⚠️
Common++/src/MacAddress.cpp 87.87% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #1847      +/-   ##
==========================================
+ Coverage   83.21%   83.24%   +0.02%     
==========================================
  Files         285      285              
  Lines       49835    49946     +111     
  Branches    10556    10779     +223     
==========================================
+ Hits        41472    41576     +104     
- Misses       7552     7567      +15     
+ Partials      811      803       -8     
Flag Coverage Δ
alpine320 75.58% <90.19%> (+0.10%) ⬆️
fedora42 75.68% <90.65%> (+0.10%) ⬆️
macos-13 80.73% <92.59%> (+0.02%) ⬆️
macos-14 80.73% <92.59%> (+0.02%) ⬆️
macos-15 80.71% <91.66%> (+0.01%) ⬆️
mingw32 70.30% <78.46%> (+0.05%) ⬆️
mingw64 70.30% <78.46%> (+0.05%) ⬆️
npcap 85.13% <89.74%> (-0.01%) ⬇️
rhel94 75.44% <86.66%> (+0.08%) ⬆️
ubuntu2004 59.40% <84.46%> (+0.20%) ⬆️
ubuntu2004-zstd 59.52% <84.46%> (+0.20%) ⬆️
ubuntu2204 75.36% <86.27%> (+0.08%) ⬆️
ubuntu2204-icpx 61.58% <76.66%> (+0.05%) ⬆️
ubuntu2404 75.61% <90.19%> (+0.07%) ⬆️
ubuntu2404-arm64 75.59% <90.19%> (+0.10%) ⬆️
unittest 83.24% <94.24%> (+0.02%) ⬆️
windows-2022 85.04% <89.74%> (-0.08%) ⬇️
windows-2025 85.17% <89.74%> (-0.01%) ⬇️
winpcap 85.29% <89.74%> (+<0.01%) ⬆️
xdp 51.30% <13.59%> (-0.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Dimi1010 Dimi1010 requested a review from tigercosmos June 21, 2025 18:27
Comment on lines 267 to 268
/// @deprecated Allocating copyTo API is deprecated.
PCPP_DEPRECATED("Allocating copyTo API is deprecated.")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to deprecate this API?
If someone wants the library to allocate the buffer for them, what's wrong with it? 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is very similar to the new size_t copyTo(uint8_t*, size_t) function that performs a pure copy to the input buffer. The only difference is that one is a pointer and the other a pointer to pointer param.

I also don't think it should be the copyTo function's job to allocate a new buffer. IMO, it breaks the SRP. I understand that is was needed because of how the previous copyTo function worked, it did not provide a way to check the required size of a buffer without the user knowing its N bytes.

The new function allows reporting of the required buffer size (query mode), so it should be left to the user to handle the memory management of how he wants to provide the required buffer. It allows for simpler, more consistent API of the class and less potential for errors due to the class allocating a buffer the user did not expect.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference is that one is a pointer and the other a pointer to pointer param.

It also allocates a new buffer before copying the data to it, which is the main difference between the 2 overloads.

I also don't think it should be the copyTo function's job to allocate a new buffer. IMO, it breaks the SRP. I understand that is was needed because of how the previous copyTo function worked, it did not provide a way to check the required size of a buffer without the user knowing its N bytes.

The user still needs to know the required size to allocate enough bytes before calling this method. Maybe in some cases, users want the library to do that for them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also allocates a new buffer before copying the data to it, which is the main difference between the 2 overloads.

This was meant as the only difference between the two signatures is a degree of indirection, so it will be easy to mistake one for the other.

I also don't think it should be the copyTo function's job to allocate a new buffer. IMO, it breaks the SRP. I understand that was needed because of how the previous copyTo function worked, it did not provide a way to check the required size of a buffer without the user knowing its N bytes.

The user still needs to know the required size to allocate enough bytes before calling this method. Maybe in some cases, users want the library to do that for them.

Well, an earlier commit had the functionality remade to copyToNewBuffer that returned an unique ptr<uint8_t[]>, but the API for it ended up kinda clunky. I could re-add that, if needed.

Yes, they still need to know it, but they can control the method of allocation. It follows the somewhat standard pattern of query -> allocate -> populate. I personally dislike black box allocations that then rely on the user knowing the precice method the library allocated the memory inside the function.

Usually, a black box allocation function is accompanied by a black box deallocation function. In this case, there is only an allocation function.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure I saw this pattern somewhere where uint8_t** means that the buffer is allocated by the library. However I'm not sure where I saw it (I wrote these methods quite a few year ago).

All I'm saying is that I don't think we should deprecate (and later remove) these methods. We can just keep them as they don't break anything and might be useful for some people.

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I have seen that pattern, yes.
My main concern is that copyTo(uint8_t** ptr, size_t& size) and copyTo(uint8_t* ptr, size_t size) can be easily mistakenly called.

For example, a user wants to copy to a new buffer but forgets to call with &ptr, and instead calls with ptr. The call will be resolved to the non allocating overload that will either do a no-op on nullptr or explode if the ptr is uninitialized.

Perhaps we can keep the operation but rename the method to something like copyToNewBuffer?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we should just keep one. IMO, **ptr looks more like C-style.

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update 019b376: Re-added allocating copyTo with the current signature under the name copyToNewBuffer(uint8_t** buffer, size_t& size) and updated deprecations.

return requiredSize;
}

if (size < requiredSize)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to support an array size of less than 16 bytes? I'm not sure if there a need to copy just a portion of an IPv6 address

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't support copying to an array of less than 16 bytes. If the buffer size is less, no copy is made, and the required buffer size is returned.

If the array is larger, the size of the array that has been written to is returned.

Essentially:

  • If the return value is greater than the size input of the buffer, a buffer of at least the size of the return value is needed.
  • If the return value is less or equal to the input size param, that is the number of bytes that are updated.

That patern allows runtime querying of the required buffer size without knowing and hardcoding it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, I misread the code. I find this API a bit confusing... as a user, I allocate a byte array of a certain size, then call this method. Now, in order to know if the copy succeeded or not, I need to check the return size and compare it to the size I provided to the function. I guess throwing an exception is better in this case...

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it is a pattern that allows the user to query the precice required size and not have to guestimate / handcode the allocation size.

Common use case is:

  1. Call the method with nullptr + 0 size -> get required bytes
  2. Allocate buffer
  3. Call method with (buffer, bufSize)
  4. Check if the return value is less or equal to bufferSize.

It also allows the user to guestimate the buffer if there is a common case. But if for some reason, the initial buffer is insufficient, the pattern allows for hot retry with the precise amount without triggering the exception mechanism. Here, its value is kinda diminished because the buffer is always 16, but it shines when the required buffer is dynamic in range.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think IP addresses are well known concepts, and even more so for people who use these classes. They should be aware that IPv4 is 4 bytes and IPv6 is 16 bytes. IMHO throwing an exception here should be good enough

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, why not check if the size is either 4 or 16?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that they are well known concepts. If the size is known, step 1 can be skipped and the buffer can be directly allocated / used. I think its a good idea to still have a way to query the size at runtime, even if it should be well known.

Additionally, I counted copyTo as a low-level operation, so I tried to minimize the exception throwing for the most common error case (insufficient buffer). Ideally, I made the API that way to keep the signature independent of the actual type that is being copied, so we can have a unified copyTo API signature for both dynamic and static length objects, thus allowing more flexibility down the line if needed. The initial signature comes from size_t writeToBuffer(uint8_t* buffer, size_t bufferLen) const; in #1820, where it was used to serialize helper structs.

In this case, why not check if the size is either 4 or 16?

The checks are not precise 4 or 16 so it can potentially write to overprovisioned buffers. That is one of primary reasons the function returns the num of written bytes, instead of just true/false on success.

Comment on lines 153 to 154
/// @deprecated Allocating copyTo API is deprecated.
PCPP_DEPRECATED("Allocating copyTo API is deprecated.")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: why deprecating?

return requiredSize;
}

if (size < m_Address.size())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: do we need to support copying to an array smaller than 6 bytes?

@Dimi1010 Dimi1010 requested a review from seladb July 11, 2025 09:34
@Dimi1010 Dimi1010 merged commit 1ea59e0 into seladb:dev Jul 12, 2025
77 of 79 checks passed
@Dimi1010 Dimi1010 deleted the refactor/sized-copy-to branch July 12, 2025 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API deprecation Pull requests that deprecate parts of the public interface. refactoring
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants