use std::io::{Read, Write};
use gix_hash::Kind;
use gix_packetline::blocking_io::encode;
use gix_packetline::decode::{self, Stream};
use gix_packetline::{Channel, PacketLineRef};
use crate::advertisement::{self, Advertisement};
use crate::command::{self, PushCommand};
use crate::error::{Error, Result};
use crate::options::{self, SendPackOptions};
use crate::report::{self, PushReport};
use crate::transport::Transport;
struct PktReader<'a> {
inner: &'a mut dyn Read,
buffer: Vec<u8>,
consumed: usize,
eof: bool,
}
impl<'a> PktReader<'a> {
fn new(inner: &'a mut dyn Read) -> Self {
PktReader {
inner,
buffer: Vec::new(),
consumed: 0,
eof: false,
}
}
fn next(&mut self) -> Result<Option<Line>> {
loop {
if self.consumed < self.buffer.len() {
match decode::streaming(&self.buffer[self.consumed..]) {
Ok(Stream::Complete {
line,
bytes_consumed,
}) => {
let out = Line::from(line);
self.consumed += bytes_consumed;
if self.consumed > 64 * 1024 {
self.buffer.drain(..self.consumed);
self.consumed = 0;
}
return Ok(Some(out));
}
Ok(Stream::Incomplete { .. }) => {}
Err(err) => return Err(Error::protocol(format!("malformed pkt-line: {err}"))),
}
}
if self.eof {
let pending = self.buffer.len() - self.consumed;
if pending > 0 {
return Err(Error::protocol(format!(
"stream ended mid-packet with {pending} bytes buffered"
)));
}
return Ok(None);
}
let mut chunk = [0u8; 8192];
let n = self.inner.read(&mut chunk)?;
if n == 0 {
self.eof = true;
} else {
self.buffer.extend_from_slice(&chunk[..n]);
}
}
}
fn payloads_until_flush(&mut self) -> Result<Vec<Vec<u8>>> {
let mut out = Vec::new();
while let Some(line) = self.next()? {
match line {
Line::Flush => return Ok(out),
Line::Data(d) => out.push(d),
other => {
return Err(Error::protocol(format!(
"unexpected control packet in a receive-pack stream: {other:?}"
)))
}
}
}
Ok(out)
}
}
#[derive(Debug)]
enum Line {
Data(Vec<u8>),
Flush,
Delimiter,
ResponseEnd,
}
impl From<PacketLineRef<'_>> for Line {
fn from(line: PacketLineRef<'_>) -> Self {
match line {
PacketLineRef::Data(d) => Line::Data(d.to_vec()),
PacketLineRef::Flush => Line::Flush,
PacketLineRef::Delimiter => Line::Delimiter,
PacketLineRef::ResponseEnd => Line::ResponseEnd,
}
}
}
pub fn read_advertisement(reader: &mut dyn Read) -> Result<Advertisement> {
let payloads = PktReader::new(reader).payloads_until_flush()?;
advertisement::parse(&payloads)
}
pub fn frame_section(lines: &[Vec<u8>], out: &mut dyn Write) -> Result<()> {
for line in lines {
encode::text_to_write(line, &mut *out)?;
}
encode::flush_to_write(&mut *out)?;
Ok(())
}
pub fn encode_command_list(commands: &[PushCommand], caps: &[String]) -> Result<Vec<u8>> {
let mut out = Vec::new();
frame_section(&command::lines(commands, caps)?, &mut out)?;
Ok(out)
}
pub fn encode_push_options(options: &[String]) -> Result<Vec<u8>> {
let mut out = Vec::new();
frame_section(&command::push_option_lines(options)?, &mut out)?;
Ok(out)
}
pub fn read_report(reader: &mut dyn Read, side_band: bool) -> Result<PushReport> {
let mut pkt = PktReader::new(reader);
if !side_band {
let payloads = pkt.payloads_until_flush()?;
return report::parse(&payloads);
}
let mut band1: Vec<u8> = Vec::new();
let mut progress: Vec<String> = Vec::new();
let mut errors: Vec<String> = Vec::new();
while let Some(line) = pkt.next()? {
match line {
Line::Flush => break,
Line::Data(d) => {
let Some((&band, rest)) = d.split_first() else {
return Err(Error::protocol(
"an empty sideband packet carries no band number",
));
};
match band {
b if b == Channel::Data as u8 => band1.extend_from_slice(rest),
b if b == Channel::Progress as u8 => {
progress.push(String::from_utf8_lossy(rest).into_owned())
}
b if b == Channel::Error as u8 => {
errors.push(String::from_utf8_lossy(rest).into_owned())
}
other => {
return Err(Error::protocol(format!(
"unknown sideband {other} in the push report"
)))
}
}
}
other => {
return Err(Error::protocol(format!(
"unexpected control packet in the push report: {other:?}"
)))
}
}
}
let mut cursor = std::io::Cursor::new(band1);
let mut inner = PktReader::new(&mut cursor);
let lines = inner.payloads_until_flush()?;
let mut out = report::parse(&lines)?;
out.progress = progress;
out.remote_errors = errors;
Ok(out)
}
pub fn send_pack<T, P>(
transport: &mut T,
advertisement: &Advertisement,
commands: &[PushCommand],
local_hash_kind: Kind,
write_pack: Option<P>,
opts: &SendPackOptions,
) -> Result<PushReport>
where
T: Transport,
P: FnOnce(&mut dyn Write) -> Result<()>,
{
let capabilities = options::negotiate(advertisement, commands, local_hash_kind, opts)?;
let side_band = capabilities.iter().any(|c| c == "side-band-64k");
let expects_report = capabilities
.iter()
.any(|c| c == "report-status" || c == "report-status-v2");
let all_deletes = commands.iter().all(PushCommand::is_delete);
if all_deletes && write_pack.is_some() {
return Err(Error::invalid(
"an all-deletes push introduces no objects and must not carry a pack",
));
}
if !all_deletes && write_pack.is_none() {
return Err(Error::invalid(
"a push that creates or updates a ref must carry a pack, even an empty one",
));
}
let commands_bytes = encode_command_list(commands, &capabilities)?;
let options_bytes = if opts.push_options.is_empty() {
Vec::new()
} else {
encode_push_options(&opts.push_options)?
};
{
let (_, writer) = transport.io();
writer.write_all(&commands_bytes)?;
if !options_bytes.is_empty() {
writer.write_all(&options_bytes)?;
}
if let Some(write_pack) = write_pack {
write_pack(writer)?;
}
writer.flush()?;
}
if !expects_report {
return Ok(PushReport {
unpack: "ok".to_string(),
..PushReport::default()
});
}
let (reader, _) = transport.io();
let mut report = read_report(reader, side_band)?;
let commanded: Vec<bstr::BString> = commands.iter().map(|c| c.name.clone()).collect();
report.reconcile(&commanded);
Ok(report)
}