diff --git a/src/handle/.gitkeep b/src/handle/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/handle/create.c b/src/handle/create.c new file mode 100644 index 0000000..1bd27b7 --- /dev/null +++ b/src/handle/create.c @@ -0,0 +1,54 @@ +#include "icmp.h" +#include "internal/icmp_internal.h" +#include "internal/icmp_socket.h" +#include +#include + +/* Forward declarations */ +static int alloc_icmp_handle(struct icmp_handle **h); +static void init_icmp_handle(struct icmp_handle *h); +/* -------------------- */ + +icmp_handle_t * +icmp_create(void) +{ + struct icmp_handle *h; + + if (-1 == alloc_icmp_handle(&h)) + return NULL; + + init_icmp_handle(h); + + if (-1 == socket_create(h)) + { + free(h); + return NULL; + } + + if ( -1 == socket_configure(h)) + { + close(h->fd); + free(h); + return NULL; + } + + return (icmp_handle_t *)h; +} + +static int +alloc_icmp_handle(struct icmp_handle **h) +{ + *h = malloc(sizeof(struct icmp_handle)); + + if (NULL == *h) + return -1; + return 0; +} + +static void +init_icmp_handle(struct icmp_handle *h) +{ + h->fd = -1; + h->last_error = ICMP_OK; + h->error_msg[0] = '\0'; +} diff --git a/src/handle/destroy.c b/src/handle/destroy.c new file mode 100644 index 0000000..0678cc8 --- /dev/null +++ b/src/handle/destroy.c @@ -0,0 +1,20 @@ +#include "icmp.h" +#include "internal/icmp_internal.h" +#include +#include + +void +icmp_destroy(icmp_handle_t *h) +{ + struct icmp_handle *handle; + + if (NULL == h) + return; + + handle = (struct icmp_handle *)h; + + if (handle->fd >= 0) + close(handle->fd); + + free(handle); +} diff --git a/src/handle/get_fd.c b/src/handle/get_fd.c new file mode 100644 index 0000000..4fbe6fc --- /dev/null +++ b/src/handle/get_fd.c @@ -0,0 +1,15 @@ +#include "icmp.h" +#include "internal/icmp_internal.h" + +int +icmp_get_fd(const icmp_handle_t *h) +{ + const struct icmp_handle *handle; + + if (NULL == h) + return -1; + + handle = (const struct icmp_handle *)h; + + return handle->fd; +} diff --git a/tests/handle/.gitkeep b/tests/handle/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/handle/test_create.c b/tests/handle/test_create.c new file mode 100644 index 0000000..48030ad --- /dev/null +++ b/tests/handle/test_create.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include "icmp.h" +#include "internal/icmp_internal.h" + +/* Helper: check if we have CAP_NET_RAW capability */ +static int +has_net_raw_capability(void) +{ + icmp_handle_t *h = icmp_create(); + + if (NULL != h) + { + icmp_destroy(h); + return 1; + } + + return 0; +} + +/* Test 1: icmp_create() success (skip without privileges) */ +Test(handle, create_success) +{ + icmp_handle_t *h = icmp_create(); + + if (NULL == h) + { + cr_skip("Test requires CAP_NET_RAW or root privileges"); + } + + cr_assert_not_null(h, "icmp_create() returned NULL"); + + /* Verify FD is valid */ + int fd = icmp_get_fd(h); + cr_assert_geq(fd, 0, "Expected valid file descriptor"); + + /* Verify no error */ + const char *err = icmp_strerror(h); + cr_assert_str_eq(err, "No error"); + + icmp_destroy(h); +} + +/* Test 2: icmp_create() returns NULL without privileges */ +Test(handle, create_without_privileges) +{ + icmp_handle_t *h = icmp_create(); + + if (NULL != h) + { + icmp_destroy(h); + cr_skip("Test requires missing CAP_NET_RAW"); + } + + cr_assert_null(h, "Expected NULL without privileges"); +} + +/* Test 3: icmp_create() sets non-blocking mode */ +Test(handle, create_nonblocking) +{ + if (0 == has_net_raw_capability()) + { + cr_skip("Test requires CAP_NET_RAW or root privileges"); + } + + icmp_handle_t *h = icmp_create(); + cr_assert_not_null(h); + + int fd = icmp_get_fd(h); + int flags = fcntl(fd, F_GETFL, 0); + + cr_assert(flags & O_NONBLOCK, "Socket should be non-blocking"); + + icmp_destroy(h); +} + +/* Test 4: icmp_create() initializes handle properly */ +Test(handle, create_initializes_handle) +{ + if (0 == has_net_raw_capability()) + { + cr_skip("Test requires CAP_NET_RAW or root privileges"); + } + + icmp_handle_t *h = icmp_create(); + cr_assert_not_null(h); + + struct icmp_handle *handle = (struct icmp_handle *)h; + + cr_assert_geq(handle->fd, 0, "FD should be valid"); + cr_assert_eq(handle->last_error, ICMP_OK, "Error should be ICMP_OK"); + cr_assert_eq(handle->error_msg[0], '\0', + "Error message should be empty"); + + icmp_destroy(h); +} diff --git a/tests/handle/test_destroy.c b/tests/handle/test_destroy.c new file mode 100644 index 0000000..9b88750 --- /dev/null +++ b/tests/handle/test_destroy.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include "icmp.h" +#include "internal/icmp_internal.h" + +/* Helper: check if we have CAP_NET_RAW capability */ +static int +has_net_raw_capability(void) +{ + icmp_handle_t *h = icmp_create(); + + if (NULL != h) + { + icmp_destroy(h); + return 1; + } + + return 0; +} + +/* Test 1: icmp_destroy() with NULL handle does not crash */ +Test(handle, destroy_null_handle) +{ + icmp_destroy(NULL); + cr_assert(1, "Should not crash with NULL handle"); +} + +/* Test 2: icmp_destroy() closes the file descriptor */ +Test(handle, destroy_closes_fd) +{ + if (0 == has_net_raw_capability()) + { + cr_skip("Test requires CAP_NET_RAW or root privileges"); + } + + icmp_handle_t *h = icmp_create(); + cr_assert_not_null(h); + + int fd = icmp_get_fd(h); + cr_assert_geq(fd, 0); + + /* Destroy handle */ + icmp_destroy(h); + + /* Verify FD is closed (fcntl should fail with EBADF) */ + int flags = fcntl(fd, F_GETFL, 0); + cr_assert_eq(flags, -1, "FD should be closed after destroy"); +} + +Test(handle, destroy_with_invalid_fd) +{ + struct icmp_handle *h = malloc(sizeof(struct icmp_handle)); + cr_assert_not_null(h); + + h->fd = -1; + h->last_error = ICMP_OK; + h->error_msg[0] = '\0'; + + /* Should not crash even with invalid FD (close will fail but OK) */ + icmp_destroy((icmp_handle_t *)h); + + cr_assert(1, "Should handle invalid FD gracefully"); +} diff --git a/tests/handle/test_get_fd.c b/tests/handle/test_get_fd.c new file mode 100644 index 0000000..b70bb3c --- /dev/null +++ b/tests/handle/test_get_fd.c @@ -0,0 +1,83 @@ +#include +#include "icmp.h" +#include "internal/icmp_internal.h" + +/* Helper: check if we have CAP_NET_RAW capability */ +static int +has_net_raw_capability(void) +{ + icmp_handle_t *h = icmp_create(); + + if (NULL != h) + { + icmp_destroy(h); + return 1; + } + + return 0; +} + +/* Test 1: icmp_get_fd() with NULL handle returns -1 */ +Test(handle, get_fd_null_handle) +{ + int fd = icmp_get_fd(NULL); + + cr_assert_eq(fd, -1, "Expected -1 for NULL handle"); +} + +/* Test 2: icmp_get_fd() returns valid file descriptor */ +Test(handle, get_fd_valid_handle) +{ + if (0 == has_net_raw_capability()) + { + cr_skip("Test requires CAP_NET_RAW or root privileges"); + } + + icmp_handle_t *h = icmp_create(); + cr_assert_not_null(h); + + int fd = icmp_get_fd(h); + + cr_assert_geq(fd, 0, "Expected valid file descriptor (>= 0)"); + + icmp_destroy(h); +} + +/* Test 3: icmp_get_fd() is consistent across calls */ +Test(handle, get_fd_consistent) +{ + if (0 == has_net_raw_capability()) + { + cr_skip("Test requires CAP_NET_RAW or root privileges"); + } + + icmp_handle_t *h = icmp_create(); + cr_assert_not_null(h); + + int fd1 = icmp_get_fd(h); + int fd2 = icmp_get_fd(h); + + cr_assert_eq(fd1, fd2, "FD should be consistent across calls"); + + icmp_destroy(h); +} + +/* Test 4: icmp_get_fd() returns same FD as internal structure */ +Test(handle, get_fd_matches_internal) +{ + if (0 == has_net_raw_capability()) + { + cr_skip("Test requires CAP_NET_RAW or root privileges"); + } + + icmp_handle_t *h = icmp_create(); + cr_assert_not_null(h); + + struct icmp_handle *handle = (struct icmp_handle *)h; + int fd_public = icmp_get_fd(h); + + cr_assert_eq(fd_public, handle->fd, + "Public FD should match internal FD"); + + icmp_destroy(h); +}