Skip to content

Commit bf402b4

Browse files
committed
feat: more scaffolding
1 parent dbfce09 commit bf402b4

File tree

5 files changed

+137
-14
lines changed

5 files changed

+137
-14
lines changed

gix-transport/src/client/async_io/connect.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,9 @@ pub(crate) mod function {
4040
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?,
4141
)
4242
}
43-
gix_url::Scheme::Ssh => Box::new(
44-
crate::client::async_io::ssh::connect(url, options.version, options.trace)
45-
.await
46-
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?,
47-
),
43+
gix_url::Scheme::Ssh => {
44+
Box::new(crate::client::async_io::ssh::connect(url, options.version, options.trace).await?)
45+
}
4846
scheme => return Err(Error::UnsupportedScheme(scheme)),
4947
})
5048
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use std::sync::Arc;
2+
3+
use russh::{
4+
client::Handle,
5+
client::{Config, Handler},
6+
};
7+
8+
pub enum AuthMode {
9+
UsernamePassword { username: String, password: String },
10+
}
11+
12+
pub struct Client {
13+
handle: Arc<Handle<ClientHandler>>,
14+
}
15+
16+
impl Client {
17+
pub(super) async fn connect(host: &str, port: u16, auth: AuthMode) -> Result<Self, super::Error> {
18+
let mut handle = russh::client::connect(Arc::new(Config::default()), (host, port), ClientHandler).await?;
19+
20+
Self::authenticate(&mut handle, auth).await?;
21+
22+
Ok(Client {
23+
handle: Arc::new(handle),
24+
})
25+
}
26+
27+
async fn authenticate(handle: &mut Handle<ClientHandler>, auth: AuthMode) -> Result<(), super::Error> {
28+
match auth {
29+
AuthMode::UsernamePassword { username, password } => {
30+
match handle.authenticate_password(username, password).await? {
31+
russh::client::AuthResult::Success => Ok(()),
32+
russh::client::AuthResult::Failure {
33+
remaining_methods,
34+
partial_success: _,
35+
} => Err(super::Error::AuthenticationFailed(remaining_methods)),
36+
}
37+
}
38+
}
39+
}
40+
}
41+
42+
struct ClientHandler;
43+
44+
impl Handler for ClientHandler {
45+
type Error = super::Error;
46+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use russh::MethodSet;
2+
3+
#[derive(thiserror::Error, Debug)]
4+
pub enum Error {
5+
#[error("The authentication method failed. Remaining methods: {0:?}")]
6+
AuthenticationFailed(MethodSet),
7+
#[error(transparent)]
8+
Ssh(#[from] russh::Error),
9+
}
Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
1-
use async_trait::async_trait;
2-
31
use crate::{
42
client::{SetServiceResponse, Transport, TransportWithoutIO},
53
Protocol, Service,
64
};
5+
use async_trait::async_trait;
76

8-
pub struct NativeSsh;
7+
mod client;
8+
mod error;
9+
10+
pub use error::Error;
11+
12+
pub struct NativeSsh {
13+
url: gix_url::Url,
14+
desired_version: Protocol,
15+
trace: bool,
16+
17+
identity: Option<gix_sec::identity::Account>,
18+
client: Option<client::Client>,
19+
connection: Option<crate::client::git::Connection<&'static [u8], Vec<u8>>>,
20+
}
921

1022
impl TransportWithoutIO for NativeSsh {
23+
fn set_identity(&mut self, identity: gix_sec::identity::Account) -> Result<(), crate::client::Error> {
24+
self.identity = Some(identity);
25+
Ok(())
26+
}
27+
1128
fn request(
1229
&mut self,
1330
write_mode: crate::client::WriteMode,
@@ -18,18 +35,18 @@ impl TransportWithoutIO for NativeSsh {
1835
}
1936

2037
fn to_url(&self) -> std::borrow::Cow<'_, bstr::BStr> {
21-
todo!()
38+
self.url.to_bstring().into()
2239
}
2340

2441
fn connection_persists_across_multiple_requests(&self) -> bool {
25-
todo!()
42+
true
2643
}
2744

2845
fn configure(
2946
&mut self,
3047
config: &dyn std::any::Any,
3148
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
32-
todo!()
49+
Ok(())
3350
}
3451
}
3552

@@ -40,14 +57,59 @@ impl Transport for NativeSsh {
4057
service: Service,
4158
extra_parameters: &'a [(&'a str, Option<&'a str>)],
4259
) -> Result<SetServiceResponse<'_>, crate::client::Error> {
43-
todo!()
60+
let host = self.url.host().expect("url has host");
61+
let port = self.url.port_or_default().expect("ssh has a default port");
62+
63+
let auth_mode = match self.identity.as_ref() {
64+
Some(crate::client::Account {
65+
username,
66+
password,
67+
oauth_refresh_token: _,
68+
}) => client::AuthMode::UsernamePassword {
69+
username: username.clone(),
70+
password: password.clone(),
71+
},
72+
None => return Err(crate::client::Error::AuthenticationUnsupported),
73+
};
74+
75+
let client = client::Client::connect(host, port, auth_mode).await?;
76+
77+
let connection = crate::client::git::Connection::new(
78+
[0u8].as_slice(), // TODO
79+
vec![], // TODO
80+
self.desired_version,
81+
self.url.path.clone(),
82+
None::<(String, _)>,
83+
crate::client::git::ConnectMode::Process,
84+
self.trace,
85+
);
86+
87+
self.client = Some(client);
88+
self.connection = Some(connection);
89+
90+
self.connection
91+
.as_mut()
92+
.expect("connection to be there right after setting it")
93+
.handshake(service, extra_parameters)
94+
.await
4495
}
4596
}
4697

98+
#[allow(clippy::unused_async)]
4799
pub async fn connect(
48100
url: gix_url::Url,
49101
desired_version: Protocol,
50102
trace: bool,
51-
) -> Result<NativeSsh, crate::client::Error> {
52-
todo!()
103+
) -> Result<NativeSsh, crate::client::connect::Error> {
104+
if url.scheme != gix_url::Scheme::Ssh {
105+
return Err(crate::client::connect::Error::UnsupportedScheme(url.scheme));
106+
}
107+
Ok(NativeSsh {
108+
url,
109+
desired_version,
110+
trace,
111+
identity: None,
112+
client: None,
113+
connection: None,
114+
})
53115
}

gix-transport/src/client/non_io_types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ mod error {
9393

9494
use bstr::BString;
9595

96+
#[cfg(feature = "russh")]
97+
use crate::client::async_io;
9698
use crate::client::capabilities;
9799
#[cfg(feature = "http-client")]
98100
use crate::client::http;
@@ -103,10 +105,14 @@ mod error {
103105
type HttpError = http::Error;
104106
#[cfg(feature = "blocking-client")]
105107
type SshInvocationError = ssh::invocation::Error;
108+
#[cfg(feature = "russh")]
109+
type NativeSshError = async_io::ssh::Error;
106110
#[cfg(not(feature = "http-client"))]
107111
type HttpError = std::convert::Infallible;
108112
#[cfg(not(feature = "blocking-client"))]
109113
type SshInvocationError = std::convert::Infallible;
114+
#[cfg(not(feature = "russh"))]
115+
type NativeSshError = std::convert::Infallible;
110116

111117
/// The error used in most methods of the [`client`][crate::client] module
112118
#[derive(thiserror::Error, Debug)]
@@ -142,6 +148,8 @@ mod error {
142148
Http(#[from] HttpError),
143149
#[error(transparent)]
144150
SshInvocation(SshInvocationError),
151+
#[error(transparent)]
152+
NativeSshError(#[from] NativeSshError),
145153
#[error("The repository path '{path}' could be mistaken for a command-line argument")]
146154
AmbiguousPath { path: BString },
147155
}

0 commit comments

Comments
 (0)