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 serde::{Deserialize, Serialize};
8use std::collections::BTreeSet;
9use std::collections::HashMap;
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12use std::time::Duration;
13use tokio::sync::RwLock;
14
15/// Connection state stub
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ConnectionState {
18    Discovered,
19    Connecting,
20    Connected,
21    Failed,
22    Disconnected,
23}
24
25/// Peer entry stub
26#[derive(Debug)]
27pub struct PeerEntry {
28    pub peer_id: PeerId,
29    pub direction: PeerDirection,
30    pub state: ConnectionState,
31    pub last_seen: std::time::Instant,
32    pub peer: Option<DummyPeer>,
33    pub pool: PeerPool,
34    pub transport: PeerTransport,
35    pub signal_paths: BTreeSet<PeerSignalPath>,
36    pub bytes_sent: u64,
37    pub bytes_received: u64,
38}
39
40/// Peer ID stub
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct PeerId {
43    pub pubkey: String,
44}
45
46impl PeerId {
47    pub fn new(pubkey: String) -> Self {
48        Self { pubkey }
49    }
50
51    pub fn short(&self) -> String {
52        self.pubkey[..8.min(self.pubkey.len())].to_string()
53    }
54}
55
56impl std::fmt::Display for PeerId {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        f.write_str(&self.pubkey)
59    }
60}
61
62/// Direction of peer connection
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum PeerDirection {
65    Inbound,
66    Outbound,
67}
68
69/// Peer transport stub
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum PeerTransport {
72    WebRtc,
73    Bluetooth,
74}
75
76impl std::fmt::Display for PeerTransport {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            PeerTransport::WebRtc => f.write_str("webrtc"),
80            PeerTransport::Bluetooth => f.write_str("bluetooth"),
81        }
82    }
83}
84
85/// Signaling/discovery path stub
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
87pub enum PeerSignalPath {
88    Relay,
89    Multicast,
90    WifiAware,
91    Bluetooth,
92}
93
94impl std::fmt::Display for PeerSignalPath {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        match self {
97            PeerSignalPath::Relay => f.write_str("relay"),
98            PeerSignalPath::Multicast => f.write_str("multicast"),
99            PeerSignalPath::WifiAware => f.write_str("wifi-aware"),
100            PeerSignalPath::Bluetooth => f.write_str("bluetooth"),
101        }
102    }
103}
104
105/// Dummy peer stub
106#[derive(Debug)]
107pub struct DummyPeer;
108
109impl DummyPeer {
110    pub fn is_ready(&self) -> bool {
111        false
112    }
113
114    pub fn has_data_channel(&self) -> bool {
115        false
116    }
117
118    pub fn state(&self) -> &str {
119        "Disabled"
120    }
121
122    pub fn as_webrtc(&self) -> Option<&Self> {
123        None
124    }
125
126    pub async fn request(&self, _hash: &str) -> Result<Option<Vec<u8>>> {
127        Ok(None)
128    }
129}
130
131/// Peer pool stub
132#[derive(Debug, Clone, Copy)]
133pub enum PeerPool {
134    Follows,
135    Other,
136}
137
138#[derive(Debug, Clone)]
139pub struct PeerRootEvent {
140    pub hash: String,
141    pub key: Option<String>,
142    pub encrypted_key: Option<String>,
143    pub self_encrypted_key: Option<String>,
144    pub event_id: String,
145    pub created_at: u64,
146    pub peer_id: String,
147}
148
149/// WebRTC state stub - always empty when P2P is disabled
150#[derive(Debug)]
151pub struct WebRTCState {
152    pub peers: Arc<RwLock<HashMap<String, PeerEntry>>>,
153    bytes_sent: AtomicU64,
154    bytes_received: AtomicU64,
155}
156
157impl Default for WebRTCState {
158    fn default() -> Self {
159        Self {
160            peers: Arc::new(RwLock::new(HashMap::new())),
161            bytes_sent: AtomicU64::new(0),
162            bytes_received: AtomicU64::new(0),
163        }
164    }
165}
166
167impl WebRTCState {
168    pub fn new() -> Self {
169        Self::default()
170    }
171
172    /// Query peers for data - always returns None when P2P is disabled
173    pub async fn query_peers_for_data(&self, _hash: &str) -> Option<Vec<u8>> {
174        None
175    }
176
177    /// Request from peers - always returns None when P2P is disabled
178    pub async fn request_from_peers(&self, _hash: &str) -> Option<Vec<u8>> {
179        None
180    }
181
182    /// Request from peers with source - always returns None when P2P is disabled
183    pub async fn request_from_peers_with_source(&self, _hash: &str) -> Option<(Vec<u8>, String)> {
184        None
185    }
186
187    pub async fn record_sent(&self, peer_id: &str, bytes: u64) {
188        self.bytes_sent.fetch_add(bytes, Ordering::Relaxed);
189        if let Some(entry) = self.peers.write().await.get_mut(peer_id) {
190            entry.bytes_sent += bytes;
191        }
192    }
193
194    pub async fn record_received(&self, peer_id: &str, bytes: u64) {
195        self.bytes_received.fetch_add(bytes, Ordering::Relaxed);
196        if let Some(entry) = self.peers.write().await.get_mut(peer_id) {
197            entry.bytes_received += bytes;
198        }
199    }
200
201    /// Get bandwidth stats - always returns zeros when P2P is disabled
202    pub fn get_bandwidth(&self) -> (u64, u64) {
203        (
204            self.bytes_sent.load(Ordering::Relaxed),
205            self.bytes_received.load(Ordering::Relaxed),
206        )
207    }
208
209    /// Get mesh stats - always returns zeros when P2P is disabled
210    pub fn get_mesh_stats(&self) -> (u64, u64, u64) {
211        (0, 0, 0)
212    }
213
214    /// Resolve roots from peers - always returns None when P2P is disabled
215    pub async fn resolve_root_from_peers(
216        &self,
217        _owner_pubkey: &str,
218        _tree_name: &str,
219        _per_peer_timeout: Duration,
220    ) -> Option<PeerRootEvent> {
221        None
222    }
223
224    pub async fn resolve_root_from_local_buses_with_source(
225        &self,
226        _owner_pubkey: &str,
227        _tree_name: &str,
228        _timeout: Duration,
229    ) -> Option<(&'static str, PeerRootEvent)> {
230        None
231    }
232}
233
234pub fn build_root_filter(owner_pubkey: &str, tree_name: &str) -> Option<Filter> {
235    let author = PublicKey::from_hex(owner_pubkey)
236        .or_else(|_| PublicKey::from_bech32(owner_pubkey))
237        .ok()?;
238    Some(
239        Filter::new()
240            .kinds(hashtree_root_kinds())
241            .author(author)
242            .custom_tag(
243                SingleLetterTag::lowercase(Alphabet::D),
244                tree_name.to_string(),
245            )
246            .custom_tag(SingleLetterTag::lowercase(Alphabet::L), "hashtree")
247            .limit(50),
248    )
249}
250
251pub fn pick_latest_event<'a, I>(events: I) -> Option<&'a Event>
252where
253    I: IntoIterator<Item = &'a Event>,
254{
255    events.into_iter().max_by(|a, b| {
256        let ordering = a.created_at.cmp(&b.created_at);
257        if ordering == std::cmp::Ordering::Equal {
258            a.id.cmp(&b.id)
259        } else {
260            ordering
261        }
262    })
263}
264
265pub fn root_event_from_peer(
266    event: &Event,
267    peer_id: &str,
268    tree_name: &str,
269) -> Option<PeerRootEvent> {
270    let mut tree_match = false;
271    let mut labeled = false;
272    let mut key = None;
273    let mut encrypted_key = None;
274    let mut self_encrypted_key = None;
275    let mut hash_tag = None;
276
277    for tag in event.tags.iter() {
278        let slice = tag.as_slice();
279        if slice.len() < 2 {
280            continue;
281        }
282        match slice[0].as_str() {
283            "d" => tree_match = slice[1].as_str() == tree_name,
284            "l" => labeled |= slice[1].as_str() == "hashtree",
285            "hash" => hash_tag = Some(slice[1].to_string()),
286            "key" => key = Some(slice[1].to_string()),
287            "encryptedKey" => encrypted_key = Some(slice[1].to_string()),
288            "selfEncryptedKey" => self_encrypted_key = Some(slice[1].to_string()),
289            _ => {}
290        }
291    }
292
293    if !tree_match || !labeled {
294        return None;
295    }
296
297    let hash = hash_tag.or_else(|| {
298        if event.content.is_empty() {
299            None
300        } else {
301            Some(event.content.clone())
302        }
303    })?;
304
305    Some(PeerRootEvent {
306        hash,
307        key,
308        encrypted_key,
309        self_encrypted_key,
310        event_id: event.id.to_hex(),
311        created_at: event.created_at.as_secs(),
312        peer_id: peer_id.to_string(),
313    })
314}
315
316/// Content store trait stub
317pub trait ContentStore: Send + Sync + 'static {
318    /// Get content by hex hash
319    fn get(&self, hash_hex: &str) -> Result<Option<Vec<u8>>>;
320}
321
322pub mod types {
323    use super::*;
324
325    pub const MAX_HTL: u8 = 7;
326    pub const MSG_TYPE_REQUEST: u8 = 0x00;
327    pub const MSG_TYPE_RESPONSE: u8 = 0x01;
328    pub const MSG_TYPE_QUOTE_REQUEST: u8 = 0x02;
329    pub const MSG_TYPE_QUOTE_RESPONSE: u8 = 0x03;
330    pub const MSG_TYPE_PAYMENT: u8 = 0x04;
331    pub const MSG_TYPE_PAYMENT_ACK: u8 = 0x05;
332    pub const MSG_TYPE_CHUNK: u8 = 0x06;
333    pub const MSG_TYPE_PEER_HINTS: u8 = 0x07;
334    pub const MSG_TYPE_PUBSUB_INTEREST: u8 = 0x08;
335    pub const MSG_TYPE_PUBSUB_FRAME: u8 = 0x09;
336    pub const MSG_TYPE_PUBSUB_INVENTORY: u8 = 0x0a;
337    pub const MSG_TYPE_PUBSUB_WANT: u8 = 0x0b;
338
339    #[derive(Debug, Clone, Serialize, Deserialize)]
340    pub struct DataRequest {
341        #[serde(with = "serde_bytes")]
342        pub h: Vec<u8>,
343        #[serde(default = "default_htl")]
344        pub htl: u8,
345        #[serde(skip_serializing_if = "Option::is_none")]
346        pub q: Option<u64>,
347    }
348
349    #[derive(Debug, Clone, Serialize, Deserialize)]
350    pub struct DataResponse {
351        #[serde(with = "serde_bytes")]
352        pub h: Vec<u8>,
353        #[serde(with = "serde_bytes")]
354        pub d: Vec<u8>,
355        #[serde(skip_serializing_if = "Option::is_none")]
356        pub i: Option<u32>,
357        #[serde(skip_serializing_if = "Option::is_none")]
358        pub n: Option<u32>,
359    }
360
361    #[derive(Debug, Clone, Serialize, Deserialize)]
362    pub struct DataQuoteRequest {
363        #[serde(with = "serde_bytes")]
364        pub h: Vec<u8>,
365        pub p: u64,
366        pub t: u32,
367        #[serde(skip_serializing_if = "Option::is_none")]
368        pub m: Option<String>,
369    }
370
371    #[derive(Debug, Clone, Serialize, Deserialize)]
372    pub struct DataQuoteResponse {
373        #[serde(with = "serde_bytes")]
374        pub h: Vec<u8>,
375        pub a: bool,
376        #[serde(skip_serializing_if = "Option::is_none")]
377        pub q: Option<u64>,
378        #[serde(skip_serializing_if = "Option::is_none")]
379        pub p: Option<u64>,
380        #[serde(skip_serializing_if = "Option::is_none")]
381        pub t: Option<u32>,
382        #[serde(skip_serializing_if = "Option::is_none")]
383        pub m: Option<String>,
384    }
385
386    #[derive(Debug, Clone, Serialize, Deserialize)]
387    pub struct DataPayment {
388        #[serde(with = "serde_bytes")]
389        pub h: Vec<u8>,
390        pub q: u64,
391        pub c: u32,
392        pub p: u64,
393        #[serde(skip_serializing_if = "Option::is_none")]
394        pub m: Option<String>,
395        pub tok: String,
396    }
397
398    #[derive(Debug, Clone, Serialize, Deserialize)]
399    pub struct DataPaymentAck {
400        #[serde(with = "serde_bytes")]
401        pub h: Vec<u8>,
402        pub q: u64,
403        pub c: u32,
404        pub a: bool,
405        #[serde(skip_serializing_if = "Option::is_none")]
406        pub e: Option<String>,
407    }
408
409    #[derive(Debug, Clone, Serialize, Deserialize)]
410    pub struct DataChunk {
411        #[serde(with = "serde_bytes")]
412        pub h: Vec<u8>,
413        pub q: u64,
414        pub c: u32,
415        pub n: u32,
416        pub p: u64,
417        #[serde(with = "serde_bytes")]
418        pub d: Vec<u8>,
419    }
420
421    #[derive(Debug, Clone, Serialize, Deserialize)]
422    pub struct PeerHints {
423        #[serde(default, rename = "u")]
424        pub signal_urls: Vec<String>,
425    }
426
427    #[derive(Debug, Clone, Serialize, Deserialize)]
428    pub struct PubsubInterest {
429        #[serde(rename = "s")]
430        pub stream_id: String,
431        #[serde(rename = "sub")]
432        pub subscriber_peer_id: String,
433        #[serde(rename = "q")]
434        pub seq: u64,
435        #[serde(rename = "a")]
436        pub active: bool,
437        #[serde(default = "default_htl", skip_serializing_if = "is_max_htl")]
438        pub htl: u8,
439    }
440
441    #[derive(Debug, Clone, Serialize, Deserialize)]
442    pub struct PubsubFrame {
443        #[serde(rename = "s")]
444        pub stream_id: String,
445        #[serde(rename = "q")]
446        pub seq: u64,
447        #[serde(rename = "o")]
448        pub origin_peer_id: String,
449        #[serde(default = "default_htl", skip_serializing_if = "is_max_htl")]
450        pub htl: u8,
451        #[serde(with = "serde_bytes", rename = "d")]
452        pub payload: Vec<u8>,
453    }
454
455    #[derive(Debug, Clone, Serialize, Deserialize)]
456    pub struct PubsubInventory {
457        #[serde(rename = "s")]
458        pub stream_id: String,
459        #[serde(rename = "q")]
460        pub seq: u64,
461        #[serde(rename = "o")]
462        pub origin_peer_id: String,
463        #[serde(rename = "b")]
464        pub payload_bytes: u64,
465        #[serde(default = "default_htl", skip_serializing_if = "is_max_htl")]
466        pub htl: u8,
467    }
468
469    #[derive(Debug, Clone, Serialize, Deserialize)]
470    pub struct PubsubWant {
471        #[serde(rename = "s")]
472        pub stream_id: String,
473        #[serde(rename = "q")]
474        pub seq: u64,
475        #[serde(rename = "o")]
476        pub origin_peer_id: String,
477    }
478
479    #[derive(Debug, Clone)]
480    pub enum DataMessage {
481        Request(DataRequest),
482        Response(DataResponse),
483        QuoteRequest(DataQuoteRequest),
484        QuoteResponse(DataQuoteResponse),
485        Payment(DataPayment),
486        PaymentAck(DataPaymentAck),
487        Chunk(DataChunk),
488        PeerHints(PeerHints),
489        PubsubInterest(PubsubInterest),
490        PubsubFrame(PubsubFrame),
491        PubsubInventory(PubsubInventory),
492        PubsubWant(PubsubWant),
493    }
494
495    fn default_htl() -> u8 {
496        MAX_HTL
497    }
498
499    fn is_max_htl(htl: &u8) -> bool {
500        *htl == MAX_HTL
501    }
502
503    pub fn encode_request(req: &DataRequest) -> Result<Vec<u8>, rmp_serde::encode::Error> {
504        let body = rmp_serde::to_vec_named(req)?;
505        let mut result = Vec::with_capacity(1 + body.len());
506        result.push(MSG_TYPE_REQUEST);
507        result.extend(body);
508        Ok(result)
509    }
510
511    pub fn encode_response(res: &DataResponse) -> Result<Vec<u8>, rmp_serde::encode::Error> {
512        let body = rmp_serde::to_vec_named(res)?;
513        let mut result = Vec::with_capacity(1 + body.len());
514        result.push(MSG_TYPE_RESPONSE);
515        result.extend(body);
516        Ok(result)
517    }
518
519    pub fn encode_quote_request(
520        req: &DataQuoteRequest,
521    ) -> Result<Vec<u8>, rmp_serde::encode::Error> {
522        let body = rmp_serde::to_vec_named(req)?;
523        let mut result = Vec::with_capacity(1 + body.len());
524        result.push(MSG_TYPE_QUOTE_REQUEST);
525        result.extend(body);
526        Ok(result)
527    }
528
529    pub fn encode_quote_response(
530        res: &DataQuoteResponse,
531    ) -> Result<Vec<u8>, rmp_serde::encode::Error> {
532        let body = rmp_serde::to_vec_named(res)?;
533        let mut result = Vec::with_capacity(1 + body.len());
534        result.push(MSG_TYPE_QUOTE_RESPONSE);
535        result.extend(body);
536        Ok(result)
537    }
538
539    pub fn encode_payment(req: &DataPayment) -> Result<Vec<u8>, rmp_serde::encode::Error> {
540        let body = rmp_serde::to_vec_named(req)?;
541        let mut result = Vec::with_capacity(1 + body.len());
542        result.push(MSG_TYPE_PAYMENT);
543        result.extend(body);
544        Ok(result)
545    }
546
547    pub fn encode_payment_ack(res: &DataPaymentAck) -> Result<Vec<u8>, rmp_serde::encode::Error> {
548        let body = rmp_serde::to_vec_named(res)?;
549        let mut result = Vec::with_capacity(1 + body.len());
550        result.push(MSG_TYPE_PAYMENT_ACK);
551        result.extend(body);
552        Ok(result)
553    }
554
555    pub fn encode_chunk(chunk: &DataChunk) -> Result<Vec<u8>, rmp_serde::encode::Error> {
556        let body = rmp_serde::to_vec_named(chunk)?;
557        let mut result = Vec::with_capacity(1 + body.len());
558        result.push(MSG_TYPE_CHUNK);
559        result.extend(body);
560        Ok(result)
561    }
562
563    pub fn parse_message(data: &[u8]) -> Result<DataMessage, rmp_serde::decode::Error> {
564        if data.is_empty() {
565            return Err(rmp_serde::decode::Error::LengthMismatch(0));
566        }
567
568        match data[0] {
569            MSG_TYPE_REQUEST => Ok(DataMessage::Request(rmp_serde::from_slice(&data[1..])?)),
570            MSG_TYPE_RESPONSE => Ok(DataMessage::Response(rmp_serde::from_slice(&data[1..])?)),
571            MSG_TYPE_QUOTE_REQUEST => Ok(DataMessage::QuoteRequest(rmp_serde::from_slice(
572                &data[1..],
573            )?)),
574            MSG_TYPE_QUOTE_RESPONSE => Ok(DataMessage::QuoteResponse(rmp_serde::from_slice(
575                &data[1..],
576            )?)),
577            MSG_TYPE_PAYMENT => Ok(DataMessage::Payment(rmp_serde::from_slice(&data[1..])?)),
578            MSG_TYPE_PAYMENT_ACK => Ok(DataMessage::PaymentAck(rmp_serde::from_slice(&data[1..])?)),
579            MSG_TYPE_CHUNK => Ok(DataMessage::Chunk(rmp_serde::from_slice(&data[1..])?)),
580            MSG_TYPE_PEER_HINTS => Ok(DataMessage::PeerHints(rmp_serde::from_slice(&data[1..])?)),
581            MSG_TYPE_PUBSUB_INTEREST => Ok(DataMessage::PubsubInterest(rmp_serde::from_slice(
582                &data[1..],
583            )?)),
584            MSG_TYPE_PUBSUB_FRAME => {
585                Ok(DataMessage::PubsubFrame(rmp_serde::from_slice(&data[1..])?))
586            }
587            MSG_TYPE_PUBSUB_INVENTORY => Ok(DataMessage::PubsubInventory(rmp_serde::from_slice(
588                &data[1..],
589            )?)),
590            MSG_TYPE_PUBSUB_WANT => Ok(DataMessage::PubsubWant(rmp_serde::from_slice(&data[1..])?)),
591            other => Err(rmp_serde::decode::Error::LengthMismatch(other as u32)),
592        }
593    }
594}