1use anyhow::Result;
5use git_remote_htree::nostr_client::hashtree_root_kinds;
6use nostr::{nips::nip19::FromBech32, Alphabet, Event, Filter, PublicKey, SingleLetterTag};
7use std::collections::BTreeSet;
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::Arc;
11use std::time::Duration;
12use tokio::sync::RwLock;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ConnectionState {
17 Discovered,
18 Connecting,
19 Connected,
20 Failed,
21 Disconnected,
22}
23
24#[derive(Debug)]
26pub struct PeerEntry {
27 pub peer_id: PeerId,
28 pub direction: PeerDirection,
29 pub state: ConnectionState,
30 pub last_seen: std::time::Instant,
31 pub peer: Option<DummyPeer>,
32 pub pool: PeerPool,
33 pub transport: PeerTransport,
34 pub signal_paths: BTreeSet<PeerSignalPath>,
35 pub bytes_sent: u64,
36 pub bytes_received: u64,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
41pub struct PeerId {
42 pub pubkey: String,
43}
44
45impl PeerId {
46 pub fn new(pubkey: String) -> Self {
47 Self { pubkey }
48 }
49
50 pub fn short(&self) -> String {
51 self.pubkey[..8.min(self.pubkey.len())].to_string()
52 }
53}
54
55impl std::fmt::Display for PeerId {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.write_str(&self.pubkey)
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum PeerDirection {
64 Inbound,
65 Outbound,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum PeerTransport {
71 WebRtc,
72 Bluetooth,
73}
74
75impl std::fmt::Display for PeerTransport {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 PeerTransport::WebRtc => f.write_str("webrtc"),
79 PeerTransport::Bluetooth => f.write_str("bluetooth"),
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
86pub enum PeerSignalPath {
87 Relay,
88 Multicast,
89 WifiAware,
90 Bluetooth,
91}
92
93impl std::fmt::Display for PeerSignalPath {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 match self {
96 PeerSignalPath::Relay => f.write_str("relay"),
97 PeerSignalPath::Multicast => f.write_str("multicast"),
98 PeerSignalPath::WifiAware => f.write_str("wifi-aware"),
99 PeerSignalPath::Bluetooth => f.write_str("bluetooth"),
100 }
101 }
102}
103
104#[derive(Debug)]
106pub struct DummyPeer;
107
108impl DummyPeer {
109 pub fn is_ready(&self) -> bool {
110 false
111 }
112
113 pub fn has_data_channel(&self) -> bool {
114 false
115 }
116
117 pub fn state(&self) -> &str {
118 "Disabled"
119 }
120
121 pub fn as_webrtc(&self) -> Option<&Self> {
122 None
123 }
124
125 pub async fn request(&self, _hash: &str) -> Result<Option<Vec<u8>>> {
126 Ok(None)
127 }
128}
129
130#[derive(Debug, Clone, Copy)]
132pub enum PeerPool {
133 Follows,
134 Other,
135}
136
137#[derive(Debug, Clone)]
138pub struct PeerRootEvent {
139 pub hash: String,
140 pub key: Option<String>,
141 pub encrypted_key: Option<String>,
142 pub self_encrypted_key: Option<String>,
143 pub event_id: String,
144 pub created_at: u64,
145 pub peer_id: String,
146}
147
148#[derive(Debug)]
150pub struct WebRTCState {
151 pub peers: Arc<RwLock<HashMap<String, PeerEntry>>>,
152 bytes_sent: AtomicU64,
153 bytes_received: AtomicU64,
154}
155
156impl Default for WebRTCState {
157 fn default() -> Self {
158 Self {
159 peers: Arc::new(RwLock::new(HashMap::new())),
160 bytes_sent: AtomicU64::new(0),
161 bytes_received: AtomicU64::new(0),
162 }
163 }
164}
165
166impl WebRTCState {
167 pub fn new() -> Self {
168 Self::default()
169 }
170
171 pub async fn query_peers_for_data(&self, _hash: &str) -> Option<Vec<u8>> {
173 None
174 }
175
176 pub async fn request_from_peers(&self, _hash: &str) -> Option<Vec<u8>> {
178 None
179 }
180
181 pub async fn request_from_peers_with_source(&self, _hash: &str) -> Option<(Vec<u8>, String)> {
183 None
184 }
185
186 pub async fn record_sent(&self, peer_id: &str, bytes: u64) {
187 self.bytes_sent.fetch_add(bytes, Ordering::Relaxed);
188 if let Some(entry) = self.peers.write().await.get_mut(peer_id) {
189 entry.bytes_sent += bytes;
190 }
191 }
192
193 pub async fn record_received(&self, peer_id: &str, bytes: u64) {
194 self.bytes_received.fetch_add(bytes, Ordering::Relaxed);
195 if let Some(entry) = self.peers.write().await.get_mut(peer_id) {
196 entry.bytes_received += bytes;
197 }
198 }
199
200 pub fn get_bandwidth(&self) -> (u64, u64) {
202 (
203 self.bytes_sent.load(Ordering::Relaxed),
204 self.bytes_received.load(Ordering::Relaxed),
205 )
206 }
207
208 pub fn get_mesh_stats(&self) -> (u64, u64, u64) {
210 (0, 0, 0)
211 }
212
213 pub async fn resolve_root_from_peers(
215 &self,
216 _owner_pubkey: &str,
217 _tree_name: &str,
218 _per_peer_timeout: Duration,
219 ) -> Option<PeerRootEvent> {
220 None
221 }
222
223 pub async fn resolve_root_from_local_buses_with_source(
224 &self,
225 _owner_pubkey: &str,
226 _tree_name: &str,
227 _timeout: Duration,
228 ) -> Option<(&'static str, PeerRootEvent)> {
229 None
230 }
231}
232
233pub fn build_root_filter(owner_pubkey: &str, tree_name: &str) -> Option<Filter> {
234 let author = PublicKey::from_hex(owner_pubkey)
235 .or_else(|_| PublicKey::from_bech32(owner_pubkey))
236 .ok()?;
237 Some(
238 Filter::new()
239 .kinds(hashtree_root_kinds())
240 .author(author)
241 .custom_tag(
242 SingleLetterTag::lowercase(Alphabet::D),
243 tree_name.to_string(),
244 )
245 .custom_tag(SingleLetterTag::lowercase(Alphabet::L), "hashtree")
246 .limit(50),
247 )
248}
249
250pub fn pick_latest_event<'a, I>(events: I) -> Option<&'a Event>
251where
252 I: IntoIterator<Item = &'a Event>,
253{
254 events.into_iter().max_by(|a, b| {
255 let ordering = a.created_at.cmp(&b.created_at);
256 if ordering == std::cmp::Ordering::Equal {
257 b.id.cmp(&a.id)
258 } else {
259 ordering
260 }
261 })
262}
263
264pub fn root_event_from_peer(
265 event: &Event,
266 peer_id: &str,
267 tree_name: &str,
268) -> Option<PeerRootEvent> {
269 let mut tree_match = false;
270 let mut labeled = false;
271 let mut key = None;
272 let mut encrypted_key = None;
273 let mut self_encrypted_key = None;
274 let mut hash_tag = None;
275
276 for tag in event.tags.iter() {
277 let slice = tag.as_slice();
278 if slice.len() < 2 {
279 continue;
280 }
281 match slice[0].as_str() {
282 "d" => tree_match = slice[1].as_str() == tree_name,
283 "l" => labeled |= slice[1].as_str() == "hashtree",
284 "hash" => hash_tag = Some(slice[1].to_string()),
285 "key" => key = Some(slice[1].to_string()),
286 "encryptedKey" => encrypted_key = Some(slice[1].to_string()),
287 "selfEncryptedKey" => self_encrypted_key = Some(slice[1].to_string()),
288 _ => {}
289 }
290 }
291
292 if !tree_match || !labeled {
293 return None;
294 }
295
296 let hash = hash_tag.or_else(|| {
297 if event.content.is_empty() {
298 None
299 } else {
300 Some(event.content.clone())
301 }
302 })?;
303
304 Some(PeerRootEvent {
305 hash,
306 key,
307 encrypted_key,
308 self_encrypted_key,
309 event_id: event.id.to_hex(),
310 created_at: event.created_at.as_secs(),
311 peer_id: peer_id.to_string(),
312 })
313}
314
315pub trait ContentStore: Send + Sync + 'static {
317 fn get(&self, hash_hex: &str) -> Result<Option<Vec<u8>>>;
319}
320
321pub mod types {
324 pub use hashtree_network::{
325 encode_chunk, encode_payment, encode_payment_ack, encode_peer_hints, encode_pubsub_frame,
326 encode_pubsub_interest, encode_pubsub_inventory, encode_pubsub_want, encode_quote_request,
327 encode_quote_response, encode_request, encode_response, parse_message, DataChunk,
328 DataMessage, DataPayment, DataPaymentAck, DataQuoteRequest, DataQuoteResponse, DataRequest,
329 DataResponse, PeerHints, PubsubFrame, PubsubInterest, PubsubInventory, PubsubWant, MAX_HTL,
330 };
331}