Skip to content

Commit 9b4fb3e

Browse files
committed
[protocol] side-band channel encoding and decoding
1 parent 4e46719 commit 9b4fb3e

File tree

4 files changed

+91
-15
lines changed

4 files changed

+91
-15
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Please see _'Development Status'_ for a listing of all crates and their capabili
9797
* [x] encode
9898
* [x] decode (zero-copy)
9999
* [x] [error line](https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L28:L28)
100-
* [ ] [V2 additions](https://github.com/git/git/blob/master/Documentation/technical/protocol-v2.txt#L35:L36)
100+
* [x] [V2 additions](https://github.com/git/git/blob/master/Documentation/technical/protocol-v2.txt#L35:L36)
101101
* [ ] [side-band mode](https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L467:L467)
102102
* [ ] `Iterator` for multi-plexed pack lines from `Read`
103103
* [ ] parse and serialize [capabilities](https://github.com/git/git/blob/master/Documentation/technical/protocol-capabilities.txt#L1:L1)

git-protocol/src/packet_line/encode.rs

+19-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::packet_line::{DELIMITER_LINE, ERR_PREFIX, FLUSH_LINE, MAX_DATA_LEN, RESPONSE_END_LINE};
1+
use crate::packet_line::{Channel, DELIMITER_LINE, ERR_PREFIX, FLUSH_LINE, MAX_DATA_LEN, RESPONSE_END_LINE};
22
use quick_error::quick_error;
33
use std::io;
44

@@ -32,29 +32,34 @@ pub fn flush_to_write(mut out: impl io::Write) -> io::Result<usize> {
3232
}
3333

3434
pub fn error_to_write(data: &[u8], out: impl io::Write) -> Result<usize, Error> {
35-
let data_with_prefix_end = data.len() + ERR_PREFIX.len();
36-
if data_with_prefix_end > MAX_DATA_LEN {
37-
return Err(Error::DataLengthLimitExceeded(data.len() - ERR_PREFIX.len()));
38-
}
39-
// This is a big buffer, but it's only used on error, so the program is on the way out
40-
let mut buf = [0u8; MAX_DATA_LEN];
41-
buf[..ERR_PREFIX.len()].copy_from_slice(ERR_PREFIX);
42-
buf[ERR_PREFIX.len()..data_with_prefix_end].copy_from_slice(data);
43-
data_to_write(&buf[..data_with_prefix_end], out)
35+
prefixed_data_to_write(ERR_PREFIX, data, out)
36+
}
37+
38+
pub fn band_to_write(kind: Channel, data: &[u8], out: impl io::Write) -> Result<usize, Error> {
39+
prefixed_data_to_write(&[kind as u8], data, out)
4440
}
4541

46-
pub fn data_to_write(data: &[u8], mut out: impl io::Write) -> Result<usize, Error> {
47-
if data.len() > MAX_DATA_LEN {
48-
return Err(Error::DataLengthLimitExceeded(data.len()));
42+
pub fn data_to_write(data: &[u8], out: impl io::Write) -> Result<usize, Error> {
43+
prefixed_data_to_write(&[], data, out)
44+
}
45+
46+
fn prefixed_data_to_write(prefix: &[u8], data: &[u8], mut out: impl io::Write) -> Result<usize, Error> {
47+
let data_len = prefix.len() + data.len();
48+
if data_len > MAX_DATA_LEN {
49+
return Err(Error::DataLengthLimitExceeded(data_len));
4950
}
5051
if data.is_empty() {
5152
return Err(Error::DataIsEmpty);
5253
}
5354

5455
let mut buf = [0u8; 4];
55-
let data_len = data.len() + 4;
56+
let data_len = data_len + 4;
5657
hex::encode_to_slice((data_len as u16).to_be_bytes(), &mut buf).expect("two bytes to 4 hex chars never fails");
58+
5759
out.write_all(&buf)?;
60+
if !prefix.is_empty() {
61+
out.write_all(prefix)?;
62+
}
5863
out.write_all(data)?;
5964
Ok(data_len)
6065
}

git-protocol/src/packet_line/mod.rs

+53
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,35 @@ impl<'a> Borrowed<'a> {
4040
pub fn to_error(&self) -> Error {
4141
Error(self.as_slice())
4242
}
43+
pub fn to_band(&self, kind: Channel) -> Band {
44+
let d = match self {
45+
Borrowed::Data(d) => d,
46+
_ => panic!("cannot side-channel non-data lines"),
47+
};
48+
49+
match kind {
50+
Channel::Data => Band::Data(d),
51+
Channel::Progress => Band::Progress(d),
52+
Channel::Error => Band::Error(d),
53+
}
54+
}
55+
/// Decode the band of the line, or panic if it is not actually a side-band line
56+
pub fn decode_band(&self) -> Band {
57+
let d = match self {
58+
Borrowed::Data(d) => d,
59+
_ => panic!("cannot decode side-channel information from non-data lines"),
60+
};
61+
match d[0] {
62+
1 => Band::Data(&d[1..]),
63+
2 => Band::Progress(&d[1..]),
64+
3 => Band::Error(&d[1..]),
65+
_ => panic!("attempt to decode a non-side channel line"),
66+
}
67+
}
4368
}
4469

70+
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
71+
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
4572
pub struct Error<'a>(&'a [u8]);
4673

4774
impl<'a> Error<'a> {
@@ -50,5 +77,31 @@ impl<'a> Error<'a> {
5077
}
5178
}
5279

80+
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
81+
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
82+
pub enum Band<'a> {
83+
Data(&'a [u8]),
84+
Progress(&'a [u8]),
85+
Error(&'a [u8]),
86+
}
87+
88+
impl<'a> Band<'a> {
89+
pub fn to_write(&self, out: impl io::Write) -> Result<usize, encode::Error> {
90+
match self {
91+
Band::Data(d) => encode::band_to_write(Channel::Data, d, out),
92+
Band::Progress(d) => encode::band_to_write(Channel::Progress, d, out),
93+
Band::Error(d) => encode::band_to_write(Channel::Error, d, out),
94+
}
95+
}
96+
}
97+
98+
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
99+
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
100+
pub enum Channel {
101+
Data = 1,
102+
Progress = 2,
103+
Error = 3,
104+
}
105+
53106
pub mod decode;
54107
pub mod encode;

git-protocol/tests/packet_line/decode.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod streaming {
22
use crate::packet_line::assert_err_display;
3+
use git_protocol::packet_line::Channel;
34
use git_protocol::{
45
packet_line::decode::{self, streaming, Stream},
56
PacketLine,
@@ -87,6 +88,23 @@ mod streaming {
8788
Ok(())
8889
}
8990

91+
#[test]
92+
fn roundtrip_side_bands() -> crate::Result {
93+
for channel in &[Channel::Data, Channel::Error, Channel::Progress] {
94+
let mut out = Vec::new();
95+
let band = PacketLine::Data(b"band data").to_band(*channel);
96+
band.to_write(&mut out)?;
97+
match streaming(&out)? {
98+
Stream::Complete { line, bytes_consumed } => {
99+
assert_eq!(bytes_consumed, out.len());
100+
assert_eq!(line.decode_band(), band);
101+
}
102+
Stream::Incomplete { .. } => panic!("roundtrips are never incomplete"),
103+
}
104+
}
105+
Ok(())
106+
}
107+
90108
mod incomplete {
91109
use git_protocol::packet_line::decode::{self, streaming, Stream};
92110

0 commit comments

Comments
 (0)