use std::sync::Arc;
use alkcall::core::auth::AuthContext;
use alkcall::core::Identity;
use alkcall::core::{Connection, HandlerError, ProtocolHandler, StreamError};
use async_trait::async_trait;
use tokio::io::{AsyncRead as TokioAsyncRead, AsyncWrite as TokioAsyncWrite};
use tokio::sync::Semaphore;
use crate::backend::{GitPackGen, GitPackIngest, GitRefs, GitRegistry};
use crate::duplex::{deny_decline, deny_generic, serve_duplex_resolved};
use crate::limits::Limits;
use crate::preamble::{read_request_line, RequestLineError};
pub struct GitAdapter {
registry: Arc<dyn GitRegistry>,
refs: Arc<dyn GitRefs>,
packgen: Arc<dyn GitPackGen>,
ingest: Arc<dyn GitPackIngest>,
limits: Limits,
push_options_negotiated: bool,
pipeline: Arc<Semaphore>,
}
impl GitAdapter {
pub fn new(
registry: Arc<dyn GitRegistry>,
refs: Arc<dyn GitRefs>,
packgen: Arc<dyn GitPackGen>,
ingest: Arc<dyn GitPackIngest>,
push_options_negotiated: bool,
limits: Limits,
pipeline: Arc<Semaphore>,
) -> Self {
GitAdapter {
registry,
refs,
packgen,
ingest,
limits,
push_options_negotiated,
pipeline,
}
}
}
#[async_trait]
impl ProtocolHandler for GitAdapter {
fn alpn(&self) -> &'static [u8] {
b"alk/git"
}
async fn handle(&self, connection: Connection, auth: &AuthContext) -> Result<(), HandlerError> {
loop {
let stream = match connection.accept_bi().await {
Ok(stream) => stream,
Err(StreamError::ConnectionClosed | StreamError::StreamClosed) => break,
Err(e) => return Err(HandlerError::from(e)),
};
let session = SessionHandles {
identity: auth.identity.clone(),
registry: Arc::clone(&self.registry),
refs: Arc::clone(&self.refs),
packgen: Arc::clone(&self.packgen),
ingest: Arc::clone(&self.ingest),
push_options_negotiated: self.push_options_negotiated,
limits: self.limits,
pipeline: Arc::clone(&self.pipeline),
};
tokio::spawn(drive_native_session(stream, session));
}
Ok(())
}
}
pub(crate) struct SessionHandles {
pub(crate) identity: Option<Identity>,
pub(crate) registry: Arc<dyn GitRegistry>,
pub(crate) refs: Arc<dyn GitRefs>,
pub(crate) packgen: Arc<dyn GitPackGen>,
pub(crate) ingest: Arc<dyn GitPackIngest>,
pub(crate) push_options_negotiated: bool,
pub(crate) limits: Limits,
pub(crate) pipeline: Arc<Semaphore>,
}
pub(crate) async fn drive_native_session<T>(stream: T, handles: SessionHandles)
where
T: TokioAsyncRead + TokioAsyncWrite + Unpin + Send + 'static,
{
let SessionHandles {
identity,
registry,
refs,
packgen,
ingest,
push_options_negotiated,
limits,
pipeline,
} = handles;
let mut stream = stream;
let line = match read_request_line(&mut stream).await {
Ok(Some(line)) => line,
Ok(None) => return,
Err(RequestLineError::RepoInvalid { repo, .. }) => {
let _ = deny_generic(&repo, stream).await;
return;
}
Err(error) => {
if let Some(message) = error.decline_message() {
deny_decline(stream, &message).await;
}
return;
}
};
let _ = serve_duplex_resolved(
line.repo(),
identity.as_ref(),
line.service(),
stream,
limits,
registry,
refs,
packgen,
ingest,
push_options_negotiated,
pipeline,
)
.await;
}