From 14a3a3764ce1c567ea2e65fde5f240bd761ede55 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Thu, 26 Dec 2024 21:25:15 +0100 Subject: [PATCH] Add `socket:getopt/2` Also fix error tuple of `socket:setopt/3` to be `{error, closed}` following OTP. Signed-off-by: Paul Guyot --- CHANGELOG.md | 1 + libs/estdlib/src/socket.erl | 31 +++++++- src/libAtomVM/inet.c | 5 ++ src/libAtomVM/inet.h | 8 ++ src/libAtomVM/otp_socket.c | 100 ++++++++++++++++++++++++- tests/libs/estdlib/test_tcp_socket.erl | 10 +++ tests/libs/estdlib/test_udp_socket.erl | 10 +++ 7 files changed, 161 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 881c66b0a..9e436e129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Partial support for `erlang:fun_info/2` - Added support for `registered_name` in `erlang:process_info/2` and `Process.info/2` - Added `net:gethostname/0` on platforms with gethostname(3). +- Added `socket:getopt/2` ### Fixed - ESP32: improved sntp sync speed from a cold boot. diff --git a/libs/estdlib/src/socket.erl b/libs/estdlib/src/socket.erl index c7f95c807..bd4444d81 100644 --- a/libs/estdlib/src/socket.erl +++ b/libs/estdlib/src/socket.erl @@ -38,6 +38,7 @@ send/2, sendto/3, setopt/3, + getopt/2, connect/2, shutdown/2 ]). @@ -66,7 +67,9 @@ -type in_addr() :: {0..255, 0..255, 0..255, 0..255}. -type port_number() :: 0..65535. --type socket_option() :: {socket, reuseaddr} | {socket, linger}. +-type socket_option() :: + {socket, reuseaddr | linger | type} + | {otp, recvbuf}. -export_type([ socket/0, @@ -443,11 +446,32 @@ sendto(Socket, Data, Dest) when is_binary(Data) -> sendto(Socket, Data, Dest) -> ?MODULE:nif_sendto(Socket, erlang:iolist_to_binary(Data), Dest). +%%----------------------------------------------------------------------------- +%% @param Socket the socket +%% @param SocketOption the option +%% @returns `{ok, Value}' if successful; `{error, Reason}', otherwise. +%% @doc Get a socket option. +%% +%% Currently, the following options are supported: +%% +%% +%%
`{socket, type}'`type()'
+%% +%% Example: +%% +%% `{ok, stream} = socket:getopt(ListeningSocket, {socket, type})' +%% @end +%%----------------------------------------------------------------------------- +-spec getopt(Socket :: socket(), SocketOption :: socket_option()) -> + {ok, Value :: term()} | {error, Reason :: term()}. +getopt(_Socket, _SocketOption) -> + erlang:nif_error(undefined). + %%----------------------------------------------------------------------------- %% @param Socket the socket %% @param SocketOption the option %% @param Value the option value -%% @returns `{ok, Address}' if successful; `{error, Reason}', otherwise. +%% @returns `ok' if successful; `{error, Reason}', otherwise. %% @doc Set a socket option. %% %% Set an option on a socket. @@ -456,6 +480,7 @@ sendto(Socket, Data, Dest) -> %% %% %% +%% %%
`{socket, reuseaddr}'`boolean()'
`{socket, linger}'`#{onoff => boolean(), linger => non_neg_integer()}'
`{otp, recvbuf}'`non_neg_integer()'
%% %% Example: @@ -465,7 +490,7 @@ sendto(Socket, Data, Dest) -> %% @end %%----------------------------------------------------------------------------- -spec setopt(Socket :: socket(), SocketOption :: socket_option(), Value :: term()) -> - ok | {error, Reason :: term()}. + ok | {error, any()}. setopt(_Socket, _SocketOption, _Value) -> erlang:nif_error(undefined). diff --git a/src/libAtomVM/inet.c b/src/libAtomVM/inet.c index e3a858fd3..54227362e 100644 --- a/src/libAtomVM/inet.c +++ b/src/libAtomVM/inet.c @@ -44,6 +44,11 @@ enum inet_type inet_atom_to_type(term type, GlobalContext *global) return interop_atom_term_select_int(inet_type_table, type, global); } +term inet_type_to_atom(enum inet_type type, GlobalContext *global) +{ + return interop_atom_term_select_atom(inet_type_table, (int) type, global); +} + static const AtomStringIntPair inet_protocol_table[] = { { ATOM_STR("\x2", "ip"), InetIpProtocol }, { ATOM_STR("\x3", "tcp"), InetTcpProtocol }, diff --git a/src/libAtomVM/inet.h b/src/libAtomVM/inet.h index e422bcd50..1b2490bc7 100644 --- a/src/libAtomVM/inet.h +++ b/src/libAtomVM/inet.h @@ -59,6 +59,14 @@ enum inet_type */ enum inet_type inet_atom_to_type(term type, GlobalContext *global); +/** + * @brief Convert an inet type to an atom + * @param type the inet type + * @param global the global context + * @returns an atom representing the inet type + */ +term inet_type_to_atom(enum inet_type type, GlobalContext *global); + enum inet_protocol { InetInvalidProtocol = 0, diff --git a/src/libAtomVM/otp_socket.c b/src/libAtomVM/otp_socket.c index 5655d7ba8..b3c3dcf24 100644 --- a/src/libAtomVM/otp_socket.c +++ b/src/libAtomVM/otp_socket.c @@ -190,6 +190,7 @@ static const char *const onoff_atom = ATOM_STR("\x5", "onoff"); static const char *const port_atom = ATOM_STR("\x4", "port"); static const char *const rcvbuf_atom = ATOM_STR("\x6", "rcvbuf"); static const char *const reuseaddr_atom = ATOM_STR("\x9", "reuseaddr"); +static const char *const type_atom = ATOM_STR("\x4", "type"); #define CLOSED_FD 0 @@ -1064,6 +1065,95 @@ static term nif_socket_select_stop(Context *ctx, int argc, term argv[]) return OK_ATOM; } +// +// getopt +// + +static term nif_socket_getopt(Context *ctx, int argc, term argv[]) +{ + TRACE("nif_socket_getopt\n"); + UNUSED(argc); + + VALIDATE_VALUE(argv[0], term_is_otp_socket); + + GlobalContext *global = ctx->global; + + struct SocketResource *rsrc_obj; + if (UNLIKELY(!term_to_otp_socket(argv[0], &rsrc_obj, ctx))) { + RAISE_ERROR(BADARG_ATOM); + } + + SMP_RWLOCK_RDLOCK(rsrc_obj->socket_lock); + +#if OTP_SOCKET_BSD + if (rsrc_obj->fd == 0) { +#elif OTP_SOCKET_LWIP + if (rsrc_obj->socket_state == SocketStateClosed) { +#endif + SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); + return make_error_tuple(CLOSED_ATOM, ctx); + } + term level_tuple = argv[1]; + + term level = term_get_tuple_element(level_tuple, 0); + int level_val = interop_atom_term_select_int(otp_socket_setopt_level_table, level, global); + switch (level_val) { + case OtpSocketSetoptLevelSocket: { + term opt = term_get_tuple_element(level_tuple, 1); + if (globalcontext_is_term_equal_to_atom_string(global, opt, type_atom)) { + enum inet_type type; +#if OTP_SOCKET_BSD + int option_value; + socklen_t option_len = sizeof(option_value); + int res = getsockopt(rsrc_obj->fd, SOL_SOCKET, SO_TYPE, &option_value, &option_len); + if (UNLIKELY(res != 0)) { + SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); + return make_errno_tuple(ctx); + } else { + switch (option_value) { + case SOCK_STREAM: + type = InetStreamType; + break; + case SOCK_DGRAM: + type = InetDgramType; + break; + default: + SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); + RAISE_ERROR(BADARG_ATOM); + } + } +#elif OTP_SOCKET_LWIP + LWIP_BEGIN(); + if (rsrc_obj->socket_state & SocketStateTCP) { + type = InetStreamType; + } else { + type = InetDgramType; + } + LWIP_END(); +#endif + if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__); + SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + term result = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(result, 0, OK_ATOM); + term_put_tuple_element(result, 1, inet_type_to_atom(type, global)); + SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); + return result; + } else { + SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); + RAISE_ERROR(BADARG_ATOM); + } + } + default: { + AVM_LOGE(TAG, "socket:getopt: Unsupported level"); + SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); + RAISE_ERROR(BADARG_ATOM); + } + } +} + // // setopt // @@ -1090,7 +1180,7 @@ static term nif_socket_setopt(Context *ctx, int argc, term argv[]) if (rsrc_obj->socket_state == SocketStateClosed) { #endif SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); - return make_error_tuple(posix_errno_to_term(EBADF, global), ctx); + return make_error_tuple(CLOSED_ATOM, ctx); } term level_tuple = argv[1]; term value = argv[2]; @@ -2573,6 +2663,10 @@ static const struct Nif socket_select_stop_nif = { .base.type = NIFFunctionType, .nif_ptr = nif_socket_select_stop }; +static const struct Nif socket_getopt_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_socket_getopt +}; static const struct Nif socket_setopt_nif = { .base.type = NIFFunctionType, .nif_ptr = nif_socket_setopt @@ -2643,6 +2737,10 @@ const struct Nif *otp_socket_nif_get_nif(const char *nifname) TRACE("Resolved platform nif %s ...\n", nifname); return &socket_select_stop_nif; } + if (strcmp("getopt/2", rest) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &socket_getopt_nif; + } if (strcmp("setopt/3", rest) == 0) { TRACE("Resolved platform nif %s ...\n", nifname); return &socket_setopt_nif; diff --git a/tests/libs/estdlib/test_tcp_socket.erl b/tests/libs/estdlib/test_tcp_socket.erl index f49c5c1e4..cca597891 100644 --- a/tests/libs/estdlib/test_tcp_socket.erl +++ b/tests/libs/estdlib/test_tcp_socket.erl @@ -28,6 +28,7 @@ test() -> ok = test_close_by_another_process(), ok = test_buf_size(), ok = test_override_buf_size(), + ok = test_setopt_getopt(), case get_otp_version() of atomvm -> ok = test_abandon_select(); @@ -345,6 +346,15 @@ send_receive_loop(Socket, I) -> Error end. +test_setopt_getopt() -> + {ok, Socket} = socket:open(inet, stream, tcp), + {ok, stream} = socket:getopt(Socket, {socket, type}), + ok = socket:setopt(Socket, {socket, reuseaddr}, true), + ok = socket:close(Socket), + {error, closed} = socket:getopt(Socket, {socket, type}), + {error, closed} = socket:setopt(Socket, {socket, reuseaddr}, true), + ok. + %% %% abandon_select test %% diff --git a/tests/libs/estdlib/test_udp_socket.erl b/tests/libs/estdlib/test_udp_socket.erl index 4ca20d687..f861f57d7 100644 --- a/tests/libs/estdlib/test_udp_socket.erl +++ b/tests/libs/estdlib/test_udp_socket.erl @@ -24,6 +24,7 @@ test() -> ok = test_echo_server(), + ok = test_setopt_getopt(), ok. test_echo_server() -> @@ -100,3 +101,12 @@ loop(Socket, Port, I) -> io:format("Error on sendto: ~p~n", [Error]), Error end. + +test_setopt_getopt() -> + {ok, Socket} = socket:open(inet, dgram, udp), + {ok, dgram} = socket:getopt(Socket, {socket, type}), + ok = socket:setopt(Socket, {socket, reuseaddr}, true), + ok = socket:close(Socket), + {error, closed} = socket:getopt(Socket, {socket, type}), + {error, closed} = socket:setopt(Socket, {socket, reuseaddr}, true), + ok.