Skip to content
Open
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
205 changes: 167 additions & 38 deletions app/src/sm_at_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ static struct sm_socket {
int family; /* Socket address family */
int fd; /* Socket descriptor. */
uint16_t cid; /* PDP Context ID, 0: primary; 1~10: secondary */
uint16_t local_port; /* Explicitly bound local port. */
int send_flags; /* Send flags */
bool send_cb_set: 1; /* Send callback set */
bool connected: 1; /* Connected flag. */
bool listen: 1; /* Listen flag for TCP server sockets. */
struct sm_async_poll async_poll; /* Async poll info. */
struct sm_send_ntf send_ntf; /* Send notification info. */
} socks[SM_MAX_SOCKET_COUNT];
Expand Down Expand Up @@ -120,9 +122,11 @@ static void init_socket(struct sm_socket *socket)
socket->family = AF_UNSPEC;
socket->fd = INVALID_SOCKET;
socket->cid = 0;
socket->local_port = 0;
socket->send_flags = 0;
socket->send_cb_set = false;
socket->connected = false;
socket->listen = false;
socket->send_ntf = (struct sm_send_ntf){0};
socket->async_poll = (struct sm_async_poll){0};
}
Expand Down Expand Up @@ -220,6 +224,10 @@ static void auto_reception(struct sm_socket *sock)
if (sock == NULL) {
return;
}
if (sock->listen) {
return;
}

if (sock->connected || sock->type == NRF_SOCK_RAW) {
err = do_recv(sock, 0, NRF_MSG_DONTWAIT,
sock->async_poll.adr_hex ? AT_SOCKET_MODE_HEX
Expand Down Expand Up @@ -508,10 +516,6 @@ static int do_socket_open(struct sm_socket *sock)
ret = nrf_socket(sock->family, NRF_SOCK_DGRAM, NRF_IPPROTO_UDP);
proto = NRF_IPPROTO_UDP;
} else if (sock->type == NRF_SOCK_RAW) {
if (sock->role != NRF_SO_SEC_ROLE_CLIENT) {
LOG_ERR("Raw socket: Role must be client");
return -EINVAL;
}
ret = nrf_socket(sock->family, NRF_SOCK_RAW, NRF_IPPROTO_RAW);
proto = NRF_IPPROTO_IP;
} else {
Expand Down Expand Up @@ -610,18 +614,6 @@ static int do_secure_socket_open(struct sm_socket *sock, int peer_verify)
ret = -errno;
goto error;
}
/* Set up (D)TLS server role if applicable */
if (sock->role == AT_SOCKET_ROLE_SERVER) {
int tls_role = NRF_SO_SEC_ROLE_SERVER;

ret = nrf_setsockopt(sock->fd, NRF_SOL_SECURE, NRF_SO_SEC_ROLE, &tls_role,
sizeof(int));
if (ret) {
LOG_ERR("nrf_setsockopt(%d) error: %d", NRF_SO_SEC_ROLE, -errno);
ret = -errno;
goto error;
}
}

rsp_send("\r\n#XSSOCKET: %d,%d,%d\r\n", sock->fd, sock->type, proto);

Expand Down Expand Up @@ -877,7 +869,7 @@ static int sec_sockopt_get(struct sm_socket *sock, enum at_sec_sockopt at_option
return ret;
}

int bind_to_local_addr(struct sm_socket *sock, uint16_t port)
static int bind_to_local_addr(struct sm_socket *sock, uint16_t port)
{
int ret;

Expand Down Expand Up @@ -936,6 +928,7 @@ int bind_to_local_addr(struct sm_socket *sock, uint16_t port)
return -EINVAL;
}

sock->local_port = port;
return 0;
}

Expand Down Expand Up @@ -1337,15 +1330,6 @@ STATIC int handle_at_secure_socket(enum at_parser_cmd_type cmd_type,
if (err) {
goto error;
}
/** Peer verification level for TLS connection.
* - 0 - none
* - 1 - optional
* - 2 - required
* If not set, socket will use defaults (none for servers,
* required for clients)
*/
uint16_t peer_verify;

err = at_parser_num_get(parser, 2, &sock->type);
if (err) {
goto error;
Expand All @@ -1354,19 +1338,14 @@ STATIC int handle_at_secure_socket(enum at_parser_cmd_type cmd_type,
if (err) {
goto error;
}
if (sock->role == AT_SOCKET_ROLE_SERVER) {
peer_verify = TLS_PEER_VERIFY_NONE;
} else if (sock->role == AT_SOCKET_ROLE_CLIENT) {
peer_verify = TLS_PEER_VERIFY_REQUIRED;
} else {
err = -EINVAL;
goto error;
}
sock->sec_tag = SEC_TAG_TLS_INVALID;
err = at_parser_num_get(parser, 4, &sock->sec_tag);
if (err) {
goto error;
}

uint16_t peer_verify = TLS_PEER_VERIFY_REQUIRED;

if (param_count > 5) {
err = at_parser_num_get(parser, 5, &peer_verify);
if (err) {
Expand Down Expand Up @@ -1647,10 +1626,6 @@ STATIC int handle_at_connect(enum at_parser_cmd_type cmd_type, struct at_parser
if (sock == NULL) {
return -EINVAL;
}
if (sock->role != AT_SOCKET_ROLE_CLIENT) {
LOG_ERR("Invalid role");
return -EOPNOTSUPP;
}
err = util_string_get(parser, 2, url, &size);
if (err) {
return err;
Expand Down Expand Up @@ -1955,6 +1930,160 @@ STATIC int handle_at_recvfrom(enum at_parser_cmd_type cmd_type, struct at_parser
return err;
}

static int do_listen(struct sm_socket *sock)
{
int ret;

if (sock->type != NRF_SOCK_STREAM || sock->local_port == 0 ||
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do_listen() rejects listening unless sock->local_port != 0, but #XBIND explicitly documents the port range as 0–65535. If a socket is bound with port 0 (ephemeral), it is still bound but will be treated as “not bound” here. Consider tracking a separate “is_bound” flag (or querying getsockname) instead of using local_port == 0 as a proxy.

Suggested change
if (sock->type != NRF_SOCK_STREAM || sock->local_port == 0 ||
if (sock->type != NRF_SOCK_STREAM ||

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks valid, right? 0 is valid port number.
Add a unit test for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on how the modem deals with bind to 0. But in any case either this or the port range in bind is wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the way this works is that the 0 assigns an ephemeral port, which we cannot currently be aware as we lack the getsockname(). So we are right to deny this here as without being bound to specific port we would not know which port the listen went to, which makes it useless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a note in the bind about 0 being a special value.

sock->sec_tag != SEC_TAG_TLS_INVALID) {
return -EOPNOTSUPP;
}

/* Set the socket to non-blocking mode, so accept() won't block. */
ret = nrf_fcntl(sock->fd, NRF_F_SETFL, NRF_O_NONBLOCK);
if (ret) {
LOG_ERR("nrf_fcntl() failed: %d", -errno);
return -errno;
}

/* nRF modem ignores the backlog parameter. Backlog in modem is fixed to 2. */
ret = nrf_listen(sock->fd, 2);
if (ret) {
LOG_ERR("nrf_listen() failed: %d", -errno);
return -errno;
}

sock->listen = true;

return 0;
}

SM_AT_CMD_CUSTOM(xlisten, "AT#XLISTEN", handle_at_listen);
STATIC int handle_at_listen(enum at_parser_cmd_type cmd_type, struct at_parser *parser, uint32_t)
{
int err = -EINVAL;
int fd;

struct sm_socket *sock = NULL;

switch (cmd_type) {
case AT_PARSER_CMD_TYPE_SET:
err = at_parser_num_get(parser, 1, &fd);
if (err) {
return err;
}
sock = find_socket(fd);
if (sock == NULL) {
return -EINVAL;
}
err = do_listen(sock);
break;

case AT_PARSER_CMD_TYPE_READ:
for (int i = 0; i < SM_MAX_SOCKET_COUNT; i++) {
if (socks[i].fd != INVALID_SOCKET && socks[i].listen) {
rsp_send("\r\n#XLISTEN: %d,%d,%d\r\n", socks[i].fd, socks[i].cid,
socks[i].local_port);
}
}
err = 0;
break;

case AT_PARSER_CMD_TYPE_TEST:
rsp_send("\r\n#XLISTEN: <handle>\r\n");
err = 0;
break;

default:
break;
}

return err;
}

static int do_accept(struct sm_socket *sock)
{
int ret;
struct sockaddr remote;
socklen_t addrlen = sizeof(struct sockaddr);
char peer_addr[NRF_INET6_ADDRSTRLEN] = {0};
uint16_t peer_port = 0;

if (sock->type != NRF_SOCK_STREAM || !sock->listen) {
return -EOPNOTSUPP;
}

ret = nrf_accept(sock->fd, (struct nrf_sockaddr *)&remote, (nrf_socklen_t *)&addrlen);
if (ret < 0) {
LOG_ERR("nrf_accept() failed: %d", -errno);
return -errno;
}

struct sm_socket *new_sock = find_avail_socket();
if (new_sock == NULL) {
LOG_ERR("Max socket count reached, closing accepted socket");
nrf_close(ret);
return -EINVAL;
}
init_socket(new_sock);
new_sock->fd = ret;
new_sock->family = remote.sa_family;
new_sock->type = NRF_SOCK_STREAM;
new_sock->role = AT_SOCKET_ROLE_CLIENT;
new_sock->cid = sock->cid;
new_sock->connected = true;

util_get_peer_addr(&remote, peer_addr, &peer_port);
rsp_send("\r\n#XACCEPT: %d,%d,\"%s\",%d\r\n", new_sock->fd, new_sock->cid, peer_addr,
peer_port);

/* Update poll events for xapoll and automatic data reception */
new_sock->async_poll.adr_flags = poll_ctx.adr_flags;
new_sock->async_poll.adr_hex = poll_ctx.adr_hex;
new_sock->async_poll.xapoll_events_requested = poll_ctx.xapoll_events_requested;
update_poll_events(new_sock,
NRF_POLLIN | NRF_POLLOUT | NRF_POLLERR | NRF_POLLHUP | NRF_POLLNVAL,
true);

/* Restore POLLIN for listening socket. */
update_poll_events(sock, NRF_POLLIN, true);

return 0;
}

SM_AT_CMD_CUSTOM(xaccept, "AT#XACCEPT", handle_at_accept);
STATIC int handle_at_accept(enum at_parser_cmd_type cmd_type, struct at_parser *parser, uint32_t)
{
int err = -EINVAL;
int fd;

struct sm_socket *sock = NULL;

switch (cmd_type) {
case AT_PARSER_CMD_TYPE_SET:
err = at_parser_num_get(parser, 1, &fd);
if (err) {
return err;
}
sock = find_socket(fd);
if (sock == NULL) {
return -EINVAL;
}
err = do_accept(sock);
break;

case AT_PARSER_CMD_TYPE_TEST:
rsp_send("\r\n#XACCEPT: <handle>\r\n");
err = 0;
break;

default:
break;
}

return err;
}

SM_AT_CMD_CUSTOM(xgetaddrinfo, "AT#XGETADDRINFO", handle_at_getaddrinfo);
STATIC int handle_at_getaddrinfo(enum at_parser_cmd_type cmd_type, struct at_parser *parser,
uint32_t param_count)
Expand Down
1 change: 1 addition & 0 deletions app/tests/at_socket/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ target_sources(app PRIVATE
../stubs/control_pin_stubs.c
../stubs/pm_stubs.c
../stubs/at_cmd_custom_stubs.c
../stubs/kernel_stubs.c
../../src/sm_util.c
../../src/sm_at_socket.c
../../src/sm_at_host.c
Expand Down
6 changes: 6 additions & 0 deletions app/tests/at_socket/src/nrf_modem_at_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ extern int handle_at_recvcfg_wrapper_xrecvcfg(char *buf, size_t len, char *at_cm
extern int handle_at_socketopt_wrapper_xsocketopt(char *buf, size_t len, char *at_cmd);
extern int handle_at_secure_socket_wrapper_xssocket(char *buf, size_t len, char *at_cmd);
extern int handle_at_secure_socketopt_wrapper_xssocketopt(char *buf, size_t len, char *at_cmd);
extern int handle_at_listen_wrapper_xlisten(char *buf, size_t len, char *at_cmd);
extern int handle_at_accept_wrapper_xaccept(char *buf, size_t len, char *at_cmd);

/* Wrapper for nrf_modem_at_cmd that handles custom commands */
int nrf_modem_at_cmd(void *buf, size_t buf_size, const char *fmt, ...)
Expand Down Expand Up @@ -73,6 +75,10 @@ int nrf_modem_at_cmd(void *buf, size_t buf_size, const char *fmt, ...)
ret = handle_at_close_wrapper_xclose((char *)buf, buf_size, at_cmd);
} else if (strncasecmp(at_cmd, "AT#XBIND", 8) == 0) {
ret = handle_at_bind_wrapper_xbind((char *)buf, buf_size, at_cmd);
} else if (strncasecmp(at_cmd, "AT#XLISTEN", 10) == 0) {
ret = handle_at_listen_wrapper_xlisten((char *)buf, buf_size, at_cmd);
} else if (strncasecmp(at_cmd, "AT#XACCEPT", 10) == 0) {
ret = handle_at_accept_wrapper_xaccept((char *)buf, buf_size, at_cmd);
} else if (strncasecmp(at_cmd, "AT#XCONNECT", 11) == 0) {
ret = handle_at_connect_wrapper_xconnect((char *)buf, buf_size, at_cmd);
} else if (strncasecmp(at_cmd, "AT#XSENDTO", 10) == 0) {
Expand Down
Loading
Loading