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.