Merge pull request #3 from alexheretic/chunk-pkt-lines
Fix pkt-line encoding to handle encoding of data larger than 65516 bytes & release 0.1.2
Diff
CHANGELOG.md | 3 +++
Cargo.toml | 2 +-
src/high_level.rs | 2 +-
src/low_level.rs | 2 +-
src/packet_line.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
5 files changed, 72 insertions(+), 14 deletions(-)
@@ -1,3 +1,6 @@
# v0.1.2
* Fix pkt-line encoding to handle encoding of data larger than 65516 bytes.
# v0.1.1
This release makes packfile generation deterministic by setting the committed and authored time to the unix epoch. Thanks @david-monroe for the contribution.
@@ -1,8 +1,8 @@
[package]
name = "packfile"
authors = ["Jordan Doyle <jordan@doyle.la>"]
description = "A simple library providing utilities to generate Git Packfiles in memory and send them to clients"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
license = "WTFPL"
keywords = ["git", "packfile", "in-memory", "protocol"]
@@ -226,7 +226,7 @@
let stdout = crate::test::verify_pack_file(output.freeze());
insta::with_settings!({filters => vec![
(r#"/(.*)/example.pack"#, "/path/to/example.pack")
(r"/(.*)/example.pack", "/path/to/example.pack")
]}, {
insta::assert_snapshot!(stdout);
});
@@ -411,7 +411,7 @@
let stdout = crate::test::verify_pack_file(example());
insta::with_settings!({filters => vec![
(r#"/(.*)/example.pack"#, "/path/to/example.pack")
(r"/(.*)/example.pack", "/path/to/example.pack")
]}, {
insta::assert_snapshot!(stdout);
});
@@ -1,8 +1,13 @@
use std::fmt::Write;
use crate::{low_level::PackFile, Error};
use bytes::{BufMut, BytesMut};
use std::fmt::Write;
use crate::{low_level::PackFile, Error};
const MAX_DATA_LEN: usize = 65516;
pub enum PktLine<'a> {
@@ -27,25 +32,37 @@
pub fn encode_to(&self, buf: &mut BytesMut) -> Result<(), Error> {
match self {
Self::Data(data) => {
write!(buf, "{:04x}", data.len() + 4)?;
buf.extend_from_slice(data);
for chunk in data.chunks(MAX_DATA_LEN) {
write!(buf, "{:04x}", chunk.len() + 4)?;
buf.extend_from_slice(chunk);
}
}
Self::SidebandData(packfile) => {
let mut data_buf = buf.split_off(buf.len());
data_buf.put_u8(1);
packfile.encode_to(&mut data_buf)?;
write!(buf, "{:04x}", data_buf.len() + 4)?;
buf.unsplit(data_buf);
if data_buf.len() + 5 <= MAX_DATA_LEN - 1 {
write!(buf, "{:04x}", data_buf.len() + 5)?;
buf.put_u8(1);
buf.unsplit(data_buf);
} else {
for chunk in data_buf.chunks(MAX_DATA_LEN - 1) {
write!(buf, "{:04x}", chunk.len() + 5)?;
buf.put_u8(1);
buf.extend_from_slice(chunk);
}
}
}
Self::SidebandMsg(msg) => {
write!(buf, "{:04x}", msg.len() + 4 + 1)?;
buf.put_u8(2);
buf.extend_from_slice(msg);
for chunk in msg.chunks(MAX_DATA_LEN - 1) {
write!(buf, "{:04x}", chunk.len() + 5)?;
buf.put_u8(2);
buf.extend_from_slice(chunk);
}
}
Self::Flush => buf.extend_from_slice(b"0000"),
Self::Delimiter => buf.extend_from_slice(b"0001"),
@@ -64,6 +81,7 @@
#[cfg(test)]
mod test {
use crate::packet_line::MAX_DATA_LEN;
use bytes::BytesMut;
#[test]
@@ -73,5 +91,42 @@
.encode_to(&mut buffer)
.unwrap();
assert_eq!(buffer.as_ref(), b"0015agent=git/2.32.0\n");
}
#[test]
fn test_large_pkt_line() {
let mut buffer = BytesMut::new();
super::PktLine::from("a".repeat(70000).as_str())
.encode_to(&mut buffer)
.unwrap();
assert_eq!(
buffer.len(),
70008,
"should be two chunks each with a 4-byte len header"
);
assert_eq!(
std::str::from_utf8(&buffer[..4]).unwrap(),
format!("{:04x}", 4 + MAX_DATA_LEN)
);
assert!(
&buffer[4..4 + MAX_DATA_LEN]
.iter()
.all(|b| char::from(*b) == 'a'),
"data should be all 'a's"
);
assert_eq!(
std::str::from_utf8(&buffer[4 + MAX_DATA_LEN..][..4]).unwrap(),
format!("{:04x}", 4 + (70000 - MAX_DATA_LEN))
);
assert!(
&buffer[4 + MAX_DATA_LEN + 4..]
.iter()
.all(|b| char::from(*b) == 'a'),
"data should be all 'a's"
);
}
}