feat(handle): add public api init and destroy utilities
- Added `icmp_create`. - Added `icmp_destroy`. - Added `icmp_get_fd`. - Added unit tests.
This commit is contained in:
parent
9e35dd86bb
commit
b7be251a18
8 changed files with 334 additions and 0 deletions
54
src/handle/create.c
Normal file
54
src/handle/create.c
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#include "icmp.h"
|
||||||
|
#include "internal/icmp_internal.h"
|
||||||
|
#include "internal/icmp_socket.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* 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';
|
||||||
|
}
|
||||||
20
src/handle/destroy.c
Normal file
20
src/handle/destroy.c
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "icmp.h"
|
||||||
|
#include "internal/icmp_internal.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
15
src/handle/get_fd.c
Normal file
15
src/handle/get_fd.c
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
97
tests/handle/test_create.c
Normal file
97
tests/handle/test_create.c
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
#include <criterion/criterion.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
65
tests/handle/test_destroy.c
Normal file
65
tests/handle/test_destroy.c
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#include <criterion/criterion.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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");
|
||||||
|
}
|
||||||
83
tests/handle/test_get_fd.c
Normal file
83
tests/handle/test_get_fd.c
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#include <criterion/criterion.h>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue