use std::{fmt::Display, fs::create_dir_all, path::PathBuf, str::FromStr};
mod auth;
mod cli;
mod client;
mod keys;
mod server;
use anyhow::Context;
pub use cli::{Args, ClientArgs};
pub use client::Client;
pub use server::Server;
use tokio::io::{AsyncWrite, AsyncWriteExt};
pub const GIT_IROH_ALPN: &[u8] = b"git/iroh/0";
fn config_dir() -> Result<PathBuf, anyhow::Error> {
let config_dir = dirs::config_local_dir()
.unwrap_or(PathBuf::from(
std::env::var("HOME").context("HOME not set")?,
))
.join("git-remote-iroh");
create_dir_all(&config_dir)?;
Ok(config_dir)
}
fn keys_dir(config_dir: &PathBuf) -> PathBuf {
config_dir.join("keys")
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GitOp {
ClientFetchServerPush,
ClientPushServerFetch,
}
impl GitOp {
pub fn resource_id<S: Display>(self, resource: S) -> Vec<u8> {
format!(
"{}:{}",
match self {
GitOp::ClientFetchServerPush => "fetch",
GitOp::ClientPushServerFetch => "push",
},
resource
)
.as_bytes()
.to_vec()
}
pub async fn write_request<W: AsyncWrite + Unpin, S: Display>(
self,
w: &mut W,
resource: S,
) -> Result<(), anyhow::Error> {
let resource_id = self.resource_id(resource);
w.write_u16_le(resource_id.len().try_into()?).await?;
w.write(resource_id.as_slice()).await?;
w.write_u8(match self {
GitOp::ClientFetchServerPush => 1u8,
GitOp::ClientPushServerFetch => 2u8,
})
.await?;
Ok(())
}
pub fn from_resource_id(resource_id: &[u8]) -> Result<GitOp, anyhow::Error> {
Ok(match resource_id.split(|c| *c == b':').next() {
Some(b"fetch") => GitOp::ClientFetchServerPush,
Some(b"push") => GitOp::ClientPushServerFetch,
_ => anyhow::bail!("invalid resource"),
})
}
pub fn to_byte(self) -> u8 {
match self {
GitOp::ClientFetchServerPush => 1,
GitOp::ClientPushServerFetch => 2,
}
}
pub fn from_byte(b: u8) -> Option<Self> {
match b {
1 => Some(GitOp::ClientFetchServerPush),
2 => Some(GitOp::ClientPushServerFetch),
_ => None,
}
}
}
impl FromStr for GitOp {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"git-upload-pack" => GitOp::ClientFetchServerPush,
"git-receive-pack" => GitOp::ClientPushServerFetch,
_ => anyhow::bail!("unsupported command {s}"),
})
}
}
impl Display for GitOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GitOp::ClientPushServerFetch => f.write_str("git-receive-pack"),
GitOp::ClientFetchServerPush => f.write_str("git-upload-pack"),
}
}
}
fn parse_iroh_url(url: &str) -> Result<iroh::PublicKey, anyhow::Error> {
let node_id_str = url.strip_prefix("iroh://").ok_or(anyhow::anyhow!(
"invalid iroh URL (expected iroh://): {}",
url
))?;
Ok(node_id_str.parse::<iroh::PublicKey>()?)
}