git_remote_iroh/
client.rs1use std::str::FromStr;
2
3use anyhow::Context;
4use clap::Parser;
5use iroh::{Endpoint, EndpointAddr, SecretKey};
6use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, stdin};
7use tracing::debug;
8
9use crate::{GIT_IROH_ALPN, GitOp, keys, parse_iroh_url};
10
11#[derive(Parser, Debug)]
12#[command()]
13pub struct ClientArgs {
14 remote_name: String,
15 url: String,
16
17 #[arg(env = "GIT_DIR")]
18 git_dir: String,
19}
20
21pub struct Client {
22 args: ClientArgs,
23 key: SecretKey,
24}
25
26impl Client {
27 pub fn new(args: ClientArgs) -> Result<Self, anyhow::Error> {
28 let key = keys::load_or_create_key(&args.git_dir)?;
29 Ok(Self { args, key })
30 }
31
32 pub async fn run<R: AsyncRead + Unpin, W: AsyncWrite + Unpin>(
33 self,
34 input: R,
35 mut output: W,
36 ) -> Result<(), anyhow::Error> {
37 let bufr = BufReader::new(input);
38 let mut lines = bufr.lines();
39 loop {
40 let line = match lines.next_line().await? {
41 Some(line) => line,
42 None => break,
43 };
44 let command: Vec<&str> = line.split_whitespace().collect();
45 if command.is_empty() {
46 continue;
47 }
48 debug!(?command, "client");
49 match command[0] {
50 "capabilities" => self.capabilities(&mut output).await?,
51 "connect" => {
52 if command.len() < 2 {
53 anyhow::bail!("connect: missing <url>");
54 }
55 self.connect(&mut output, command[1]).await?
56 }
57 s => anyhow::bail!("unsupported command: {:?}", s),
58 }
59 output.write(b"\n").await?;
60 }
61 Ok(())
62 }
63
64 async fn capabilities<W: AsyncWrite + Unpin>(
65 &self,
66 output: &mut W,
67 ) -> Result<(), anyhow::Error> {
68 output.write(b"connect\n").await?;
69 Ok(())
70 }
71
72 async fn connect<W: AsyncWrite + Unpin>(
73 &self,
74 output: &mut W,
75 cmd: &str,
76 ) -> Result<(), anyhow::Error> {
77 let remote_node_id = parse_iroh_url(self.args.url.as_str())?;
78 let remote_op = GitOp::from_str(cmd)?;
79 let ep = Endpoint::builder(iroh::endpoint::presets::N0)
80 .secret_key(self.key.clone())
81 .bind()
82 .await?;
83
84 ep.online().await;
85
86 let addr = EndpointAddr::new(remote_node_id);
88 let conn = ep.connect(addr, GIT_IROH_ALPN).await?;
89 let (mut send, mut recv) = conn.open_bi().await?;
90
91 send.write_u8(remote_op.to_byte()).await?;
93
94 println!();
96
97 let mut stdin = stdin();
99 let (r1, r2) = tokio::join!(
100 tokio::io::copy(&mut stdin, &mut send),
101 tokio::io::copy(&mut recv, output),
102 );
103 r1.context("stdin -> send")?;
104 r2.context("recv -> stdout")?;
105 ep.close().await;
106 Ok(())
107 }
108}