nostr_types/types/
naddr.rs1use super::{EventKind, PublicKey, UncheckedUrl};
2use crate::Error;
3use serde::{Deserialize, Serialize};
4#[cfg(feature = "speedy")]
5use speedy::{Readable, Writable};
6use std::hash::{Hash, Hasher};
7
8#[derive(Clone, Debug, Serialize, Deserialize)]
10#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
11pub struct NAddr {
12 pub d: String,
14
15 pub relays: Vec<UncheckedUrl>,
17
18 pub kind: EventKind,
20
21 pub author: PublicKey,
23}
24
25impl NAddr {
26 pub fn as_bech32_string(&self) -> String {
28 let mut tlv: Vec<u8> = Vec::new();
30
31 tlv.push(0); let len = self.d.len() as u8;
34 tlv.push(len); tlv.extend(&self.d.as_bytes()[..len as usize]);
36
37 for relay in &self.relays {
39 tlv.push(1); let len = relay.0.len() as u8;
41 tlv.push(len); tlv.extend(&relay.0.as_bytes()[..len as usize]);
43 }
44
45 let kindnum: u32 = From::from(self.kind);
47 let bytes = kindnum.to_be_bytes();
48 tlv.push(3); tlv.push(bytes.len() as u8); tlv.extend(bytes);
51
52 tlv.push(2); tlv.push(32); tlv.extend(self.author.as_bytes());
56
57 bech32::encode::<bech32::Bech32>(*crate::HRP_NADDR, &tlv).unwrap()
58 }
59
60 pub fn try_from_bech32_string(s: &str) -> Result<NAddr, Error> {
62 let data = bech32::decode(s)?;
63 if data.0 != *crate::HRP_NADDR {
64 Err(Error::WrongBech32(
65 crate::HRP_NADDR.to_lowercase(),
66 data.0.to_lowercase(),
67 ))
68 } else {
69 let mut maybe_d: Option<String> = None;
70 let mut relays: Vec<UncheckedUrl> = Vec::new();
71 let mut maybe_kind: Option<EventKind> = None;
72 let mut maybe_author: Option<PublicKey> = None;
73
74 let tlv = data.1;
75 let mut pos = 0;
76 loop {
77 if pos > tlv.len() - 2 {
79 break;
80 }
81 let ty = tlv[pos];
82 let len = tlv[pos + 1] as usize;
83 pos += 2;
84 if pos + len > tlv.len() {
85 return Err(Error::InvalidProfile);
86 }
87 let raw = &tlv[pos..pos + len];
88 match ty {
89 0 => {
90 maybe_d = Some(std::str::from_utf8(raw)?.to_string());
92 }
93 1 => {
94 let relay_str = std::str::from_utf8(raw)?;
96 let relay = UncheckedUrl::from_str(relay_str);
97 relays.push(relay);
98 }
99 2 => {
100 if let Ok(pk) = PublicKey::from_bytes(raw, true) {
106 maybe_author = Some(pk);
107 }
108 }
109 3 => {
110 let kindnum = u32::from_be_bytes(
112 raw.try_into().map_err(|_| Error::WrongLengthKindBytes)?,
113 );
114 maybe_kind = Some(kindnum.into());
115 }
116 _ => {} }
118 pos += len;
119 }
120
121 match (maybe_d, maybe_kind, maybe_author) {
122 (Some(d), Some(kind), Some(author)) => {
123 if !kind.is_replaceable() {
124 Err(Error::NonReplaceableAddr)
125 } else {
126 Ok(NAddr {
127 d,
128 relays,
129 kind,
130 author,
131 })
132 }
133 }
134 _ => Err(Error::InvalidNAddr),
135 }
136 }
137 }
138
139 #[allow(dead_code)]
141 pub(crate) fn mock() -> NAddr {
142 let d = "Test D Indentifier 1lkjf23".to_string();
143
144 NAddr {
145 d,
146 relays: vec![
147 UncheckedUrl::from_str("wss://relay.example.com"),
148 UncheckedUrl::from_str("wss://relay2.example.com"),
149 ],
150 kind: EventKind::LongFormContent,
151 author: PublicKey::mock_deterministic(),
152 }
153 }
154}
155
156impl PartialEq for NAddr {
157 fn eq(&self, other: &Self) -> bool {
158 self.d == other.d && self.kind == other.kind && self.author == other.author
159 }
161}
162
163impl Eq for NAddr {}
164
165impl Hash for NAddr {
166 fn hash<H: Hasher>(&self, state: &mut H) {
167 self.d.hash(state);
168 self.kind.hash(state);
169 self.author.hash(state);
170 }
172}
173
174#[cfg(test)]
175mod test {
176 use super::*;
177
178 test_serde! {NAddr, test_naddr_serde}
179
180 #[test]
181 fn test_profile_bech32() {
182 let bech32 = NAddr::mock().as_bech32_string();
183 println!("{bech32}");
184 assert_eq!(
185 NAddr::mock(),
186 NAddr::try_from_bech32_string(&bech32).unwrap()
187 );
188 }
189}