node: Use `gix_packetline`

`gix-packetline` already is in the dependency closure of `radicle-node`.
Use the well-tested and -used reader instead of ours.
This commit is contained in:
Lorenz Leutgeb 2026-02-16 02:20:55 +01:00
parent c96aea0699
commit 5fa68ed814
No known key found for this signature in database
5 changed files with 94 additions and 111 deletions

1
Cargo.lock generated
View File

@ -3133,6 +3133,7 @@ dependencies = [
"crossbeam-channel",
"cyphernet",
"fastrand",
"gix-packetline",
"lexopt",
"log",
"mio 1.0.4",

View File

@ -31,6 +31,7 @@ dunce = "1.0.5"
fastrand = { version = "2.0.0", default-features = false }
git2 = { version = "0.19.0", default-features = false, features = ["vendored-libgit2"] }
gix-hash = { version = "0.22.1", default-features = false, features = ["sha1"] }
gix-packetline = { version = "0.21.1", default-features = false }
human-panic = "2.0.6"
itertools = "0.14"
lexopt = "0.3.0"

View File

@ -23,6 +23,7 @@ colored = { workspace = true }
crossbeam-channel = { workspace = true }
cyphernet = { workspace = true, features = ["tor", "dns", "ed25519", "p2p-ed25519", "noise-framework", "noise_sha2"] }
fastrand = { workspace = true }
gix-packetline = { workspace = true, features = ["blocking-io"] }
lexopt = { workspace = true }
log = { workspace = true, features = ["kv", "std"] }
mio = { version = "1", features = ["net", "os-poll"] }

View File

@ -141,15 +141,51 @@ impl Worker {
let timeout = channels.timeout();
let (mut stream_r, stream_w) = channels.split();
let header = match upload_pack::pktline::git_request(&mut stream_r) {
Ok(header) => header,
Err(e) => {
let mut iter = gix_packetline::blocking_io::StreamingPeekableIter::new(
&mut stream_r,
&[gix_packetline::PacketLineRef::Flush],
false, /* packet tracing */
);
let header = match iter.read_line() {
None => {
return FetchResult::Responder {
rid: None,
result: Err(UploadError::PacketLine(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"unexpected end of stream while reading upload-pack header",
))),
}
}
Some(Err(e)) => {
return FetchResult::Responder {
rid: None,
result: Err(UploadError::PacketLine(e)),
}
}
Some(Ok(Err(e))) => {
return FetchResult::Responder {
rid: None,
result: Err(UploadError::PacketLine(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("invalid upload-pack header: {e}"),
))),
}
}
Some(Ok(Ok(header))) => header,
};
let Some(header) = upload_pack::GitRequest::from_packetline(header) else {
return FetchResult::Responder {
rid: None,
result: Err(UploadError::PacketLine(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"failed to parse upload-pack header",
))),
};
};
log::debug!(target: "worker", "Spawning upload-pack process for {} on stream {stream}..", header.repo);
if let Err(e) = self.is_authorized(remote, header.repo) {

View File

@ -25,7 +25,7 @@ pub fn upload_pack<R, W>(
remote: NodeId,
storage: &Storage,
emitter: &Emitter<Event>,
header: &pktline::GitRequest,
header: &GitRequest,
mut recv: R,
send: W,
timeout: Duration,
@ -239,86 +239,31 @@ where
}
}
pub(super) mod pktline {
use std::io;
use std::io::Read;
use std::str;
use radicle::prelude::RepoId;
pub const HEADER_LEN: usize = 4;
/// Read and parse the `GitRequest` data from the client side.
pub fn git_request<R>(reader: &mut R) -> io::Result<GitRequest>
where
R: io::Read,
{
let mut reader = Reader::new(reader);
let (header, _) = reader.read_request_pktline()?;
Ok(header)
}
struct Reader<'a, R> {
stream: &'a mut R,
}
impl<'a, R: io::Read> Reader<'a, R> {
/// Create a new packet-line reader.
pub fn new(stream: &'a mut R) -> Self {
Self { stream }
}
/// Parse a Git request packet-line.
///
/// Example: `0032git-upload-pack /project.git\0host=myserver.com\0`
///
fn read_request_pktline(&mut self) -> io::Result<(GitRequest, Vec<u8>)> {
let mut pktline = [0u8; 1024];
let length = self.read_pktline(&mut pktline)?;
let Some(cmd) = GitRequest::parse(&pktline[4..length]) else {
return Err(io::ErrorKind::InvalidInput.into());
};
Ok((cmd, Vec::from(&pktline[..length])))
}
/// Parse a Git packet-line.
fn read_pktline(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.read_exact(&mut buf[..HEADER_LEN])?;
let length = str::from_utf8(&buf[..HEADER_LEN])
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
let length = usize::from_str_radix(length, 16)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
self.read_exact(&mut buf[HEADER_LEN..length])?;
Ok(length)
}
}
impl<R: io::Read> io::Read for Reader<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stream.read(buf)
}
}
/// The Git request packet-line for a Heartwood repository.
///
/// Example: `0032git-upload-pack /rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5.git\0host=myserver.com\0`
#[derive(Debug)]
pub struct GitRequest {
/// The Git request packet-line for a repository.
///
/// See <https://git-scm.com/docs/pack-protocol.html#_git_transport>.
///
/// Example: `0032git-upload-pack /rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5.git\0host=myserver.com\0`
#[derive(Debug)]
pub struct GitRequest {
pub repo: RepoId,
#[allow(dead_code)]
pub path: String,
#[allow(dead_code)]
pub host: Option<(String, Option<u16>)>,
pub extra: Vec<(String, Option<String>)>,
}
impl GitRequest {
pub(super) fn from_packetline(
packet_line: gix_packetline::PacketLineRef<'_>,
) -> Option<GitRequest> {
packet_line.as_slice().and_then(Self::parse)
}
impl GitRequest {
/// Parse a Git command from a packet-line.
fn parse(input: &[u8]) -> Option<Self> {
let input = str::from_utf8(input).ok()?;
let input = std::str::from_utf8(input).ok()?;
let mut parts = input
.strip_prefix("git-upload-pack ")?
.split_terminator('\0');
@ -353,5 +298,4 @@ pub(super) mod pktline {
extra,
})
}
}
}