1#![cfg_attr(not(feature = "std"), no_std)]
2
3extern crate alloc;
6
7use alloc::string::String;
8use core::fmt;
9
10use lwuri::prelude::*;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Error {
15 InvalidUtf8,
16 InvalidUri,
17 InvalidBase58,
18 InvalidLength { expected: usize, actual: usize },
19 BufferTooSmall,
20}
21
22impl fmt::Display for Error {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "{self:?}")
25 }
26}
27
28#[cfg(feature = "std")]
29impl std::error::Error for Error {}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
33pub enum UmshUri<'a> {
34 Node(NodeUri<'a>),
35 ChannelByName(ChannelNameUri<'a>),
36 ChannelByKey(ChannelKeyUri<'a>),
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct NodeUri<'a> {
42 pub public_key: umsh_core::PublicKey,
43 pub identity_data: Option<&'a str>,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct ChannelParams<'a> {
49 pub display_name: Option<&'a str>,
50 pub max_flood_hops: Option<u8>,
51 pub region: Option<&'a str>,
52 pub raw_query: Option<&'a str>,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct ChannelNameUri<'a> {
58 pub name: &'a str,
59 pub params: ChannelParams<'a>,
60}
61
62#[derive(Clone)]
64pub struct ChannelKeyUri<'a> {
65 pub key: umsh_core::ChannelKey,
66 pub params: ChannelParams<'a>,
67}
68
69impl core::fmt::Debug for ChannelKeyUri<'_> {
70 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71 f.debug_struct("ChannelKeyUri")
72 .field("key", &&self.key.0[..])
73 .field("params", &self.params)
74 .finish()
75 }
76}
77
78impl PartialEq for ChannelKeyUri<'_> {
79 fn eq(&self, other: &Self) -> bool {
80 self.key.0 == other.key.0 && self.params == other.params
81 }
82}
83
84impl Eq for ChannelKeyUri<'_> {}
85
86pub fn parse_umsh_uri<'a>(uri: &'a UriRef) -> Result<UmshUri<'a>, Error> {
88 if uri.scheme() != Some("umsh") {
89 return Err(Error::InvalidUri);
90 }
91
92 let mut parts = uri.raw_path().splitn(3, ':');
93 let kind = parts.next().ok_or(Error::InvalidUri)?;
94 let value = parts.next().ok_or(Error::InvalidUri)?;
95 let tail = parts.next();
96 let params = parse_channel_params(uri)?;
97
98 match kind {
99 "n" => Ok(UmshUri::Node(NodeUri {
100 public_key: umsh_core::PublicKey(decode_base58_32(value)?),
101 identity_data: tail,
102 })),
103 "cs" => Ok(UmshUri::ChannelByName(ChannelNameUri {
104 name: value,
105 params,
106 })),
107 "ck" => Ok(UmshUri::ChannelByKey(ChannelKeyUri {
108 key: umsh_core::ChannelKey(decode_base58_32(value)?),
109 params,
110 })),
111 _ => Err(Error::InvalidUri),
112 }
113}
114
115fn parse_channel_params<'a>(uri: &'a UriRef) -> Result<ChannelParams<'a>, Error> {
116 let mut display_name = None;
117 let mut max_flood_hops = None;
118 let mut region = None;
119
120 for (key, value) in uri.raw_query_key_values() {
121 match key {
122 "n" => display_name = Some(value),
123 "mh" => {
124 max_flood_hops = Some(value.parse::<u8>().map_err(|_| Error::InvalidUri)?);
125 }
126 "r" => region = Some(value),
127 _ => {}
128 }
129 }
130
131 Ok(ChannelParams {
132 display_name,
133 max_flood_hops,
134 region,
135 raw_query: uri.raw_query(),
136 })
137}
138
139fn decode_base58_32(input: &str) -> Result<[u8; 32], Error> {
140 let mut out = [0u8; 32];
141 let len = bs58::decode(input)
142 .onto(&mut out)
143 .map_err(|_| Error::InvalidBase58)?;
144 if len != 32 {
145 return Err(Error::InvalidLength {
146 expected: 32,
147 actual: len,
148 });
149 }
150 Ok(out)
151}
152
153pub fn parse_public_key_base58(input: &str) -> Result<umsh_core::PublicKey, Error> {
155 Ok(umsh_core::PublicKey(decode_base58_32(input)?))
156}
157
158pub fn parse_channel_key_base58(input: &str) -> Result<umsh_core::ChannelKey, Error> {
160 Ok(umsh_core::ChannelKey(decode_base58_32(input)?))
161}
162
163pub fn encode_public_key_base58(key: &umsh_core::PublicKey) -> String {
165 bs58::encode(key.0).into_string()
166}
167
168pub fn encode_channel_key_base58(key: &umsh_core::ChannelKey) -> String {
170 bs58::encode(key.0).into_string()
171}
172
173pub fn format_node_uri(key: &umsh_core::PublicKey, buf: &mut [u8]) -> Result<usize, Error> {
174 let mut pos = 0usize;
175 copy_into(buf, &mut pos, b"umsh:n:")?;
176 let written = bs58::encode(key.0)
177 .onto(&mut buf[pos..])
178 .map_err(|_| Error::BufferTooSmall)?;
179 Ok(pos + written)
180}
181
182pub fn format_channel_name_uri(name: &str, buf: &mut [u8]) -> Result<usize, Error> {
183 let mut pos = 0usize;
184 copy_into(buf, &mut pos, b"umsh:cs:")?;
185 copy_into(buf, &mut pos, name.as_bytes())?;
186 Ok(pos)
187}
188
189pub fn format_channel_key_uri(key: &umsh_core::ChannelKey, buf: &mut [u8]) -> Result<usize, Error> {
190 let mut pos = 0usize;
191 copy_into(buf, &mut pos, b"umsh:ck:")?;
192 let written = bs58::encode(&key.0)
193 .onto(&mut buf[pos..])
194 .map_err(|_| Error::BufferTooSmall)?;
195 Ok(pos + written)
196}
197
198pub fn format_channel_name_uri_with_params(
199 name: &str,
200 params: &ChannelParams<'_>,
201 buf: &mut [u8],
202) -> Result<usize, Error> {
203 let mut pos = format_channel_name_uri(name, buf)?;
204 write_params(params, buf, &mut pos)?;
205 Ok(pos)
206}
207
208fn write_params(params: &ChannelParams<'_>, buf: &mut [u8], pos: &mut usize) -> Result<(), Error> {
209 let mut wrote = false;
210 if let Some(display_name) = params.display_name {
211 push_byte(buf, pos, if wrote { b';' } else { b'?' })?;
212 wrote = true;
213 copy_into(buf, pos, b"n=")?;
214 copy_into(buf, pos, display_name.as_bytes())?;
215 }
216 if let Some(max_flood_hops) = params.max_flood_hops {
217 push_byte(buf, pos, if wrote { b';' } else { b'?' })?;
218 wrote = true;
219 copy_into(buf, pos, b"mh=")?;
220 let mut tmp = [0u8; 3];
221 let digits = write_decimal_u8(max_flood_hops, &mut tmp);
222 copy_into(buf, pos, &tmp[..digits])?;
223 }
224 if let Some(region) = params.region {
225 push_byte(buf, pos, if wrote { b';' } else { b'?' })?;
226 copy_into(buf, pos, b"r=")?;
227 copy_into(buf, pos, region.as_bytes())?;
228 }
229 Ok(())
230}
231
232fn write_decimal_u8(value: u8, out: &mut [u8; 3]) -> usize {
233 if value >= 100 {
234 out[0] = b'0' + value / 100;
235 out[1] = b'0' + (value / 10) % 10;
236 out[2] = b'0' + value % 10;
237 3
238 } else if value >= 10 {
239 out[0] = b'0' + value / 10;
240 out[1] = b'0' + value % 10;
241 2
242 } else {
243 out[0] = b'0' + value;
244 1
245 }
246}
247
248fn copy_into(dst: &mut [u8], pos: &mut usize, src: &[u8]) -> Result<(), Error> {
249 if dst.len().saturating_sub(*pos) < src.len() {
250 return Err(Error::BufferTooSmall);
251 }
252 dst[*pos..*pos + src.len()].copy_from_slice(src);
253 *pos += src.len();
254 Ok(())
255}
256
257fn push_byte(dst: &mut [u8], pos: &mut usize, byte: u8) -> Result<(), Error> {
258 copy_into(dst, pos, &[byte])
259}
260
261#[cfg(test)]
262mod tests {
263 use lwuri::UriRef;
264
265 use super::*;
266
267 #[test]
268 fn uri_parse_and_format_cover_node_channel_name_and_key() {
269 let key = umsh_core::PublicKey([0x33; 32]);
270 let mut buf = [0u8; 128];
271 let node_len = format_node_uri(&key, &mut buf).unwrap();
272 let node_uri = UriRef::from_str(core::str::from_utf8(&buf[..node_len]).unwrap()).unwrap();
273 match parse_umsh_uri(node_uri).unwrap() {
274 UmshUri::Node(parsed) => assert_eq!(parsed.public_key, key),
275 _ => panic!("expected node uri"),
276 }
277
278 let params = ChannelParams {
279 display_name: Some("Local"),
280 max_flood_hops: Some(6),
281 region: Some("Eugine"),
282 raw_query: None,
283 };
284 let channel_name_len =
285 format_channel_name_uri_with_params("Public", ¶ms, &mut buf).unwrap();
286 let channel_name_uri =
287 UriRef::from_str(core::str::from_utf8(&buf[..channel_name_len]).unwrap()).unwrap();
288 match parse_umsh_uri(channel_name_uri).unwrap() {
289 UmshUri::ChannelByName(parsed) => {
290 assert_eq!(parsed.name, "Public");
291 assert_eq!(parsed.params.display_name, Some("Local"));
292 assert_eq!(parsed.params.max_flood_hops, Some(6));
293 assert_eq!(parsed.params.region, Some("Eugine"));
294 }
295 _ => panic!("expected channel name uri"),
296 }
297
298 let channel_key = umsh_core::ChannelKey([0x44; 32]);
299 let channel_key_len = format_channel_key_uri(&channel_key, &mut buf).unwrap();
300 let channel_key_uri =
301 UriRef::from_str(core::str::from_utf8(&buf[..channel_key_len]).unwrap()).unwrap();
302 match parse_umsh_uri(channel_key_uri).unwrap() {
303 UmshUri::ChannelByKey(parsed) => assert_eq!(parsed.key.0, channel_key.0),
304 _ => panic!("expected channel key uri"),
305 }
306 }
307
308 #[test]
309 fn base58_key_helpers_round_trip() {
310 let public_key = umsh_core::PublicKey([0x33; 32]);
311 let channel_key = umsh_core::ChannelKey([0x77; 32]);
312
313 let public_text = encode_public_key_base58(&public_key);
314 let channel_text = encode_channel_key_base58(&channel_key);
315
316 assert_eq!(parse_public_key_base58(&public_text).unwrap(), public_key);
317 assert_eq!(
318 parse_channel_key_base58(&channel_text).unwrap(),
319 channel_key
320 );
321 }
322}