Skip to content
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

Pcap device list refactoring to use smart pointers. #1386

Closed

Conversation

Dimi1010
Copy link
Collaborator

@Dimi1010 Dimi1010 commented May 6, 2024

This PR mainly aims to update PcapLiveDeviceList and PcapRemoteDeviceList to utilize cpp11's unique_ptr for dynamic memory management.

Overview of changes:

  • Refactor PcapLiveDeviceList to use smart pointers internally for memory management.
  • Refactor PcapRemoteDeviceList to use smart pointers internally for memory management.
  • Refactor PcapRemoteDevice to keep its own RemoteAuthentication copy or a shared reference of RemoteAuthentication with PcapRemoteDeviceList via std::shared_ptr as it currently uses a raw pointer to share authentication with the device list.
  • Add shared_ptr overloads to PcapLiveDeviceList methods. Deprecate raw pointer methods.
  • Add shared_ptr overloads to PcapRemoteDeviceList methods. Deprecate raw pointer methods.
  • Add unit tests for smart pointer API methods for PcapLiveDeviceList.
  • Add unit tests for smart pointer API methods for PcapRemoteDeviceList.
  • Find a better way to keep the old iterators than keeping a duplicate list?
  • Add unique_ptr overloads for clone to PcapLiveDevice and PcapRemoteDevice.
  • Add unit tests for smart pointer API method for PcapLiveDevice.

Additional minor changes:

  • Marked PcapLiveDeviceList and PCapRemoteDeviceList's copy ctors as explicitly deleted instead of keeping them private.
  • Moved internal helper deleters to MemoryUtils.h.
  • Centralized code for fetching device list from Libpcap/Winpcap/Npcap in DeviceUtils.h/cpp. (It was duplicated a couple of times)

…eviceList with smart pointers.

Added a secondary vector of non-owning pointers to the devices to keep the signature of some getter methods as they return references.
…ress, instead of copying the address to a new bytes array.
@Dimi1010
Copy link
Collaborator Author

Dimi1010 commented May 6, 2024

@seladb On the task list above, regarding PcapRemoteDevice's RemoteAuthentication, should I keep the current functionality by using a shared_ptr or make each device hold a unique copy?

Edit: Decided to go with shared_ptr for now.

Dimi1010 added 24 commits May 7, 2024 20:37
…ore freedom on the caller, to skip a reference counter increase/decrease by moving.
…optimizations if the caller does not require his own ptr anymore
…nagement.

Added a second helper 'view' vector to keep raw pointers of the devices for backward compat.
Replaced iterator types with auto keyword.
Removed destructor as the smart pointers handle cleanup.
… a non-owning raw pointer to allow the device to outlive the list if nessesary.
@Dimi1010 Dimi1010 force-pushed the feature/device-list-smart-pointers branch from bb2cc09 to 541b198 Compare May 27, 2024 19:26
Copy link
Collaborator Author

@Dimi1010 Dimi1010 left a comment

Choose a reason for hiding this comment

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

@seladb @tigercosmos could you please look at these comments and give an opinion?

@@ -49,50 +57,105 @@ namespace pcpp

/**
* @return A vector containing pointers to all live devices currently installed on the machine
* @deprecated This method is deprecated in favor of the SmartPtrAPI overload.
*/
PCPP_DEPRECATED_RAW_PTR_API const std::vector<PcapLiveDevice*>& getPcapLiveDevicesList() const;
Copy link
Collaborator Author

@Dimi1010 Dimi1010 May 27, 2024

Choose a reason for hiding this comment

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

PcapLiveDeviceList provides a getter API to get all registered devices, while PcapRemoteDeviceList provides a container-like API to get all registered devices.

In my opinion, it would be best if both lists provided a standardized API. Which one would be better?
Here are my opinions on the pros and cons for both:

Getter method:
✔️ A getter approach would be consistent with the rest of the getter methods.
✔️ A getter can be made more easily to have overloads via tag dispatching.
❌ Reference getter locks down the internal implementation to always keeping a member of this type.
❌ Value getter will have a cost every time the getter is invoked, constructing the vector and coping the shared pointers.

Iterator API:
✔️ An iterator API would allow direct integration with other iterator algorithms and for-each statements.
✔️ Low overhead iteration over the devices.
❌ Complications with deprecation of existing iterators.[1]

[1] As the current PcapRemoteDeviceList already has an iterator API, that would be difficult to change with respect to the deprecation polity of 1 version notice of deprecation. Ideally, the iterators would refer to const std::shared_ptr<T>& with T being the device class. The current implementation returns iterators to *T. Theoretically it is possible to write a converting iterator, but it is going to be complicated. The current stopgap I ended up on is keeping a duplicate list of raw pointers to the devices, which is not ideal.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it makes sense to have both a getter and an iterator for a container-like object.

Regarding the iterator type, since std::shared_ptr<T> and T* are pointer types, the users' existing code (such as ->) should not have much impact.

However, if we really don't want to break the API, as you said, it's still possible to have some conversion, but it's just troublesome.

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 3, 2024

Choose a reason for hiding this comment

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

I think it makes sense to have both a getter and an iterator for a container-like object.

Both is an option.

Regarding the iterator type, since std::shared_ptr<T> and T* are pointer types, the users' existing code (such as ->) should not have much impact.

Yeah, they shouldn't really. unless they decided to save the value from the iterator somewhere. That is a breaking change, but it would only need a get() call from the smart pointer to fix, as old code should still keep the list alive to keep the devices alive.

However, if we really don't want to break the API, as you said, it's still possible to have some conversion, but it's just troublesome.

I tried my hand at converting iterator a bit earlier. It is technically possible but it quickly becomes templated black magic if you try to make a general case converting iterator.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I prefer to remove the old API, and have both getter and new iterator. Then we can also remove the hack of SmartPtrApiTag and m_LiveDeviceListView

@seladb what do you say?


/**
* @return An iterator object pointing to the first PcapRemoteDevice in list
*/
RemoteDeviceListIterator begin() { return m_RemoteDeviceList.begin(); }
RemoteDeviceListIterator begin() { return m_RemoteDeviceListView.begin(); }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See comment in PcapLiveDeviceList on getPcapLiveDevicesList



/// @file

#define PCPP_DEPRECATED_RAW_PTR_API PCPP_DEPRECATED("This method is deprecated in favor of the SmartPtrAPI overload.")
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Doxygen generation really dislikes this macro for some reason, when used on a member function. No idea why... tried several things to fix them including mirroring the way the define is written in TcpLayer.h, but none of them worked. Would like a second opinion on why the generation fails on the macro.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Doxygen test fails with the following:

Preprocessing /tmp/cirrus-ci-build/Pcap++/head/tmp/cirrus-ci-build/Pcap++/header/PcapLiveDeviceList.h:62: error: Found ';' while parsing initializer list! (doxygen could be confused by a macro call without semicolon) (warning treated as error, aborting now)
er/MBufRawPacket.h...

@Dimi1010 Dimi1010 marked this pull request as ready for review June 2, 2024 18:49
@Dimi1010 Dimi1010 requested a review from seladb as a code owner June 2, 2024 18:49
@@ -0,0 +1,34 @@
#pragma once
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggest to put all util functions in one file, as there is not much code.

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 3, 2024

Choose a reason for hiding this comment

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

Which one to merge into the other tho? I initially separated them as they have different use cases.

MemoryUtils is for general utilities w.r.t. smart pointer utilities and deleters.
DeviceUtils is for utility functions for Pcap devices, like fetching devices, etc.

MemoryUtils will be used a lot more as any time a smart pointer to a Pcap handle is used, it would need to be included for the deleter.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see. originally I thought we could merge MemoryUtils and DeviceUtils, but it did make sense to be separated.

*/
struct SmartPtrApiTag{};
/**
* Helper tag constant for disambuguating smart pointer API.
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo: disambiguating, btw I rarely see this word, I wonder if it is precise here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed a95c946. I think it should be better now?

@@ -130,6 +137,8 @@ namespace pcpp
static void onPacketArrivesNoCallback(uint8_t* user, const struct pcap_pkthdr* pkthdr, const uint8_t* packet);
static void onPacketArrivesBlockingMode(uint8_t* user, const struct pcap_pkthdr* pkthdr, const uint8_t* packet);
public:
PcapLiveDevice(const PcapLiveDevice& other) = delete;
Copy link
Collaborator

Choose a reason for hiding this comment

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

please also implement moving construction/assignment.

Copy link
Collaborator

Choose a reason for hiding this comment

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

btw, if the underlying are shared pointers, why do we disallow coping?

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 3, 2024

Choose a reason for hiding this comment

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

btw, if the underlying are shared pointers, why do we disallow coping?

IDK. I think the reason a LiveDevice itself is not copiable is something with the underlying Pcap descriptors. Although there is still the clone method so not really sure. The current change is mostly a stylistic from the pre-Cpp11 style of private ctors to post-Cpp11 deleted ctors.

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 keep the old clone, but I think we can now embrace the copy constructor/assignment?

Copy link
Collaborator Author

@Dimi1010 Dimi1010 Jun 3, 2024

Choose a reason for hiding this comment

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

Ah, I figured out at least partly why its private / deleted. LiveDevice is a virtual class and usually handled by pointers to base class (i.e. WinPcapLiveDevice device is used through PcapLiveDevice pointer). A copy ctor used from a base pointer will only use the base copy ctor, not the actual copy ctor of the class the base ptr points to.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

please also implement moving construction/assignment.

Added 4bcb50e

Pcap++/header/PcapLiveDeviceList.h Show resolved Hide resolved
Pcap++/src/PcapFilter.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDeviceList.cpp Outdated Show resolved Hide resolved
@Dimi1010 Dimi1010 marked this pull request as draft June 5, 2024 07:22
@Dimi1010
Copy link
Collaborator Author

Dimi1010 commented Jun 5, 2024

This PR will be split into multiple smaller PRs as part of #1431.

@Dimi1010 Dimi1010 closed this Jun 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants