1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! 命令行参数定义。
//!
//! 本文件定义:Args, Commands, SendArgs, ReceiveArgs, CommonArgs, Format。
use anyhow::Context;
use clap::{Parser, Subcommand};
use iroh_blobs::ticket::BlobTicket;
use std::fmt::{Display, Formatter};
use std::net::{SocketAddrV4, SocketAddrV6};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::OnceLock;
use super::options::{AddrInfoOptions, RelayModeOption};
static PROCESS_SECRET: OnceLock<iroh::SecretKey> = OnceLock::new();
#[derive(Parser, Debug)]
#[command(version, about)]
pub struct Args {
#[clap(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Send a file or directory.
Send(SendArgs),
/// Receive a file or directory.
#[clap(visible_alias = "recv")]
Receive(ReceiveArgs),
}
#[derive(Parser, Debug)]
pub struct CommonArgs {
/// The IPv4 address that magicsocket will listen on.
///
/// If None, defaults to a random free port, but it can be useful to specify a fixed
/// port, e.g. to configure a firewall rule.
#[clap(long, default_value = None)]
pub magic_ipv4_addr: Option<SocketAddrV4>,
/// The IPv6 address that magicsocket will listen on.
///
/// If None, defaults to a random free port, but it can be useful to specify a fixed
/// port, e.g. to configure a firewall rule.
#[clap(long, default_value = None)]
pub magic_ipv6_addr: Option<SocketAddrV6>,
#[clap(long, default_value_t = Format::Hex)]
pub format: Format,
#[clap(short = 'v', long, action = clap::ArgAction::Count)]
pub verbose: u8,
/// Suppress progress bars.
#[clap(long, default_value_t = false)]
pub no_progress: bool,
/// The relay URL to use as a home relay,
///
/// Can be set to "disabled" to disable relay servers and "default"
/// to configure default servers.
#[clap(long, default_value_t = RelayModeOption::Default)]
pub relay: RelayModeOption,
#[clap(long)]
pub show_secret: bool,
}
#[derive(Parser, Debug)]
pub struct SendArgs {
/// Path to the file or directory to send.
///
/// The last component of the path will be used as the name of the data
/// being shared.
pub path: PathBuf,
/// What type of ticket to use.
///
/// Use "id" for the shortest type only including the node ID,
/// "addresses" to only add IP addresses without a relay url,
/// "relay" to only add a relay address, and leave the option out
/// to use the biggest type of ticket that includes both relay and
/// address information.
///
/// Generally, the more information the higher the likelyhood of
/// a successful connection, but also the bigger a ticket to connect.
///
/// This is most useful for debugging which methods of connection
/// establishment work well.
#[clap(long, default_value_t = AddrInfoOptions::RelayAndAddresses)]
pub ticket_type: AddrInfoOptions,
#[clap(flatten)]
pub common: CommonArgs,
/// Store the receive command in the clipboard.
#[cfg(feature = "clipboard")]
#[clap(short = 'c', long)]
pub clipboard: bool,
}
#[derive(Parser, Debug)]
pub struct ReceiveArgs {
/// The ticket to use to connect to the sender.
pub ticket: BlobTicket,
/// Output directory for received files.
///
/// Defaults to the current working directory when omitted.
#[clap(long)]
pub output_dir: Option<PathBuf>,
#[clap(flatten)]
pub common: CommonArgs,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Format {
#[default]
Hex,
Cid,
}
impl FromStr for Format {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"hex" => Ok(Self::Hex),
"cid" => Ok(Self::Cid),
_ => Err(anyhow::anyhow!("invalid format")),
}
}
}
impl Display for Format {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Hex => write!(f, "hex"),
Self::Cid => write!(f, "cid"),
}
}
}
pub fn print_hash(hash: &iroh_blobs::Hash, format: Format) -> String {
match format {
Format::Hex => hash.to_hex(),
Format::Cid => hash.to_string(),
}
}
pub fn get_or_create_secret() -> anyhow::Result<iroh::SecretKey> {
std::env::var("IROH_SECRET").map_or_else(
|_| Ok(PROCESS_SECRET.get_or_init(new_secret_key).clone()),
|secret| iroh::SecretKey::from_str(&secret).context("invalid secret"),
)
}
fn new_secret_key() -> iroh::SecretKey {
iroh::SecretKey::generate(&mut rand::rng())
}