1pub use hashtree_network::{
4 decrement_htl_with_policy, should_forward_htl, validate_mesh_frame, HtlMode, HtlPolicy,
5 IceCandidate, MeshNostrFrame, MeshNostrPayload, PeerHTLConfig, PeerPool, PoolConfig,
6 PoolSettings, RequestDispatchConfig, SelectionStrategy, SignalingMessage, TimedSeenSet,
7 BLOB_REQUEST_POLICY, DECREMENT_AT_MAX_PROB, DECREMENT_AT_MIN_PROB, MAX_HTL, MESH_DEFAULT_HTL,
8 MESH_EVENT_POLICY, MESH_MAX_HTL, MESH_PROTOCOL, MESH_PROTOCOL_VERSION,
9};
10use serde::{Deserialize, Serialize};
11
12pub fn decrement_htl(htl: u8, config: &PeerHTLConfig) -> u8 {
14 decrement_htl_with_policy(htl, &BLOB_REQUEST_POLICY, config)
15}
16
17pub fn should_forward(htl: u8) -> bool {
19 should_forward_htl(htl)
20}
21
22pub const WEBRTC_KIND: u64 = 25050;
25
26pub const HELLO_TAG: &str = "hello";
28
29pub const WEBRTC_TAG: &str = "webrtc";
31
32pub fn generate_uuid() -> String {
34 use rand::Rng;
35 let mut rng = rand::thread_rng();
36 format!(
37 "{}{}",
38 (0..15)
39 .map(|_| char::from_digit(rng.gen_range(0..36), 36).unwrap())
40 .collect::<String>(),
41 (0..15)
42 .map(|_| char::from_digit(rng.gen_range(0..36), 36).unwrap())
43 .collect::<String>()
44 )
45}
46
47fn configured_peer_uuid() -> Option<String> {
48 std::env::var("HTREE_PEER_UUID")
49 .ok()
50 .map(|value| value.trim().to_string())
51 .filter(|value| !value.is_empty())
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
56pub struct PeerId {
57 pub pubkey: String,
58 pub uuid: String,
59}
60
61impl PeerId {
62 pub fn new(pubkey: String, uuid: Option<String>) -> Self {
63 Self {
64 pubkey,
65 uuid: uuid
66 .or_else(configured_peer_uuid)
67 .unwrap_or_else(generate_uuid),
68 }
69 }
70
71 pub fn from_string(s: &str) -> Option<Self> {
72 let parts: Vec<&str> = s.split(':').collect();
73 if parts.len() == 2 {
74 Some(Self {
75 pubkey: parts[0].to_string(),
76 uuid: parts[1].to_string(),
77 })
78 } else {
79 None
80 }
81 }
82
83 pub fn short(&self) -> String {
84 format!(
85 "{}:{}",
86 &self.pubkey[..8.min(self.pubkey.len())],
87 &self.uuid[..6.min(self.uuid.len())]
88 )
89 }
90}
91
92impl std::fmt::Display for PeerId {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 write!(f, "{}:{}", self.pubkey, self.uuid)
95 }
96}
97
98#[derive(Clone)]
100pub struct WebRTCConfig {
101 pub relays: Vec<String>,
103 pub signaling_enabled: bool,
105 pub max_outbound: usize,
107 pub max_inbound: usize,
109 pub hello_interval_ms: u64,
111 pub message_timeout_ms: u64,
113 pub stun_servers: Vec<String>,
115 pub debug: bool,
117 pub multicast: super::multicast::MulticastConfig,
119 pub wifi_aware: super::wifi_aware::WifiAwareConfig,
121 pub bluetooth: super::bluetooth::BluetoothConfig,
123 pub pools: PoolSettings,
125 pub request_selection_strategy: SelectionStrategy,
127 pub request_fairness_enabled: bool,
129 pub request_dispatch: RequestDispatchConfig,
131}
132
133impl Default for WebRTCConfig {
134 fn default() -> Self {
135 Self {
136 relays: vec![
137 "wss://relay.damus.io".to_string(),
138 "wss://relay.primal.net".to_string(),
139 "wss://temp.iris.to".to_string(),
140 "wss://relay.snort.social".to_string(),
141 ],
142 signaling_enabled: true,
143 max_outbound: 6,
144 max_inbound: 6,
145 hello_interval_ms: 3000,
146 message_timeout_ms: 15000,
147 stun_servers: vec![
148 "stun:stun.iris.to:3478".to_string(),
149 "stun:stun.l.google.com:19302".to_string(),
150 "stun:stun.cloudflare.com:3478".to_string(),
151 ],
152 debug: false,
153 multicast: super::multicast::MulticastConfig::default(),
154 wifi_aware: super::wifi_aware::WifiAwareConfig::default(),
155 bluetooth: super::bluetooth::BluetoothConfig::default(),
156 pools: PoolSettings::default(),
157 request_selection_strategy: SelectionStrategy::TitForTat,
158 request_fairness_enabled: true,
159 request_dispatch: RequestDispatchConfig {
160 initial_fanout: 2,
161 hedge_fanout: 1,
162 max_fanout: 8,
163 hedge_interval_ms: 120,
164 },
165 }
166 }
167}
168
169pub type PeerRouterConfig = WebRTCConfig;
170
171#[derive(Debug, Clone)]
173pub struct PeerStatus {
174 pub peer_id: String,
175 pub pubkey: String,
176 pub state: String,
177 pub direction: PeerDirection,
178 pub connected_at: Option<std::time::Instant>,
179 pub pool: PeerPool,
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub enum PeerDirection {
185 Inbound,
186 Outbound,
187}
188
189#[derive(Debug, Clone)]
191pub enum PeerStateEvent {
192 Connected(PeerId),
194 Failed(PeerId),
196 Disconnected(PeerId),
198}
199
200impl std::fmt::Display for PeerDirection {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 match self {
203 PeerDirection::Inbound => write!(f, "inbound"),
204 PeerDirection::Outbound => write!(f, "outbound"),
205 }
206 }
207}
208
209pub const MSG_TYPE_REQUEST: u8 = 0x00;
211pub const MSG_TYPE_RESPONSE: u8 = 0x01;
212pub const MSG_TYPE_QUOTE_REQUEST: u8 = 0x02;
213pub const MSG_TYPE_QUOTE_RESPONSE: u8 = 0x03;
214pub const MSG_TYPE_PAYMENT: u8 = 0x04;
215pub const MSG_TYPE_PAYMENT_ACK: u8 = 0x05;
216pub const MSG_TYPE_CHUNK: u8 = 0x06;
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct DataRequest {
232 #[serde(with = "serde_bytes")]
233 pub h: Vec<u8>, #[serde(default = "default_htl", skip_serializing_if = "is_max_htl")]
235 pub htl: u8,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub q: Option<u64>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct DataResponse {
242 #[serde(with = "serde_bytes")]
243 pub h: Vec<u8>, #[serde(with = "serde_bytes")]
245 pub d: Vec<u8>, }
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct DataQuoteRequest {
250 #[serde(with = "serde_bytes")]
251 pub h: Vec<u8>,
252 pub p: u64,
253 pub t: u32,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub m: Option<String>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct DataQuoteResponse {
260 #[serde(with = "serde_bytes")]
261 pub h: Vec<u8>,
262 pub a: bool,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub q: Option<u64>,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub p: Option<u64>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub t: Option<u32>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub m: Option<String>,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct DataPayment {
275 #[serde(with = "serde_bytes")]
276 pub h: Vec<u8>,
277 pub q: u64,
278 pub c: u32,
279 pub p: u64,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 pub m: Option<String>,
282 pub tok: String,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct DataPaymentAck {
287 #[serde(with = "serde_bytes")]
288 pub h: Vec<u8>,
289 pub q: u64,
290 pub c: u32,
291 pub a: bool,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub e: Option<String>,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct DataChunk {
298 #[serde(with = "serde_bytes")]
299 pub h: Vec<u8>,
300 pub q: u64,
301 pub c: u32,
302 pub n: u32,
303 pub p: u64,
304 #[serde(with = "serde_bytes")]
305 pub d: Vec<u8>,
306}
307
308#[derive(Debug, Clone)]
309pub enum DataMessage {
310 Request(DataRequest),
311 Response(DataResponse),
312 QuoteRequest(DataQuoteRequest),
313 QuoteResponse(DataQuoteResponse),
314 Payment(DataPayment),
315 PaymentAck(DataPaymentAck),
316 Chunk(DataChunk),
317}
318
319fn default_htl() -> u8 {
320 BLOB_REQUEST_POLICY.max_htl
321}
322
323fn is_max_htl(htl: &u8) -> bool {
324 *htl == BLOB_REQUEST_POLICY.max_htl
325}
326
327pub fn encode_request(req: &DataRequest) -> Result<Vec<u8>, rmp_serde::encode::Error> {
330 let body = rmp_serde::to_vec_named(req)?;
331 let mut result = Vec::with_capacity(1 + body.len());
332 result.push(MSG_TYPE_REQUEST);
333 result.extend(body);
334 Ok(result)
335}
336
337pub fn encode_response(res: &DataResponse) -> Result<Vec<u8>, rmp_serde::encode::Error> {
340 let body = rmp_serde::to_vec_named(res)?;
341 let mut result = Vec::with_capacity(1 + body.len());
342 result.push(MSG_TYPE_RESPONSE);
343 result.extend(body);
344 Ok(result)
345}
346
347pub fn encode_quote_request(req: &DataQuoteRequest) -> Result<Vec<u8>, rmp_serde::encode::Error> {
349 let body = rmp_serde::to_vec_named(req)?;
350 let mut result = Vec::with_capacity(1 + body.len());
351 result.push(MSG_TYPE_QUOTE_REQUEST);
352 result.extend(body);
353 Ok(result)
354}
355
356pub fn encode_quote_response(res: &DataQuoteResponse) -> Result<Vec<u8>, rmp_serde::encode::Error> {
358 let body = rmp_serde::to_vec_named(res)?;
359 let mut result = Vec::with_capacity(1 + body.len());
360 result.push(MSG_TYPE_QUOTE_RESPONSE);
361 result.extend(body);
362 Ok(result)
363}
364
365pub fn encode_payment(req: &DataPayment) -> Result<Vec<u8>, rmp_serde::encode::Error> {
366 let body = rmp_serde::to_vec_named(req)?;
367 let mut result = Vec::with_capacity(1 + body.len());
368 result.push(MSG_TYPE_PAYMENT);
369 result.extend(body);
370 Ok(result)
371}
372
373pub fn encode_payment_ack(res: &DataPaymentAck) -> Result<Vec<u8>, rmp_serde::encode::Error> {
374 let body = rmp_serde::to_vec_named(res)?;
375 let mut result = Vec::with_capacity(1 + body.len());
376 result.push(MSG_TYPE_PAYMENT_ACK);
377 result.extend(body);
378 Ok(result)
379}
380
381pub fn encode_chunk(chunk: &DataChunk) -> Result<Vec<u8>, rmp_serde::encode::Error> {
382 let body = rmp_serde::to_vec_named(chunk)?;
383 let mut result = Vec::with_capacity(1 + body.len());
384 result.push(MSG_TYPE_CHUNK);
385 result.extend(body);
386 Ok(result)
387}
388
389pub fn parse_message(data: &[u8]) -> Result<DataMessage, rmp_serde::decode::Error> {
391 if data.is_empty() {
392 return Err(rmp_serde::decode::Error::LengthMismatch(0));
393 }
394
395 let msg_type = data[0];
396 let body = &data[1..];
397
398 match msg_type {
399 MSG_TYPE_REQUEST => {
400 let req: DataRequest = rmp_serde::from_slice(body)?;
401 Ok(DataMessage::Request(req))
402 }
403 MSG_TYPE_RESPONSE => {
404 let res: DataResponse = rmp_serde::from_slice(body)?;
405 Ok(DataMessage::Response(res))
406 }
407 MSG_TYPE_QUOTE_REQUEST => {
408 let req: DataQuoteRequest = rmp_serde::from_slice(body)?;
409 Ok(DataMessage::QuoteRequest(req))
410 }
411 MSG_TYPE_QUOTE_RESPONSE => {
412 let res: DataQuoteResponse = rmp_serde::from_slice(body)?;
413 Ok(DataMessage::QuoteResponse(res))
414 }
415 MSG_TYPE_PAYMENT => {
416 let req: DataPayment = rmp_serde::from_slice(body)?;
417 Ok(DataMessage::Payment(req))
418 }
419 MSG_TYPE_PAYMENT_ACK => {
420 let res: DataPaymentAck = rmp_serde::from_slice(body)?;
421 Ok(DataMessage::PaymentAck(res))
422 }
423 MSG_TYPE_CHUNK => {
424 let chunk: DataChunk = rmp_serde::from_slice(body)?;
425 Ok(DataMessage::Chunk(chunk))
426 }
427 _ => Err(rmp_serde::decode::Error::LengthMismatch(msg_type as u32)),
428 }
429}
430
431pub fn hash_to_hex(hash: &[u8]) -> String {
433 hash.iter().map(|b| format!("{:02x}", b)).collect()
434}
435
436pub fn encode_message(msg: &DataMessage) -> Result<Vec<u8>, rmp_serde::encode::Error> {
438 match msg {
439 DataMessage::Request(req) => encode_request(req),
440 DataMessage::Response(res) => encode_response(res),
441 DataMessage::QuoteRequest(req) => encode_quote_request(req),
442 DataMessage::QuoteResponse(res) => encode_quote_response(res),
443 DataMessage::Payment(req) => encode_payment(req),
444 DataMessage::PaymentAck(res) => encode_payment_ack(res),
445 DataMessage::Chunk(chunk) => encode_chunk(chunk),
446 }
447}