CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
host_api_socket.cpp
Go to the documentation of this file.
1
16
20#include "cdc_log.h"
21
22#include "lwip/sockets.h"
23#include "lwip/netdb.h"
24
25#include <cerrno>
26#include <cstdio>
27#include <fcntl.h>
28#include <unistd.h>
29
30extern "C" void* plg_get_active_plugin(void);
31
32namespace {
33
34static constexpr const char* TAG = "SOCK";
35
36constexpr size_t MAX_SOCKET_SLOTS = 8;
37constexpr uint32_t kDefaultTimeoutMs = 5000;
38
39struct SocketSlot {
40 bool used = false;
41 void* owner = nullptr; // plugin that opened the socket
42 int fd = -1;
43 uint8_t proto = HOST_SOCK_TCP;
44};
45
47
48bool socketAllowed()
49{
50 auto* p = static_cast<cdc::plugin_manager::Plugin*>(plg_get_active_plugin());
51 return p && p->manifest().capabilities.socket;
52}
53
54// Read/write/close are also allowed for a net_listen plugin operating on a
55// connection it accepted via the listener (host_net_accept adopts the fd into
56// this slot pool). Opening outbound connections still requires `socket`.
57bool socketIoAllowed()
58{
59 auto* p = static_cast<cdc::plugin_manager::Plugin*>(plg_get_active_plugin());
60 return p && (p->manifest().capabilities.socket || p->manifest().capabilities.net_listen);
61}
62
63SocketSlot* slotFor(int handle) { return s_slots.lookup(handle); }
64
65void closeSlot(SocketSlot& slot)
66{
67 if (slot.fd >= 0) ::close(slot.fd);
68 slot = SocketSlot{};
69}
70
71// Apply a per-call send or receive timeout to a socket fd.
72void applyTimeout(int fd, int optname, uint32_t to_ms)
73{
74 struct timeval tv;
75 tv.tv_sec = static_cast<time_t>(to_ms / 1000);
76 tv.tv_usec = static_cast<suseconds_t>((to_ms % 1000) * 1000);
77 ::setsockopt(fd, SOL_SOCKET, optname, &tv, sizeof(tv));
78}
79
80// TCP: bound the connect with a non-blocking handshake + select(); UDP:
81// connect() only fixes the default peer and returns immediately.
82int connectEndpoint(int fd, const struct sockaddr* addr, socklen_t addrlen,
83 uint8_t proto, uint32_t to_ms)
84{
85 if (proto == HOST_SOCK_UDP) {
86 return ::connect(fd, addr, addrlen) == 0 ? HOST_OK : HOST_ERR_GENERIC;
87 }
88
89 int flags = ::fcntl(fd, F_GETFL, 0);
90 if (flags < 0) return HOST_ERR_GENERIC;
91 ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
92
93 int rc = ::connect(fd, addr, addrlen);
94 if (rc != 0 && errno != EINPROGRESS) return HOST_ERR_GENERIC;
95
96 if (rc != 0) {
97 fd_set wset;
98 FD_ZERO(&wset);
99 FD_SET(fd, &wset);
100 struct timeval tv;
101 tv.tv_sec = static_cast<time_t>(to_ms / 1000);
102 tv.tv_usec = static_cast<suseconds_t>((to_ms % 1000) * 1000);
103 int sel = ::select(fd + 1, nullptr, &wset, nullptr, &tv);
104 if (sel == 0) return HOST_ERR_TIMEOUT;
105 if (sel < 0) return HOST_ERR_GENERIC;
106
107 int soerr = 0;
108 socklen_t len = sizeof(soerr);
109 if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerr, &len) != 0 || soerr != 0) {
110 return HOST_ERR_GENERIC;
111 }
112 }
113
114 ::fcntl(fd, F_SETFL, flags);
115 return HOST_OK;
116}
117
118} // namespace
119
120extern "C" {
121
122int host_socket_open(uint8_t proto, const char* host, uint16_t port, uint32_t timeout_ms)
123{
124 if (!socketAllowed()) return HOST_ERR_NO_CAPABILITY;
125 if (!host || !*host) return HOST_ERR_INVALID_ARG;
126 if (proto != HOST_SOCK_TCP && proto != HOST_SOCK_UDP) return HOST_ERR_INVALID_ARG;
127
128 const int socktype = (proto == HOST_SOCK_UDP) ? SOCK_DGRAM : SOCK_STREAM;
129 const uint32_t to_ms = timeout_ms ? timeout_ms : kDefaultTimeoutMs;
130
131 int slot_id = 0;
132 SocketSlot* slot = s_slots.allocate(slot_id);
133 if (!slot) return HOST_ERR_NO_MEMORY;
134 *slot = SocketSlot{};
135 slot->used = true;
136 slot->owner = plg_get_active_plugin();
137
138 char port_str[6];
139 std::snprintf(port_str, sizeof(port_str), "%u", static_cast<unsigned>(port));
140
141 struct addrinfo hints{};
142 hints.ai_family = AF_UNSPEC;
143 hints.ai_socktype = socktype;
144 struct addrinfo* res = nullptr;
145 if (::getaddrinfo(host, port_str, &hints, &res) != 0 || !res) {
146 LOG_W(TAG, "resolve failed for '%s'", host);
147 *slot = SocketSlot{};
148 return HOST_ERR_NOT_FOUND;
149 }
150
151 int fd = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
152 if (fd < 0) {
153 ::freeaddrinfo(res);
154 *slot = SocketSlot{};
155 return HOST_ERR_GENERIC;
156 }
157
158 int rc = connectEndpoint(fd, res->ai_addr, res->ai_addrlen, proto, to_ms);
159 ::freeaddrinfo(res);
160 if (rc != HOST_OK) {
161 ::close(fd);
162 *slot = SocketSlot{};
163 return rc;
164 }
165
166 slot->fd = fd;
167 slot->proto = proto;
168 return slot_id;
169}
170
171int host_socket_write(int handle, const uint8_t* data, size_t len, uint32_t timeout_ms)
172{
173 if (!socketIoAllowed()) return HOST_ERR_NO_CAPABILITY;
174 auto* slot = slotFor(handle);
175 if (!slot || slot->fd < 0) return HOST_ERR_INVALID_ARG;
176 if (len == 0) return 0;
177 if (!data) return HOST_ERR_INVALID_ARG;
178
179 applyTimeout(slot->fd, SO_SNDTIMEO, timeout_ms ? timeout_ms : kDefaultTimeoutMs);
180 int n = static_cast<int>(::send(slot->fd, data, len, 0));
181 if (n < 0) {
182 return (errno == EAGAIN || errno == EWOULDBLOCK) ? HOST_ERR_TIMEOUT
184 }
185 return n;
186}
187
188int host_socket_read(int handle, uint8_t* out, size_t cap, uint32_t timeout_ms)
189{
190 if (!socketIoAllowed()) return HOST_ERR_NO_CAPABILITY;
191 auto* slot = slotFor(handle);
192 if (!slot || slot->fd < 0) return HOST_ERR_INVALID_ARG;
193 if (cap == 0) return 0;
194 if (!out) return HOST_ERR_INVALID_ARG;
195
196 applyTimeout(slot->fd, SO_RCVTIMEO, timeout_ms ? timeout_ms : kDefaultTimeoutMs);
197 int n = static_cast<int>(::recv(slot->fd, out, cap, 0));
198 if (n < 0) {
199 return (errno == EAGAIN || errno == EWOULDBLOCK) ? HOST_ERR_TIMEOUT
201 }
202 return n; // 0 == peer closed the connection (TCP EOF)
203}
204
205int host_socket_close(int handle)
206{
207 auto* slot = slotFor(handle);
208 if (!slot) return HOST_ERR_INVALID_ARG;
209 closeSlot(*slot);
210 return HOST_OK;
211}
212
213// Adopt an already-connected TCP fd (e.g. accepted by the net listener) into a
214// socket slot so the plugin can drive it via host_socket_read/write/close.
215// Returns a 1-based handle, or a negative HOST_ERR_* code. Not WASM-exported.
216int plg_socket_adopt(int fd, void* owner)
217{
218 if (fd < 0) return HOST_ERR_INVALID_ARG;
219 int slot_id = 0;
220 SocketSlot* slot = s_slots.allocate(slot_id);
221 if (!slot) return HOST_ERR_NO_MEMORY;
222 *slot = SocketSlot{};
223 slot->used = true;
224 slot->owner = owner;
225 slot->fd = fd;
226 slot->proto = HOST_SOCK_TCP;
227 return slot_id;
228}
229
230void plg_socket_on_unload(void* plugin)
231{
232 if (!plugin) return;
233 for (size_t i = 0; i < MAX_SOCKET_SLOTS; ++i) {
234 SocketSlot& slot = s_slots.slots[i];
235 if (!slot.used || slot.owner != plugin) continue;
236 LOG_W(TAG, "force-closing leaked socket slot %u", static_cast<unsigned>(i + 1));
237 closeSlot(slot);
238 }
239}
240
241} // extern "C"
static const char * TAG
Owned WAMR module instance + per-plugin state.
Fixed-capacity, 1-based slot table for host-API resources.
uint8_t flags
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
const PluginManifest & manifest() const noexcept
Definition Plugin.h:88
#define HOST_SOCK_UDP
Definition host_api.h:381
int host_socket_read(int handle, uint8_t *out, size_t cap, uint32_t timeout_ms)
Read bytes from the stream / receive a datagram from the connected peer.
#define HOST_SOCK_TCP
Protocol selector for host_socket_open.
Definition host_api.h:380
int host_socket_close(int handle)
Close a socket handle.
int host_socket_write(int handle, const uint8_t *data, size_t len, uint32_t timeout_ms)
Write bytes to the stream / send a datagram to the connected peer.
int host_socket_open(uint8_t proto, const char *host, uint16_t port, uint32_t timeout_ms)
Open an outbound connection to a single remote endpoint.
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_ERR_NO_CAPABILITY
Definition host_api.h:40
#define HOST_OK
Definition host_api.h:37
#define HOST_ERR_INVALID_ARG
Definition host_api.h:39
#define HOST_ERR_NO_MEMORY
Definition host_api.h:43
#define HOST_ERR_TIMEOUT
Definition host_api.h:42
#define HOST_ERR_NOT_FOUND
Definition host_api.h:41
#define HOST_ERR_GENERIC
Definition host_api.h:38
void * plg_get_active_plugin(void)
void plg_socket_on_unload(void *plugin)
int plg_socket_adopt(int fd, void *owner)
T * lookup(int id)
1-based lookup. Returns nullptr if id is out of range or slot unused.
Definition SlotTable.h:26
std::array< T, N > slots
Definition SlotTable.h:21
T * allocate(int &out_id)
Definition SlotTable.h:37