Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
639 changes: 639 additions & 0 deletions devices/airlift/common-hal/socketpool/Socket.c

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions devices/airlift/common-hal/socketpool/Socket.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Dan Halbert for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#pragma once

#include "py/obj.h"

#include "common-hal/socketpool/SocketPool.h"
#include "shared-bindings/wifi/Radio.h"

typedef enum {
SOCKET_CLOSED = 0,
SOCKET_LISTEN = 1,
SOCKET_SYN_SENT = 2,
SOCKET_SYN_RCVD = 3,
SOCKET_ESTABLISHED = 4,
SOCKET_FIN_WAIT_1 = 5,
SOCKET_FIN_WAIT_2 = 6,
SOCKET_CLOSE_WAIT = 7,
SOCKET_CLOSING = 8,
SOCKET_LAST_ACK = 9,
SOCKET_TIME_WAIT = 10,
} airlift_client_socket_status_t;

typedef struct ssl_sslsocket_obj ssl_sslsocket_obj_t;

typedef struct {
mp_obj_base_t base;
socketpool_socketpool_obj_t *socketpool;
ssl_sslsocket_obj_t *ssl_socket;
mp_uint_t timeout_ms; // SOCKET_BLOCK_FOREVER is (mp_uint_t)-1.
size_t hostname_len;
uint8_t hostname[MAX_HOSTNAME_LENGTH + 1];
mp_uint_t port;
uint8_t num;
uint8_t type;
uint8_t family;
uint8_t proto;
bool connected;
bool bound;
bool client_started;
bool server_started;
} socketpool_socket_obj_t;

#define AIRLIFT_SOCKET_DEFAULT_TIMEOUT (3000)

#define IPPROTO_IP 0
#define IPPROTO_ICMP 1
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17

#define AF_UNSPEC 0
#define AF_INET 2

#define SOCK_STREAM 1
#define SOCK_DGRAM 2
#define SOCK_RAW 3

// Used both internally and by AirLift.
#define NO_SOCKET 255

void socket_user_reset(void);
// Unblock workflow socket select thread (platform specific)
void socketpool_socket_poll_resume(void);

void socketpool_socket_start_client_mode(socketpool_socket_obj_t *self, const char *host, size_t hostlen, uint32_t port, airlift_conn_mode_t mode);
void socketpool_socket_stop_client(socketpool_socket_obj_t *self);

void socketpool_socket_start_server_mode(socketpool_socket_obj_t *self, airlift_conn_mode_t mode);
93 changes: 93 additions & 0 deletions devices/airlift/common-hal/socketpool/SocketPool.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Dan Halbert for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#include "shared-bindings/socketpool/SocketPool.h"
#include "common-hal/socketpool/Socket.h"

#include "py/runtime.h"
#include "shared-bindings/ipaddress/IPv4Address.h"
#include "shared-bindings/wifi/__init__.h"
#include "common-hal/socketpool/__init__.h"

void common_hal_socketpool_socketpool_construct(socketpool_socketpool_obj_t *self, mp_obj_t radio) {
if (radio != MP_OBJ_FROM_PTR(&common_hal_wifi_radio_obj)) {
mp_raise_ValueError(MP_ERROR_TEXT("SocketPool can only be used with wifi.radio"));
}
// Not really needed, but more convenient.
self->radio = radio;
}

// common_hal_socketpool_socket is in socketpool/Socket.c to centralize open socket tracking.

bool socketpool_gethostbyname_ipv4(socketpool_socketpool_obj_t *self, const char *host, uint8_t ipv4[4]) {
const uint8_t *req_host_params[1] = { (uint8_t *)host };
size_t req_host_param_lengths[1] = { strlen(host) };

uint8_t result = 0;
uint8_t *req_host_responses[1] = { &result };
size_t req_host_response_lengths[1] = { 1 };

// If host is a numeric IP address, AirLift will just parse and return the address.

size_t num_responses = wifi_radio_send_command_get_response(self->radio, REQ_HOST_BY_NAME_CMD,
req_host_params, req_host_param_lengths, LENGTHS_8, MP_ARRAY_SIZE(req_host_params),
req_host_responses, req_host_response_lengths, LENGTHS_8, MP_ARRAY_SIZE(req_host_responses),
AIRLIFT_DEFAULT_TIMEOUT_MS);

if (num_responses >= 1) {
if (result == 0) {
return false;
}

uint8_t *get_host_responses[1] = { ipv4 };
size_t get_host_response_lengths[1] = { IPV4_LENGTH };

// Now actually get the name.
num_responses = wifi_radio_send_command_get_response(self->radio, GET_HOST_BY_NAME_CMD,
NULL, NULL, LENGTHS_8, 0,
get_host_responses, get_host_response_lengths, LENGTHS_8, MP_ARRAY_SIZE(get_host_responses),
AIRLIFT_DEFAULT_TIMEOUT_MS);
if (num_responses == 1) {
return true;
}
}

return false;
}

static mp_obj_t socketpool_socketpool_gethostbyname_str(socketpool_socketpool_obj_t *self, const char *host) {
uint8_t ipv4[4] = { 0 };

if (!socketpool_gethostbyname_ipv4(self, host, ipv4)) {
// Could not resolve or parse hostname.
return mp_const_none;
}

vstr_t vstr;
mp_print_t print;
vstr_init_print(&vstr, 16, &print);
mp_printf(&print, "%d.%d.%d.%d", ipv4[0], ipv4[1], ipv4[2], ipv4[3]);
return mp_obj_new_str_from_vstr(&vstr);
}

mp_obj_t common_hal_socketpool_getaddrinfo_raise(socketpool_socketpool_obj_t *self, const char *host, int port, int family, int type, int proto, int flags) {
mp_obj_t ip_str = socketpool_socketpool_gethostbyname_str(self, host);
if (ip_str == mp_const_none) {
// Could not resolve hostname.
common_hal_socketpool_socketpool_raise_gaierror_noname();
}

mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(5, NULL));
tuple->items[0] = MP_OBJ_NEW_SMALL_INT(SOCKETPOOL_AF_INET);
tuple->items[1] = MP_OBJ_NEW_SMALL_INT(SOCKETPOOL_SOCK_STREAM);
tuple->items[2] = MP_OBJ_NEW_SMALL_INT(0);
tuple->items[3] = MP_OBJ_NEW_QSTR(MP_QSTR_);
mp_obj_tuple_t *sockaddr = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL));
sockaddr->items[0] = ip_str;
sockaddr->items[1] = MP_OBJ_NEW_SMALL_INT(port);
tuple->items[4] = MP_OBJ_FROM_PTR(sockaddr);
return mp_obj_new_list(1, (mp_obj_t *)&tuple);
}
17 changes: 17 additions & 0 deletions devices/airlift/common-hal/socketpool/SocketPool.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Dan Halbert for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#pragma once

#include "py/obj.h"
#include "shared-bindings/wifi/Radio.h"

typedef struct {
mp_obj_base_t base;
wifi_radio_obj_t *radio;
} socketpool_socketpool_obj_t;

bool socketpool_gethostbyname_ipv4(socketpool_socketpool_obj_t *self, const char *host, uint8_t ipv4[IPV4_LENGTH]);
13 changes: 13 additions & 0 deletions devices/airlift/common-hal/socketpool/__init__.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Dan Halbert for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#include "shared-bindings/socketpool/__init__.h"

#include "common-hal/socketpool/Socket.h"

void socketpool_user_reset(void) {
socket_user_reset();
}
7 changes: 7 additions & 0 deletions devices/airlift/common-hal/socketpool/__init__.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Dan Halbert for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#pragma once
38 changes: 38 additions & 0 deletions devices/airlift/common-hal/ssl/SSLContext.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Dan Halbert for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#include "shared-bindings/ssl/SSLContext.h"
#include "shared-bindings/ssl/SSLSocket.h"

#include "py/runtime.h"
#include "py/stream.h"

void common_hal_ssl_sslcontext_construct(ssl_sslcontext_obj_t *self) {
common_hal_ssl_sslcontext_set_default_verify_paths(self);
}

void common_hal_ssl_sslcontext_load_verify_locations(ssl_sslcontext_obj_t *self,
const char *cadata) {
mp_raise_NotImplementedError(NULL);
}

void common_hal_ssl_sslcontext_set_default_verify_paths(ssl_sslcontext_obj_t *self) {
// The default paths are what's built in to NINA-FW, so nothing need be done.
}

bool common_hal_ssl_sslcontext_get_check_hostname(ssl_sslcontext_obj_t *self) {
return true;
}

void common_hal_ssl_sslcontext_set_check_hostname(ssl_sslcontext_obj_t *self, bool value) {
if (!value) {
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be %q"), MP_QSTR_check_hostname, MP_QSTR_true);
}
}

void common_hal_ssl_sslcontext_load_cert_chain(ssl_sslcontext_obj_t *self, mp_buffer_info_t *cert_buf, mp_buffer_info_t *key_buf) {
mp_raise_NotImplementedError(NULL);
}
18 changes: 18 additions & 0 deletions devices/airlift/common-hal/ssl/SSLContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Dan Halbert for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#pragma once

#include "py/obj.h"

typedef struct {
mp_obj_base_t base;
// bool check_name, use_global_ca_store;
// const unsigned char *cacert_buf;
// size_t cacert_bytes;
// int (*crt_bundle_attach)(mbedtls_ssl_config *conf);
// mp_buffer_info_t cert_buf, key_buf;
} ssl_sslcontext_obj_t;
132 changes: 132 additions & 0 deletions devices/airlift/common-hal/ssl/SSLSocket.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Dan Halbert for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#include "shared-bindings/ssl/SSLSocket.h"
#include "shared-bindings/ssl/SSLContext.h"

#include "shared/runtime/interrupt_char.h"
#include "shared/netutils/netutils.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#include "py/objstr.h"
#include "py/runtime.h"
#include "py/stream.h"
#include "supervisor/shared/tick.h"

#include "shared-bindings/socketpool/enum.h"

ssl_sslsocket_obj_t *common_hal_ssl_sslcontext_wrap_socket(ssl_sslcontext_obj_t *self,
mp_obj_t socket, bool server_side, const char *server_hostname) {

if (server_side) {
mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q not implemented"), MP_QSTR_server_side);
}

mp_arg_validate_length_max(strlen(server_hostname), MAX_HOSTNAME_LENGTH, MP_QSTR_hostname);

ssl_sslsocket_obj_t *ssl_socket = mp_obj_malloc(ssl_sslsocket_obj_t, &ssl_sslsocket_type);
ssl_socket->socket = MP_OBJ_TO_PTR(socket);
// Max length already validated above, so this is safe.
strcpy(ssl_socket->hostname, server_hostname);
ssl_socket->server_side = server_side;
return ssl_socket;
}

mp_uint_t common_hal_ssl_sslsocket_recv_into(ssl_sslsocket_obj_t *self, uint8_t *buf, mp_uint_t len) {
return common_hal_socketpool_socket_recv_into(self->socket, buf, len);
}

mp_uint_t common_hal_ssl_sslsocket_send(ssl_sslsocket_obj_t *self, const uint8_t *buf, mp_uint_t len) {
return common_hal_socketpool_socket_send(self->socket, buf, len);
}

void common_hal_ssl_sslsocket_bind(ssl_sslsocket_obj_t *self, mp_obj_t addr_in) {
mp_obj_t *addr_items;
mp_obj_get_array_fixed_n(addr_in, 2, &addr_items);

size_t hostlen;
const char *host = mp_obj_str_get_data(addr_items[0], &hostlen);
mp_int_t port = mp_arg_validate_int_min(mp_obj_get_int(addr_items[1]), 0, MP_QSTR_port);

common_hal_socketpool_socket_bind(self->socket, host, hostlen, port);
}

void common_hal_ssl_sslsocket_close(ssl_sslsocket_obj_t *self) {
common_hal_socketpool_socket_close(self->socket);
}

void common_hal_ssl_sslsocket_connect(ssl_sslsocket_obj_t *self, mp_obj_t addr_in) {
mp_obj_t *addr_items;
mp_obj_get_array_fixed_n(addr_in, 2, &addr_items);

size_t hostlen;
const char *host = mp_obj_str_get_data(addr_items[0], &hostlen);
mp_int_t port = mp_arg_validate_int_min(mp_obj_get_int(addr_items[1]), 0, MP_QSTR_port);

socketpool_socket_start_client_mode(self->socket, host, hostlen, (uint32_t)port, AIRLIFT_TLS_MODE);
}

bool common_hal_ssl_sslsocket_get_closed(ssl_sslsocket_obj_t *self) {
return common_hal_socketpool_socket_get_closed(self->socket);
}

bool common_hal_ssl_sslsocket_get_connected(ssl_sslsocket_obj_t *self) {
return common_hal_socketpool_socket_get_connected(self->socket);
}

void common_hal_ssl_sslsocket_listen(ssl_sslsocket_obj_t *self, int backlog) {
mp_raise_NotImplementedError(NULL);
}

mp_obj_t common_hal_ssl_sslsocket_accept(ssl_sslsocket_obj_t *self) {
mp_raise_NotImplementedError(NULL);
}

void common_hal_ssl_sslsocket_setsockopt(ssl_sslsocket_obj_t *self, mp_obj_t level_obj, mp_obj_t optname_obj, mp_obj_t optval_obj) {
mp_int_t level = mp_obj_get_int(level_obj);
mp_int_t optname = mp_obj_get_int(optname_obj);

const void *optval;
mp_uint_t optlen;
mp_int_t val;
if (mp_obj_is_integer(optval_obj)) {
val = mp_obj_get_int_truncated(optval_obj);
optval = &val;
optlen = sizeof(val);
} else {
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(optval_obj, &bufinfo, MP_BUFFER_READ);
optval = bufinfo.buf;
optlen = bufinfo.len;
}

int _errno = common_hal_socketpool_socket_setsockopt(self->socket, level, optname, optval, optlen);
if (_errno < 0) {
mp_raise_OSError(-_errno);
}
}

void common_hal_ssl_sslsocket_settimeout(ssl_sslsocket_obj_t *self, mp_obj_t timeout_obj) {
mp_uint_t timeout_ms;
if (timeout_obj == mp_const_none) {
timeout_ms = SOCKET_BLOCK_FOREVER;
} else {
#if MICROPY_PY_BUILTINS_FLOAT
timeout_ms = 1000 * mp_obj_get_float(timeout_obj);
#else
timeout_ms = 1000 * mp_obj_get_int(timeout_obj);
#endif
}
common_hal_socketpool_socket_settimeout(self->socket, timeout_ms);
}

bool common_hal_ssl_sslsocket_readable(ssl_sslsocket_obj_t *self) {
return common_hal_socketpool_socket_readable(self->socket);
}

bool common_hal_ssl_sslsocket_writable(ssl_sslsocket_obj_t *self) {
return common_hal_socketpool_socket_writable(self->socket);
}
Loading
Loading