Skip to content

Commit e924a59

Browse files
committed
packet line encoding with flush support
1 parent a0bebd1 commit e924a59

File tree

7 files changed

+113
-1
lines changed

7 files changed

+113
-1
lines changed

Cargo.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

git-protocol/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ description = "A WIP crate of the gitoxide project for implementing git protocol
77
authors = ["Sebastian Thiel <[email protected]>"]
88
edition = "2018"
99

10-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10+
11+
[lib]
12+
doctest = false
1113

1214
[dependencies]
15+
quick-error = "2.0.0"
16+
hex = "0.4.2"
17+
18+
[dev-dependencies]
19+
bstr = { version = "0.2.13", default-features = false, features = ["std"] }

git-protocol/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
#![forbid(unsafe_code)]
2+
3+
pub mod packet_line;

git-protocol/src/packet_line.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
pub mod encode {
2+
use quick_error::quick_error;
3+
use std::io;
4+
5+
const MAX_DATA_LEN: usize = 65516;
6+
7+
quick_error! {
8+
#[derive(Debug)]
9+
pub enum Error {
10+
Io(err: io::Error) {
11+
display("An error occurred while writing")
12+
from()
13+
source(err)
14+
}
15+
DataLengthLimitExceeded(length_in_bytes: usize) {
16+
display("Cannot encode more than {} bytes, got {}", MAX_DATA_LEN, length_in_bytes)
17+
}
18+
DataIsEmpty {
19+
display("Empty lines are invalid")
20+
}
21+
}
22+
}
23+
pub fn flush_to_write(mut out: impl io::Write) -> io::Result<usize> {
24+
out.write_all(b"0000").map(|_| 4)
25+
}
26+
27+
pub fn data_to_write(data: &[u8], mut out: impl io::Write) -> Result<usize, Error> {
28+
if data.len() > MAX_DATA_LEN {
29+
return Err(Error::DataLengthLimitExceeded(data.len()));
30+
}
31+
if data.is_empty() {
32+
return Err(Error::DataIsEmpty);
33+
}
34+
35+
let mut buf = [0u8; 4];
36+
let data_len = data.len() + 4;
37+
hex::encode_to_slice((data_len as u16).to_be_bytes(), &mut buf).expect("two bytes to 4 hex chars never fails");
38+
out.write_all(&buf)?;
39+
out.write_all(data)?;
40+
Ok(data_len)
41+
}
42+
}

git-protocol/tests/packet_line/mod.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
mod encode {
2+
mod data_to_write {
3+
use bstr::ByteSlice;
4+
use git_protocol::packet_line::encode::{data_to_write, flush_to_write};
5+
use std::io;
6+
7+
fn vec_sized(size: usize) -> Vec<u8> {
8+
let mut v = Vec::new();
9+
v.resize(size, 0);
10+
v
11+
}
12+
13+
fn assert_err_display<T, E: std::error::Error>(res: Result<T, E>, expected: impl AsRef<str>) {
14+
match res {
15+
Ok(_) => assert!(false, "Expected error '{}', got value", expected.as_ref()),
16+
Err(err) => assert_eq!(err.to_string(), expected.as_ref()),
17+
}
18+
}
19+
20+
#[test]
21+
fn success_binary_and_non_binary() -> crate::Result {
22+
let mut out = Vec::new();
23+
assert_eq!(data_to_write(b"\0", &mut out)?, 5);
24+
assert_eq!(out.as_bstr(), b"0005\0".as_bstr());
25+
26+
out.clear();
27+
assert_eq!(data_to_write("hello world, it works\n".as_bytes(), &mut out)?, 26);
28+
assert_eq!(out.as_bstr(), b"001ahello world, it works\n".as_bstr());
29+
Ok(())
30+
}
31+
32+
#[test]
33+
fn success_flush() -> crate::Result {
34+
let mut out = Vec::new();
35+
assert_eq!(flush_to_write(&mut out)?, 4);
36+
assert_eq!(out.as_bstr(), b"0000".as_bstr());
37+
Ok(())
38+
}
39+
40+
#[test]
41+
fn error_if_data_exceeds_limit() {
42+
assert_err_display(
43+
data_to_write(&vec_sized(65516 + 1), io::sink()),
44+
"Cannot encode more than 65516 bytes, got 65517",
45+
);
46+
}
47+
#[test]
48+
fn error_if_data_is_empty() {
49+
assert_err_display(data_to_write(&[], io::sink()), "Empty lines are invalid");
50+
}
51+
}
52+
}

git-protocol/tests/protocol.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub type Result = std::result::Result<(), Box<dyn std::error::Error>>;
2+
3+
mod packet_line;

tasks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* to be used to obtain more real-world samples of typical git interactions for use in the test-suite
77
* **git-protocol**
88
* [ ] parse pkt-lines support
9+
* [x] encoding including flush lines
910
* [ ] basic V1 parsing to understand data frames to allow placing them into individual files
1011
* **a way to intercept git-http communication**
1112
* Maybe with a custom proxy as well, can't hurt to try APIs in real-world programs

0 commit comments

Comments
 (0)