From 9272df4102ea4cb614b719f47e55c2d940f6bda1 Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Mon, 19 Jun 2023 21:52:12 -0500 Subject: [PATCH] Feature: Add f_socket_name_host() and f_socket_name_peer(). Add missing network socket related functions for getting the "name" of the local or remote connections. The local name is retuned as a string. The remote name is returned on the f_socket_t structure. I considered doing the work necessary to better generalize this but I decided not to. I can leave that for once I am better experience in the network programming. --- level_0/f_socket/c/socket.c | 56 +++++++++++ level_0/f_socket/c/socket.h | 64 ++++++++++++ level_0/f_socket/c/socket/common.h | 2 + level_0/f_socket/data/build/settings-mocks | 2 + level_0/f_socket/data/build/settings-tests | 2 +- level_0/f_socket/tests/unit/c/mock-socket.c | 26 +++++ level_0/f_socket/tests/unit/c/mock-socket.h | 2 + .../f_socket/tests/unit/c/test-socket-name_host.c | 112 +++++++++++++++++++++ .../f_socket/tests/unit/c/test-socket-name_host.h | 41 ++++++++ .../f_socket/tests/unit/c/test-socket-name_peer.c | 67 ++++++++++++ .../f_socket/tests/unit/c/test-socket-name_peer.h | 34 +++++++ level_0/f_socket/tests/unit/c/test-socket.c | 9 ++ level_0/f_socket/tests/unit/c/test-socket.h | 2 + 13 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 level_0/f_socket/tests/unit/c/test-socket-name_host.c create mode 100644 level_0/f_socket/tests/unit/c/test-socket-name_host.h create mode 100644 level_0/f_socket/tests/unit/c/test-socket-name_peer.c create mode 100644 level_0/f_socket/tests/unit/c/test-socket-name_peer.h diff --git a/level_0/f_socket/c/socket.c b/level_0/f_socket/c/socket.c index f4ed967..f7aa11f 100644 --- a/level_0/f_socket/c/socket.c +++ b/level_0/f_socket/c/socket.c @@ -316,6 +316,62 @@ extern "C" { } #endif // _di_f_socket_option_set_ +#ifndef _di_f_socket_name_host_ + f_status_t f_socket_name_host(f_socket_t * const socket, f_string_dynamic_t * const name) { + #ifndef _di_level_0_parameter_checking_ + if (!socket) return F_status_set_error(F_parameter); + if (!name) return F_status_set_error(F_parameter); + #endif // _di_level_0_parameter_checking_ + + if (!name->size) { + const f_status_t status = f_string_dynamic_resize(F_socket_default_name_max_d, name); + if (F_status_is_error(status)) return status; + } + + { + const int result = gethostname(name->string, (size_t) (name->used ? (name->size - name->used) : name->size)); + + if (result < 0) { + if (errno == EFAULT) return F_status_set_error(F_buffer); + if (errno == EINVAL) return F_status_set_error(F_parameter); + if (errno == ENAMETOOLONG) return F_status_set_error(F_string_too_large); + if (errno == EPERM) return F_status_set_error(F_prohibited); + + return F_status_set_error(F_failure); + } + } + + for (; name->used < name->size; ++name->used) { + if (!name->string[name->used]) break; + } // while + + return F_none; + } +#endif // _di_f_socket_host_name_ + +#ifndef _di_f_socket_name_peer_ + f_status_t f_socket_name_peer(f_socket_t * const socket) { + #ifndef _di_level_0_parameter_checking_ + if (!socket) return F_status_set_error(F_parameter); + #endif // _di_level_0_parameter_checking_ + + const int result = getpeername(socket->id, socket->address, &socket->length); + + if (result < 0) { + if (errno == EBADF) return F_status_set_error(F_file_descriptor); + if (errno == EFAULT) return F_status_set_error(F_buffer); + if (errno == EINVAL) return F_status_set_error(F_parameter); + if (errno == ENOBUFS) return F_status_set_error(F_buffer_not); + if (errno == ENOTCONN) return F_status_set_error(F_connect_not); + if (errno == ENOTSOCK) return F_status_set_error(F_socket_not); + + return F_status_set_error(F_failure); + } + + return F_none; + } +#endif // _di_f_socket_name_peer_ + #ifndef _di_f_socket_read_ f_status_t f_socket_read(f_socket_t * const socket, const int flags, void * const buffer, size_t * const length) { #ifndef _di_level_0_parameter_checking_ diff --git a/level_0/f_socket/c/socket.h b/level_0/f_socket/c/socket.h index 03d333d..3ee54b0 100644 --- a/level_0/f_socket/c/socket.h +++ b/level_0/f_socket/c/socket.h @@ -396,6 +396,70 @@ extern "C" { #endif // _di_f_socket_option_set_ /** + * Get the host name from a socket. + * + * The host is the name of the local connection on the socket. + * + * @param socket + * The socket structure. + * The socket.id must represent a valid socket file descriptor. + * The socket.size_read is used to represent the buffer size in buffer and must not be larger than the actual size of the buffer. + * @param name + * The retrieved host name. + * The name.size is used to determine as the max size. + * If name.size is 0, then a default max (F_socket_default_name_max_d) is used. + * + * @return + * F_none on success. + * + * F_buffer (with error bit) if the buffer is invalid. + * F_parameter (with error bit) if a parameter is invalid. + * F_prohibited (with error bit) if the system does not permit this operation (could be missing CAP_SYS_ADMIN in the appropraite user namespace). + * F_string_too_large (with error bit) if the name is to large for the max size (name.size). + * + * F_failure (with error bit) for any other error. + * + * Errors (with error bit) from: f_string_dynamic_resize() + * + * @see gethostname() + * + * @see f_string_dynamic_resize() + */ +#ifndef _di_f_socket_name_host_ + extern f_status_t f_socket_name_host(f_socket_t * const socket, f_string_dynamic_t * const name); +#endif // _di_f_socket_host_name_ + +/** + * Get the peer name from a socket. + * + * The peer is the name of the remote connection on the socket. + * The name of the remote connection refers to the connection itself such as the socket file name or the ip address and the port numbers. + * + * @param socket + * The socket structure. + * The socket.id must represent a valid socket file descriptor. + * The socket.size_read is used to represent the buffer size in buffer and must not be larger than the actual size of the buffer. + * The socket.address is used to store the name of the remote connection. + * + * @return + * F_none on success. + * + * F_file_descriptor (with error bit) if id is an invalid descriptor. + * F_buffer (with error bit) if the buffer is invalid. + * F_buffer_not (with error bit) due to resource restrictions (maps to ENOBUFS). + * F_connect_not (with error bit) if the socket is not connected. + * F_parameter (with error bit) if a parameter is invalid. + * F_socket_not (with error bit) if the id is not a socket descriptor. + * + * F_failure (with error bit) for any other error. + * + * @see getpeername() + */ +#ifndef _di_f_socket_name_peer_ + extern f_status_t f_socket_name_peer(f_socket_t * const socket); +#endif // _di_f_socket_name_peer_ + +/** * Read from a socket. * * @param socket diff --git a/level_0/f_socket/c/socket/common.h b/level_0/f_socket/c/socket/common.h index 05f057d..340a0e1 100644 --- a/level_0/f_socket/c/socket/common.h +++ b/level_0/f_socket/c/socket/common.h @@ -20,10 +20,12 @@ extern "C" { * Provide socket defaults. * * F_file_default_*: + * - name_max: Default buffer size for names. * - read_size: Default read size in bytes. * - write_size: Default write size in bytes. */ #ifndef _di_f_socket_default_d_ + #define F_socket_default_name_max_d 256 #define F_socket_default_read_size_d 8192 #define F_socket_default_write_size_d 8192 #endif // _di_f_socket_default_d_ diff --git a/level_0/f_socket/data/build/settings-mocks b/level_0/f_socket/data/build/settings-mocks index 6b2e0bc..ddeb717 100644 --- a/level_0/f_socket/data/build/settings-mocks +++ b/level_0/f_socket/data/build/settings-mocks @@ -64,6 +64,8 @@ flags -Wl,--wrap=close flags -Wl,--wrap=connect flags -Wl,--wrap=getsockopt flags -Wl,--wrap=listen +flags -Wl,--wrap=gethostname +flags -Wl,--wrap=getpeername flags -Wl,--wrap=recvfrom flags -Wl,--wrap=recvmsg flags -Wl,--wrap=sendmsg diff --git a/level_0/f_socket/data/build/settings-tests b/level_0/f_socket/data/build/settings-tests index d844cec..ca9c4f2 100644 --- a/level_0/f_socket/data/build/settings-tests +++ b/level_0/f_socket/data/build/settings-tests @@ -25,7 +25,7 @@ build_language c build_libraries -lc -lcmocka build_libraries-individual -lf_memory -lf_string -lf_type_array -lf_socket -build_sources_program test-socket-accept.c test-socket-bind.c test-socket-bind_local.c test-socket-connect.c test-socket-create.c test-socket-create_pair.c test-socket-disconnect.c test-socket-listen.c test-socket-option_get.c test-socket-option_set.c test-socket-read.c test-socket-read_message.c test-socket-write.c test-socket-write_message.c +build_sources_program test-socket-accept.c test-socket-bind.c test-socket-bind_local.c test-socket-connect.c test-socket-create.c test-socket-create_pair.c test-socket-disconnect.c test-socket-listen.c test-socket-name_host.c test-socket-name_peer.c test-socket-option_get.c test-socket-option_set.c test-socket-read.c test-socket-read_message.c test-socket-write.c test-socket-write_message.c build_sources_program test-socket.c build_script no diff --git a/level_0/f_socket/tests/unit/c/mock-socket.c b/level_0/f_socket/tests/unit/c/mock-socket.c index 5e35fca..ea3dbf3 100644 --- a/level_0/f_socket/tests/unit/c/mock-socket.c +++ b/level_0/f_socket/tests/unit/c/mock-socket.c @@ -56,6 +56,32 @@ int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { return 0; } +int __wrap_gethostname(char *name, size_t len) { + + const bool failure = mock_type(bool); + + if (failure) { + errno = mock_type(int); + + return -1; + } + + return 0; +} + +int __wrap_getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + + const bool failure = mock_type(bool); + + if (failure) { + errno = mock_type(int); + + return -1; + } + + return 0; +} + int __wrap_getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { const bool failure = mock_type(bool); diff --git a/level_0/f_socket/tests/unit/c/mock-socket.h b/level_0/f_socket/tests/unit/c/mock-socket.h index 3ebcbc0..32d2972 100644 --- a/level_0/f_socket/tests/unit/c/mock-socket.h +++ b/level_0/f_socket/tests/unit/c/mock-socket.h @@ -32,6 +32,8 @@ int __wrap_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int __wrap_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int __wrap_close(int fd); int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int __wrap_gethostname(char *name, size_t len); +int __wrap_getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int __wrap_getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); int __wrap_listen(int sockfd, int backlog); ssize_t __wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); diff --git a/level_0/f_socket/tests/unit/c/test-socket-name_host.c b/level_0/f_socket/tests/unit/c/test-socket-name_host.c new file mode 100644 index 0000000..79f2ef3 --- /dev/null +++ b/level_0/f_socket/tests/unit/c/test-socket-name_host.c @@ -0,0 +1,112 @@ +#include "test-socket.h" +#include "test-socket-listen.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void test__f_socket_name_host__allocates_default(void **state) { + + f_string_dynamic_t name = f_string_static_t_initialize; + f_socket_t socket = f_socket_t_initialize; + + { + will_return(__wrap_gethostname, false); + + const f_status_t status = f_socket_name_host(&socket, &name); + + assert_int_equal(status, F_none); + assert_int_equal(name.size, F_socket_default_name_max_d); + } + + free(name.string); +} + +void test__f_socket_name_host__fails(void **state) { + + f_string_t name_string = ""; + f_string_static_t name = f_string_static_t_initialize; + name.string = name_string; + name.used = 0; + name.size = 1; + + f_socket_t socket = f_socket_t_initialize; + + int errnos[] = { + EFAULT, + EINVAL, + ENAMETOOLONG, + EPERM, + mock_errno_generic, + }; + + f_status_t statuss[] = { + F_buffer, + F_parameter, + F_string_too_large, + F_prohibited, + F_failure, + }; + + for (uint8_t i = 0; i < 5; ++i) { + + will_return(__wrap_gethostname, true); + will_return(__wrap_gethostname, errnos[i]); + + const f_status_t status = f_socket_name_host(&socket, &name); + + assert_int_equal(status, F_status_set_error(statuss[i])); + } // for +} + +void test__f_socket_name_host__parameter_checking(void **state) { + + f_string_t name_string = ""; + f_string_static_t name = f_string_static_t_initialize; + name.string = name_string; + name.used = 0; + name.size = 1; + + f_socket_t socket = f_socket_t_initialize; + + { + const f_status_t status = f_socket_name_host(0, 0); + + assert_int_equal(status, F_status_set_error(F_parameter)); + } + + { + const f_status_t status = f_socket_name_host(&socket, 0); + + assert_int_equal(status, F_status_set_error(F_parameter)); + } + + { + const f_status_t status = f_socket_name_host(0, &name); + + assert_int_equal(status, F_status_set_error(F_parameter)); + } +} + +void test__f_socket_name_host__works(void **state) { + + f_string_t name_string = ""; + f_string_static_t name = f_string_static_t_initialize; + name.string = name_string; + name.used = 0; + name.size = 1; + + f_socket_t socket = f_socket_t_initialize; + + { + will_return(__wrap_gethostname, false); + + const f_status_t status = f_socket_name_host(&socket, &name); + + assert_int_equal(status, F_none); + } +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_0/f_socket/tests/unit/c/test-socket-name_host.h b/level_0/f_socket/tests/unit/c/test-socket-name_host.h new file mode 100644 index 0000000..e5032b5 --- /dev/null +++ b/level_0/f_socket/tests/unit/c/test-socket-name_host.h @@ -0,0 +1,41 @@ +/** + * FLL - Level 0 + * + * Project: Socket + * API Version: 0.7 + * Licenses: lgpl-2.1-or-later + * + * Test the socket project. + */ +#ifndef _TEST__F_socket_name_host_h +#define _TEST__F_socket_name_host_h + +/** + * Test that function allocates the string size using the default size. + * + * @see f_socket_name_host() + */ +extern void test__f_socket_name_host__allocates_default(void **state); + +/** + * Test that function fails. + * + * @see f_socket_name_host() + */ +extern void test__f_socket_name_host__fails(void **state); + +/** + * Test that parameter checking works as expected. + * + * @see f_socket_name_host() + */ +extern void test__f_socket_name_host__parameter_checking(void **state); + +/** + * Test that function works. + * + * @see f_socket_name_host() + */ +extern void test__f_socket_name_host__works(void **state); + +#endif // _TEST__F_socket_name_host_h diff --git a/level_0/f_socket/tests/unit/c/test-socket-name_peer.c b/level_0/f_socket/tests/unit/c/test-socket-name_peer.c new file mode 100644 index 0000000..25a9c42 --- /dev/null +++ b/level_0/f_socket/tests/unit/c/test-socket-name_peer.c @@ -0,0 +1,67 @@ +#include "test-socket.h" +#include "test-socket-listen.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void test__f_socket_name_peer__fails(void **state) { + + f_socket_t socket = f_socket_t_initialize; + + int errnos[] = { + EBADF, + EFAULT, + EINVAL, + ENOBUFS, + ENOTCONN, + ENOTSOCK, + mock_errno_generic, + }; + + f_status_t statuss[] = { + F_file_descriptor, + F_buffer, + F_parameter, + F_buffer_not, + F_connect_not, + F_socket_not, + F_failure, + }; + + for (uint8_t i = 0; i < 7; ++i) { + + will_return(__wrap_getpeername, true); + will_return(__wrap_getpeername, errnos[i]); + + const f_status_t status = f_socket_name_peer(&socket); + + assert_int_equal(status, F_status_set_error(statuss[i])); + } // for +} + +void test__f_socket_name_peer__parameter_checking(void **state) { + + { + const f_status_t status = f_socket_name_peer(0); + + assert_int_equal(status, F_status_set_error(F_parameter)); + } +} + +void test__f_socket_name_peer__works(void **state) { + + f_socket_t socket = f_socket_t_initialize; + + { + will_return(__wrap_getpeername, false); + + const f_status_t status = f_socket_name_peer(&socket); + + assert_int_equal(status, F_none); + } +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_0/f_socket/tests/unit/c/test-socket-name_peer.h b/level_0/f_socket/tests/unit/c/test-socket-name_peer.h new file mode 100644 index 0000000..ce2ae93 --- /dev/null +++ b/level_0/f_socket/tests/unit/c/test-socket-name_peer.h @@ -0,0 +1,34 @@ +/** + * FLL - Level 0 + * + * Project: Socket + * API Version: 0.7 + * Licenses: lgpl-2.1-or-later + * + * Test the socket project. + */ +#ifndef _TEST__F_socket_name_peer_h +#define _TEST__F_socket_name_peer_h + +/** + * Test that function fails. + * + * @see f_socket_name_peer() + */ +extern void test__f_socket_name_peer__fails(void **state); + +/** + * Test that parameter checking works as expected. + * + * @see f_socket_name_peer() + */ +extern void test__f_socket_name_peer__parameter_checking(void **state); + +/** + * Test that function works. + * + * @see f_socket_name_peer() + */ +extern void test__f_socket_name_peer__works(void **state); + +#endif // _TEST__F_socket_name_peer_h diff --git a/level_0/f_socket/tests/unit/c/test-socket.c b/level_0/f_socket/tests/unit/c/test-socket.c index 820723e..8fb134e 100644 --- a/level_0/f_socket/tests/unit/c/test-socket.c +++ b/level_0/f_socket/tests/unit/c/test-socket.c @@ -48,6 +48,13 @@ int main(void) { cmocka_unit_test(test__f_socket_listen__fails), cmocka_unit_test(test__f_socket_listen__works), + cmocka_unit_test(test__f_socket_name_host__allocates_default), + cmocka_unit_test(test__f_socket_name_host__fails), + cmocka_unit_test(test__f_socket_name_host__works), + + cmocka_unit_test(test__f_socket_name_peer__fails), + cmocka_unit_test(test__f_socket_name_peer__works), + cmocka_unit_test(test__f_socket_option_get__fails), cmocka_unit_test(test__f_socket_option_get__works), @@ -74,6 +81,8 @@ int main(void) { cmocka_unit_test(test__f_socket_create_pair__parameter_checking), cmocka_unit_test(test__f_socket_disconnect__parameter_checking), cmocka_unit_test(test__f_socket_listen__parameter_checking), + cmocka_unit_test(test__f_socket_name_host__parameter_checking), + cmocka_unit_test(test__f_socket_name_peer__parameter_checking), cmocka_unit_test(test__f_socket_option_get__parameter_checking), cmocka_unit_test(test__f_socket_option_set__parameter_checking), cmocka_unit_test(test__f_socket_read__parameter_checking), diff --git a/level_0/f_socket/tests/unit/c/test-socket.h b/level_0/f_socket/tests/unit/c/test-socket.h index 6758d8b..d8e56b5 100644 --- a/level_0/f_socket/tests/unit/c/test-socket.h +++ b/level_0/f_socket/tests/unit/c/test-socket.h @@ -34,6 +34,8 @@ #include "test-socket-create_pair.h" #include "test-socket-disconnect.h" #include "test-socket-listen.h" +#include "test-socket-name_host.h" +#include "test-socket-name_peer.h" #include "test-socket-option_get.h" #include "test-socket-option_set.h" #include "test-socket-read.h" -- 1.8.3.1