1#![deny(unsafe_code)]
6
7pub mod client;
8pub mod endpoint;
9pub mod events;
10pub(crate) mod io;
11pub(crate) mod pool;
12pub mod registry;
13pub mod server;
14pub mod session;
15pub mod stream;
16
17pub use client::{fetch, raw_connect};
18#[cfg(feature = "compression")]
19pub use endpoint::CompressionOptions;
20pub use endpoint::{
21 parse_direct_addrs, ConnectionEvent, DiscoveryOptions, EndpointStats, IrohEndpoint,
22 NetworkingOptions, NodeAddrInfo, NodeOptions, PathInfo, PeerStats, PoolOptions,
23 StreamingOptions,
24};
25pub use events::TransportEvent;
26pub use registry::{get_endpoint, insert_endpoint, remove_endpoint};
27pub use server::respond;
28pub use server::serve;
29pub use server::serve_with_events;
30pub use server::ServeHandle;
31pub use server::ServeOptions;
32pub use session::{
33 session_accept, session_close, session_closed, session_connect, session_create_bidi_stream,
34 session_create_uni_stream, session_max_datagram_size, session_next_bidi_stream,
35 session_next_uni_stream, session_ready, session_recv_datagram, session_remote_id,
36 session_send_datagram, CloseInfo,
37};
38pub use stream::{BodyReader, HandleStore, StoreConfig};
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46#[non_exhaustive]
47pub enum ErrorCode {
48 InvalidInput,
49 ConnectionFailed,
50 Timeout,
51 BodyTooLarge,
52 HeaderTooLarge,
53 PeerRejected,
54 Cancelled,
55 Internal,
56}
57
58#[derive(Debug, Clone)]
62pub struct CoreError {
63 pub code: ErrorCode,
64 pub message: String,
65}
66
67impl CoreError {
68 pub fn invalid_input(detail: impl std::fmt::Display) -> Self {
69 CoreError {
70 code: ErrorCode::InvalidInput,
71 message: detail.to_string(),
72 }
73 }
74 pub fn connection_failed(detail: impl std::fmt::Display) -> Self {
75 CoreError {
76 code: ErrorCode::ConnectionFailed,
77 message: detail.to_string(),
78 }
79 }
80 pub fn timeout(detail: impl std::fmt::Display) -> Self {
81 CoreError {
82 code: ErrorCode::Timeout,
83 message: detail.to_string(),
84 }
85 }
86 pub fn body_too_large(detail: impl std::fmt::Display) -> Self {
87 CoreError {
88 code: ErrorCode::BodyTooLarge,
89 message: detail.to_string(),
90 }
91 }
92 pub fn header_too_large(detail: impl std::fmt::Display) -> Self {
93 CoreError {
94 code: ErrorCode::HeaderTooLarge,
95 message: detail.to_string(),
96 }
97 }
98 pub fn peer_rejected(detail: impl std::fmt::Display) -> Self {
99 CoreError {
100 code: ErrorCode::PeerRejected,
101 message: detail.to_string(),
102 }
103 }
104 pub fn internal(detail: impl std::fmt::Display) -> Self {
105 CoreError {
106 code: ErrorCode::Internal,
107 message: detail.to_string(),
108 }
109 }
110 pub fn invalid_handle(handle: u64) -> Self {
111 CoreError {
112 code: ErrorCode::InvalidInput,
113 message: format!("unknown handle: {handle}"),
114 }
115 }
116 pub fn cancelled() -> Self {
117 CoreError {
118 code: ErrorCode::Cancelled,
119 message: "aborted".to_string(),
120 }
121 }
122}
123
124impl std::fmt::Display for CoreError {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "{:?}: {}", self.code, self.message)
127 }
128}
129
130impl std::error::Error for CoreError {}
131
132pub const ALPN: &[u8] = b"iroh-http/2";
136pub const ALPN_DUPLEX: &[u8] = b"iroh-http/2-duplex";
138
139pub const ALPN_STR: &str = "iroh-http/2";
141pub const ALPN_DUPLEX_STR: &str = "iroh-http/2-duplex";
143
144pub const KNOWN_ALPNS: &[&str] = &[ALPN_STR, ALPN_DUPLEX_STR];
146
147pub(crate) type BoxBody =
151 http_body_util::combinators::BoxBody<bytes::Bytes, std::convert::Infallible>;
152
153pub(crate) fn box_body<B>(body: B) -> BoxBody
155where
156 B: http_body::Body<Data = bytes::Bytes, Error = std::convert::Infallible>
157 + Send
158 + Sync
159 + 'static,
160{
161 use http_body_util::BodyExt;
162 body.map_err(|_| unreachable!()).boxed()
163}
164
165pub fn secret_key_sign(secret_key_bytes: &[u8; 32], data: &[u8]) -> Result<[u8; 64], CoreError> {
170 std::panic::catch_unwind(|| {
171 let key = iroh::SecretKey::from_bytes(secret_key_bytes);
172 key.sign(data).to_bytes()
173 })
174 .map_err(|_| CoreError::internal("secret_key_sign panicked"))
175}
176
177pub fn public_key_verify(public_key_bytes: &[u8; 32], data: &[u8], sig_bytes: &[u8; 64]) -> bool {
180 std::panic::catch_unwind(|| {
181 let Ok(key) = iroh::PublicKey::from_bytes(public_key_bytes) else {
182 return false;
183 };
184 let sig = iroh::Signature::from_bytes(sig_bytes);
185 key.verify(data, &sig).is_ok()
186 })
187 .unwrap_or(false)
188}
189
190pub fn generate_secret_key() -> Result<[u8; 32], CoreError> {
192 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
193 iroh::SecretKey::generate().to_bytes()
194 }))
195 .map_err(|_| CoreError::internal("generate_secret_key panicked"))
196}
197
198pub fn base32_encode(bytes: &[u8]) -> String {
202 base32::encode(base32::Alphabet::Rfc4648Lower { padding: false }, bytes)
203}
204
205pub(crate) fn base32_decode(s: &str) -> Result<Vec<u8>, String> {
207 base32::decode(base32::Alphabet::Rfc4648Lower { padding: false }, s)
208 .ok_or_else(|| format!("invalid base32 string: {s}"))
209}
210
211pub(crate) fn parse_node_id(s: &str) -> Result<iroh::PublicKey, CoreError> {
213 let bytes = base32_decode(s).map_err(CoreError::invalid_input)?;
214 let arr: [u8; 32] = bytes
215 .try_into()
216 .map_err(|_| CoreError::invalid_input("node-id must be 32 bytes"))?;
217 iroh::PublicKey::from_bytes(&arr).map_err(|e| CoreError::invalid_input(e.to_string()))
218}
219
220pub fn node_ticket(ep: &IrohEndpoint) -> Result<String, CoreError> {
227 let info = ep.node_addr();
228 serde_json::to_string(&info)
229 .map_err(|e| CoreError::internal(format!("failed to serialize node ticket: {e}")))
230}
231
232pub struct ParsedNodeAddr {
234 pub node_id: iroh::PublicKey,
235 pub direct_addrs: Vec<std::net::SocketAddr>,
236}
237
238pub fn parse_node_addr(s: &str) -> Result<ParsedNodeAddr, CoreError> {
246 if let Ok(info) = serde_json::from_str::<NodeAddrInfo>(s) {
247 let node_id = parse_node_id(&info.id)?;
248 let mut direct_addrs = Vec::new();
249 for addr_str in &info.addrs {
250 if addr_str.contains("://") {
252 continue;
253 }
254 let addr = addr_str
255 .parse::<std::net::SocketAddr>()
256 .map_err(|_| CoreError::invalid_input(format!("malformed address: {addr_str}")))?;
257 direct_addrs.push(addr);
258 }
259 return Ok(ParsedNodeAddr {
260 node_id,
261 direct_addrs,
262 });
263 }
264 let node_id = parse_node_id(s)?;
265 Ok(ParsedNodeAddr {
266 node_id,
267 direct_addrs: Vec::new(),
268 })
269}
270
271#[derive(Debug, Clone)]
279pub struct FfiResponse {
280 pub status: u16,
281 pub headers: Vec<(String, String)>,
282 pub body_handle: u64,
284 pub url: String,
286}
287
288#[derive(Debug)]
290pub struct RequestPayload {
291 pub req_handle: u64,
292 pub req_body_handle: u64,
293 pub res_body_handle: u64,
294 pub method: String,
295 pub url: String,
296 pub headers: Vec<(String, String)>,
297 pub remote_node_id: String,
298 pub is_bidi: bool,
299}
300
301#[derive(Debug)]
303pub struct FfiDuplexStream {
304 pub read_handle: u64,
305 pub write_handle: u64,
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn base32_round_trip() {
314 let original: Vec<u8> = (0..32).collect();
315 let encoded = base32_encode(&original);
316 let decoded = base32_decode(&encoded).unwrap();
317 assert_eq!(decoded, original);
318 }
319
320 #[test]
321 fn base32_empty() {
322 let encoded = base32_encode(&[]);
323 assert_eq!(encoded, "");
324 let decoded = base32_decode("").unwrap();
325 assert!(decoded.is_empty());
326 }
327
328 #[test]
329 fn base32_decode_invalid_char() {
330 let result = base32_decode("!!!invalid!!!");
331 assert!(result.is_err());
332 }
333
334 #[test]
335 fn parse_node_id_invalid_base32() {
336 let result = parse_node_id("!!!not-base32!!!");
337 assert!(result.is_err());
338 }
339
340 #[test]
341 fn parse_node_id_wrong_length() {
342 let result = parse_node_id("aa");
343 assert!(result.is_err());
344 }
345
346 #[test]
347 fn core_error_display() {
348 let e = CoreError::timeout("30s elapsed");
349 assert!(e.to_string().contains("Timeout"));
350 assert!(e.to_string().contains("30s elapsed"));
351 }
352}