From 9f20cceb19541afa520c443191489e194ec4d9e3 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Thu, 2 Nov 2023 17:30:27 +0100 Subject: [PATCH] Add support for net:getaddrinfo/1,2 on Pico-W Also factorize common functions Signed-off-by: Paul Guyot --- src/libAtomVM/inet.c | 77 ++++++ src/libAtomVM/inet.h | 99 ++++++++ src/libAtomVM/otp_net.c | 14 +- src/libAtomVM/otp_socket.c | 99 ++------ src/libAtomVM/otp_socket.h | 7 + src/libAtomVM/term.h | 6 +- .../esp32/components/avm_sys/CMakeLists.txt | 1 + src/platforms/generic_unix/lib/CMakeLists.txt | 1 + src/platforms/rp2040/src/lib/CMakeLists.txt | 8 +- .../rp2040/src/lib/otp_net_lwip_raw.c | 234 ++++++++++++++++++ .../rp2040/src/lib/otp_net_lwip_raw.h | 36 +++ 11 files changed, 488 insertions(+), 94 deletions(-) create mode 100644 src/libAtomVM/inet.c create mode 100644 src/libAtomVM/inet.h create mode 100644 src/platforms/rp2040/src/lib/otp_net_lwip_raw.c create mode 100644 src/platforms/rp2040/src/lib/otp_net_lwip_raw.h diff --git a/src/libAtomVM/inet.c b/src/libAtomVM/inet.c new file mode 100644 index 0000000000..0ef954405d --- /dev/null +++ b/src/libAtomVM/inet.c @@ -0,0 +1,77 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 by Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#include "inet.h" +#include "term.h" + +#include + +static const AtomStringIntPair inet_domain_table[] = { + { ATOM_STR("\x4", "inet"), InetDomain }, + SELECT_INT_DEFAULT(InetInvalidDomain) +}; + +enum inet_domain inet_atom_to_domain(term domain, GlobalContext *global) +{ + return interop_atom_term_select_int(inet_domain_table, domain, global); +} + +static const AtomStringIntPair inet_type_table[] = { + { ATOM_STR("\x6", "stream"), InetStreamType }, + { ATOM_STR("\x5", "dgram"), InetDgramType }, + SELECT_INT_DEFAULT(InetInvalidType) +}; + +enum inet_type inet_atom_to_type(term type, GlobalContext *global) +{ + return interop_atom_term_select_int(inet_type_table, type, global); +} + +static const AtomStringIntPair inet_protocol_table[] = { + { ATOM_STR("\x2", "ip"), InetIpProtocol }, + { ATOM_STR("\x3", "tcp"), InetTcpProtocol }, + { ATOM_STR("\x3", "udp"), InetUdpProtocol }, + SELECT_INT_DEFAULT(InetInvalidProtocol) +}; + +enum inet_protocol inet_atom_to_protocol(term protocol, GlobalContext *global) +{ + return interop_atom_term_select_int(inet_protocol_table, protocol, global); +} + +uint32_t inet_addr4_to_uint32(term addr_tuple) +{ + return ((term_to_int32(term_get_tuple_element(addr_tuple, 0)) & 0xFF) << 24) + | ((term_to_int32(term_get_tuple_element(addr_tuple, 1)) & 0xFF) << 16) + | ((term_to_int32(term_get_tuple_element(addr_tuple, 2)) & 0xFF) << 8) + | (term_to_int32(term_get_tuple_element(addr_tuple, 3)) & 0xFF); +} + +#define INET_ADDR4_TUPLE_SIZE TUPLE_SIZE(4) + +term inet_make_addr4(uint32_t addr, Heap *heap) +{ + term result = term_alloc_tuple(4, heap); + term_put_tuple_element(result, 0, term_from_int32((addr >> 24) & 0xFF)); + term_put_tuple_element(result, 1, term_from_int32((addr >> 16) & 0xFF)); + term_put_tuple_element(result, 2, term_from_int32((addr >> 8) & 0xFF)); + term_put_tuple_element(result, 3, term_from_int32(addr & 0xFF)); + return result; +} diff --git a/src/libAtomVM/inet.h b/src/libAtomVM/inet.h new file mode 100644 index 0000000000..0f1cd1e418 --- /dev/null +++ b/src/libAtomVM/inet.h @@ -0,0 +1,99 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 by Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#ifndef _INET_COMMON_H_ +#define _INET_COMMON_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum inet_domain +{ + InetInvalidDomain = 0, + InetDomain +}; + +/** + * @brief Parse an inet domain atom + * @param domain the inet domain atom + * @param global the global context + * @returns InetDomain or InetInvalidDomain + */ +enum inet_domain inet_atom_to_domain(term domain, GlobalContext *global); + +enum inet_type +{ + InetInvalidType = 0, + InetStreamType, + InetDgramType +}; + +/** + * @brief Parse an inet type + * @param type the inet type atom + * @param global the global context + * @returns the parsed type + */ +enum inet_type inet_atom_to_type(term type, GlobalContext *global); + +enum inet_protocol +{ + InetInvalidProtocol = 0, + InetIpProtocol, + InetTcpProtocol, + InetUdpProtocol +}; + +/** + * @brief Parse an inet protocol + * @param type the inet protocol atom + * @param global the global context + * @returns the parsed protocol + */ +enum inet_protocol inet_atom_to_protocol(term protocol, GlobalContext *global); + +/** + * @brief Parse an inet IPv4 address tuple + * @param addr_tuple the tuple to parse + * @returns the address as a uint32_t, in host order + */ +uint32_t inet_addr4_to_uint32(term addr_tuple); + +#define INET_ADDR4_TUPLE_SIZE TUPLE_SIZE(4) + +/** + * @brief Make an inet IPv4 address tuple + * @param addr the address to make as a tuple, as a uint32_t in host order + * @param heap the heap to build the tuple in + * @returns the tuple + * @details this function requires that at least INET_ADDR4_TUPLE_SIZE terms + * are free on the heap. + */ +term inet_make_addr4(uint32_t addr, Heap *heap); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libAtomVM/otp_net.c b/src/libAtomVM/otp_net.c index 2be903822f..3792ba4f03 100644 --- a/src/libAtomVM/otp_net.c +++ b/src/libAtomVM/otp_net.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -53,17 +54,6 @@ static const AtomStringIntPair type_table[] = { // utilities // -static term socket_tuple_from_addr(Context *ctx, uint32_t addr) -{ - term terms[4]; - terms[0] = term_from_int32((addr >> 24) & 0xFF); - terms[1] = term_from_int32((addr >> 16) & 0xFF); - terms[2] = term_from_int32((addr >> 8) & 0xFF); - terms[3] = term_from_int32(addr & 0xFF); - - return port_create_tuple_n(ctx, 4, terms); -} - static inline term make_error_tuple(term reason, Context *ctx) { term error_tuple = term_alloc_tuple(2, &ctx->heap); @@ -107,7 +97,7 @@ static term eai_errno_to_term(int err, GlobalContext *glb) static inline term make_address(Context *ctx, struct sockaddr_in *addr) { - return socket_tuple_from_addr(ctx, ntohl(addr->sin_addr.s_addr)); + return inet_make_addr4(ntohl(addr->sin_addr.s_addr), &ctx->heap); } // diff --git a/src/libAtomVM/otp_socket.c b/src/libAtomVM/otp_socket.c index 08397fbe21..a950552fe1 100644 --- a/src/libAtomVM/otp_socket.c +++ b/src/libAtomVM/otp_socket.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -183,45 +184,6 @@ static const char *const reuseaddr_atom = ATOM_STR("\x9", "reuseaddr"); #define ACCEPT_ATOM globalcontext_make_atom(global, accept_atom) #define RECV_ATOM globalcontext_make_atom(global, recv_atom) -enum otp_socket_domain -{ - OtpSocketInvalidDomain = 0, - OtpSocketInetDomain -}; - -static const AtomStringIntPair otp_socket_domain_table[] = { - { ATOM_STR("\x4", "inet"), OtpSocketInetDomain }, - SELECT_INT_DEFAULT(OtpSocketInvalidDomain) -}; - -enum otp_socket_type -{ - OtpSocketInvalidType = 0, - OtpSocketStreamType, - OtpSocketDgramType -}; - -static const AtomStringIntPair otp_socket_type_table[] = { - { ATOM_STR("\x6", "stream"), OtpSocketStreamType }, - { ATOM_STR("\x5", "dgram"), OtpSocketDgramType }, - SELECT_INT_DEFAULT(OtpSocketInvalidType) -}; - -enum otp_socket_protocol -{ - OtpSocketInvalidProtocol = 0, - OtpSocketIpProtocol, - OtpSocketTcpProtocol, - OtpSocketUdpProtocol -}; - -static const AtomStringIntPair otp_socket_protocol_table[] = { - { ATOM_STR("\x2", "ip"), OtpSocketIpProtocol }, - { ATOM_STR("\x3", "tcp"), OtpSocketTcpProtocol }, - { ATOM_STR("\x3", "udp"), OtpSocketUdpProtocol }, - SELECT_INT_DEFAULT(OtpSocketInvalidProtocol) -}; - enum otp_socket_shutdown_direction { OtpSocketInvalidShutdownDirection = 0, @@ -391,33 +353,14 @@ void otp_socket_init(GlobalContext *global) // socket operations // -static uint32_t socket_tuple_to_addr(term addr_tuple) -{ - return ((term_to_int32(term_get_tuple_element(addr_tuple, 0)) & 0xFF) << 24) - | ((term_to_int32(term_get_tuple_element(addr_tuple, 1)) & 0xFF) << 16) - | ((term_to_int32(term_get_tuple_element(addr_tuple, 2)) & 0xFF) << 8) - | (term_to_int32(term_get_tuple_element(addr_tuple, 3)) & 0xFF); -} - -static term socket_tuple_from_addr(Context *ctx, uint32_t addr) -{ - term terms[4]; - terms[0] = term_from_int32((addr >> 24) & 0xFF); - terms[1] = term_from_int32((addr >> 16) & 0xFF); - terms[2] = term_from_int32((addr >> 8) & 0xFF); - terms[3] = term_from_int32(addr & 0xFF); - - return port_create_tuple_n(ctx, 4, terms); -} - static inline int get_domain(GlobalContext *global, term domain_term, bool *ok) { *ok = true; - int val = interop_atom_term_select_int(otp_socket_domain_table, domain_term, global); + int val = inet_atom_to_domain(domain_term, global); switch (val) { - case OtpSocketInetDomain: + case InetDomain: return PF_INET; default: @@ -431,13 +374,13 @@ static inline int get_type(GlobalContext *global, term type_term, bool *ok) { *ok = true; - int val = interop_atom_term_select_int(otp_socket_type_table, type_term, global); + int val = inet_atom_to_type(type_term, global); switch (val) { - case OtpSocketStreamType: + case InetStreamType: return SOCK_STREAM; - case OtpSocketDgramType: + case InetDgramType: return SOCK_DGRAM; default: @@ -451,16 +394,16 @@ static inline int get_protocol(GlobalContext *global, term protocol_term, bool * { *ok = true; - int val = interop_atom_term_select_int(otp_socket_protocol_table, protocol_term, global); + int val = inet_atom_to_protocol(protocol_term, global); switch (val) { - case OtpSocketIpProtocol: + case InetIpProtocol: return IPPROTO_IP; - case OtpSocketTcpProtocol: + case InetTcpProtocol: return IPPROTO_TCP; - case OtpSocketUdpProtocol: + case InetUdpProtocol: return IPPROTO_UDP; default: @@ -1186,7 +1129,7 @@ static term nif_socket_sockname(Context *ctx, int argc, term argv[]) AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__); RAISE_ERROR(OUT_OF_MEMORY_ATOM); } else { - term address = socket_tuple_from_addr(ctx, ip4_u32); + term address = inet_make_addr4(ip4_u32, &ctx->heap); term port_number = term_from_int(port_u16); term map = term_alloc_map(2, &ctx->heap); @@ -1256,7 +1199,7 @@ static term nif_socket_peername(Context *ctx, int argc, term argv[]) AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__); RAISE_ERROR(OUT_OF_MEMORY_ATOM); } else { - term address = socket_tuple_from_addr(ctx, ip4_u32); + term address = inet_make_addr4(ip4_u32, &ctx->heap); term port_number = term_from_int(port_u16); term map = term_alloc_map(2, &ctx->heap); @@ -1339,9 +1282,9 @@ static term nif_socket_bind(Context *ctx, int argc, term argv[]) #endif } else { #if OTP_SOCKET_BSD - serveraddr.sin_addr.s_addr = htonl(socket_tuple_to_addr(addr)); + serveraddr.sin_addr.s_addr = htonl(inet_addr4_to_uint32(addr)); #elif OTP_SOCKET_LWIP - ip_addr_set_ip4_u32(&ip_addr, htonl(socket_tuple_to_addr(addr))); + ip_addr_set_ip4_u32(&ip_addr, htonl(inet_addr4_to_uint32(addr))); #endif } } @@ -1608,7 +1551,7 @@ static term nif_socket_recv_with_peek(Context *ctx, struct SocketResource *rsrc_ term payload = term_invalid_term(); if (is_recvfrom) { - term address = socket_tuple_from_addr(ctx, ntohl(addr.sin_addr.s_addr)); + term address = inet_make_addr4(ntohl(addr.sin_addr.s_addr), &ctx->heap); term port_number = term_from_int(ntohs(addr.sin_port)); term map = term_alloc_map(2, &ctx->heap); @@ -1680,7 +1623,7 @@ static term nif_socket_recv_without_peek(Context *ctx, struct SocketResource *rs term data = term_from_literal_binary(buffer, len, &ctx->heap, global); if (is_recvfrom) { - term address = socket_tuple_from_addr(ctx, ntohl(addr.sin_addr.s_addr)); + term address = inet_make_addr4(ntohl(addr.sin_addr.s_addr), &ctx->heap); term port_number = term_from_int(ntohs(addr.sin_port)); term map = term_alloc_map(2, &ctx->heap); @@ -1834,7 +1777,7 @@ static term nif_socket_recv_lwip(Context *ctx, struct SocketResource *rsrc_obj, term payload; if (is_recvfrom) { - term address = socket_tuple_from_addr(ctx, ip4_u32); + term address = inet_make_addr4(ip4_u32, &ctx->heap); term port_number = term_from_int(port_u16); term map = term_alloc_map(2, &ctx->heap); @@ -1963,7 +1906,7 @@ static term nif_socket_send_internal(Context *ctx, int argc, term argv[], bool i if (globalcontext_is_term_equal_to_atom_string(global, addr, loopback_atom)) { destaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); } else { - destaddr.sin_addr.s_addr = htonl(socket_tuple_to_addr(addr)); + destaddr.sin_addr.s_addr = htonl(inet_addr4_to_uint32(addr)); } sent_data = sendto(rsrc_obj->fd, buf, len, 0, (struct sockaddr *) &destaddr, sizeof(destaddr)); @@ -1988,7 +1931,7 @@ static term nif_socket_send_internal(Context *ctx, int argc, term argv[], bool i if (globalcontext_is_term_equal_to_atom_string(global, addr, loopback_atom)) { ip_addr_set_loopback(false, &ip_addr); } else { - ip_addr_set_ip4_u32(&ip_addr, htonl(socket_tuple_to_addr(addr))); + ip_addr_set_ip4_u32(&ip_addr, htonl(inet_addr4_to_uint32(addr))); } } @@ -2182,7 +2125,7 @@ static term nif_socket_connect(Context *ctx, int argc, term argv[]) address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); } else { // TODO more validation on addr tuple - address.sin_addr.s_addr = htonl(socket_tuple_to_addr(addr)); + address.sin_addr.s_addr = htonl(inet_addr4_to_uint32(addr)); } socklen_t addr_len = sizeof(struct sockaddr_in); @@ -2205,7 +2148,7 @@ static term nif_socket_connect(Context *ctx, int argc, term argv[]) } #elif OTP_SOCKET_LWIP ip_addr_t ip_addr; - ip_addr_set_ip4_u32(&ip_addr, htonl(socket_tuple_to_addr(addr))); + ip_addr_set_ip4_u32(&ip_addr, htonl(inet_addr4_to_uint32(addr))); err_t err; LWIP_BEGIN(); if (rsrc_obj->socket_state & SocketStateUDP) { diff --git a/src/libAtomVM/otp_socket.h b/src/libAtomVM/otp_socket.h index 2ebf297003..04d12e0c3f 100644 --- a/src/libAtomVM/otp_socket.h +++ b/src/libAtomVM/otp_socket.h @@ -80,6 +80,13 @@ struct LWIPEvent { struct SocketResource *rsrc_obj; } finalize_close; + // Used by otp_net + struct + { + void *callback_arg; + bool success; + uint32_t addr; + } dns_gethostbyname; }; }; diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index 66f9d1ca4c..786f247e5a 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -88,6 +88,8 @@ extern "C" { #define CONS_SIZE 2 #define REFC_BINARY_CONS_OFFSET 4 #define LIST_SIZE(num_elements, element_size) ((num_elements) * ((element_size) + CONS_SIZE)) +#define TERM_MAP_SIZE(num_elements) (3 + 2 * num_elements) +#define TERM_MAP_SHARED_SIZE(num_elements) (2 + num_elements) #define TERM_BINARY_SIZE_IS_HEAP(size) ((size) < REFC_BINARY_MIN) @@ -1593,12 +1595,12 @@ static inline size_t term_get_map_value_offset() static inline int term_map_size_in_terms_maybe_shared(size_t num_entries, bool is_shared) { - return 2 + (is_shared ? 0 : (1 + num_entries)) + num_entries; + return is_shared ? TERM_MAP_SHARED_SIZE(num_entries) : TERM_MAP_SIZE(num_entries); } static inline int term_map_size_in_terms(size_t num_entries) { - return term_map_size_in_terms_maybe_shared(num_entries, false); + return TERM_MAP_SIZE(num_entries); } static inline term term_alloc_map_maybe_shared(avm_uint_t size, term keys, Heap *heap) diff --git a/src/platforms/esp32/components/avm_sys/CMakeLists.txt b/src/platforms/esp32/components/avm_sys/CMakeLists.txt index 0e3cdb6c6c..240fa466cb 100644 --- a/src/platforms/esp32/components/avm_sys/CMakeLists.txt +++ b/src/platforms/esp32/components/avm_sys/CMakeLists.txt @@ -25,6 +25,7 @@ set(AVM_SYS_COMPONENT_SRCS "sys.c" "platform_nifs.c" "platform_defaultatoms.c" + "../../../../libAtomVM/inet.c" "../../../../libAtomVM/otp_net.c" "../../../../libAtomVM/otp_socket.c" ) diff --git a/src/platforms/generic_unix/lib/CMakeLists.txt b/src/platforms/generic_unix/lib/CMakeLists.txt index d5a2f469fd..8fba09b365 100644 --- a/src/platforms/generic_unix/lib/CMakeLists.txt +++ b/src/platforms/generic_unix/lib/CMakeLists.txt @@ -38,6 +38,7 @@ set(SOURCE_FILES smp.c socket_driver.c sys.c + ../../../libAtomVM/inet.c ../../../libAtomVM/otp_net.c ../../../libAtomVM/otp_socket.c ) diff --git a/src/platforms/rp2040/src/lib/CMakeLists.txt b/src/platforms/rp2040/src/lib/CMakeLists.txt index 8fa7b034ad..2fa764dc93 100644 --- a/src/platforms/rp2040/src/lib/CMakeLists.txt +++ b/src/platforms/rp2040/src/lib/CMakeLists.txt @@ -80,12 +80,16 @@ if (PICO_CYW43_SUPPORTED) target_sources( libAtomVM${PLATFORM_LIB_SUFFIX} PRIVATE + ../../../../libAtomVM/inet.c otp_socket_platform.c ../../../../libAtomVM/otp_socket.c + ../../../../libAtomVM/inet.h otp_socket_platform.h - ../../../../libAtomVM/otp_socket.h) + ../../../../libAtomVM/otp_socket.h + otp_net_lwip_raw.c + otp_net_lwip_raw.h) target_link_libraries(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC pico_cyw43_arch_lwip_threadsafe_background pico_lwip_sntp INTERFACE pan_lwip_dhserver) - target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,networkregister_port_driver -Wl,-u -Wl,otp_socket_nif") + target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,networkregister_port_driver -Wl,-u -Wl,otp_socket_nif -Wl,-u -Wl,otp_net_nif") endif() target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,gpio_nif") diff --git a/src/platforms/rp2040/src/lib/otp_net_lwip_raw.c b/src/platforms/rp2040/src/lib/otp_net_lwip_raw.c new file mode 100644 index 0000000000..dd60c61d6f --- /dev/null +++ b/src/platforms/rp2040/src/lib/otp_net_lwip_raw.c @@ -0,0 +1,234 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 by Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#ifdef LIB_PICO_CYW43_ARCH + +#include +#include +#include +#include +#include +#include + +#include + +// #define ENABLE_TRACE +#include + +// +// net:getaddrinfo/1 +// + +// addr map is shared by the two items + +#define GETADDRINFO_RESULT_SIZE \ + (TUPLE_SIZE(2) + (CONS_SIZE * 2) + TERM_MAP_SIZE(3) + TERM_MAP_SIZE(5) + TERM_MAP_SHARED_SIZE(5) + INET_ADDR4_TUPLE_SIZE) + +static term make_getaddrinfo_result(uint32_t addr, GlobalContext *global, Heap *heap) +{ + term addr_term = inet_make_addr4(addr, heap); + term addr_map = term_alloc_map(3, heap); + + term family_atom = globalcontext_make_atom(global, ATOM_STR("\x6", "family")); + term inet_atom = globalcontext_make_atom(global, ATOM_STR("\x4", "inet")); + term_set_map_assoc(addr_map, 0, family_atom, inet_atom); + + term addr_atom = globalcontext_make_atom(global, ATOM_STR("\x4", "addr")); + term_set_map_assoc(addr_map, 1, addr_atom, addr_term); + + term port_atom = globalcontext_make_atom(global, ATOM_STR("\x4", "port")); + term_set_map_assoc(addr_map, 2, port_atom, term_from_int(0)); + + term udp_info_map = term_alloc_map(5, heap); + term_set_map_assoc(udp_info_map, 0, family_atom, inet_atom); + + // Compatibility with OTP + term_set_map_assoc(udp_info_map, 1, addr_atom, addr_map); + + term address_atom = globalcontext_make_atom(global, ATOM_STR("\x7", "address")); + term_set_map_assoc(udp_info_map, 2, address_atom, addr_map); + + term type_atom = globalcontext_make_atom(global, ATOM_STR("\x4", "type")); + term dgram_atom = globalcontext_make_atom(global, ATOM_STR("\x5", "dgram")); + term stream_atom = globalcontext_make_atom(global, ATOM_STR("\x6", "stream")); + term_set_map_assoc(udp_info_map, 3, type_atom, dgram_atom); + + term protocol_atom = globalcontext_make_atom(global, ATOM_STR("\x8", "protocol")); + term udp_atom = globalcontext_make_atom(global, ATOM_STR("\x3", "udp")); + term tcp_atom = globalcontext_make_atom(global, ATOM_STR("\x3", "tcp")); + term_set_map_assoc(udp_info_map, 4, protocol_atom, udp_atom); + + term tcp_info_map = term_alloc_map_maybe_shared(5, term_get_map_keys(udp_info_map), heap); + term_set_map_assoc(tcp_info_map, 0, family_atom, inet_atom); + term_set_map_assoc(tcp_info_map, 1, addr_atom, addr_map); + term_set_map_assoc(tcp_info_map, 2, address_atom, addr_map); + term_set_map_assoc(tcp_info_map, 3, type_atom, stream_atom); + term_set_map_assoc(tcp_info_map, 4, protocol_atom, tcp_atom); + + term info_list = term_nil(); + info_list = term_list_prepend(tcp_info_map, info_list, heap); + info_list = term_list_prepend(udp_info_map, info_list, heap); + + term result_tuple = term_alloc_tuple(2, heap); + term_put_tuple_element(result_tuple, 0, OK_ATOM); + term_put_tuple_element(result_tuple, 1, info_list); + + return result_tuple; +} + +static term make_error_enoname_tuple(GlobalContext *global, Heap *heap) +{ + term error_tuple = term_alloc_tuple(2, heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, globalcontext_make_atom(global, ATOM_STR("\x7", "enoname"))); + return error_tuple; +} + +struct DNSCallbackArg +{ + GlobalContext *global; + int32_t process_id; +}; + +static void dns_found_callback_handler(struct LWIPEvent *event) +{ + TRACE("dns_found_callback_handler\n"); + + struct DNSCallbackArg *callback_arg = event->dns_gethostbyname.callback_arg; + if (event->dns_gethostbyname.success) { + BEGIN_WITH_STACK_HEAP(2 * GETADDRINFO_RESULT_SIZE, heap) + term result = make_getaddrinfo_result(event->dns_gethostbyname.addr, callback_arg->global, &heap); + Context *target_ctx = globalcontext_get_process_lock(callback_arg->global, callback_arg->process_id); + if (target_ctx) { + mailbox_send_term_signal(target_ctx, TrapAnswerSignal, result); + globalcontext_get_process_unlock(callback_arg->global, target_ctx); + } + END_WITH_STACK_HEAP(heap, callback_arg->global) + } else { + BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2), heap) + term result = make_error_enoname_tuple(callback_arg->global, &heap); + Context *target_ctx = globalcontext_get_process_lock(callback_arg->global, callback_arg->process_id); + if (target_ctx) { + mailbox_send_term_signal(target_ctx, TrapAnswerSignal, result); + globalcontext_get_process_unlock(callback_arg->global, target_ctx); + } + END_WITH_STACK_HEAP(heap, callback_arg->global) + } + free(callback_arg); +} + +static void dns_found_callback_cb(const char *name, const ip_addr_t *ipaddr, void *callback_arg) +{ + TRACE("dns_found_callback_cb\n"); + + UNUSED(name); + struct LWIPEvent event; + event.handler = dns_found_callback_handler; + event.dns_gethostbyname.callback_arg = callback_arg; + if (ipaddr) { + event.dns_gethostbyname.success = true; + event.dns_gethostbyname.addr = ntohl(ip_addr_get_ip4_u32(ipaddr)); + } else { + event.dns_gethostbyname.success = false; + } + otp_socket_lwip_enqueue(&event); +} + +static term nif_net_getaddrinfo(Context *ctx, int argc, term argv[]) +{ + TRACE("nif_net_getaddrinfo\n"); + UNUSED(argc); + + term host = argv[0]; + + if (host == UNDEFINED_ATOM) { + TRACE("Host param may not be undefined"); + RAISE_ERROR(BADARG_ATOM); + } + + int ok; + char *host_str = interop_term_to_string(host, &ok); + if (!ok) { + RAISE_ERROR(BADARG_ATOM); + } + + ip_addr_t cached_result; + + // We need to allocate a callback struct here because we need Global + // in the handler. + struct DNSCallbackArg *callback_arg = malloc(sizeof(struct DNSCallbackArg)); + callback_arg->global = ctx->global; + callback_arg->process_id = ctx->process_id; + err_t err = dns_gethostbyname(host_str, &cached_result, dns_found_callback_cb, callback_arg); + if (err == ERR_OK) { + free(callback_arg); + if (UNLIKELY(memory_ensure_free(ctx, GETADDRINFO_RESULT_SIZE) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + return make_getaddrinfo_result(ntohl(ip_addr_get_ip4_u32(&cached_result)), ctx->global, &ctx->heap); + } + + if (err == ERR_ARG) { + free(callback_arg); + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + return make_error_enoname_tuple(ctx->global, &ctx->heap); + } + + // Trap caller + context_update_flags(ctx, ~NoFlags, Trap); + return term_invalid_term(); +} + +// +// Nifs +// + +static const struct Nif net_getaddrinfo_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_net_getaddrinfo +}; + +// +// Entrypoints +// + +const struct Nif *otp_net_nif_get_nif(const char *nifname) +{ + if (strncmp("net:", nifname, 4) == 0) { + const char *rest = nifname + 4; + if (strcmp("getaddrinfo_nif/2", rest) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &net_getaddrinfo_nif; + } + } + return NULL; +} + +static void otp_net_nif_init(GlobalContext *global) +{ + UNUSED(global); + dns_init(); +} + +REGISTER_NIF_COLLECTION(otp_net, otp_net_nif_init, NULL, otp_net_nif_get_nif) + +#endif diff --git a/src/platforms/rp2040/src/lib/otp_net_lwip_raw.h b/src/platforms/rp2040/src/lib/otp_net_lwip_raw.h new file mode 100644 index 0000000000..8f1e4e580f --- /dev/null +++ b/src/platforms/rp2040/src/lib/otp_net_lwip_raw.h @@ -0,0 +1,36 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 by Paul Guyot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#ifndef _OTP_NET_LWIP_RAW_H_ +#define _OTP_NET_LWIP_RAW_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +const struct Nif *otp_net_nif_get_nif(const char *nifname); + +#ifdef __cplusplus +} +#endif + +#endif