Skip to content

Commit

Permalink
Merge pull request atomvm#1422 from pguyot/w52/add-socket-getopt
Browse files Browse the repository at this point in the history
Add socket:getopt/2

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
  • Loading branch information
bettio committed Dec 27, 2024
2 parents 3220e39 + 14a3a37 commit 74258a1
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 28 additions & 3 deletions libs/estdlib/src/socket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
send/2,
sendto/3,
setopt/3,
getopt/2,
connect/2,
shutdown/2
]).
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
%% <table>
%% <tr><td>`{socket, type}'</td><td>`type()'</td></tr>
%% </table>
%%
%% 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.
Expand All @@ -456,6 +480,7 @@ sendto(Socket, Data, Dest) ->
%% <table>
%% <tr><td>`{socket, reuseaddr}'</td><td>`boolean()'</td></tr>
%% <tr><td>`{socket, linger}'</td><td>`#{onoff => boolean(), linger => non_neg_integer()}'</td></tr>
%% <tr><td>`{otp, recvbuf}'</td><td>`non_neg_integer()'</td></tr>
%% </table>
%%
%% Example:
Expand All @@ -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).

Expand Down
5 changes: 5 additions & 0 deletions src/libAtomVM/inet.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
8 changes: 8 additions & 0 deletions src/libAtomVM/inet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
100 changes: 99 additions & 1 deletion src/libAtomVM/otp_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
//
Expand All @@ -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];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions tests/libs/estdlib/test_tcp_socket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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
%%
Expand Down
10 changes: 10 additions & 0 deletions tests/libs/estdlib/test_udp_socket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

test() ->
ok = test_echo_server(),
ok = test_setopt_getopt(),
ok.

test_echo_server() ->
Expand Down Expand Up @@ -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.

0 comments on commit 74258a1

Please sign in to comment.