1pub mod behavior;
23pub mod daemon;
24pub mod host;
25pub mod swarm;
26
27pub use behavior::FerripfsBehavior;
28pub use daemon::{Daemon, DaemonConfig, DaemonHandle};
29pub use host::{HostConfig, NetworkHost};
30pub use swarm::{SwarmBuilder, SwarmConfig};
31
32use thiserror::Error;
33
34#[derive(Debug, Error)]
36pub enum NetworkError {
37 #[error("Failed to initialize libp2p: {0}")]
38 Init(String),
39
40 #[error("Transport error: {0}")]
41 Transport(String),
42
43 #[error("Swarm error: {0}")]
44 Swarm(String),
45
46 #[error("Connection error: {0}")]
47 Connection(String),
48
49 #[error("Peer not found: {0}")]
50 PeerNotFound(String),
51
52 #[error("Invalid multiaddr: {0}")]
53 InvalidMultiaddr(String),
54
55 #[error("Invalid peer ID: {0}")]
56 InvalidPeerId(String),
57
58 #[error("Daemon error: {0}")]
59 Daemon(String),
60
61 #[error("Already running")]
62 AlreadyRunning,
63
64 #[error("Not running")]
65 NotRunning,
66
67 #[error("Config error: {0}")]
68 Config(#[from] ferripfs_config::ConfigError),
69
70 #[error("Repo error: {0}")]
71 Repo(#[from] ferripfs_repo::RepoError),
72
73 #[error("IO error: {0}")]
74 Io(#[from] std::io::Error),
75}
76
77pub type NetworkResult<T> = Result<T, NetworkError>;
79
80#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
82pub struct PeerInfo {
83 #[serde(rename = "ID")]
84 pub id: String,
85 #[serde(rename = "PublicKey")]
86 pub public_key: String,
87 #[serde(rename = "Addresses")]
88 pub addresses: Vec<String>,
89 #[serde(rename = "AgentVersion")]
90 pub agent_version: String,
91 #[serde(rename = "ProtocolVersion")]
92 pub protocol_version: String,
93 #[serde(rename = "Protocols")]
94 pub protocols: Vec<String>,
95}
96
97#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
99pub struct ConnectedPeer {
100 #[serde(rename = "Addr")]
101 pub addr: String,
102 #[serde(rename = "Peer")]
103 pub peer: String,
104 #[serde(rename = "Latency")]
105 pub latency: Option<String>,
106 #[serde(rename = "Muxer")]
107 pub muxer: Option<String>,
108 #[serde(rename = "Direction")]
109 pub direction: String,
110}
111
112#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
114pub struct PingResult {
115 #[serde(rename = "Success")]
116 pub success: bool,
117 #[serde(rename = "Time")]
118 pub time: u64,
119 #[serde(rename = "Text")]
120 pub text: String,
121}
122
123#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
125pub struct DhtProvider {
126 #[serde(rename = "ID")]
127 pub id: String,
128 #[serde(rename = "Addrs")]
129 pub addrs: Vec<String>,
130}
131
132#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
134pub struct DhtQueryResult {
135 #[serde(rename = "Type")]
136 pub result_type: String,
137 #[serde(rename = "Responses")]
138 pub responses: Vec<DhtProvider>,
139 #[serde(rename = "Extra")]
140 pub extra: Option<String>,
141 #[serde(rename = "Success")]
142 pub success: bool,
143 #[serde(rename = "Error")]
144 pub error: Option<String>,
145}
146
147impl DhtQueryResult {
148 pub fn success(result_type: &str, responses: Vec<DhtProvider>) -> Self {
150 Self {
151 result_type: result_type.to_string(),
152 responses,
153 extra: None,
154 success: true,
155 error: None,
156 }
157 }
158
159 pub fn success_with_extra(result_type: &str, extra: String) -> Self {
161 Self {
162 result_type: result_type.to_string(),
163 responses: vec![],
164 extra: Some(extra),
165 success: true,
166 error: None,
167 }
168 }
169
170 pub fn failure(result_type: &str, error: String) -> Self {
172 Self {
173 result_type: result_type.to_string(),
174 responses: vec![],
175 extra: None,
176 success: false,
177 error: Some(error),
178 }
179 }
180}
181
182#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
184pub struct DhtStats {
185 #[serde(rename = "Name")]
186 pub name: String,
187 #[serde(rename = "Buckets")]
188 pub buckets: u32,
189 #[serde(rename = "TotalPeers")]
190 pub total_peers: u32,
191 #[serde(rename = "Mode")]
192 pub mode: String,
193}
194
195#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
203pub struct BitswapStats {
204 #[serde(rename = "ProvideBufLen")]
206 pub provide_buf_len: u64,
207 #[serde(rename = "BlocksReceived")]
209 pub blocks_received: u64,
210 #[serde(rename = "DataReceived")]
212 pub data_received: u64,
213 #[serde(rename = "BlocksSent")]
215 pub blocks_sent: u64,
216 #[serde(rename = "DataSent")]
218 pub data_sent: u64,
219 #[serde(rename = "DupBlksReceived")]
221 pub dup_blks_received: u64,
222 #[serde(rename = "DupDataReceived")]
224 pub dup_data_received: u64,
225 #[serde(rename = "MessagesReceived")]
227 pub messages_received: u64,
228 #[serde(rename = "Wantlist")]
230 pub wantlist: Vec<WantlistEntry>,
231 #[serde(rename = "Peers")]
233 pub peers: Vec<String>,
234}
235
236impl BitswapStats {
237 pub fn new() -> Self {
239 Self::default()
240 }
241
242 pub fn record_block_received(&mut self, size: u64) {
244 self.blocks_received += 1;
245 self.data_received += size;
246 }
247
248 pub fn record_block_sent(&mut self, size: u64) {
250 self.blocks_sent += 1;
251 self.data_sent += size;
252 }
253
254 pub fn record_duplicate(&mut self, size: u64) {
256 self.dup_blks_received += 1;
257 self.dup_data_received += size;
258 }
259
260 pub fn record_message(&mut self) {
262 self.messages_received += 1;
263 }
264}
265
266#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
269pub struct WantlistEntry {
270 #[serde(rename = "/")]
272 pub cid: String,
273 #[serde(rename = "Priority", skip_serializing_if = "Option::is_none")]
275 pub priority: Option<i32>,
276 #[serde(rename = "WantType", skip_serializing_if = "Option::is_none")]
278 pub want_type: Option<String>,
279}
280
281impl WantlistEntry {
282 pub fn new(cid: String) -> Self {
284 Self {
285 cid,
286 priority: None,
287 want_type: None,
288 }
289 }
290
291 pub fn with_priority(cid: String, priority: i32) -> Self {
293 Self {
294 cid,
295 priority: Some(priority),
296 want_type: None,
297 }
298 }
299
300 pub fn want_block(cid: String, priority: i32) -> Self {
302 Self {
303 cid,
304 priority: Some(priority),
305 want_type: Some("Block".to_string()),
306 }
307 }
308
309 pub fn want_have(cid: String, priority: i32) -> Self {
311 Self {
312 cid,
313 priority: Some(priority),
314 want_type: Some("Have".to_string()),
315 }
316 }
317}
318
319#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
322pub struct BitswapLedger {
323 #[serde(rename = "Peer")]
325 pub peer: String,
326 #[serde(rename = "Value")]
328 pub value: f64,
329 #[serde(rename = "Sent")]
331 pub sent: u64,
332 #[serde(rename = "Recv")]
334 pub recv: u64,
335 #[serde(rename = "Exchanged")]
337 pub exchanged: u64,
338}
339
340impl BitswapLedger {
341 pub fn new(peer: String) -> Self {
343 Self {
344 peer,
345 value: 0.0,
346 sent: 0,
347 recv: 0,
348 exchanged: 0,
349 }
350 }
351
352 pub fn record_sent(&mut self, bytes: u64) {
354 self.sent += bytes;
355 self.exchanged += 1;
356 self.update_value();
357 }
358
359 pub fn record_recv(&mut self, bytes: u64) {
361 self.recv += bytes;
362 self.exchanged += 1;
363 self.update_value();
364 }
365
366 fn update_value(&mut self) {
368 if self.recv > 0 {
369 self.value = self.sent as f64 / self.recv as f64;
370 } else if self.sent > 0 {
371 self.value = f64::INFINITY;
372 } else {
373 self.value = 0.0;
374 }
375 }
376}
377
378#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
380pub struct ReprovideResult {
381 #[serde(rename = "Count")]
383 pub count: u64,
384 #[serde(rename = "Duration")]
386 pub duration_ms: u64,
387}
388
389pub const BITSWAP_PROTOCOL_VERSION: &str = "/ipfs/bitswap/1.2.0";
391
392pub const AGENT_VERSION: &str = concat!("ferripfs/", env!("CARGO_PKG_VERSION"));
394
395pub const PROTOCOL_VERSION: &str = "ipfs/0.1.0";
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
406#[derive(Default)]
407pub enum IpnsValidityType {
408 #[serde(rename = "EOL")]
410 #[default]
411 Eol = 0,
412}
413
414
415impl std::fmt::Display for IpnsValidityType {
416 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417 match self {
418 Self::Eol => write!(f, "EOL"),
419 }
420 }
421}
422
423#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
426pub struct IpnsRecord {
427 #[serde(rename = "Value")]
429 pub value: String,
430 #[serde(rename = "SignatureV2")]
432 pub signature_v2: String,
433 #[serde(rename = "ValidityType")]
435 pub validity_type: IpnsValidityType,
436 #[serde(rename = "Validity")]
438 pub validity: String,
439 #[serde(rename = "Sequence")]
441 pub sequence: u64,
442 #[serde(rename = "TTL")]
444 pub ttl: u64,
445 #[serde(rename = "PubKey", skip_serializing_if = "Option::is_none")]
447 pub pubkey: Option<String>,
448}
449
450impl IpnsRecord {
451 pub fn new(value: String, sequence: u64, validity: String, ttl: u64) -> Self {
453 Self {
454 value,
455 signature_v2: String::new(),
456 validity_type: IpnsValidityType::Eol,
457 validity,
458 sequence,
459 ttl,
460 pubkey: None,
461 }
462 }
463
464 pub fn is_expired(&self) -> bool {
466 use std::time::SystemTime;
467
468 if let Ok(validity_time) = chrono_parse_rfc3339(&self.validity) {
470 let now = SystemTime::now()
471 .duration_since(SystemTime::UNIX_EPOCH)
472 .unwrap_or_default()
473 .as_secs();
474 validity_time < now
475 } else {
476 true }
478 }
479
480 pub fn get_sequence(&self) -> u64 {
482 self.sequence
483 }
484
485 pub fn increment_sequence(&mut self) {
487 self.sequence += 1;
488 }
489}
490
491fn chrono_parse_rfc3339(s: &str) -> Result<u64, ()> {
493 if s.len() < 20 {
496 return Err(());
497 }
498
499 let parts: Vec<&str> = s.split('T').collect();
500 if parts.len() != 2 {
501 return Err(());
502 }
503
504 let date_parts: Vec<&str> = parts[0].split('-').collect();
505 if date_parts.len() != 3 {
506 return Err(());
507 }
508
509 let year: u64 = date_parts[0].parse().map_err(|_| ())?;
510 let month: u64 = date_parts[1].parse().map_err(|_| ())?;
511 let day: u64 = date_parts[2].parse().map_err(|_| ())?;
512
513 let days_since_epoch = (year - 1970) * 365 + (month - 1) * 30 + day;
515 Ok(days_since_epoch * 86400)
516}
517
518#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
521pub struct IpnsEntry {
522 #[serde(rename = "Name")]
524 pub name: String,
525 #[serde(rename = "Value")]
527 pub value: String,
528}
529
530impl IpnsEntry {
531 pub fn new(name: String, value: String) -> Self {
533 Self { name, value }
534 }
535}
536
537#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
540pub struct IpnsResolveResult {
541 #[serde(rename = "Path")]
543 pub path: String,
544}
545
546impl IpnsResolveResult {
547 pub fn new(path: String) -> Self {
549 Self { path }
550 }
551}
552
553#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
556pub struct IpnsInspectResult {
557 #[serde(rename = "Record")]
559 pub record: IpnsRecord,
560 #[serde(rename = "Validation")]
562 pub validation: IpnsValidation,
563}
564
565#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
567pub struct IpnsValidation {
568 #[serde(rename = "Valid")]
570 pub valid: bool,
571 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
573 pub error: Option<String>,
574 #[serde(rename = "Expired")]
576 pub expired: bool,
577}
578
579impl IpnsValidation {
580 pub fn valid(expired: bool) -> Self {
582 Self {
583 valid: true,
584 error: None,
585 expired,
586 }
587 }
588
589 pub fn invalid(error: String) -> Self {
591 Self {
592 valid: false,
593 error: Some(error),
594 expired: false,
595 }
596 }
597}
598
599#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
607#[derive(Default)]
608pub enum KeyType {
609 #[serde(rename = "ed25519")]
610 #[default]
611 Ed25519,
612 #[serde(rename = "rsa")]
613 Rsa,
614 #[serde(rename = "ecdsa")]
615 Ecdsa,
616 #[serde(rename = "secp256k1")]
617 Secp256k1,
618}
619
620
621impl std::fmt::Display for KeyType {
622 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
623 match self {
624 Self::Ed25519 => write!(f, "ed25519"),
625 Self::Rsa => write!(f, "rsa"),
626 Self::Ecdsa => write!(f, "ecdsa"),
627 Self::Secp256k1 => write!(f, "secp256k1"),
628 }
629 }
630}
631
632impl std::str::FromStr for KeyType {
633 type Err = String;
634
635 fn from_str(s: &str) -> Result<Self, Self::Err> {
636 match s.to_lowercase().as_str() {
637 "ed25519" => Ok(Self::Ed25519),
638 "rsa" => Ok(Self::Rsa),
639 "ecdsa" => Ok(Self::Ecdsa),
640 "secp256k1" => Ok(Self::Secp256k1),
641 _ => Err(format!("unknown key type: {}", s)),
642 }
643 }
644}
645
646#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
649pub struct KeyInfo {
650 #[serde(rename = "Name")]
652 pub name: String,
653 #[serde(rename = "Id")]
655 pub id: String,
656}
657
658impl KeyInfo {
659 pub fn new(name: String, id: String) -> Self {
661 Self { name, id }
662 }
663}
664
665#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
668pub struct KeyList {
669 #[serde(rename = "Keys")]
671 pub keys: Vec<KeyInfo>,
672}
673
674impl KeyList {
675 pub fn new(keys: Vec<KeyInfo>) -> Self {
677 Self { keys }
678 }
679
680 pub fn empty() -> Self {
682 Self { keys: Vec::new() }
683 }
684
685 pub fn add(&mut self, key: KeyInfo) {
687 self.keys.push(key);
688 }
689
690 pub fn len(&self) -> usize {
692 self.keys.len()
693 }
694
695 pub fn is_empty(&self) -> bool {
697 self.keys.is_empty()
698 }
699}
700
701#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
704pub struct KeyGenResult {
705 #[serde(rename = "Name")]
707 pub name: String,
708 #[serde(rename = "Id")]
710 pub id: String,
711}
712
713impl KeyGenResult {
714 pub fn new(name: String, id: String) -> Self {
716 Self { name, id }
717 }
718}
719
720#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
723pub struct KeyRenameResult {
724 #[serde(rename = "Was")]
726 pub was: String,
727 #[serde(rename = "Now")]
729 pub now: String,
730 #[serde(rename = "Id")]
732 pub id: String,
733 #[serde(rename = "Overwrite")]
735 pub overwrite: bool,
736}
737
738impl KeyRenameResult {
739 pub fn new(was: String, now: String, id: String, overwrite: bool) -> Self {
741 Self {
742 was,
743 now,
744 id,
745 overwrite,
746 }
747 }
748}
749
750#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
753pub struct KeyRmResult {
754 #[serde(rename = "Keys")]
756 pub keys: Vec<KeyInfo>,
757}
758
759impl KeyRmResult {
760 pub fn new(keys: Vec<KeyInfo>) -> Self {
762 Self { keys }
763 }
764
765 pub fn single(name: String, id: String) -> Self {
767 Self {
768 keys: vec![KeyInfo::new(name, id)],
769 }
770 }
771}
772
773#[derive(Debug, Clone)]
776pub struct IpnsPublishOptions {
777 pub key: String,
779 pub resolve: bool,
781 pub lifetime: String,
783 pub ttl: Option<String>,
785 pub quieter: bool,
787 pub allow_offline: bool,
789}
790
791impl Default for IpnsPublishOptions {
792 fn default() -> Self {
793 Self {
794 key: "self".to_string(),
795 resolve: true,
796 lifetime: "24h".to_string(),
797 ttl: None,
798 quieter: false,
799 allow_offline: false,
800 }
801 }
802}
803
804#[derive(Debug, Clone)]
807pub struct IpnsResolveOptions {
808 pub recursive: bool,
810 pub nocache: bool,
812 pub dht_record_count: u32,
814 pub dht_timeout: Option<String>,
816 pub stream: bool,
818}
819
820impl Default for IpnsResolveOptions {
821 fn default() -> Self {
822 Self {
823 recursive: true,
824 nocache: false,
825 dht_record_count: 16,
826 dht_timeout: None,
827 stream: false,
828 }
829 }
830}
831
832pub const IPNS_DEFAULT_TTL: u64 = 3_600_000_000_000;
834
835pub const IPNS_DEFAULT_LIFETIME: &str = "24h";
837
838pub const IPNS_PREFIX: &str = "/ipns/";
840
841pub const IPFS_PREFIX: &str = "/ipfs/";
843
844#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
887#[derive(Default)]
888pub enum MfsNodeType {
889 #[serde(rename = "file")]
891 #[default]
892 File,
893 #[serde(rename = "directory")]
895 Directory,
896}
897
898
899impl std::fmt::Display for MfsNodeType {
900 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
901 match self {
902 Self::File => write!(f, "file"),
903 Self::Directory => write!(f, "directory"),
904 }
905 }
906}
907
908#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
944pub struct MfsStatResult {
945 #[serde(rename = "Hash")]
950 pub hash: String,
951
952 #[serde(rename = "Size")]
957 pub size: u64,
958
959 #[serde(rename = "CumulativeSize")]
964 pub cumulative_size: u64,
965
966 #[serde(rename = "Blocks")]
971 pub blocks: u64,
972
973 #[serde(rename = "Type")]
975 pub node_type: MfsNodeType,
976
977 #[serde(rename = "WithLocality", skip_serializing_if = "Option::is_none")]
982 pub with_locality: Option<bool>,
983
984 #[serde(rename = "Local", skip_serializing_if = "Option::is_none")]
988 pub local: Option<bool>,
989
990 #[serde(rename = "SizeLocal", skip_serializing_if = "Option::is_none")]
994 pub size_local: Option<u64>,
995}
996
997impl MfsStatResult {
998 pub fn file(hash: String, size: u64, cumulative_size: u64, blocks: u64) -> Self {
1012 Self {
1013 hash,
1014 size,
1015 cumulative_size,
1016 blocks,
1017 node_type: MfsNodeType::File,
1018 with_locality: None,
1019 local: None,
1020 size_local: None,
1021 }
1022 }
1023
1024 pub fn directory(hash: String, size: u64, cumulative_size: u64, blocks: u64) -> Self {
1038 Self {
1039 hash,
1040 size,
1041 cumulative_size,
1042 blocks,
1043 node_type: MfsNodeType::Directory,
1044 with_locality: None,
1045 local: None,
1046 size_local: None,
1047 }
1048 }
1049}
1050
1051#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1082pub struct MfsLsEntry {
1083 #[serde(rename = "Name")]
1087 pub name: String,
1088
1089 #[serde(rename = "Type")]
1093 pub entry_type: i32,
1094
1095 #[serde(rename = "Size")]
1100 pub size: u64,
1101
1102 #[serde(rename = "Hash")]
1104 pub hash: String,
1105}
1106
1107impl MfsLsEntry {
1108 pub fn file(name: String, size: u64, hash: String) -> Self {
1116 Self {
1117 name,
1118 entry_type: 0,
1119 size,
1120 hash,
1121 }
1122 }
1123
1124 pub fn directory(name: String, size: u64, hash: String) -> Self {
1132 Self {
1133 name,
1134 entry_type: 1,
1135 size,
1136 hash,
1137 }
1138 }
1139
1140 pub fn is_file(&self) -> bool {
1142 self.entry_type == 0
1143 }
1144
1145 pub fn is_directory(&self) -> bool {
1147 self.entry_type == 1
1148 }
1149}
1150
1151#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1173pub struct MfsLsResult {
1174 #[serde(rename = "Entries")]
1178 pub entries: Vec<MfsLsEntry>,
1179}
1180
1181impl MfsLsResult {
1182 pub fn new(entries: Vec<MfsLsEntry>) -> Self {
1184 Self { entries }
1185 }
1186
1187 pub fn empty() -> Self {
1191 Self {
1192 entries: Vec::new(),
1193 }
1194 }
1195
1196 pub fn add(&mut self, entry: MfsLsEntry) {
1198 self.entries.push(entry);
1199 }
1200
1201 pub fn len(&self) -> usize {
1203 self.entries.len()
1204 }
1205
1206 pub fn is_empty(&self) -> bool {
1208 self.entries.is_empty()
1209 }
1210}
1211
1212#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1232pub struct MfsFlushResult {
1233 #[serde(rename = "Cid")]
1238 pub cid: String,
1239}
1240
1241impl MfsFlushResult {
1242 pub fn new(cid: String) -> Self {
1244 Self { cid }
1245 }
1246}
1247
1248#[derive(Debug, Clone)]
1262pub struct MfsReadResult {
1263 pub data: Vec<u8>,
1265
1266 pub offset: u64,
1268
1269 pub length: u64,
1271}
1272
1273impl MfsReadResult {
1274 pub fn new(data: Vec<u8>, offset: u64) -> Self {
1278 let length = data.len() as u64;
1279 Self {
1280 data,
1281 offset,
1282 length,
1283 }
1284 }
1285}
1286
1287#[derive(Debug, Clone)]
1311pub struct MfsWriteOptions {
1312 pub create: bool,
1316
1317 pub parents: bool,
1321
1322 pub truncate: bool,
1326
1327 pub count: Option<u64>,
1331
1332 pub offset: u64,
1336
1337 pub raw_leaves: bool,
1341
1342 pub cid_version: u8,
1344
1345 pub hash: String,
1349}
1350
1351impl Default for MfsWriteOptions {
1352 fn default() -> Self {
1353 Self {
1354 create: false,
1355 parents: false,
1356 truncate: false,
1357 count: None,
1358 offset: 0,
1359 raw_leaves: false,
1360 cid_version: 0,
1361 hash: "sha2-256".to_string(),
1362 }
1363 }
1364}
1365
1366impl MfsWriteOptions {
1367 pub fn create_new() -> Self {
1369 Self {
1370 create: true,
1371 truncate: true,
1372 ..Self::default()
1373 }
1374 }
1375
1376 pub fn append() -> Self {
1378 Self {
1379 create: true,
1380 offset: u64::MAX, ..Self::default()
1382 }
1383 }
1384}
1385
1386#[derive(Debug, Clone, Default)]
1395pub struct MfsCpOptions {
1396 pub parents: bool,
1400}
1401
1402#[derive(Debug, Clone)]
1411pub struct MfsMkdirOptions {
1412 pub parents: bool,
1416
1417 pub cid_version: u8,
1419
1420 pub hash: String,
1424}
1425
1426impl Default for MfsMkdirOptions {
1427 fn default() -> Self {
1428 Self {
1429 parents: false,
1430 cid_version: 0,
1431 hash: "sha2-256".to_string(),
1432 }
1433 }
1434}
1435
1436#[derive(Debug, Clone, Default)]
1445pub struct MfsRmOptions {
1446 pub recursive: bool,
1450
1451 pub force: bool,
1453}
1454
1455#[derive(Debug, Clone, Default)]
1464pub struct MfsMvOptions {
1465 pub flush: bool,
1467}
1468
1469#[derive(Debug, Clone)]
1479#[derive(Default)]
1480pub struct MfsChcidOptions {
1481 pub cid_version: Option<u8>,
1485
1486 pub hash: Option<String>,
1490}
1491
1492
1493#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1511pub struct MfsOpResult {
1512 #[serde(rename = "Success")]
1514 pub success: bool,
1515
1516 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
1518 pub error: Option<String>,
1519}
1520
1521impl MfsOpResult {
1522 pub fn success() -> Self {
1524 Self {
1525 success: true,
1526 error: None,
1527 }
1528 }
1529
1530 pub fn failure(error: String) -> Self {
1532 Self {
1533 success: false,
1534 error: Some(error),
1535 }
1536 }
1537}
1538
1539pub const MFS_ROOT: &str = "/";
1543
1544pub const MFS_SEPARATOR: char = '/';
1546
1547#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1583pub struct PubsubMessage {
1584 #[serde(rename = "from")]
1586 pub from: String,
1587
1588 #[serde(rename = "data", with = "base64_serde")]
1590 pub data: Vec<u8>,
1591
1592 #[serde(rename = "seqno", with = "base64_serde")]
1594 pub seqno: Vec<u8>,
1595
1596 #[serde(rename = "topicIDs")]
1598 pub topic_ids: Vec<String>,
1599
1600 #[serde(skip)]
1602 pub topic: String,
1603}
1604
1605impl PubsubMessage {
1606 pub fn new(
1616 from: String,
1617 data: Vec<u8>,
1618 seqno: Vec<u8>,
1619 topic_ids: Vec<String>,
1620 topic: String,
1621 ) -> Self {
1622 Self {
1623 from,
1624 data,
1625 seqno,
1626 topic_ids,
1627 topic,
1628 }
1629 }
1630
1631 pub fn simple(from: String, topic: String, data: Vec<u8>) -> Self {
1636 Self {
1637 from,
1638 data,
1639 seqno: Vec::new(),
1640 topic_ids: vec![topic.clone()],
1641 topic,
1642 }
1643 }
1644
1645 pub fn with_seqno(from: String, topic: String, data: Vec<u8>, seqno: Vec<u8>) -> Self {
1647 Self {
1648 from,
1649 data,
1650 seqno,
1651 topic_ids: vec![topic.clone()],
1652 topic,
1653 }
1654 }
1655
1656 pub fn data_string(&self) -> Option<String> {
1658 String::from_utf8(self.data.clone()).ok()
1659 }
1660}
1661
1662mod base64_serde {
1664 use base64::{engine::general_purpose::STANDARD, Engine};
1665 use serde::{Deserialize, Deserializer, Serializer};
1666
1667 pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
1668 where
1669 S: Serializer,
1670 {
1671 serializer.serialize_str(&STANDARD.encode(bytes))
1672 }
1673
1674 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
1675 where
1676 D: Deserializer<'de>,
1677 {
1678 let s = String::deserialize(deserializer)?;
1679 STANDARD.decode(&s).map_err(serde::de::Error::custom)
1680 }
1681}
1682
1683#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1690pub struct PubsubPeer {
1691 #[serde(rename = "ID")]
1693 pub id: String,
1694}
1695
1696impl PubsubPeer {
1697 pub fn new(id: String) -> Self {
1699 Self { id }
1700 }
1701}
1702
1703#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1721pub struct PubsubLsResult {
1722 #[serde(rename = "Strings")]
1724 pub strings: Vec<String>,
1725}
1726
1727impl PubsubLsResult {
1728 pub fn new(strings: Vec<String>) -> Self {
1730 Self { strings }
1731 }
1732
1733 pub fn empty() -> Self {
1735 Self {
1736 strings: Vec::new(),
1737 }
1738 }
1739
1740 pub fn add(&mut self, topic: String) {
1742 self.strings.push(topic);
1743 }
1744
1745 pub fn len(&self) -> usize {
1747 self.strings.len()
1748 }
1749
1750 pub fn is_empty(&self) -> bool {
1752 self.strings.is_empty()
1753 }
1754}
1755
1756#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1773pub struct PubsubPeersResult {
1774 #[serde(rename = "Strings")]
1776 pub strings: Vec<String>,
1777}
1778
1779impl PubsubPeersResult {
1780 pub fn new(peers: Vec<String>) -> Self {
1782 Self { strings: peers }
1783 }
1784
1785 pub fn empty() -> Self {
1787 Self {
1788 strings: Vec::new(),
1789 }
1790 }
1791
1792 pub fn add(&mut self, peer: PubsubPeer) {
1794 self.strings.push(peer.id);
1795 }
1796
1797 pub fn add_id(&mut self, id: String) {
1799 self.strings.push(id);
1800 }
1801
1802 pub fn len(&self) -> usize {
1804 self.strings.len()
1805 }
1806
1807 pub fn is_empty(&self) -> bool {
1809 self.strings.is_empty()
1810 }
1811}
1812
1813#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1820pub struct PubsubPublishResult {
1821 #[serde(rename = "Success")]
1823 pub success: bool,
1824
1825 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
1827 pub error: Option<String>,
1828}
1829
1830impl PubsubPublishResult {
1831 pub fn success() -> Self {
1833 Self {
1834 success: true,
1835 error: None,
1836 }
1837 }
1838
1839 pub fn failure(error: String) -> Self {
1841 Self {
1842 success: false,
1843 error: Some(error),
1844 }
1845 }
1846}
1847
1848#[derive(Debug, Clone)]
1852pub struct PubsubSubscription {
1853 pub topic: String,
1855
1856 pub active: bool,
1858}
1859
1860impl PubsubSubscription {
1861 pub fn new(topic: String) -> Self {
1863 Self {
1864 topic,
1865 active: true,
1866 }
1867 }
1868
1869 pub fn cancel(&mut self) {
1871 self.active = false;
1872 }
1873}
1874
1875#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
1882#[serde(rename_all = "lowercase")]
1883pub enum PubsubRouter {
1884 #[default]
1886 #[serde(rename = "gossipsub")]
1887 Gossipsub,
1888
1889 #[serde(rename = "floodsub")]
1891 Floodsub,
1892}
1893
1894impl std::fmt::Display for PubsubRouter {
1895 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1896 match self {
1897 Self::Gossipsub => write!(f, "gossipsub"),
1898 Self::Floodsub => write!(f, "floodsub"),
1899 }
1900 }
1901}
1902
1903impl std::str::FromStr for PubsubRouter {
1904 type Err = String;
1905
1906 fn from_str(s: &str) -> Result<Self, Self::Err> {
1907 match s.to_lowercase().as_str() {
1908 "gossipsub" => Ok(Self::Gossipsub),
1909 "floodsub" => Ok(Self::Floodsub),
1910 _ => Err(format!("unknown pubsub router: {}", s)),
1911 }
1912 }
1913}
1914
1915#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
1922pub struct PubsubStats {
1923 #[serde(rename = "NumTopics")]
1925 pub num_topics: u64,
1926
1927 #[serde(rename = "NumPeers")]
1929 pub num_peers: u64,
1930
1931 #[serde(rename = "MessagesReceived")]
1933 pub messages_received: u64,
1934
1935 #[serde(rename = "MessagesSent")]
1937 pub messages_sent: u64,
1938
1939 #[serde(rename = "BytesReceived")]
1941 pub bytes_received: u64,
1942
1943 #[serde(rename = "BytesSent")]
1945 pub bytes_sent: u64,
1946}
1947
1948impl PubsubStats {
1949 pub fn new(
1960 num_topics: u64,
1961 num_peers: u64,
1962 messages_received: u64,
1963 messages_sent: u64,
1964 bytes_received: u64,
1965 bytes_sent: u64,
1966 ) -> Self {
1967 Self {
1968 num_topics,
1969 num_peers,
1970 messages_received,
1971 messages_sent,
1972 bytes_received,
1973 bytes_sent,
1974 }
1975 }
1976
1977 pub fn empty() -> Self {
1979 Self::default()
1980 }
1981
1982 pub fn record_received(&mut self, bytes: u64) {
1984 self.messages_received += 1;
1985 self.bytes_received += bytes;
1986 }
1987
1988 pub fn record_sent(&mut self, bytes: u64) {
1990 self.messages_sent += 1;
1991 self.bytes_sent += bytes;
1992 }
1993}
1994
1995pub const PUBSUB_PROTOCOL_VERSION: &str = "/meshsub/1.1.0";
1997
1998pub const GOSSIPSUB_PROTOCOL_ID: &str = "/meshsub/1.1.0";
2000
2001pub const FLOODSUB_PROTOCOL_ID: &str = "/floodsub/1.0.0";
2003
2004pub const DEFAULT_GATEWAY_PORT: u16 = 8080;
2010
2011pub const DEFAULT_GATEWAY_ADDRESS: &str = "127.0.0.1:8080";
2013
2014pub const IPFS_PATH_PREFIX: &str = "/ipfs/";
2016
2017pub const IPNS_PATH_PREFIX: &str = "/ipns/";
2019
2020#[derive(Debug, Clone, PartialEq, Eq)]
2037pub struct GatewayPath {
2038 pub path_type: GatewayPathType,
2040 pub root: String,
2042 pub remainder: Option<String>,
2044}
2045
2046impl GatewayPath {
2047 pub fn parse(path: &str) -> Option<Self> {
2057 let path = path.trim_start_matches('/');
2058
2059 if let Some(rest) = path.strip_prefix("ipfs/") {
2060 Self::parse_with_type(rest, GatewayPathType::Ipfs)
2061 } else if let Some(rest) = path.strip_prefix("ipns/") {
2062 Self::parse_with_type(rest, GatewayPathType::Ipns)
2063 } else {
2064 None
2065 }
2066 }
2067
2068 fn parse_with_type(rest: &str, path_type: GatewayPathType) -> Option<Self> {
2069 if rest.is_empty() {
2070 return None;
2071 }
2072
2073 let parts: Vec<&str> = rest.splitn(2, '/').collect();
2074 let root = parts[0].to_string();
2075
2076 if root.is_empty() {
2077 return None;
2078 }
2079
2080 let remainder = if parts.len() > 1 && !parts[1].is_empty() {
2081 Some(parts[1].to_string())
2082 } else {
2083 None
2084 };
2085
2086 Some(Self {
2087 path_type,
2088 root,
2089 remainder,
2090 })
2091 }
2092
2093 pub fn is_ipfs(&self) -> bool {
2095 self.path_type == GatewayPathType::Ipfs
2096 }
2097
2098 pub fn is_ipns(&self) -> bool {
2100 self.path_type == GatewayPathType::Ipns
2101 }
2102
2103 pub fn root(&self) -> &str {
2105 &self.root
2106 }
2107
2108 pub fn remainder(&self) -> Option<String> {
2110 self.remainder.clone()
2111 }
2112
2113 pub fn to_path_string(&self) -> String {
2115 let prefix = match self.path_type {
2116 GatewayPathType::Ipfs => IPFS_PATH_PREFIX,
2117 GatewayPathType::Ipns => IPNS_PATH_PREFIX,
2118 };
2119 match &self.remainder {
2120 Some(r) => format!("{}{}/{}", prefix, self.root, r),
2121 None => format!("{}{}", prefix, self.root),
2122 }
2123 }
2124}
2125
2126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2128pub enum GatewayPathType {
2129 Ipfs,
2131 Ipns,
2133}
2134
2135impl std::fmt::Display for GatewayPathType {
2136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2137 match self {
2138 GatewayPathType::Ipfs => write!(f, "ipfs"),
2139 GatewayPathType::Ipns => write!(f, "ipns"),
2140 }
2141 }
2142}
2143
2144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2146pub enum GatewayMethod {
2147 Get,
2149 Head,
2151 Options,
2153}
2154
2155impl std::fmt::Display for GatewayMethod {
2156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2157 match self {
2158 GatewayMethod::Get => write!(f, "GET"),
2159 GatewayMethod::Head => write!(f, "HEAD"),
2160 GatewayMethod::Options => write!(f, "OPTIONS"),
2161 }
2162 }
2163}
2164
2165#[derive(Debug, Clone, PartialEq, Eq)]
2171pub enum GatewayContentType {
2172 Raw,
2174 Json,
2176 Cbor,
2178 Car,
2180 Html,
2182 Text,
2184 Directory,
2186 Custom(String),
2188}
2189
2190impl GatewayContentType {
2191 pub fn mime_type(&self) -> &str {
2193 match self {
2194 GatewayContentType::Raw => "application/octet-stream",
2195 GatewayContentType::Json => "application/json",
2196 GatewayContentType::Cbor => "application/cbor",
2197 GatewayContentType::Car => "application/vnd.ipld.car",
2198 GatewayContentType::Html => "text/html; charset=utf-8",
2199 GatewayContentType::Text => "text/plain; charset=utf-8",
2200 GatewayContentType::Directory => "text/html; charset=utf-8",
2201 GatewayContentType::Custom(s) => s,
2202 }
2203 }
2204
2205 pub fn from_accept(accept: &str) -> Self {
2207 let accept_lower = accept.to_lowercase();
2208 if accept_lower.contains("application/vnd.ipld.car") {
2209 GatewayContentType::Car
2210 } else if accept_lower.contains("application/vnd.ipld.raw") {
2211 GatewayContentType::Raw
2212 } else if accept_lower.contains("application/json") {
2213 GatewayContentType::Json
2214 } else if accept_lower.contains("application/cbor") {
2215 GatewayContentType::Cbor
2216 } else if accept_lower.contains("text/html") {
2217 GatewayContentType::Html
2218 } else {
2219 GatewayContentType::Raw
2220 }
2221 }
2222}
2223
2224impl std::fmt::Display for GatewayContentType {
2225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2226 write!(f, "{}", self.mime_type())
2227 }
2228}
2229
2230#[derive(Debug, Clone)]
2236pub struct GatewayRequest {
2237 pub method: GatewayMethod,
2239 pub path: GatewayPath,
2241 pub accept: Option<String>,
2243 pub range: Option<String>,
2245 pub host: Option<String>,
2247 pub query: std::collections::HashMap<String, String>,
2249}
2250
2251impl GatewayRequest {
2252 pub fn new(method: GatewayMethod, path: GatewayPath) -> Self {
2254 Self {
2255 method,
2256 path,
2257 accept: None,
2258 range: None,
2259 host: None,
2260 query: std::collections::HashMap::new(),
2261 }
2262 }
2263
2264 pub fn with_accept(mut self, accept: String) -> Self {
2266 self.accept = Some(accept);
2267 self
2268 }
2269
2270 pub fn with_range(mut self, range: String) -> Self {
2272 self.range = Some(range);
2273 self
2274 }
2275
2276 pub fn with_host(mut self, host: String) -> Self {
2278 self.host = Some(host);
2279 self
2280 }
2281
2282 pub fn content_type(&self) -> GatewayContentType {
2284 match &self.accept {
2285 Some(accept) => GatewayContentType::from_accept(accept),
2286 None => GatewayContentType::Raw,
2287 }
2288 }
2289
2290 pub fn is_subdomain_request(&self) -> bool {
2292 if let Some(host) = &self.host {
2293 host.contains(".ipfs.") || host.contains(".ipns.")
2295 } else {
2296 false
2297 }
2298 }
2299}
2300
2301#[derive(Debug, Clone)]
2307pub struct GatewayResponse {
2308 pub status: u16,
2310 pub content_type: GatewayContentType,
2312 pub body: Vec<u8>,
2314 pub headers: std::collections::HashMap<String, String>,
2316 pub content_length: Option<u64>,
2318}
2319
2320impl GatewayResponse {
2321 pub fn ok(content_type: GatewayContentType, body: Vec<u8>) -> Self {
2323 let content_length = Some(body.len() as u64);
2324 Self {
2325 status: 200,
2326 content_type,
2327 body,
2328 headers: std::collections::HashMap::new(),
2329 content_length,
2330 }
2331 }
2332
2333 pub fn not_found(message: &str) -> Self {
2335 Self {
2336 status: 404,
2337 content_type: GatewayContentType::Text,
2338 body: message.as_bytes().to_vec(),
2339 headers: std::collections::HashMap::new(),
2340 content_length: Some(message.len() as u64),
2341 }
2342 }
2343
2344 pub fn bad_request(message: &str) -> Self {
2346 Self {
2347 status: 400,
2348 content_type: GatewayContentType::Text,
2349 body: message.as_bytes().to_vec(),
2350 headers: std::collections::HashMap::new(),
2351 content_length: Some(message.len() as u64),
2352 }
2353 }
2354
2355 pub fn internal_error(message: &str) -> Self {
2357 Self {
2358 status: 500,
2359 content_type: GatewayContentType::Text,
2360 body: message.as_bytes().to_vec(),
2361 headers: std::collections::HashMap::new(),
2362 content_length: Some(message.len() as u64),
2363 }
2364 }
2365
2366 pub fn redirect(location: &str) -> Self {
2368 let mut headers = std::collections::HashMap::new();
2369 headers.insert("Location".to_string(), location.to_string());
2370 Self {
2371 status: 301,
2372 content_type: GatewayContentType::Text,
2373 body: Vec::new(),
2374 headers,
2375 content_length: Some(0),
2376 }
2377 }
2378
2379 pub fn with_header(mut self, key: &str, value: &str) -> Self {
2381 self.headers.insert(key.to_string(), value.to_string());
2382 self
2383 }
2384
2385 pub fn with_cors(self) -> Self {
2387 self.with_header("Access-Control-Allow-Origin", "*")
2388 .with_header("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
2389 .with_header(
2390 "Access-Control-Allow-Headers",
2391 "Content-Type, Range, X-Requested-With",
2392 )
2393 .with_header(
2394 "Access-Control-Expose-Headers",
2395 "Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots",
2396 )
2397 }
2398
2399 pub fn with_cache_control(self, immutable: bool) -> Self {
2401 if immutable {
2402 self.with_header("Cache-Control", "public, max-age=31536000, immutable")
2404 } else {
2405 self.with_header("Cache-Control", "public, max-age=60")
2407 }
2408 }
2409
2410 pub fn with_ipfs_headers(self, path: &GatewayPath) -> Self {
2412 self.with_header("X-Ipfs-Path", &path.to_path_string())
2413 }
2414}
2415
2416#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2422pub struct GatewayStats {
2423 #[serde(rename = "TotalRequests")]
2425 pub total_requests: u64,
2426
2427 #[serde(rename = "SuccessfulRequests")]
2429 pub successful_requests: u64,
2430
2431 #[serde(rename = "FailedRequests")]
2433 pub failed_requests: u64,
2434
2435 #[serde(rename = "BytesSent")]
2437 pub bytes_sent: u64,
2438
2439 #[serde(rename = "IpfsRequests")]
2441 pub ipfs_requests: u64,
2442
2443 #[serde(rename = "IpnsRequests")]
2445 pub ipns_requests: u64,
2446
2447 #[serde(rename = "SubdomainRequests")]
2449 pub subdomain_requests: u64,
2450}
2451
2452impl GatewayStats {
2453 pub fn new() -> Self {
2455 Self::default()
2456 }
2457
2458 pub fn record_request(&mut self, path: &GatewayPath, subdomain: bool) {
2460 self.total_requests += 1;
2461 if path.is_ipfs() {
2462 self.ipfs_requests += 1;
2463 } else {
2464 self.ipns_requests += 1;
2465 }
2466 if subdomain {
2467 self.subdomain_requests += 1;
2468 }
2469 }
2470
2471 pub fn record_success(&mut self, bytes: u64) {
2473 self.successful_requests += 1;
2474 self.bytes_sent += bytes;
2475 }
2476
2477 pub fn record_failure(&mut self) {
2479 self.failed_requests += 1;
2480 }
2481}
2482
2483#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2489pub struct GatewayDirEntry {
2490 #[serde(rename = "Name")]
2492 pub name: String,
2493
2494 #[serde(rename = "Type")]
2496 pub entry_type: GatewayEntryType,
2497
2498 #[serde(rename = "Size")]
2500 pub size: u64,
2501
2502 #[serde(rename = "Cid")]
2504 pub cid: String,
2505}
2506
2507impl GatewayDirEntry {
2508 pub fn file(name: String, size: u64, cid: String) -> Self {
2510 Self {
2511 name,
2512 entry_type: GatewayEntryType::File,
2513 size,
2514 cid,
2515 }
2516 }
2517
2518 pub fn directory(name: String, cid: String) -> Self {
2520 Self {
2521 name,
2522 entry_type: GatewayEntryType::Directory,
2523 size: 0,
2524 cid,
2525 }
2526 }
2527}
2528
2529#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2531#[serde(rename_all = "lowercase")]
2532pub enum GatewayEntryType {
2533 File,
2535 Directory,
2537}
2538
2539impl std::fmt::Display for GatewayEntryType {
2540 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2541 match self {
2542 GatewayEntryType::File => write!(f, "file"),
2543 GatewayEntryType::Directory => write!(f, "directory"),
2544 }
2545 }
2546}
2547
2548#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2554pub struct GatewayDirListing {
2555 #[serde(rename = "Path")]
2557 pub path: String,
2558
2559 #[serde(rename = "Cid")]
2561 pub cid: String,
2562
2563 #[serde(rename = "Entries")]
2565 pub entries: Vec<GatewayDirEntry>,
2566}
2567
2568impl GatewayDirListing {
2569 pub fn new(path: String, cid: String, entries: Vec<GatewayDirEntry>) -> Self {
2571 Self { path, cid, entries }
2572 }
2573
2574 pub fn to_html(&self) -> String {
2576 let mut html = String::new();
2577 html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
2578 html.push_str(&format!("<title>Index of {}</title>\n", self.path));
2579 html.push_str("<style>body{font-family:monospace;} table{border-collapse:collapse;} td,th{padding:4px 8px;text-align:left;}</style>\n");
2580 html.push_str("</head>\n<body>\n");
2581 html.push_str(&format!("<h1>Index of {}</h1>\n", self.path));
2582 html.push_str("<table>\n<tr><th>Name</th><th>Size</th><th>CID</th></tr>\n");
2583
2584 if self.path != "/" && !self.path.is_empty() {
2586 html.push_str("<tr><td><a href=\"..\">..</a></td><td>-</td><td>-</td></tr>\n");
2587 }
2588
2589 for entry in &self.entries {
2590 let link = if entry.entry_type == GatewayEntryType::Directory {
2591 format!("{}/", entry.name)
2592 } else {
2593 entry.name.clone()
2594 };
2595 let size_str = if entry.entry_type == GatewayEntryType::Directory {
2596 "-".to_string()
2597 } else {
2598 format_bytes(entry.size)
2599 };
2600 html.push_str(&format!(
2601 "<tr><td><a href=\"{}\">{}</a></td><td>{}</td><td>{}</td></tr>\n",
2602 link,
2603 entry.name,
2604 size_str,
2605 &entry.cid[..12.min(entry.cid.len())]
2606 ));
2607 }
2608
2609 html.push_str("</table>\n</body>\n</html>");
2610 html
2611 }
2612
2613 pub fn to_json(&self) -> String {
2615 serde_json::to_string_pretty(self).unwrap_or_default()
2616 }
2617}
2618
2619fn format_bytes(bytes: u64) -> String {
2621 const KB: u64 = 1024;
2622 const MB: u64 = KB * 1024;
2623 const GB: u64 = MB * 1024;
2624
2625 if bytes >= GB {
2626 format!("{:.1}G", bytes as f64 / GB as f64)
2627 } else if bytes >= MB {
2628 format!("{:.1}M", bytes as f64 / MB as f64)
2629 } else if bytes >= KB {
2630 format!("{:.1}K", bytes as f64 / KB as f64)
2631 } else {
2632 format!("{}B", bytes)
2633 }
2634}
2635
2636pub const DEFAULT_API_PORT: u16 = 5001;
2642
2643pub const DEFAULT_API_ADDRESS: &str = "127.0.0.1:5001";
2645
2646pub const API_VERSION: &str = "v0";
2648
2649#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2655pub struct ApiRequest {
2656 #[serde(rename = "Endpoint")]
2658 pub endpoint: String,
2659
2660 #[serde(rename = "Method")]
2662 pub method: String,
2663
2664 #[serde(rename = "Arguments")]
2666 pub arguments: std::collections::HashMap<String, String>,
2667
2668 #[serde(rename = "Body", skip_serializing_if = "Option::is_none")]
2670 pub body: Option<Vec<u8>>,
2671}
2672
2673impl ApiRequest {
2674 pub fn new(endpoint: &str) -> Self {
2676 Self {
2677 endpoint: endpoint.to_string(),
2678 method: "POST".to_string(),
2679 arguments: std::collections::HashMap::new(),
2680 body: None,
2681 }
2682 }
2683
2684 pub fn with_arg(mut self, key: &str, value: &str) -> Self {
2686 self.arguments.insert(key.to_string(), value.to_string());
2687 self
2688 }
2689
2690 pub fn with_body(mut self, body: Vec<u8>) -> Self {
2692 self.body = Some(body);
2693 self
2694 }
2695
2696 pub fn build_url(&self, base_url: &str) -> String {
2698 let mut url = format!("{}/api/{}{}", base_url, API_VERSION, self.endpoint);
2699 if !self.arguments.is_empty() {
2700 url.push('?');
2701 let params: Vec<String> = self
2702 .arguments
2703 .iter()
2704 .map(|(k, v)| format!("{}={}", k, urlencoding(v)))
2705 .collect();
2706 url.push_str(¶ms.join("&"));
2707 }
2708 url
2709 }
2710}
2711
2712fn urlencoding(s: &str) -> String {
2714 s.replace('%', "%25")
2715 .replace(' ', "%20")
2716 .replace('&', "%26")
2717 .replace('=', "%3D")
2718 .replace('?', "%3F")
2719 .replace('#', "%23")
2720}
2721
2722#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2728pub struct ApiResponse {
2729 #[serde(rename = "StatusCode")]
2731 pub status_code: u16,
2732
2733 #[serde(rename = "Headers")]
2735 pub headers: std::collections::HashMap<String, String>,
2736
2737 #[serde(rename = "Body")]
2739 pub body: Vec<u8>,
2740
2741 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
2743 pub error: Option<ApiError>,
2744}
2745
2746impl ApiResponse {
2747 pub fn ok(body: Vec<u8>) -> Self {
2749 Self {
2750 status_code: 200,
2751 headers: std::collections::HashMap::new(),
2752 body,
2753 error: None,
2754 }
2755 }
2756
2757 pub fn error(status_code: u16, message: &str, code: i32) -> Self {
2759 Self {
2760 status_code,
2761 headers: std::collections::HashMap::new(),
2762 body: Vec::new(),
2763 error: Some(ApiError {
2764 message: message.to_string(),
2765 code,
2766 error_type: "error".to_string(),
2767 }),
2768 }
2769 }
2770
2771 pub fn not_found(message: &str) -> Self {
2773 Self::error(404, message, 0)
2774 }
2775
2776 pub fn bad_request(message: &str) -> Self {
2778 Self::error(400, message, 0)
2779 }
2780
2781 pub fn internal_error(message: &str) -> Self {
2783 Self::error(500, message, 0)
2784 }
2785
2786 pub fn is_success(&self) -> bool {
2788 self.status_code >= 200 && self.status_code < 300
2789 }
2790
2791 pub fn body_string(&self) -> String {
2793 String::from_utf8_lossy(&self.body).to_string()
2794 }
2795
2796 pub fn body_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
2798 serde_json::from_slice(&self.body)
2799 }
2800
2801 pub fn with_header(mut self, key: &str, value: &str) -> Self {
2803 self.headers.insert(key.to_string(), value.to_string());
2804 self
2805 }
2806
2807 pub fn with_content_type(self, content_type: &str) -> Self {
2809 self.with_header("Content-Type", content_type)
2810 }
2811
2812 pub fn with_cors(self) -> Self {
2814 self.with_header("Access-Control-Allow-Origin", "*")
2815 .with_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
2816 .with_header(
2817 "Access-Control-Allow-Headers",
2818 "Content-Type, Authorization",
2819 )
2820 }
2821}
2822
2823#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2829pub struct ApiError {
2830 #[serde(rename = "Message")]
2832 pub message: String,
2833
2834 #[serde(rename = "Code")]
2836 pub code: i32,
2837
2838 #[serde(rename = "Type")]
2840 pub error_type: String,
2841}
2842
2843impl std::fmt::Display for ApiError {
2844 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2845 write!(f, "{}", self.message)
2846 }
2847}
2848
2849impl std::error::Error for ApiError {}
2850
2851#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2855pub struct ApiEndpoint {
2856 #[serde(rename = "Path")]
2858 pub path: String,
2859
2860 #[serde(rename = "Description")]
2862 pub description: String,
2863
2864 #[serde(rename = "Methods")]
2866 pub methods: Vec<String>,
2867
2868 #[serde(rename = "Arguments")]
2870 pub arguments: Vec<ApiArgument>,
2871
2872 #[serde(rename = "RequiresAuth")]
2874 pub requires_auth: bool,
2875}
2876
2877impl ApiEndpoint {
2878 pub fn new(path: &str, description: &str) -> Self {
2880 Self {
2881 path: path.to_string(),
2882 description: description.to_string(),
2883 methods: vec!["POST".to_string()],
2884 arguments: Vec::new(),
2885 requires_auth: false,
2886 }
2887 }
2888
2889 pub fn with_arg(mut self, arg: ApiArgument) -> Self {
2891 self.arguments.push(arg);
2892 self
2893 }
2894
2895 pub fn with_auth(mut self) -> Self {
2897 self.requires_auth = true;
2898 self
2899 }
2900}
2901
2902#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2904pub struct ApiArgument {
2905 #[serde(rename = "Name")]
2907 pub name: String,
2908
2909 #[serde(rename = "Description")]
2911 pub description: String,
2912
2913 #[serde(rename = "Type")]
2915 pub arg_type: String,
2916
2917 #[serde(rename = "Required")]
2919 pub required: bool,
2920
2921 #[serde(rename = "Default", skip_serializing_if = "Option::is_none")]
2923 pub default: Option<String>,
2924}
2925
2926impl ApiArgument {
2927 pub fn required(name: &str, arg_type: &str, description: &str) -> Self {
2929 Self {
2930 name: name.to_string(),
2931 description: description.to_string(),
2932 arg_type: arg_type.to_string(),
2933 required: true,
2934 default: None,
2935 }
2936 }
2937
2938 pub fn optional(name: &str, arg_type: &str, description: &str) -> Self {
2940 Self {
2941 name: name.to_string(),
2942 description: description.to_string(),
2943 arg_type: arg_type.to_string(),
2944 required: false,
2945 default: None,
2946 }
2947 }
2948
2949 pub fn with_default(mut self, default: &str) -> Self {
2951 self.default = Some(default.to_string());
2952 self
2953 }
2954}
2955
2956#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2962pub struct ApiServerConfig {
2963 #[serde(rename = "Address")]
2965 pub address: String,
2966
2967 #[serde(rename = "EnableCORS")]
2969 pub enable_cors: bool,
2970
2971 #[serde(rename = "AllowedOrigins")]
2973 pub allowed_origins: Vec<String>,
2974
2975 #[serde(rename = "Timeout")]
2977 pub timeout: u64,
2978
2979 #[serde(rename = "MaxBodySize")]
2981 pub max_body_size: u64,
2982
2983 #[serde(rename = "EnableAuth")]
2985 pub enable_auth: bool,
2986}
2987
2988impl Default for ApiServerConfig {
2989 fn default() -> Self {
2990 Self {
2991 address: DEFAULT_API_ADDRESS.to_string(),
2992 enable_cors: true,
2993 allowed_origins: vec!["*".to_string()],
2994 timeout: 60,
2995 max_body_size: 50 * 1024 * 1024, enable_auth: false,
2997 }
2998 }
2999}
3000
3001impl ApiServerConfig {
3002 pub fn new(address: &str) -> Self {
3004 Self {
3005 address: address.to_string(),
3006 ..Default::default()
3007 }
3008 }
3009}
3010
3011#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
3017pub struct ApiStats {
3018 #[serde(rename = "TotalRequests")]
3020 pub total_requests: u64,
3021
3022 #[serde(rename = "SuccessfulRequests")]
3024 pub successful_requests: u64,
3025
3026 #[serde(rename = "FailedRequests")]
3028 pub failed_requests: u64,
3029
3030 #[serde(rename = "BytesReceived")]
3032 pub bytes_received: u64,
3033
3034 #[serde(rename = "BytesSent")]
3036 pub bytes_sent: u64,
3037
3038 #[serde(rename = "ActiveConnections")]
3040 pub active_connections: u32,
3041
3042 #[serde(rename = "AvgResponseTimeMs")]
3044 pub avg_response_time_ms: f64,
3045}
3046
3047impl ApiStats {
3048 pub fn new() -> Self {
3050 Self::default()
3051 }
3052
3053 pub fn record_request(&mut self, bytes_in: u64) {
3055 self.total_requests += 1;
3056 self.bytes_received += bytes_in;
3057 }
3058
3059 pub fn record_success(&mut self, bytes_out: u64, response_time_ms: f64) {
3061 self.successful_requests += 1;
3062 self.bytes_sent += bytes_out;
3063 let total = self.successful_requests as f64;
3065 self.avg_response_time_ms =
3066 (self.avg_response_time_ms * (total - 1.0) + response_time_ms) / total;
3067 }
3068
3069 pub fn record_failure(&mut self) {
3071 self.failed_requests += 1;
3072 }
3073}
3074
3075#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3081pub struct ApiCommandsResult {
3082 #[serde(rename = "Commands")]
3084 pub commands: Vec<ApiCommandInfo>,
3085}
3086
3087#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3089pub struct ApiCommandInfo {
3090 #[serde(rename = "Name")]
3092 pub name: String,
3093
3094 #[serde(rename = "Path")]
3096 pub path: String,
3097
3098 #[serde(rename = "Subcommands", skip_serializing_if = "Vec::is_empty")]
3100 pub subcommands: Vec<ApiCommandInfo>,
3101
3102 #[serde(rename = "Options", skip_serializing_if = "Vec::is_empty")]
3104 pub options: Vec<ApiOptionInfo>,
3105}
3106
3107impl ApiCommandInfo {
3108 pub fn new(name: &str, path: &str) -> Self {
3110 Self {
3111 name: name.to_string(),
3112 path: path.to_string(),
3113 subcommands: Vec::new(),
3114 options: Vec::new(),
3115 }
3116 }
3117
3118 pub fn with_subcommand(mut self, subcmd: ApiCommandInfo) -> Self {
3120 self.subcommands.push(subcmd);
3121 self
3122 }
3123
3124 pub fn with_option(mut self, opt: ApiOptionInfo) -> Self {
3126 self.options.push(opt);
3127 self
3128 }
3129}
3130
3131#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3133pub struct ApiOptionInfo {
3134 #[serde(rename = "Name")]
3136 pub name: String,
3137
3138 #[serde(rename = "Description")]
3140 pub description: String,
3141}
3142
3143impl ApiOptionInfo {
3144 pub fn new(name: &str, description: &str) -> Self {
3146 Self {
3147 name: name.to_string(),
3148 description: description.to_string(),
3149 }
3150 }
3151}
3152
3153#[derive(Debug, Clone)]
3159pub struct ApiClient {
3160 pub base_url: String,
3162
3163 pub auth_token: Option<String>,
3165
3166 pub timeout: u64,
3168}
3169
3170impl ApiClient {
3171 pub fn new(base_url: &str) -> Self {
3173 Self {
3174 base_url: base_url.trim_end_matches('/').to_string(),
3175 auth_token: None,
3176 timeout: 60,
3177 }
3178 }
3179
3180 pub fn from_repo(repo_path: &std::path::Path) -> Option<Self> {
3182 let api_file = repo_path.join("api");
3183 if api_file.exists() {
3184 let content = std::fs::read_to_string(&api_file).ok()?;
3185 let addr = content.trim();
3186 let url = if addr.starts_with("/ip4/") || addr.starts_with("/ip6/") {
3188 multiaddr_to_url(addr)
3189 } else {
3190 addr.to_string()
3191 };
3192 Some(Self::new(&url))
3193 } else {
3194 None
3195 }
3196 }
3197
3198 pub fn with_auth(mut self, token: &str) -> Self {
3200 self.auth_token = Some(token.to_string());
3201 self
3202 }
3203
3204 pub fn with_timeout(mut self, timeout: u64) -> Self {
3206 self.timeout = timeout;
3207 self
3208 }
3209
3210 pub fn url(&self, endpoint: &str) -> String {
3212 format!("{}/api/{}{}", self.base_url, API_VERSION, endpoint)
3213 }
3214
3215 pub fn is_online(&self) -> bool {
3217 !self.base_url.is_empty()
3220 }
3221}
3222
3223fn multiaddr_to_url(addr: &str) -> String {
3225 let parts: Vec<&str> = addr.split('/').filter(|s| !s.is_empty()).collect();
3227
3228 let mut host = "127.0.0.1";
3229 let mut port = "5001";
3230
3231 let mut i = 0;
3232 while i < parts.len() {
3233 match parts[i] {
3234 "ip4" | "ip6" => {
3235 if i + 1 < parts.len() {
3236 host = parts[i + 1];
3237 i += 2;
3238 } else {
3239 i += 1;
3240 }
3241 }
3242 "tcp" => {
3243 if i + 1 < parts.len() {
3244 port = parts[i + 1];
3245 i += 2;
3246 } else {
3247 i += 1;
3248 }
3249 }
3250 _ => i += 1,
3251 }
3252 }
3253
3254 format!("http://{}:{}", host, port)
3255}
3256
3257#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3263pub struct ApiVersionResult {
3264 #[serde(rename = "Version")]
3266 pub version: String,
3267
3268 #[serde(rename = "Commit")]
3270 pub commit: String,
3271
3272 #[serde(rename = "Repo")]
3274 pub repo: String,
3275
3276 #[serde(rename = "System")]
3278 pub system: String,
3279
3280 #[serde(rename = "Golang")]
3282 pub golang: String,
3283}
3284
3285impl ApiVersionResult {
3286 pub fn ferripfs() -> Self {
3288 Self {
3289 version: "0.1.0".to_string(),
3290 commit: "unknown".to_string(),
3291 repo: "18".to_string(),
3292 system: format!("{}/{}", std::env::consts::ARCH, std::env::consts::OS),
3293 golang: "rust".to_string(),
3294 }
3295 }
3296}
3297
3298#[cfg(test)]
3299mod tests {
3300 use super::*;
3301
3302 #[test]
3303 fn test_agent_version() {
3304 assert!(AGENT_VERSION.starts_with("ferripfs/"));
3305 }
3306
3307 #[test]
3308 fn test_api_request_building() {
3309 let req = ApiRequest::new("/id").with_arg("format", "json");
3310
3311 let url = req.build_url("http://localhost:5001");
3312 assert!(url.contains("/api/v0/id"));
3313 assert!(url.contains("format=json"));
3314 }
3315
3316 #[test]
3317 fn test_api_response() {
3318 let resp = ApiResponse::ok(b"test".to_vec());
3319 assert!(resp.is_success());
3320 assert_eq!(resp.body_string(), "test");
3321
3322 let err = ApiResponse::error(500, "internal error", 1);
3323 assert!(!err.is_success());
3324 assert!(err.error.is_some());
3325 }
3326
3327 #[test]
3328 fn test_multiaddr_to_url() {
3329 assert_eq!(
3330 multiaddr_to_url("/ip4/127.0.0.1/tcp/5001"),
3331 "http://127.0.0.1:5001"
3332 );
3333 assert_eq!(
3334 multiaddr_to_url("/ip4/0.0.0.0/tcp/8080"),
3335 "http://0.0.0.0:8080"
3336 );
3337 }
3338
3339 #[test]
3340 fn test_api_stats() {
3341 let mut stats = ApiStats::new();
3342 stats.record_request(100);
3343 stats.record_success(200, 50.0);
3344 stats.record_failure();
3345
3346 assert_eq!(stats.total_requests, 1);
3347 assert_eq!(stats.successful_requests, 1);
3348 assert_eq!(stats.failed_requests, 1);
3349 assert_eq!(stats.bytes_received, 100);
3350 assert_eq!(stats.bytes_sent, 200);
3351 }
3352}