1use crate::{low_level::PackFile, Error};
2use bytes::{BufMut, BytesMut};
3use std::fmt::Write;
4
5const MAX_DATA_LEN: usize = 65516;
11
12pub enum PktLine<'a> {
14 Data(&'a [u8]),
16 SidebandData(PackFile<'a>),
19 SidebandMsg(&'a [u8]),
22 Flush,
24 Delimiter,
26 ResponseEnd,
28}
29
30impl PktLine<'_> {
31 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, buf), err))]
32 pub fn encode_to(&self, buf: &mut BytesMut) -> Result<(), Error> {
33 match self {
34 Self::Data(data) => {
35 for chunk in data.chunks(MAX_DATA_LEN) {
36 write!(buf, "{:04x}", chunk.len() + 4)?;
37 buf.extend_from_slice(chunk);
38 }
39 }
40 Self::SidebandData(packfile) => {
41 let mut data_buf = buf.split_off(buf.len());
44
45 packfile.encode_to(&mut data_buf)?;
46
47 if data_buf.len() + 5 <= MAX_DATA_LEN - 1 {
49 write!(buf, "{:04x}", data_buf.len() + 5)?;
50 buf.put_u8(1); buf.unsplit(data_buf);
52 } else {
53 for chunk in data_buf.chunks(MAX_DATA_LEN - 1) {
54 write!(buf, "{:04x}", chunk.len() + 5)?;
55 buf.put_u8(1); buf.extend_from_slice(chunk);
57 }
58 }
59 }
60 Self::SidebandMsg(msg) => {
61 for chunk in msg.chunks(MAX_DATA_LEN - 1) {
62 write!(buf, "{:04x}", chunk.len() + 5)?;
63 buf.put_u8(2); buf.extend_from_slice(chunk);
65 }
66 }
67 Self::Flush => buf.extend_from_slice(b"0000"),
68 Self::Delimiter => buf.extend_from_slice(b"0001"),
69 Self::ResponseEnd => buf.extend_from_slice(b"0002"),
70 }
71
72 Ok(())
73 }
74}
75
76impl<'a> From<&'a str> for PktLine<'a> {
77 fn from(val: &'a str) -> Self {
78 PktLine::Data(val.as_bytes())
79 }
80}
81
82#[cfg(test)]
83mod test {
84 use crate::packet_line::MAX_DATA_LEN;
85 use bytes::BytesMut;
86
87 #[test]
88 fn test_pkt_line() {
89 let mut buffer = BytesMut::new();
90 super::PktLine::from("agent=git/2.32.0\n")
91 .encode_to(&mut buffer)
92 .unwrap();
93 assert_eq!(buffer.as_ref(), b"0015agent=git/2.32.0\n");
94 }
95
96 #[test]
97 fn test_large_pkt_line() {
98 let mut buffer = BytesMut::new();
99 super::PktLine::from("a".repeat(70000).as_str())
100 .encode_to(&mut buffer)
101 .unwrap();
102 assert_eq!(
103 buffer.len(),
104 70008,
105 "should be two chunks each with a 4-byte len header"
106 );
107
108 assert_eq!(
110 std::str::from_utf8(&buffer[..4]).unwrap(),
111 format!("{:04x}", 4 + MAX_DATA_LEN)
112 );
113 assert!(
114 &buffer[4..4 + MAX_DATA_LEN]
115 .iter()
116 .all(|b| char::from(*b) == 'a'),
117 "data should be all 'a's"
118 );
119
120 assert_eq!(
122 std::str::from_utf8(&buffer[4 + MAX_DATA_LEN..][..4]).unwrap(),
123 format!("{:04x}", 4 + (70000 - MAX_DATA_LEN))
124 );
125 assert!(
126 &buffer[4 + MAX_DATA_LEN + 4..]
127 .iter()
128 .all(|b| char::from(*b) == 'a'),
129 "data should be all 'a's"
130 );
131 }
132}