1use std::net::{Ipv4Addr, ToSocketAddrs};
2use std::time::Duration;
3
4use base64::engine::general_purpose::STANDARD;
5use base64::Engine;
6
7use crate::error::{ensure, err, Error, Result};
8
9pub type Bytes = Vec<u8>;
10
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct HostPort {
13 pub host: String,
14 pub port: u16,
15}
16
17pub fn split_ws(s: &str) -> Vec<&str> {
18 s.split_whitespace().collect()
19}
20
21pub fn lower(s: impl AsRef<str>) -> String {
22 s.as_ref().to_ascii_lowercase()
23}
24
25pub fn put_u16(b: &mut Bytes, v: u16) {
26 b.extend_from_slice(&v.to_be_bytes());
27}
28
29pub fn put_u32(b: &mut Bytes, v: u32) {
30 b.extend_from_slice(&v.to_be_bytes());
31}
32
33pub fn put_u64(b: &mut Bytes, v: u64) {
34 b.extend_from_slice(&v.to_be_bytes());
35}
36
37pub fn read_u16(b: &[u8], off: usize) -> Result<u16> {
38 ensure(off + 2 <= b.len(), "short u16")?;
39 Ok(u16::from_be_bytes([b[off], b[off + 1]]))
40}
41
42pub fn read_u32(b: &[u8], off: usize) -> Result<u32> {
43 ensure(off + 4 <= b.len(), "short u32")?;
44 Ok(u32::from_be_bytes([
45 b[off],
46 b[off + 1],
47 b[off + 2],
48 b[off + 3],
49 ]))
50}
51
52pub fn from_string(s: impl AsRef<str>) -> Bytes {
53 s.as_ref().as_bytes().to_vec()
54}
55
56pub fn to_string_lossy(b: &[u8]) -> String {
57 String::from_utf8_lossy(b).into_owned()
58}
59
60pub fn hex(b: &[u8]) -> String {
61 const HEX: &[u8; 16] = b"0123456789ABCDEF";
62 let mut out = String::with_capacity(b.len() * 2);
63 for &c in b {
64 out.push(HEX[(c >> 4) as usize] as char);
65 out.push(HEX[(c & 0x0f) as usize] as char);
66 }
67 out
68}
69
70pub fn parse_hostport(s: &str, default_port: u16) -> Result<HostPort> {
71 ensure(!s.is_empty(), "empty host")?;
72 let (host, port) = if let Some(rest) = s.strip_prefix('[') {
73 let close = rest
74 .find(']')
75 .ok_or_else(|| Error::new("bad IPv6 host:port"))?;
76 let host = rest[..close].to_string();
77 let after = &rest[close + 1..];
78 let port = if after.is_empty() {
79 default_port
80 } else {
81 ensure(after.starts_with(':'), "bad IPv6 port separator")?;
82 after[1..].parse::<u16>()?
83 };
84 (host, port)
85 } else if let Some(colon) = s.rfind(':') {
86 if s[..colon].contains(':') {
87 (s.to_string(), default_port)
88 } else {
89 (s[..colon].to_string(), s[colon + 1..].parse::<u16>()?)
90 }
91 } else {
92 (s.to_string(), default_port)
93 };
94 ensure(!host.is_empty(), "missing host")?;
95 ensure(port != 0, "missing port")?;
96 Ok(HostPort { host, port })
97}
98
99pub fn base64_decode(s: &str) -> Result<Bytes> {
100 let mut cleaned: String = s.chars().filter(|c| !c.is_ascii_whitespace()).collect();
101 let padding = (4 - cleaned.len() % 4) % 4;
102 for _ in 0..padding {
103 cleaned.push('=');
104 }
105 Ok(STANDARD.decode(cleaned)?)
106}
107
108pub fn base64_encode_unpadded(b: &[u8]) -> String {
109 let mut out = STANDARD.encode(b);
110 while out.ends_with('=') {
111 out.pop();
112 }
113 out
114}
115
116pub fn base32_decode_onion(s: &str) -> Result<Bytes> {
117 let mut s = lower(s);
118 if s.len() > 6 && s.ends_with(".onion") {
119 s.truncate(s.len() - 6);
120 }
121 ensure(
122 s.len() == 56,
123 "v3 onion address must have 56 base32 characters",
124 )?;
125 let mut bits = 0;
126 let mut acc = 0u32;
127 let mut out = Bytes::new();
128 for ch in s.bytes() {
129 let v = match ch {
130 b'a'..=b'z' => ch - b'a',
131 b'2'..=b'7' => ch - b'2' + 26,
132 _ => return err("invalid base32 character in onion address"),
133 } as u32;
134 acc = (acc << 5) | v;
135 bits += 5;
136 while bits >= 8 {
137 bits -= 8;
138 out.push(((acc >> bits) & 0xff) as u8);
139 }
140 }
141 ensure(out.len() == 35, "invalid v3 onion address length")?;
142 Ok(out)
143}
144
145pub fn duration_from_timeout_ms(timeout_ms: i32) -> Result<Duration> {
146 ensure(timeout_ms >= 0, "timeout must be non-negative")?;
147 Ok(Duration::from_millis(timeout_ms as u64))
148}
149
150pub fn resolve_socket_addrs(host: &str, port: u16) -> Result<Vec<std::net::SocketAddr>> {
151 let addrs: Vec<_> = (host, port).to_socket_addrs()?.collect();
152 ensure(
153 !addrs.is_empty(),
154 format!("getaddrinfo failed for {host}: no address found"),
155 )?;
156 Ok(addrs)
157}
158
159pub fn ipv4_to_link_bytes(ip: &str, port: u16) -> Result<Bytes> {
160 let addr: Ipv4Addr = ip
161 .parse()
162 .map_err(|_| Error::new("relay has non-IPv4 address"))?;
163 let mut out = addr.octets().to_vec();
164 put_u16(&mut out, port);
165 Ok(out)
166}