Skip to content

Commit 234819b

Browse files
authored
Replace python scripts with C++ classes (#481)
* draft echo server * replace echo server * update http fuzzer * move echoserver.hpp * add include * remove echo server python * replace rawsocket * replace telnet * remove zmq python tests * remove requirements * no need for submodules * it should hpp
1 parent 4e31c9f commit 234819b

23 files changed

Lines changed: 802 additions & 354 deletions

.github/dependabot.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,3 @@ updates:
1313
actions-dependencies:
1414
patterns:
1515
- "*" # Include all actions
16-
17-
- package-ecosystem: "pip"
18-
directory: "/tests/data/requirements.txt"
19-
schedule:
20-
interval: "monthly"
21-
groups:
22-
pip-dependencies:
23-
patterns:
24-
- "*" # Include all dependencies

.github/workflows/build_and_test.yml

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,12 @@ jobs:
5454
key: memleak-ccache-${{ github.run_id }}
5555
restore-keys: |
5656
memleak-ccache
57-
- name: Install Test Requirements
58-
run: |
59-
python3 -m venv .venv
60-
. .venv/bin/activate
61-
pip3 install -r tests/data/requirements.txt
6257
- name: Configure
6358
run: cmake -DXXX_ENABLE_MEMLEAK_CHECK=ON -DXXX_BUILD_TESTS=ON -S . -B build
6459
- name: Build
6560
run: cmake --build build --parallel
6661
- name: Run tests
67-
run: |
68-
. .venv/bin/activate
69-
ctest --output-on-failure --test-dir build
62+
run: ctest --output-on-failure --test-dir build
7063
- name: Save ccache
7164
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
7265
with:
@@ -95,19 +88,12 @@ jobs:
9588
key: ${{ matrix.sanitizer }}-ccache-${{ github.run_id }}
9689
restore-keys: |
9790
${{ matrix.sanitizer }}-ccache
98-
- name: Install Test Requirements
99-
run: |
100-
python3 -m venv .venv
101-
. .venv/bin/activate
102-
pip3 install -r tests/data/requirements.txt
10391
- name: Configure
10492
run: cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DXXX_BUILD_TESTS=ON -DXXX_BUILD_UNITTESTS=OFF -DXXX_BUILD_FUZZTESTS=ON -DXXX_ENABLE_${{ matrix.sanitizer }}=ON -S . -B build
10593
- name: Build
10694
run: cmake --build build --parallel
10795
- name: Run tests
108-
run: |
109-
. .venv/bin/activate
110-
ctest --output-on-failure --test-dir build
96+
run: ctest --output-on-failure --test-dir build
11197
- name: Save ccache
11298
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
11399
with:
@@ -146,19 +132,12 @@ jobs:
146132
key: ${{ matrix.image }}-ccache-${{ github.run_id }}
147133
restore-keys: |
148134
${{ matrix.image }}-ccache
149-
- name: Install Test Requirements
150-
run: |
151-
python3 -m venv .venv
152-
. .venv/bin/activate
153-
pip3 install -r tests/data/requirements.txt
154135
- name: Configure
155136
run: cmake -DXXX_BUILD_TESTS=ON -S . -B build
156137
- name: Build
157138
run: cmake --build build --parallel
158139
- name: Run Tests
159-
run: |
160-
. .venv/bin/activate
161-
ctest --output-on-failure --test-dir build
140+
run: ctest --output-on-failure --test-dir build
162141
- name: Save ccache
163142
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
164143
with:

.github/workflows/sonarcloud.yml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,12 @@ jobs:
2424
submodules: recursive
2525
- name: Prepare
2626
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
27-
- name: Install Test Requirements
28-
run: |
29-
python3 -m venv .venv
30-
. .venv/bin/activate
31-
pip install -r tests/data/requirements.txt
3227
- name: Install Build Wrapper
3328
uses: SonarSource/sonarqube-scan-action/install-build-wrapper@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9
3429
- name: Configure
3530
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DXXX_ENABLE_COVERAGE=ON -DXXX_BUILD_TESTS=ON
3631
- name: Run build-wrapper
37-
run: |
38-
. .venv/bin/activate
39-
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/ --config Debug --parallel --target coverage-xml
32+
run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/ --config Debug --parallel --target coverage-xml
4033
- name: Run sonar-scanner
4134
uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9
4235
env:

scripts/clang-tidy-changed.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ BUILD_DIR=${2:-build}
1717

1818
if [ "$MODE" = "changed" ]; then
1919
# Get the list of changed files from origin/master
20-
git fetch origin master
21-
files=$(git diff --name-only origin/master -- '*.cpp' '*.h' | grep -v 'thirdparty/' || true)
20+
git fetch --no-recurse-submodules origin master
21+
files=$(git diff --name-only origin/master -- '*.cpp' '*.hpp' | grep -v 'thirdparty/' || true)
2222
else
2323
# Find all relevant files
24-
files=$(find "${ROOTPATH}" -type f \( -name '*.cpp' -o -name '*.h' \) -not -path "*/thirdparty/*")
24+
files=$(find "${ROOTPATH}" -type f \( -name '*.cpp' -o -name '*.hpp' \) -not -path "*/thirdparty/*")
2525
fi
2626

2727
# Check if there are any files to process

tests/EchoServer.hpp

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#pragma once
2+
3+
#include <arpa/inet.h>
4+
#include <netinet/in.h>
5+
#include <sys/socket.h>
6+
#include <unistd.h>
7+
8+
#include <cstring>
9+
#include <sstream>
10+
#include <stdexcept>
11+
#include <string>
12+
#include <thread>
13+
14+
/**
15+
* @class EchoServer
16+
* A simple HTTP echo server for testing purposes
17+
* Listens on a TCP socket and echoes back the received HTTP request
18+
*/
19+
class EchoServer {
20+
private:
21+
int _serverSocket{-1};
22+
int _port;
23+
std::jthread _serverThread;
24+
25+
/**
26+
* Server loop that handles incoming connections
27+
* @param[in] stopToken Stop token for cooperative cancellation
28+
*/
29+
void serverLoop(std::stop_token stopToken)
30+
{
31+
while (!stopToken.stop_requested())
32+
{
33+
// Accept incoming connection
34+
sockaddr_in clientAddr{};
35+
socklen_t clientAddrLen = sizeof(clientAddr);
36+
int clientSocket = accept(_serverSocket, reinterpret_cast<sockaddr *>(&clientAddr), &clientAddrLen);
37+
38+
if (clientSocket < 0)
39+
{
40+
if (!stopToken.stop_requested())
41+
{
42+
continue;
43+
}
44+
break;
45+
}
46+
47+
// Read the HTTP request
48+
char buffer[4096] = {0};
49+
ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
50+
51+
if (bytesRead > 0)
52+
{
53+
// Use explicit length constructor to ensure exact byte count
54+
std::string request(buffer, bytesRead);
55+
56+
// Extract only the body from the HTTP request
57+
// HTTP headers end with "\r\n\r\n", body starts after that
58+
std::string body;
59+
size_t bodyStart = request.find("\r\n\r\n");
60+
if (bodyStart != std::string::npos)
61+
{
62+
body = request.substr(bodyStart + 4); // +4 to skip "\r\n\r\n"
63+
}
64+
65+
// Build HTTP response with echoed body content
66+
std::ostringstream response;
67+
response << "HTTP/1.1 200 OK\r\n";
68+
response << "Content-Type: text/plain\r\n";
69+
response << "Content-Length: " << body.length() << "\r\n";
70+
response << "Connection: close\r\n";
71+
response << "\r\n";
72+
response << body;
73+
74+
// Send response
75+
std::string responseStr = response.str();
76+
send(clientSocket, responseStr.c_str(), responseStr.length(), 0);
77+
}
78+
79+
close(clientSocket);
80+
}
81+
}
82+
83+
public:
84+
/**
85+
* Constructs a new EchoServer object and starts listening
86+
* @param[in] port The port to listen on
87+
* @throws std::runtime_error if server fails to start
88+
*/
89+
explicit EchoServer(int port) : _port(port)
90+
{
91+
_serverSocket = socket(AF_INET, SOCK_STREAM, 0);
92+
if (_serverSocket < 0)
93+
{
94+
throw std::runtime_error("Failed to create socket");
95+
}
96+
97+
int opt = 1;
98+
if (setsockopt(_serverSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
99+
{
100+
close(_serverSocket);
101+
throw std::runtime_error("Failed to set socket options");
102+
}
103+
104+
sockaddr_in serverAddr{};
105+
serverAddr.sin_family = AF_INET;
106+
serverAddr.sin_addr.s_addr = INADDR_ANY;
107+
serverAddr.sin_port = htons(_port);
108+
109+
if (bind(_serverSocket, reinterpret_cast<sockaddr *>(&serverAddr), sizeof(serverAddr)) < 0)
110+
{
111+
close(_serverSocket);
112+
throw std::runtime_error("Failed to bind socket to port");
113+
}
114+
115+
if (listen(_serverSocket, 5) < 0)
116+
{
117+
close(_serverSocket);
118+
throw std::runtime_error("Failed to listen on socket");
119+
}
120+
121+
// Start server thread
122+
_serverThread = std::jthread(&EchoServer::serverLoop, this);
123+
}
124+
125+
/**
126+
* Gets the port number
127+
* @return The port number the server is listening on
128+
*/
129+
[[nodiscard]] int getPort() const { return _port; }
130+
131+
/// Destructor - stops the server and cleans up
132+
~EchoServer()
133+
{
134+
_serverThread.request_stop();
135+
if (_serverSocket >= 0)
136+
{
137+
shutdown(_serverSocket, SHUT_RDWR);
138+
close(_serverSocket);
139+
_serverSocket = -1;
140+
}
141+
}
142+
143+
/// Deleted copy constructor
144+
EchoServer(const EchoServer &) = delete;
145+
146+
/// Deleted copy assignment operator
147+
EchoServer &operator=(const EchoServer &) = delete;
148+
149+
/// Deleted move constructor
150+
EchoServer(EchoServer &&) = delete;
151+
152+
/// Deleted move assignment operator
153+
EchoServer &operator=(EchoServer &&) = delete;
154+
};

tests/RawPacketSender.hpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#pragma once
2+
3+
#include <linux/if_packet.h>
4+
#include <net/if.h>
5+
#include <netinet/ether.h>
6+
#include <sys/ioctl.h>
7+
#include <sys/socket.h>
8+
#include <unistd.h>
9+
10+
#include <chrono>
11+
#include <cstring>
12+
#include <stdexcept>
13+
#include <string>
14+
#include <thread>
15+
16+
/**
17+
* @class RawPacketSender
18+
* A simple raw ethernet packet sender for testing purposes
19+
* Sends raw packets on a specified network interface
20+
*/
21+
class RawPacketSender {
22+
private:
23+
int _sockfd{-1};
24+
std::string _interface;
25+
std::jthread _senderThread;
26+
27+
/**
28+
* Sender loop that transmits packets
29+
* @param[in] message The message to send
30+
* @param[in] count Number of packets to send
31+
* @param[in] delayMs Delay between packets in milliseconds
32+
* @param[in] stopToken Stop token for cooperative cancellation
33+
*/
34+
void senderLoop(std::string message, int count, int delayMs, std::stop_token stopToken)
35+
{
36+
for (int i = 0; i < count && !stopToken.stop_requested(); ++i)
37+
{
38+
ssize_t sent = send(_sockfd, message.c_str(), message.length(), 0);
39+
if (sent < 0)
40+
{
41+
break;
42+
}
43+
std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
44+
}
45+
}
46+
47+
public:
48+
/**
49+
* Constructs a new RawPacketSender object
50+
* @param[in] interface The network interface name (e.g., "eth0")
51+
* @param[in] message The message to send in each packet
52+
* @param[in] count Number of packets to send (default: 10)
53+
* @param[in] delayMs Delay between packets in milliseconds (default: 100)
54+
* @throws std::runtime_error if socket creation or binding fails
55+
*/
56+
explicit RawPacketSender(const std::string &interface, const std::string &message, int count = 10, int delayMs = 100)
57+
: _interface(interface)
58+
{
59+
// Create raw socket
60+
_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
61+
if (_sockfd < 0)
62+
{
63+
throw std::runtime_error("Failed to create raw socket");
64+
}
65+
66+
// Get interface index
67+
struct ifreq ifr;
68+
memset(&ifr, 0, sizeof(ifr));
69+
strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1);
70+
if (ioctl(_sockfd, SIOCGIFINDEX, &ifr) < 0)
71+
{
72+
close(_sockfd);
73+
throw std::runtime_error("Failed to get interface index");
74+
}
75+
76+
// Bind socket to interface
77+
struct sockaddr_ll addr;
78+
memset(&addr, 0, sizeof(addr));
79+
addr.sll_family = AF_PACKET;
80+
addr.sll_protocol = htons(ETH_P_ALL);
81+
addr.sll_ifindex = ifr.ifr_ifindex;
82+
83+
if (bind(_sockfd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) < 0)
84+
{
85+
close(_sockfd);
86+
throw std::runtime_error("Failed to bind socket to interface");
87+
}
88+
89+
// Start sender thread
90+
_senderThread = std::jthread([this, message, count, delayMs](std::stop_token stopToken) {
91+
this->senderLoop(message, count, delayMs, stopToken);
92+
});
93+
}
94+
95+
/**
96+
* Destructor - cleans up socket and stops sender thread
97+
*/
98+
~RawPacketSender()
99+
{
100+
if (_senderThread.joinable())
101+
{
102+
_senderThread.request_stop();
103+
_senderThread.join();
104+
}
105+
106+
if (_sockfd >= 0)
107+
{
108+
close(_sockfd);
109+
}
110+
}
111+
112+
// Delete copy constructor and assignment operator
113+
RawPacketSender(const RawPacketSender &) = delete;
114+
RawPacketSender &operator=(const RawPacketSender &) = delete;
115+
116+
/**
117+
* Gets the interface name
118+
* @return The network interface name
119+
*/
120+
[[nodiscard]] const std::string &getInterface() const { return _interface; }
121+
};

0 commit comments

Comments
 (0)