feat: implement ping core

This commit is contained in:
lohhiiccc 2026-03-12 16:12:18 +01:00
parent a4ed2780f2
commit 04e1f3f15b
27 changed files with 996 additions and 3 deletions

View file

@ -0,0 +1,8 @@
#ifndef PING_CALLBACK_H
#define PING_CALLBACK_H
#include "icmp.h"
void ping_callback(const icmp_reply_t *reply, void *userdata);
#endif

9
includes/internal/loop.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef PING_LOOP_H
#define PING_LOOP_H
#include <stddef.h>
#include "internal/ping_state.h"
void ping_loop(t_ping_state *state, size_t payload_len);
#endif

View file

@ -0,0 +1,21 @@
#ifndef PING_OUTPUT_H
#define PING_OUTPUT_H
#include <stddef.h>
#include <netinet/in.h>
#include "icmp.h"
#include "ft_ping.h"
#include "internal/ping_state.h"
void ping_output_start(const t_ping_config *config,
struct in_addr dest, size_t payload_bytes);
void ping_output_packet(const icmp_reply_t *reply, uint16_t seq,
int64_t rtt_ns, size_t payload_bytes, const t_ping_config *config);
void ping_output_error(const icmp_reply_t *reply,
const icmp_offending_packet_t *offending,
uint16_t seq, const t_ping_config *config);
void ping_output_summary(const t_ping_state *state, struct in_addr dest);
void ping_output_flood_dot(void);
void ping_output_flood_erase(void);
#endif

View file

@ -0,0 +1,28 @@
#ifndef PING_STATE_H
#define PING_STATE_H
#include <netinet/in.h>
#include <stdint.h>
#include <signal.h>
#include <time.h>
#include "icmp.h"
#include "ft_ping.h"
#include "internal/stats.h"
#include "internal/tracker.h"
typedef struct s_ping_state {
icmp_handle_t *handle;
const t_ping_config *config;
struct in_addr dest;
struct timespec start_time;
struct timespec linger_start;
uint16_t id;
uint16_t seq;
t_ping_tracker *tracker;
t_ping_stats *stats;
volatile sig_atomic_t send_flag;
volatile sig_atomic_t stop_flag;
size_t nb_errors;
} t_ping_state;
#endif

View file

@ -0,0 +1,12 @@
#ifndef PING_SCHEDULER_H
#define PING_SCHEDULER_H
#include <sys/time.h>
#include "internal/ping_state.h"
void ping_scheduler_init(t_ping_state *state);
void ping_scheduler_arm(const t_ping_config *config);
void ping_scheduler_select_tv(const t_ping_config *config,
struct timeval *tv);
#endif

9
includes/internal/send.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef PING_SEND_H
#define PING_SEND_H
#include <stddef.h>
#include "internal/ping_state.h"
int ping_send_one(t_ping_state *state, size_t payload_len);
#endif

20
includes/internal/stats.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef PING_STATS_H
#define PING_STATS_H
#include <stdint.h>
#include <stddef.h>
typedef struct s_ping_stats {
int64_t min_ns;
int64_t max_ns;
int64_t sum_ns;
int64_t sum_sq_ns; /* sum of (rtt_ns / 1000)^2 in us^2, to avoid overflow */
size_t count;
} t_ping_stats;
void ping_stats_init(t_ping_stats *s);
void ping_stats_update(t_ping_stats *s, int64_t rtt_ns);
void ping_stats_get(const t_ping_stats *s,
double *min_ms, double *max_ms, double *avg_ms, double *mdev_ms);
#endif

View file

@ -0,0 +1,29 @@
#ifndef PING_TRACKER_H
#define PING_TRACKER_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <time.h>
#define PING_TRACKER_SLOTS 128
typedef struct s_ping_tracker_slot {
struct timespec ts;
bool used;
bool acked;
} t_ping_tracker_slot;
typedef struct s_ping_tracker {
t_ping_tracker_slot slots[PING_TRACKER_SLOTS];
size_t nb_sent;
size_t nb_recv;
} t_ping_tracker;
void ping_tracker_init(t_ping_tracker *t);
void ping_tracker_record_send(t_ping_tracker *t, uint16_t seq, const
struct timespec *ts);
int64_t ping_tracker_record_recv(t_ping_tracker *t, uint16_t seq,
const struct timespec *ts);
#endif

8
includes/ping.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef PING_H
#define PING_H
#include "ft_ping.h"
int ping_run(const t_ping_config *config);
#endif

View file

@ -3,6 +3,7 @@ SRC_DIR = src
SRCS = $(SRC_DIR)/main.c \ SRCS = $(SRC_DIR)/main.c \
$(SRC_DIR)/cli/parse.c \ $(SRC_DIR)/cli/parse.c \
$(SRC_DIR)/cli/handlers/handle_count.c \ $(SRC_DIR)/cli/handlers/handle_count.c \
$(SRC_DIR)/cli/handlers/handle_deadline.c \
$(SRC_DIR)/cli/handlers/handle_flood.c \ $(SRC_DIR)/cli/handlers/handle_flood.c \
$(SRC_DIR)/cli/handlers/handle_help.c \ $(SRC_DIR)/cli/handlers/handle_help.c \
$(SRC_DIR)/cli/handlers/handle_interval.c \ $(SRC_DIR)/cli/handlers/handle_interval.c \
@ -22,6 +23,20 @@ SRCS = $(SRC_DIR)/main.c \
$(SRC_DIR)/cli/messages/help.c \ $(SRC_DIR)/cli/messages/help.c \
$(SRC_DIR)/cli/messages/version.c \ $(SRC_DIR)/cli/messages/version.c \
$(SRC_DIR)/cli/messages/error.c \ $(SRC_DIR)/cli/messages/error.c \
$(SRC_DIR)/core/ping.c \
$(SRC_DIR)/core/loop.c \
$(SRC_DIR)/core/send.c \
$(SRC_DIR)/core/callback.c \
$(SRC_DIR)/stats/stats.c \
$(SRC_DIR)/tracker/init.c \
$(SRC_DIR)/tracker/record_send.c \
$(SRC_DIR)/tracker/record_recv.c \
$(SRC_DIR)/output/start.c \
$(SRC_DIR)/output/packet.c \
$(SRC_DIR)/output/error.c \
$(SRC_DIR)/output/summary.c \
$(SRC_DIR)/output/flood.c \
$(SRC_DIR)/scheduler/scheduler.c \
TESTS_DIR = tests TESTS_DIR = tests
TESTS = $(TESTS_DIR)/test_main.c \ TESTS = $(TESTS_DIR)/test_main.c \
@ -38,4 +53,6 @@ TESTS = $(TESTS_DIR)/test_main.c \
$(TESTS_DIR)/cli/handlers/test_handle_verbose.c \ $(TESTS_DIR)/cli/handlers/test_handle_verbose.c \
$(TESTS_DIR)/cli/parse_utils/test_parse_int.c \ $(TESTS_DIR)/cli/parse_utils/test_parse_int.c \
$(TESTS_DIR)/cli/parse_utils/test_parse_float.c \ $(TESTS_DIR)/cli/parse_utils/test_parse_float.c \
$(TESTS_DIR)/ping/stats/test_stats.c \
$(TESTS_DIR)/ping/tracker/test_tracker.c \

93
src/core/callback.c Normal file
View file

@ -0,0 +1,93 @@
#include "icmp.h"
#include "icmp_types.h"
#include "ft_ping_flags.h"
#include "internal/callback.h"
#include "internal/ping_state.h"
#include "internal/stats.h"
#include "internal/tracker.h"
#include "internal/output.h"
/* Forward declarations */
static int extract_our_echo(const icmp_reply_t *reply, uint16_t our_id,
uint16_t *seq_out);
static void handle_echo_reply(t_ping_state *state, const icmp_reply_t *reply);
static int extract_our_error(const icmp_reply_t *reply, uint16_t our_id,
icmp_offending_packet_t *out, uint16_t *seq_out);
static void handle_icmp_error(t_ping_state *state, const icmp_reply_t *reply);
/* -------------------- */
void
ping_callback(const icmp_reply_t *reply, void *userdata)
{
t_ping_state *state;
state = (t_ping_state *)userdata;
if (ICMP_TYPE_ECHO_REPLY == reply->type)
handle_echo_reply(state, reply);
else if (ICMP_TYPE_TIME_EXCEEDED == reply->type
|| ICMP_TYPE_DEST_UNREACHABLE == reply->type)
handle_icmp_error(state, reply);
}
static int
extract_our_echo(const icmp_reply_t *reply, uint16_t our_id,
uint16_t *seq_out)
{
uint16_t id;
uint16_t seq;
if (0 > icmp_reply_id_seq(reply, &id, &seq))
return 0;
if (our_id != id)
return 0;
*seq_out = seq;
return 1;
}
static void
handle_echo_reply(t_ping_state *state, const icmp_reply_t *reply)
{
uint16_t seq;
int64_t rtt;
if (0 == extract_our_echo(reply, state->id, &seq))
return;
rtt = ping_tracker_record_recv(state->tracker, seq, &reply->timestamp);
if (0 > rtt)
return;
ping_stats_update(state->stats, rtt);
if (HAS_FLAG(state->config->flags, FLAG_FLOOD))
ping_output_flood_erase();
else
ping_output_packet(reply, seq, rtt, state->config->packet_size,
state->config);
}
static int
extract_our_error(const icmp_reply_t *reply, uint16_t our_id,
icmp_offending_packet_t *out, uint16_t *seq_out)
{
if (0 > icmp_error_extract_offending(reply, out))
return 0;
if (ICMP_TYPE_ECHO_REQUEST != out->icmp_type)
return 0;
if (our_id != out->rest.echo.id)
return 0;
*seq_out = out->rest.echo.seq;
return 1;
}
static void
handle_icmp_error(t_ping_state *state, const icmp_reply_t *reply)
{
icmp_offending_packet_t offending;
uint16_t seq;
if (0 == extract_our_error(reply, state->id, &offending, &seq))
return;
if (0 > ping_tracker_record_recv(state->tracker, seq, &reply->timestamp))
return;
state->nb_errors++;
if (!HAS_FLAG(state->config->flags, FLAG_QUIET))
ping_output_error(reply, &offending, seq, state->config);
}

128
src/core/loop.c Normal file
View file

@ -0,0 +1,128 @@
#include <sys/select.h>
#include "icmp.h"
#include "ft_ping_flags.h"
#include "internal/loop.h"
#include "internal/send.h"
#include "internal/callback.h"
#include "internal/tracker.h"
#include "internal/output.h"
#include "internal/scheduler.h"
/* Forward declarations */
static int deadline_expired(const t_ping_state *state);
static int linger_expired(const t_ping_state *state);
static int should_stop(const t_ping_state *state);
static int can_send_more(const t_ping_state *state);
static void do_send(t_ping_state *state, size_t payload_len);
static void try_recv(t_ping_state *state);
static void handle_send_trigger(t_ping_state *state, size_t payload_len);
/* -------------------- */
void
ping_loop(t_ping_state *state, size_t payload_len)
{
do
{
handle_send_trigger(state, payload_len);
if (should_stop(state))
break;
try_recv(state);
} while (state->send_flag || !should_stop(state));
}
static int
deadline_expired(const t_ping_state *state)
{
struct timespec now;
double elapsed;
if (0.0 == state->config->deadline)
return 0;
icmp_get_time(&now);
elapsed = (double)(now.tv_sec - state->start_time.tv_sec)
+ (double)(now.tv_nsec - state->start_time.tv_nsec) / 1e9;
return elapsed >= state->config->deadline;
}
static int
linger_expired(const t_ping_state *state)
{
struct timespec now;
double elapsed;
if (0 == state->linger_start.tv_sec && 0 == state->linger_start.tv_nsec)
return 0;
icmp_get_time(&now);
elapsed = (double)(now.tv_sec - state->linger_start.tv_sec)
+ (double)(now.tv_nsec - state->linger_start.tv_nsec) / 1e9;
return elapsed >= state->config->timeout;
}
static int
should_stop(const t_ping_state *state)
{
size_t count;
if (state->stop_flag)
return 1;
if (deadline_expired(state))
return 1;
if (linger_expired(state))
return 1;
count = state->config->count;
if (0 != count && state->tracker->nb_recv >= count)
return 1;
return 0;
}
static int
can_send_more(const t_ping_state *state)
{
size_t count;
count = state->config->count;
return 0 == count || state->tracker->nb_sent < count;
}
static void
do_send(t_ping_state *state, size_t payload_len)
{
state->send_flag = 0;
ping_send_one(state, payload_len);
if (HAS_FLAG(state->config->flags, FLAG_FLOOD))
ping_output_flood_dot();
ping_scheduler_arm(state->config);
}
static void
try_recv(t_ping_state *state)
{
int fd;
fd_set rfds;
struct timeval tv;
int ret;
fd = icmp_get_fd(state->handle);
ping_scheduler_select_tv(state->config, &tv);
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
ret = select(fd + 1, &rfds, NULL, NULL, &tv);
if (0 < ret)
icmp_process(state->handle, ping_callback, state, 0);
}
static void
handle_send_trigger(t_ping_state *state, size_t payload_len)
{
if (!state->send_flag && !HAS_FLAG(state->config->flags, FLAG_FLOOD))
return;
if (can_send_more(state))
do_send(state, payload_len);
else
{
if (0 == state->linger_start.tv_sec && 0 == state->linger_start.tv_nsec)
icmp_get_time(&state->linger_start);
state->send_flag = 0;
}
}

91
src/core/ping.c Normal file
View file

@ -0,0 +1,91 @@
#include <string.h>
#include <unistd.h>
#include "ping.h"
#include "icmp.h"
#include "ft_ping_flags.h"
#include "internal/ping_state.h"
#include "internal/stats.h"
#include "internal/tracker.h"
#include "internal/output.h"
#include "internal/scheduler.h"
#include "internal/send.h"
#include "internal/loop.h"
/* Forward declarations */
static int ping_one(const t_ping_config *config, struct in_addr dest,
icmp_handle_t *handle);
static void ping_init_state(t_ping_state *state, t_ping_stats *stats,
t_ping_tracker *tracker, const t_ping_config *config, struct in_addr
dest, icmp_handle_t *handle);
static void ping_first_send(t_ping_state *state, size_t payload_len);
/* -------------------- */
int
ping_run(const t_ping_config *config)
{
icmp_handle_t *handle;
int ret;
size_t i;
if (0 == config->nb_destinations)
return 1;
handle = icmp_create();
if (NULL == handle)
return 1;
ret = 0;
i = 0;
while (i < config->nb_destinations)
{
if (0 != ping_one(config, config->destinations[i], handle))
ret = 1;
i++;
}
icmp_destroy(handle);
return ret;
}
static int
ping_one(const t_ping_config *config, struct in_addr dest,
icmp_handle_t *handle)
{
t_ping_state state;
t_ping_stats stats;
t_ping_tracker tracker;
size_t payload_len;
ping_init_state(&state, &stats, &tracker, config, dest, handle);
payload_len = config->packet_size;
ping_scheduler_init(&state);
ping_output_start(config, dest, payload_len);
ping_first_send(&state, payload_len);
ping_loop(&state, payload_len);
ping_output_summary(&state, dest);
return tracker.nb_recv <= state.nb_errors;
}
static void
ping_init_state(t_ping_state *state, t_ping_stats *stats,
t_ping_tracker *tracker, const t_ping_config *config,
struct in_addr dest, icmp_handle_t *handle)
{
ping_stats_init(stats);
ping_tracker_init(tracker);
memset(state, 0, sizeof(*state));
state->config = config;
state->dest = dest;
state->id = (uint16_t)((unsigned int)getpid() & 0xFFFFU);
state->tracker = tracker;
state->stats = stats;
state->handle = handle;
icmp_get_time(&state->start_time);
}
static void
ping_first_send(t_ping_state *state, size_t payload_len)
{
ping_send_one(state, payload_len);
if (HAS_FLAG(state->config->flags, FLAG_FLOOD))
ping_output_flood_dot();
ping_scheduler_arm(state->config);
}

23
src/core/send.c Normal file
View file

@ -0,0 +1,23 @@
#include <string.h>
#include "icmp.h"
#include "ft_ping_const.h"
#include "internal/send.h"
#include "internal/tracker.h"
int
ping_send_one(t_ping_state *state, size_t payload_len)
{
struct timespec ts;
uint8_t payload[MAX_PACKET_SIZE];
memset(payload, 0x42, payload_len);
icmp_get_time(&ts);
ping_tracker_record_send(state->tracker, state->seq, &ts);
return (icmp_send_echo(state->handle,
state->dest,
state->id,
state->seq++,
state->config->ttl,
payload, payload_len));
}

View file

@ -1,9 +1,9 @@
#include <stdio.h>
#include <libgen.h> #include <libgen.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "cli.h" #include "cli.h"
#include "ping.h"
#include "version_gen.h" #include "version_gen.h"
/* Forward declarations */ /* Forward declarations */
@ -15,7 +15,7 @@ int
main(int argc, char **argv) main(int argc, char **argv)
{ {
t_ping_config config; t_ping_config config;
enum e_cli_code ret; int ret;
if (0 != init_prog_name(argv[0])) if (0 != init_prog_name(argv[0]))
return EXIT_FAILURE; return EXIT_FAILURE;
@ -23,7 +23,7 @@ main(int argc, char **argv)
if (ret != CLI_SUCCESS) if (ret != CLI_SUCCESS)
goto cleanup; goto cleanup;
printf("PING: Not yet implemented\n"); ret = ping_run(&config);
cleanup: cleanup:
cli_config_free(&config); cli_config_free(&config);

34
src/output/error.c Normal file
View file

@ -0,0 +1,34 @@
#include <stdio.h>
#include <arpa/inet.h>
#include "icmp.h"
#include "icmp_types.h"
#include "internal/output.h"
/* Forward declarations */
static const char *error_msg_for(const icmp_reply_t *reply);
/* -------------------- */
void
ping_output_error(const icmp_reply_t *reply,
const icmp_offending_packet_t *offending,
uint16_t seq, const t_ping_config *config)
{
char from_str[INET_ADDRSTRLEN];
(void)offending;
(void)config;
inet_ntop(AF_INET, &reply->from, from_str, sizeof(from_str));
fprintf(stderr, "From %s: icmp_seq=%u %s\n",
from_str, (unsigned int)seq, error_msg_for(reply));
}
static const char *
error_msg_for(const icmp_reply_t *reply)
{
if (ICMP_TYPE_TIME_EXCEEDED == reply->type)
return ("Time to live exceeded");
if (ICMP_CODE_HOST_UNREACHABLE == reply->code)
return ("Destination Host Unreachable");
return ("Destination Net Unreachable");
}

15
src/output/flood.c Normal file
View file

@ -0,0 +1,15 @@
#include <unistd.h>
#include "internal/output.h"
void
ping_output_flood_dot(void)
{
write(1, ".", 1);
}
void
ping_output_flood_erase(void)
{
write(1, "\b \b", 3);
}

22
src/output/packet.c Normal file
View file

@ -0,0 +1,22 @@
#include <stdio.h>
#include <arpa/inet.h>
#include "ft_ping_flags.h"
#include "internal/output.h"
void
ping_output_packet(const icmp_reply_t *reply, uint16_t seq,
int64_t rtt_ns, size_t payload_bytes, const t_ping_config *config)
{
char from_str[INET_ADDRSTRLEN];
if (HAS_FLAG(config->flags, FLAG_QUIET) || HAS_FLAG(config->flags, FLAG_FLOOD))
return;
inet_ntop(AF_INET, &reply->from, from_str, sizeof(from_str));
printf("%zu bytes from %s: icmp_seq=%u ttl=%u time=%.3f ms\n",
payload_bytes + 8,
from_str,
(unsigned int)seq,
(unsigned int)reply->ttl,
(double)rtt_ns / 1e6);
}

24
src/output/start.c Normal file
View file

@ -0,0 +1,24 @@
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "ft_ping_flags.h"
#include "internal/output.h"
void
ping_output_start(const t_ping_config *config,
struct in_addr dest, size_t payload_bytes)
{
char dest_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &dest, dest_str, sizeof(dest_str));
printf("PING %s (%s): %zu data bytes", dest_str, dest_str, payload_bytes);
if (HAS_FLAG(config->flags, FLAG_VERBOSE))
{
unsigned int id;
id = (unsigned int)getpid() & 0xFFFFU;
printf(", id 0x%04x = %u", id, id);
}
printf("\n");
}

56
src/output/summary.c Normal file
View file

@ -0,0 +1,56 @@
#include <stdio.h>
#include <arpa/inet.h>
#include "internal/output.h"
#include "internal/stats.h"
#include "internal/tracker.h"
/* Forward declarations */
static void print_loss_line(size_t sent, size_t ok_recv, size_t errors);
static void print_rtt_line(const t_ping_stats *stats);
/* -------------------- */
void
ping_output_summary(const t_ping_state *state, struct in_addr dest)
{
char dest_str[INET_ADDRSTRLEN];
size_t sent;
size_t errors;
size_t ok_recv;
inet_ntop(AF_INET, &dest, dest_str, sizeof(dest_str));
sent = state->tracker->nb_sent;
errors = state->nb_errors;
ok_recv = state->tracker->nb_recv - errors;
printf("--- %s ping statistics ---\n", dest_str);
print_loss_line(sent, ok_recv, errors);
if (0 < ok_recv)
print_rtt_line(state->stats);
}
static void
print_loss_line(size_t sent, size_t ok_recv, size_t errors)
{
int loss_pct;
loss_pct = (0 < sent) ? (int)((sent - ok_recv) * 100 / sent) : 0;
if (0 < errors)
printf("%zu packets transmitted, %zu received, +%zu errors,"
" %d%% packet loss\n", sent, ok_recv, errors, loss_pct);
else
printf("%zu packets transmitted, %zu received, %d%% packet loss\n",
sent, ok_recv, loss_pct);
}
static void
print_rtt_line(const t_ping_stats *stats)
{
double min_ms;
double max_ms;
double avg_ms;
double mdev_ms;
ping_stats_get(stats, &min_ms, &max_ms, &avg_ms, &mdev_ms);
printf("round-trip min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n",
min_ms, avg_ms, max_ms, mdev_ms);
}

72
src/scheduler/scheduler.c Normal file
View file

@ -0,0 +1,72 @@
#include <sys/time.h>
#include <unistd.h>
#include "ft_ping_flags.h"
#include "internal/scheduler.h"
/* Forward declarations */
static void sigalrm_handler(int sig);
static void sigint_handler(int sig);
static void install_signal(int signum, void (*handler)(int));
/* -------------------- */
static t_ping_state *g_state = NULL;
void
ping_scheduler_init(t_ping_state *state)
{
g_state = state;
install_signal(SIGALRM, sigalrm_handler);
install_signal(SIGINT, sigint_handler);
}
void
ping_scheduler_arm(const t_ping_config *config)
{
struct itimerval itv;
double interval;
if (HAS_FLAG(config->flags, FLAG_FLOOD))
return;
interval = (double)config->interval;
itv.it_value.tv_sec = (time_t)interval;
itv.it_value.tv_usec = (suseconds_t)((interval - (double)(time_t)interval)
* 1e6);
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, NULL);
}
void
ping_scheduler_select_tv(const t_ping_config *config, struct timeval *tv)
{
tv->tv_usec = 0;
tv->tv_sec = (long)config->timeout * !HAS_FLAG(config->flags, FLAG_FLOOD);
}
static void
sigalrm_handler(int sig)
{
(void)sig;
if (NULL != g_state)
g_state->send_flag = 1;
}
static void
sigint_handler(int sig)
{
(void)sig;
if (NULL != g_state)
g_state->stop_flag = 1;
}
static void
install_signal(int signum, void (*handler)(int))
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(signum, &sa, NULL);
}

64
src/stats/stats.c Normal file
View file

@ -0,0 +1,64 @@
#include <math.h>
#include <stdint.h>
#include "internal/stats.h"
/* Forward declarations */
static double compute_mdev_ms(const t_ping_stats *s, double avg_ms);
/* -------------------- */
void
ping_stats_init(t_ping_stats *s)
{
s->min_ns = INT64_MAX;
s->max_ns = INT64_MIN;
s->sum_ns = 0;
s->sum_sq_ns = 0;
s->count = 0;
}
void
ping_stats_update(t_ping_stats *s, int64_t rtt_ns)
{
int64_t rtt_us;
if (rtt_ns < s->min_ns)
s->min_ns = rtt_ns;
if (rtt_ns > s->max_ns)
s->max_ns = rtt_ns;
s->sum_ns += rtt_ns;
rtt_us = rtt_ns / 1000;
s->sum_sq_ns += rtt_us * rtt_us;
s->count++;
}
void
ping_stats_get(const t_ping_stats *s,
double *min_ms, double *max_ms, double *avg_ms, double *mdev_ms)
{
if (0 == s->count)
{
*min_ms = 0.0;
*max_ms = 0.0;
*avg_ms = 0.0;
*mdev_ms = 0.0;
return;
}
*min_ms = (double)s->min_ns / 1e6;
*max_ms = (double)s->max_ns / 1e6;
*avg_ms = (double)s->sum_ns / 1e6 / (double)s->count;
*mdev_ms = compute_mdev_ms(s, *avg_ms);
}
static double
compute_mdev_ms(const t_ping_stats *s, double avg_ms)
{
double avg_us;
double var_us2;
avg_us = avg_ms * 1000.0;
var_us2 = (double)s->sum_sq_ns / (double)s->count - avg_us * avg_us;
if (0.0 > var_us2)
var_us2 = 0.0;
return (sqrt(var_us2) / 1000.0);
}

9
src/tracker/init.c Normal file
View file

@ -0,0 +1,9 @@
#include <string.h>
#include "internal/tracker.h"
void
ping_tracker_init(t_ping_tracker *t)
{
memset(t, 0, sizeof(*t));
}

18
src/tracker/record_recv.c Normal file
View file

@ -0,0 +1,18 @@
#include <stdbool.h>
#include "icmp.h"
#include "internal/tracker.h"
int64_t
ping_tracker_record_recv(t_ping_tracker *t, uint16_t seq,
const struct timespec *ts)
{
t_ping_tracker_slot *slot;
slot = &t->slots[seq % PING_TRACKER_SLOTS];
if (false == slot->used || true == slot->acked)
return (-1);
slot->acked = true;
t->nb_recv++;
return (icmp_time_diff_ns(&slot->ts, ts));
}

16
src/tracker/record_send.c Normal file
View file

@ -0,0 +1,16 @@
#include <stdbool.h>
#include "internal/tracker.h"
void
ping_tracker_record_send(t_ping_tracker *t, uint16_t seq,
const struct timespec *ts)
{
t_ping_tracker_slot *slot;
slot = &t->slots[seq % PING_TRACKER_SLOTS];
slot->ts = *ts;
slot->used = true;
slot->acked = false;
t->nb_sent++;
}

View file

@ -0,0 +1,64 @@
#include <criterion/criterion.h>
#include <stdint.h>
#include <math.h>
#include "internal/stats.h"
Test(ping_stats, init_neutral)
{
t_ping_stats s;
double min_ms, max_ms, avg_ms, mdev_ms;
ping_stats_init(&s);
cr_assert_eq(s.count, (size_t)0);
ping_stats_get(&s, &min_ms, &max_ms, &avg_ms, &mdev_ms);
cr_assert_float_eq(min_ms, 0.0, 1e-9);
cr_assert_float_eq(max_ms, 0.0, 1e-9);
cr_assert_float_eq(avg_ms, 0.0, 1e-9);
cr_assert_float_eq(mdev_ms, 0.0, 1e-9);
}
Test(ping_stats, single_update)
{
t_ping_stats s;
double min_ms, max_ms, avg_ms, mdev_ms;
ping_stats_init(&s);
ping_stats_update(&s, 10000000LL); /* 10 ms */
ping_stats_get(&s, &min_ms, &max_ms, &avg_ms, &mdev_ms);
cr_assert_float_eq(min_ms, 10.0, 1e-6);
cr_assert_float_eq(max_ms, 10.0, 1e-6);
cr_assert_float_eq(avg_ms, 10.0, 1e-6);
cr_assert_float_eq(mdev_ms, 0.0, 1e-3);
}
Test(ping_stats, multiple_updates)
{
t_ping_stats s;
double min_ms, max_ms, avg_ms, mdev_ms;
ping_stats_init(&s);
ping_stats_update(&s, 10000000LL); /* 10 ms */
ping_stats_update(&s, 20000000LL); /* 20 ms */
ping_stats_update(&s, 30000000LL); /* 30 ms */
ping_stats_get(&s, &min_ms, &max_ms, &avg_ms, &mdev_ms);
cr_assert_float_eq(min_ms, 10.0, 1e-6);
cr_assert_float_eq(max_ms, 30.0, 1e-6);
cr_assert_float_eq(avg_ms, 20.0, 1e-6);
cr_assert(mdev_ms > 0.0, "mdev should be > 0 for different values");
}
Test(ping_stats, identical_values_mdev_zero)
{
t_ping_stats s;
double min_ms, max_ms, avg_ms, mdev_ms;
ping_stats_init(&s);
ping_stats_update(&s, 15000000LL);
ping_stats_update(&s, 15000000LL);
ping_stats_update(&s, 15000000LL);
ping_stats_get(&s, &min_ms, &max_ms, &avg_ms, &mdev_ms);
cr_assert_float_eq(min_ms, 15.0, 1e-6);
cr_assert_float_eq(max_ms, 15.0, 1e-6);
cr_assert_float_eq(avg_ms, 15.0, 1e-6);
cr_assert_float_eq(mdev_ms, 0.0, 1e-3);
}

View file

@ -0,0 +1,103 @@
#include <criterion/criterion.h>
#include <stdint.h>
#include "internal/tracker.h"
static struct timespec
make_ts(long sec, long nsec)
{
struct timespec ts;
ts.tv_sec = sec;
ts.tv_nsec = nsec;
return (ts);
}
Test(ping_tracker, init)
{
t_ping_tracker t;
ping_tracker_init(&t);
cr_assert_eq(ping_tracker_sent(&t), (size_t)0);
cr_assert_eq(ping_tracker_recv(&t), (size_t)0);
cr_assert_eq(ping_tracker_lost(&t), (size_t)0);
}
Test(ping_tracker, basic_send_recv)
{
t_ping_tracker t;
struct timespec ts_send;
struct timespec ts_recv;
int64_t rtt;
ping_tracker_init(&t);
ts_send = make_ts(1, 0);
ts_recv = make_ts(1, 10000000); /* 10 ms later */
ping_tracker_record_send(&t, 1, &ts_send);
rtt = ping_tracker_record_recv(&t, 1, &ts_recv);
cr_assert_eq(rtt, (int64_t)10000000, "Expected RTT of 10ms in ns");
cr_assert_eq(ping_tracker_sent(&t), (size_t)1);
cr_assert_eq(ping_tracker_recv(&t), (size_t)1);
cr_assert_eq(ping_tracker_lost(&t), (size_t)0);
}
Test(ping_tracker, double_recv_ignored)
{
t_ping_tracker t;
struct timespec ts_send;
struct timespec ts_recv;
int64_t rtt;
ping_tracker_init(&t);
ts_send = make_ts(0, 0);
ts_recv = make_ts(0, 5000000);
ping_tracker_record_send(&t, 5, &ts_send);
rtt = ping_tracker_record_recv(&t, 5, &ts_recv);
cr_assert(rtt >= 0, "First recv should succeed");
rtt = ping_tracker_record_recv(&t, 5, &ts_recv);
cr_assert_eq(rtt, (int64_t)-1, "Duplicate recv should return -1");
cr_assert_eq(ping_tracker_recv(&t), (size_t)1);
}
Test(ping_tracker, unknown_seq_returns_minus_one)
{
t_ping_tracker t;
struct timespec ts;
int64_t rtt;
ping_tracker_init(&t);
ts = make_ts(0, 0);
rtt = ping_tracker_record_recv(&t, 42, &ts);
cr_assert_eq(rtt, (int64_t)-1);
}
Test(ping_tracker, wraparound_seq)
{
t_ping_tracker t;
struct timespec ts_send;
struct timespec ts_recv;
int64_t rtt;
ping_tracker_init(&t);
ts_send = make_ts(0, 0);
ts_recv = make_ts(0, 1000000);
/* seq 128 maps to slot 0 */
ping_tracker_record_send(&t, 128, &ts_send);
rtt = ping_tracker_record_recv(&t, 128, &ts_recv);
cr_assert_eq(rtt, (int64_t)1000000);
}
Test(ping_tracker, lost_count)
{
t_ping_tracker t;
struct timespec ts;
ping_tracker_init(&t);
ts = make_ts(0, 0);
ping_tracker_record_send(&t, 1, &ts);
ping_tracker_record_send(&t, 2, &ts);
ping_tracker_record_send(&t, 3, &ts);
ping_tracker_record_recv(&t, 2, &ts);
cr_assert_eq(ping_tracker_sent(&t), (size_t)3);
cr_assert_eq(ping_tracker_recv(&t), (size_t)1);
cr_assert_eq(ping_tracker_lost(&t), (size_t)2);
}