Skip to content

Commit 74258a1

Browse files
committed
Merge pull request atomvm#1422 from pguyot/w52/add-socket-getopt
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
2 parents 3220e39 + 14a3a37 commit 74258a1

File tree

7 files changed

+161
-4
lines changed

7 files changed

+161
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Partial support for `erlang:fun_info/2`
1414
- Added support for `registered_name` in `erlang:process_info/2` and `Process.info/2`
1515
- Added `net:gethostname/0` on platforms with gethostname(3).
16+
- Added `socket:getopt/2`
1617

1718
### Fixed
1819
- ESP32: improved sntp sync speed from a cold boot.

libs/estdlib/src/socket.erl

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
send/2,
3939
sendto/3,
4040
setopt/3,
41+
getopt/2,
4142
connect/2,
4243
shutdown/2
4344
]).
@@ -66,7 +67,9 @@
6667
-type in_addr() :: {0..255, 0..255, 0..255, 0..255}.
6768
-type port_number() :: 0..65535.
6869

69-
-type socket_option() :: {socket, reuseaddr} | {socket, linger}.
70+
-type socket_option() ::
71+
{socket, reuseaddr | linger | type}
72+
| {otp, recvbuf}.
7073

7174
-export_type([
7275
socket/0,
@@ -443,11 +446,32 @@ sendto(Socket, Data, Dest) when is_binary(Data) ->
443446
sendto(Socket, Data, Dest) ->
444447
?MODULE:nif_sendto(Socket, erlang:iolist_to_binary(Data), Dest).
445448

449+
%%-----------------------------------------------------------------------------
450+
%% @param Socket the socket
451+
%% @param SocketOption the option
452+
%% @returns `{ok, Value}' if successful; `{error, Reason}', otherwise.
453+
%% @doc Get a socket option.
454+
%%
455+
%% Currently, the following options are supported:
456+
%% <table>
457+
%% <tr><td>`{socket, type}'</td><td>`type()'</td></tr>
458+
%% </table>
459+
%%
460+
%% Example:
461+
%%
462+
%% `{ok, stream} = socket:getopt(ListeningSocket, {socket, type})'
463+
%% @end
464+
%%-----------------------------------------------------------------------------
465+
-spec getopt(Socket :: socket(), SocketOption :: socket_option()) ->
466+
{ok, Value :: term()} | {error, Reason :: term()}.
467+
getopt(_Socket, _SocketOption) ->
468+
erlang:nif_error(undefined).
469+
446470
%%-----------------------------------------------------------------------------
447471
%% @param Socket the socket
448472
%% @param SocketOption the option
449473
%% @param Value the option value
450-
%% @returns `{ok, Address}' if successful; `{error, Reason}', otherwise.
474+
%% @returns `ok' if successful; `{error, Reason}', otherwise.
451475
%% @doc Set a socket option.
452476
%%
453477
%% Set an option on a socket.
@@ -456,6 +480,7 @@ sendto(Socket, Data, Dest) ->
456480
%% <table>
457481
%% <tr><td>`{socket, reuseaddr}'</td><td>`boolean()'</td></tr>
458482
%% <tr><td>`{socket, linger}'</td><td>`#{onoff => boolean(), linger => non_neg_integer()}'</td></tr>
483+
%% <tr><td>`{otp, recvbuf}'</td><td>`non_neg_integer()'</td></tr>
459484
%% </table>
460485
%%
461486
%% Example:
@@ -465,7 +490,7 @@ sendto(Socket, Data, Dest) ->
465490
%% @end
466491
%%-----------------------------------------------------------------------------
467492
-spec setopt(Socket :: socket(), SocketOption :: socket_option(), Value :: term()) ->
468-
ok | {error, Reason :: term()}.
493+
ok | {error, any()}.
469494
setopt(_Socket, _SocketOption, _Value) ->
470495
erlang:nif_error(undefined).
471496

src/libAtomVM/inet.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ enum inet_type inet_atom_to_type(term type, GlobalContext *global)
4444
return interop_atom_term_select_int(inet_type_table, type, global);
4545
}
4646

47+
term inet_type_to_atom(enum inet_type type, GlobalContext *global)
48+
{
49+
return interop_atom_term_select_atom(inet_type_table, (int) type, global);
50+
}
51+
4752
static const AtomStringIntPair inet_protocol_table[] = {
4853
{ ATOM_STR("\x2", "ip"), InetIpProtocol },
4954
{ ATOM_STR("\x3", "tcp"), InetTcpProtocol },

src/libAtomVM/inet.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ enum inet_type
5959
*/
6060
enum inet_type inet_atom_to_type(term type, GlobalContext *global);
6161

62+
/**
63+
* @brief Convert an inet type to an atom
64+
* @param type the inet type
65+
* @param global the global context
66+
* @returns an atom representing the inet type
67+
*/
68+
term inet_type_to_atom(enum inet_type type, GlobalContext *global);
69+
6270
enum inet_protocol
6371
{
6472
InetInvalidProtocol = 0,

src/libAtomVM/otp_socket.c

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ static const char *const onoff_atom = ATOM_STR("\x5", "onoff");
190190
static const char *const port_atom = ATOM_STR("\x4", "port");
191191
static const char *const rcvbuf_atom = ATOM_STR("\x6", "rcvbuf");
192192
static const char *const reuseaddr_atom = ATOM_STR("\x9", "reuseaddr");
193+
static const char *const type_atom = ATOM_STR("\x4", "type");
193194

194195
#define CLOSED_FD 0
195196

@@ -1064,6 +1065,95 @@ static term nif_socket_select_stop(Context *ctx, int argc, term argv[])
10641065
return OK_ATOM;
10651066
}
10661067

1068+
//
1069+
// getopt
1070+
//
1071+
1072+
static term nif_socket_getopt(Context *ctx, int argc, term argv[])
1073+
{
1074+
TRACE("nif_socket_getopt\n");
1075+
UNUSED(argc);
1076+
1077+
VALIDATE_VALUE(argv[0], term_is_otp_socket);
1078+
1079+
GlobalContext *global = ctx->global;
1080+
1081+
struct SocketResource *rsrc_obj;
1082+
if (UNLIKELY(!term_to_otp_socket(argv[0], &rsrc_obj, ctx))) {
1083+
RAISE_ERROR(BADARG_ATOM);
1084+
}
1085+
1086+
SMP_RWLOCK_RDLOCK(rsrc_obj->socket_lock);
1087+
1088+
#if OTP_SOCKET_BSD
1089+
if (rsrc_obj->fd == 0) {
1090+
#elif OTP_SOCKET_LWIP
1091+
if (rsrc_obj->socket_state == SocketStateClosed) {
1092+
#endif
1093+
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
1094+
return make_error_tuple(CLOSED_ATOM, ctx);
1095+
}
1096+
term level_tuple = argv[1];
1097+
1098+
term level = term_get_tuple_element(level_tuple, 0);
1099+
int level_val = interop_atom_term_select_int(otp_socket_setopt_level_table, level, global);
1100+
switch (level_val) {
1101+
case OtpSocketSetoptLevelSocket: {
1102+
term opt = term_get_tuple_element(level_tuple, 1);
1103+
if (globalcontext_is_term_equal_to_atom_string(global, opt, type_atom)) {
1104+
enum inet_type type;
1105+
#if OTP_SOCKET_BSD
1106+
int option_value;
1107+
socklen_t option_len = sizeof(option_value);
1108+
int res = getsockopt(rsrc_obj->fd, SOL_SOCKET, SO_TYPE, &option_value, &option_len);
1109+
if (UNLIKELY(res != 0)) {
1110+
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
1111+
return make_errno_tuple(ctx);
1112+
} else {
1113+
switch (option_value) {
1114+
case SOCK_STREAM:
1115+
type = InetStreamType;
1116+
break;
1117+
case SOCK_DGRAM:
1118+
type = InetDgramType;
1119+
break;
1120+
default:
1121+
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
1122+
RAISE_ERROR(BADARG_ATOM);
1123+
}
1124+
}
1125+
#elif OTP_SOCKET_LWIP
1126+
LWIP_BEGIN();
1127+
if (rsrc_obj->socket_state & SocketStateTCP) {
1128+
type = InetStreamType;
1129+
} else {
1130+
type = InetDgramType;
1131+
}
1132+
LWIP_END();
1133+
#endif
1134+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
1135+
AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
1136+
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
1137+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
1138+
}
1139+
term result = term_alloc_tuple(2, &ctx->heap);
1140+
term_put_tuple_element(result, 0, OK_ATOM);
1141+
term_put_tuple_element(result, 1, inet_type_to_atom(type, global));
1142+
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
1143+
return result;
1144+
} else {
1145+
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
1146+
RAISE_ERROR(BADARG_ATOM);
1147+
}
1148+
}
1149+
default: {
1150+
AVM_LOGE(TAG, "socket:getopt: Unsupported level");
1151+
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
1152+
RAISE_ERROR(BADARG_ATOM);
1153+
}
1154+
}
1155+
}
1156+
10671157
//
10681158
// setopt
10691159
//
@@ -1090,7 +1180,7 @@ static term nif_socket_setopt(Context *ctx, int argc, term argv[])
10901180
if (rsrc_obj->socket_state == SocketStateClosed) {
10911181
#endif
10921182
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
1093-
return make_error_tuple(posix_errno_to_term(EBADF, global), ctx);
1183+
return make_error_tuple(CLOSED_ATOM, ctx);
10941184
}
10951185
term level_tuple = argv[1];
10961186
term value = argv[2];
@@ -2573,6 +2663,10 @@ static const struct Nif socket_select_stop_nif = {
25732663
.base.type = NIFFunctionType,
25742664
.nif_ptr = nif_socket_select_stop
25752665
};
2666+
static const struct Nif socket_getopt_nif = {
2667+
.base.type = NIFFunctionType,
2668+
.nif_ptr = nif_socket_getopt
2669+
};
25762670
static const struct Nif socket_setopt_nif = {
25772671
.base.type = NIFFunctionType,
25782672
.nif_ptr = nif_socket_setopt
@@ -2643,6 +2737,10 @@ const struct Nif *otp_socket_nif_get_nif(const char *nifname)
26432737
TRACE("Resolved platform nif %s ...\n", nifname);
26442738
return &socket_select_stop_nif;
26452739
}
2740+
if (strcmp("getopt/2", rest) == 0) {
2741+
TRACE("Resolved platform nif %s ...\n", nifname);
2742+
return &socket_getopt_nif;
2743+
}
26462744
if (strcmp("setopt/3", rest) == 0) {
26472745
TRACE("Resolved platform nif %s ...\n", nifname);
26482746
return &socket_setopt_nif;

tests/libs/estdlib/test_tcp_socket.erl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ test() ->
2828
ok = test_close_by_another_process(),
2929
ok = test_buf_size(),
3030
ok = test_override_buf_size(),
31+
ok = test_setopt_getopt(),
3132
case get_otp_version() of
3233
atomvm ->
3334
ok = test_abandon_select();
@@ -345,6 +346,15 @@ send_receive_loop(Socket, I) ->
345346
Error
346347
end.
347348

349+
test_setopt_getopt() ->
350+
{ok, Socket} = socket:open(inet, stream, tcp),
351+
{ok, stream} = socket:getopt(Socket, {socket, type}),
352+
ok = socket:setopt(Socket, {socket, reuseaddr}, true),
353+
ok = socket:close(Socket),
354+
{error, closed} = socket:getopt(Socket, {socket, type}),
355+
{error, closed} = socket:setopt(Socket, {socket, reuseaddr}, true),
356+
ok.
357+
348358
%%
349359
%% abandon_select test
350360
%%

tests/libs/estdlib/test_udp_socket.erl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
test() ->
2626
ok = test_echo_server(),
27+
ok = test_setopt_getopt(),
2728
ok.
2829

2930
test_echo_server() ->
@@ -100,3 +101,12 @@ loop(Socket, Port, I) ->
100101
io:format("Error on sendto: ~p~n", [Error]),
101102
Error
102103
end.
104+
105+
test_setopt_getopt() ->
106+
{ok, Socket} = socket:open(inet, dgram, udp),
107+
{ok, dgram} = socket:getopt(Socket, {socket, type}),
108+
ok = socket:setopt(Socket, {socket, reuseaddr}, true),
109+
ok = socket:close(Socket),
110+
{error, closed} = socket:getopt(Socket, {socket, type}),
111+
{error, closed} = socket:setopt(Socket, {socket, reuseaddr}, true),
112+
ok.

0 commit comments

Comments
 (0)