1use crate::marshal::{
17 register_typed_fn_1, register_typed_fn_2, register_typed_fn_2_full, register_typed_fn_3,
18};
19use crate::module_exports::{ModuleExports, ModuleParam};
20use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
21use shape_value::heap_value::{IoHandleData, IoResource};
22use std::io::{Read, Write};
23use std::sync::Arc;
24
25pub fn register_network_io(module: &mut ModuleExports) {
28 register_typed_fn_1::<_, Arc<String>>(
32 module,
33 "tcp_connect",
34 "Connect to a TCP server",
35 "addr",
36 "string",
37 ConcreteType::IoHandle,
38 |addr, ctx| {
39 let addr = addr.as_str();
40 crate::module_exports::check_net_permission(
41 ctx,
42 shape_abi_v1::Permission::NetConnect,
43 addr,
44 )?;
45 let stream = std::net::TcpStream::connect(addr)
46 .map_err(|e| format!("io.tcp_connect(\"{}\"): {}", addr, e))?;
47 let handle = IoHandleData::new_tcp_stream(stream, addr.to_string());
48 Ok(TypedReturn::Concrete(ConcreteReturn::IoHandle(Arc::new(
49 handle,
50 ))))
51 },
52 );
53
54 register_typed_fn_1::<_, Arc<String>>(
56 module,
57 "tcp_listen",
58 "Bind a TCP listener",
59 "addr",
60 "string",
61 ConcreteType::IoHandle,
62 |addr, ctx| {
63 let addr = addr.as_str();
64 crate::module_exports::check_net_permission(
65 ctx,
66 shape_abi_v1::Permission::NetListen,
67 addr,
68 )?;
69 let listener = std::net::TcpListener::bind(addr)
70 .map_err(|e| format!("io.tcp_listen(\"{}\"): {}", addr, e))?;
71 let handle = IoHandleData::new_tcp_listener(listener, addr.to_string());
72 Ok(TypedReturn::Concrete(ConcreteReturn::IoHandle(Arc::new(
73 handle,
74 ))))
75 },
76 );
77
78 register_typed_fn_1::<_, Arc<IoHandleData>>(
80 module,
81 "tcp_accept",
82 "Accept the next incoming TCP connection",
83 "listener",
84 "IoHandle",
85 ConcreteType::IoHandle,
86 |handle, ctx| {
87 crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::NetListen)?;
88 let guard = handle
89 .resource
90 .lock()
91 .map_err(|_| "io.tcp_accept(): lock poisoned".to_string())?;
92 let resource = guard
93 .as_ref()
94 .ok_or_else(|| "io.tcp_accept(): handle is closed".to_string())?;
95 match resource {
96 IoResource::TcpListener(listener) => {
97 let (stream, peer) = listener
98 .accept()
99 .map_err(|e| format!("io.tcp_accept(): {}", e))?;
100 let peer_str = peer.to_string();
101 let client = IoHandleData::new_tcp_stream(stream, peer_str);
102 Ok(TypedReturn::Concrete(ConcreteReturn::IoHandle(Arc::new(
103 client,
104 ))))
105 }
106 _ => Err("io.tcp_accept(): handle is not a TcpListener".to_string()),
107 }
108 },
109 );
110
111 register_typed_fn_2_full::<_, Arc<IoHandleData>, i64>(
113 module,
114 "tcp_read",
115 "Read up to n bytes from a TCP stream",
116 [
117 ModuleParam {
118 name: "handle".to_string(),
119 type_name: "IoHandle".to_string(),
120 required: true,
121 description: "TcpStream handle".to_string(),
122 ..Default::default()
123 },
124 ModuleParam {
125 name: "n".to_string(),
126 type_name: "int".to_string(),
127 required: false,
128 description: "Max bytes to read (default: 65536)".to_string(),
129 default_snippet: Some("65536".to_string()),
130 ..Default::default()
131 },
132 ],
133 ConcreteType::String,
134 |handle, n, ctx| {
135 crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::NetConnect)?;
136 let mut guard = handle
137 .resource
138 .lock()
139 .map_err(|_| "io.tcp_read(): lock poisoned".to_string())?;
140 let resource = guard
141 .as_mut()
142 .ok_or_else(|| "io.tcp_read(): handle is closed".to_string())?;
143 match resource {
144 IoResource::TcpStream(stream) => {
145 let buf_size = if n > 0 { n as usize } else { 65536 };
146 let mut buf = vec![0u8; buf_size];
147 let bytes_read = stream
148 .read(&mut buf)
149 .map_err(|e| format!("io.tcp_read(): {}", e))?;
150 buf.truncate(bytes_read);
151 let s = String::from_utf8(buf)
152 .map_err(|e| format!("io.tcp_read(): invalid UTF-8: {}", e))?;
153 Ok(TypedReturn::Concrete(ConcreteReturn::String(s)))
154 }
155 _ => Err("io.tcp_read(): handle is not a TcpStream".to_string()),
156 }
157 },
158 );
159
160 register_typed_fn_2::<_, Arc<IoHandleData>, Arc<String>>(
162 module,
163 "tcp_write",
164 "Write a string to a TCP stream, returning bytes written",
165 [("handle", "IoHandle"), ("data", "string")],
166 ConcreteType::Int,
167 |handle, data, ctx| {
168 crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::NetConnect)?;
169 let mut guard = handle
170 .resource
171 .lock()
172 .map_err(|_| "io.tcp_write(): lock poisoned".to_string())?;
173 let resource = guard
174 .as_mut()
175 .ok_or_else(|| "io.tcp_write(): handle is closed".to_string())?;
176 match resource {
177 IoResource::TcpStream(stream) => {
178 let written = stream
179 .write(data.as_bytes())
180 .map_err(|e| format!("io.tcp_write(): {}", e))?;
181 Ok(TypedReturn::Concrete(ConcreteReturn::I64(written as i64)))
182 }
183 _ => Err("io.tcp_write(): handle is not a TcpStream".to_string()),
184 }
185 },
186 );
187
188 register_typed_fn_1::<_, Arc<IoHandleData>>(
190 module,
191 "tcp_close",
192 "Close a TCP stream or listener, returning whether it was open",
193 "handle",
194 "IoHandle",
195 ConcreteType::Bool,
196 |handle, ctx| {
197 crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::NetConnect)?;
198 Ok(TypedReturn::Concrete(ConcreteReturn::Bool(handle.close())))
199 },
200 );
201
202 register_typed_fn_1::<_, Arc<String>>(
206 module,
207 "udp_bind",
208 "Bind a UDP socket to addr",
209 "addr",
210 "string",
211 ConcreteType::IoHandle,
212 |addr, ctx| {
213 let addr = addr.as_str();
214 crate::module_exports::check_net_permission(
215 ctx,
216 shape_abi_v1::Permission::NetListen,
217 addr,
218 )?;
219 let socket = std::net::UdpSocket::bind(addr)
220 .map_err(|e| format!("io.udp_bind(\"{}\"): {}", addr, e))?;
221 let local = socket
222 .local_addr()
223 .map(|a| a.to_string())
224 .unwrap_or_else(|_| addr.to_string());
225 let handle = IoHandleData::new_udp_socket(socket, local);
226 Ok(TypedReturn::Concrete(ConcreteReturn::IoHandle(Arc::new(
227 handle,
228 ))))
229 },
230 );
231
232 register_typed_fn_3::<_, Arc<IoHandleData>, Arc<String>, Arc<String>>(
234 module,
235 "udp_send",
236 "Send a UDP datagram to target, returning bytes sent",
237 [
238 ("handle", "IoHandle"),
239 ("data", "string"),
240 ("target", "string"),
241 ],
242 ConcreteType::Int,
243 |handle, data, target, ctx| {
244 crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::NetConnect)?;
245 let guard = handle
246 .resource
247 .lock()
248 .map_err(|_| "io.udp_send(): lock poisoned".to_string())?;
249 let resource = guard
250 .as_ref()
251 .ok_or_else(|| "io.udp_send(): handle is closed".to_string())?;
252 match resource {
253 IoResource::UdpSocket(socket) => {
254 let sent = socket
255 .send_to(data.as_bytes(), target.as_str())
256 .map_err(|e| format!("io.udp_send(): {}", e))?;
257 Ok(TypedReturn::Concrete(ConcreteReturn::I64(sent as i64)))
258 }
259 _ => Err("io.udp_send(): handle is not a UdpSocket".to_string()),
260 }
261 },
262 );
263
264 register_typed_fn_2_full::<_, Arc<IoHandleData>, i64>(
266 module,
267 "udp_recv",
268 "Receive a UDP datagram, returning {data, addr}",
269 [
270 ModuleParam {
271 name: "handle".to_string(),
272 type_name: "IoHandle".to_string(),
273 required: true,
274 description: "UdpSocket handle".to_string(),
275 ..Default::default()
276 },
277 ModuleParam {
278 name: "n".to_string(),
279 type_name: "int".to_string(),
280 required: false,
281 description: "Max receive buffer size (default: 65536)".to_string(),
282 default_snippet: Some("65536".to_string()),
283 ..Default::default()
284 },
285 ],
286 ConcreteType::TypedObject,
287 |handle, n, ctx| {
288 crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::NetConnect)?;
289 let guard = handle
290 .resource
291 .lock()
292 .map_err(|_| "io.udp_recv(): lock poisoned".to_string())?;
293 let resource = guard
294 .as_ref()
295 .ok_or_else(|| "io.udp_recv(): handle is closed".to_string())?;
296 match resource {
297 IoResource::UdpSocket(socket) => {
298 let buf_size = if n > 0 { n as usize } else { 65536 };
299 let mut buf = vec![0u8; buf_size];
300 let (bytes_read, src_addr) = socket
301 .recv_from(&mut buf)
302 .map_err(|e| format!("io.udp_recv(): {}", e))?;
303 buf.truncate(bytes_read);
304 let data = String::from_utf8(buf)
305 .map_err(|e| format!("io.udp_recv(): invalid UTF-8: {}", e))?;
306 Ok(TypedReturn::TypedObject(vec![
307 ("data".to_string(), ConcreteReturn::String(data)),
308 (
309 "addr".to_string(),
310 ConcreteReturn::String(src_addr.to_string()),
311 ),
312 ]))
313 }
314 _ => Err("io.udp_recv(): handle is not a UdpSocket".to_string()),
315 }
316 },
317 );
318}