Skip to main content

hashtree_cli/
webrtc_stub.rs

1//! Stub module for when P2P feature is disabled
2//! Provides minimal types to allow code to compile without webrtc dependencies
3
4use 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/// Connection state stub
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ConnectionState {
17    Discovered,
18    Connecting,
19    Connected,
20    Failed,
21    Disconnected,
22}
23
24/// Peer entry stub
25#[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/// Peer ID stub
40#[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/// Direction of peer connection
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum PeerDirection {
64    Inbound,
65    Outbound,
66}
67
68/// Peer transport stub
69#[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/// Signaling/discovery path stub
85#[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/// Dummy peer stub
105#[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/// Peer pool stub
131#[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/// WebRTC state stub - always empty when P2P is disabled
149#[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    /// Query peers for data - always returns None when P2P is disabled
172    pub async fn query_peers_for_data(&self, _hash: &str) -> Option<Vec<u8>> {
173        None
174    }
175
176    /// Request from peers - always returns None when P2P is disabled
177    pub async fn request_from_peers(&self, _hash: &str) -> Option<Vec<u8>> {
178        None
179    }
180
181    /// Request from peers with source - always returns None when P2P is disabled
182    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    /// Get bandwidth stats - always returns zeros when P2P is disabled
201    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    /// Get mesh stats - always returns zeros when P2P is disabled
209    pub fn get_mesh_stats(&self) -> (u64, u64, u64) {
210        (0, 0, 0)
211    }
212
213    /// Resolve roots from peers - always returns None when P2P is disabled
214    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
315/// Content store trait stub
316pub trait ContentStore: Send + Sync + 'static {
317    /// Get content by hex hash
318    fn get(&self, hash_hex: &str) -> Result<Option<Vec<u8>>>;
319}
320
321/// Canonical Hashtree mesh wire types. The non-WebRTC build re-exports the
322/// same codec instead of maintaining a second framing implementation.
323pub 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}