guts_git/
pktline.rs

1//! Git pkt-line format implementation.
2//!
3//! The pkt-line format is used for all git protocol communication.
4//! Each line is prefixed with a 4-character hex length, or "0000" for flush.
5
6use crate::{GitError, Result};
7use std::io::{Read, Write};
8
9/// A pkt-line packet.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum PktLine {
12    /// Data line with content.
13    Data(Vec<u8>),
14    /// Flush packet (0000).
15    Flush,
16    /// Delimiter packet (0001).
17    Delimiter,
18    /// Response-end packet (0002).
19    ResponseEnd,
20}
21
22impl PktLine {
23    /// Creates a data packet from a string slice.
24    pub fn from_string(s: &str) -> Self {
25        Self::Data(s.as_bytes().to_vec())
26    }
27
28    /// Creates a data packet from bytes.
29    pub fn from_bytes(b: impl Into<Vec<u8>>) -> Self {
30        Self::Data(b.into())
31    }
32
33    /// Encodes the packet to bytes.
34    pub fn encode(&self) -> Vec<u8> {
35        match self {
36            Self::Data(data) => {
37                let len = data.len() + 4; // 4 bytes for the length prefix
38                let mut result = format!("{:04x}", len).into_bytes();
39                result.extend_from_slice(data);
40                result
41            }
42            Self::Flush => b"0000".to_vec(),
43            Self::Delimiter => b"0001".to_vec(),
44            Self::ResponseEnd => b"0002".to_vec(),
45        }
46    }
47
48    /// Returns true if this is a flush packet.
49    pub fn is_flush(&self) -> bool {
50        matches!(self, Self::Flush)
51    }
52
53    /// Returns the data content, or None for special packets.
54    pub fn data(&self) -> Option<&[u8]> {
55        match self {
56            Self::Data(data) => Some(data),
57            _ => None,
58        }
59    }
60
61    /// Returns the data as a string, trimming any trailing newline.
62    pub fn as_str(&self) -> Option<&str> {
63        self.data()
64            .and_then(|d| std::str::from_utf8(d).ok())
65            .map(|s| s.trim_end_matches('\n'))
66    }
67}
68
69/// Reader for pkt-line format.
70pub struct PktLineReader<R> {
71    reader: R,
72}
73
74impl<R: Read> PktLineReader<R> {
75    /// Creates a new pkt-line reader.
76    pub fn new(reader: R) -> Self {
77        Self { reader }
78    }
79
80    /// Reads the next packet.
81    pub fn read(&mut self) -> Result<Option<PktLine>> {
82        let mut len_buf = [0u8; 4];
83        match self.reader.read_exact(&mut len_buf) {
84            Ok(()) => {}
85            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
86            Err(e) => return Err(e.into()),
87        }
88
89        let len_str = std::str::from_utf8(&len_buf)
90            .map_err(|_| GitError::InvalidPktLine("invalid length prefix".to_string()))?;
91
92        match len_str {
93            "0000" => Ok(Some(PktLine::Flush)),
94            "0001" => Ok(Some(PktLine::Delimiter)),
95            "0002" => Ok(Some(PktLine::ResponseEnd)),
96            _ => {
97                let len = u16::from_str_radix(len_str, 16)
98                    .map_err(|_| GitError::InvalidPktLine("invalid length".to_string()))?
99                    as usize;
100
101                if len < 4 {
102                    return Err(GitError::InvalidPktLine("length too small".to_string()));
103                }
104
105                let data_len = len - 4;
106                let mut data = vec![0u8; data_len];
107                self.reader.read_exact(&mut data)?;
108
109                Ok(Some(PktLine::Data(data)))
110            }
111        }
112    }
113
114    /// Reads all packets until a flush packet.
115    pub fn read_until_flush(&mut self) -> Result<Vec<PktLine>> {
116        let mut packets = Vec::new();
117        loop {
118            match self.read()? {
119                Some(PktLine::Flush) | None => break,
120                Some(pkt) => packets.push(pkt),
121            }
122        }
123        Ok(packets)
124    }
125
126    /// Returns a mutable reference to the inner reader.
127    pub fn inner_mut(&mut self) -> &mut R {
128        &mut self.reader
129    }
130
131    /// Consumes the reader and returns the inner reader.
132    pub fn into_inner(self) -> R {
133        self.reader
134    }
135}
136
137/// Writer for pkt-line format.
138pub struct PktLineWriter<W> {
139    writer: W,
140}
141
142impl<W: Write> PktLineWriter<W> {
143    /// Creates a new pkt-line writer.
144    pub fn new(writer: W) -> Self {
145        Self { writer }
146    }
147
148    /// Writes a packet.
149    pub fn write(&mut self, pkt: &PktLine) -> Result<()> {
150        self.writer.write_all(&pkt.encode())?;
151        Ok(())
152    }
153
154    /// Writes a data line.
155    pub fn write_data(&mut self, data: &[u8]) -> Result<()> {
156        self.write(&PktLine::Data(data.to_vec()))
157    }
158
159    /// Writes a string line (with newline).
160    pub fn write_line(&mut self, s: &str) -> Result<()> {
161        let mut data = s.as_bytes().to_vec();
162        if !s.ends_with('\n') {
163            data.push(b'\n');
164        }
165        self.write(&PktLine::Data(data))
166    }
167
168    /// Writes a flush packet.
169    pub fn flush_pkt(&mut self) -> Result<()> {
170        self.write(&PktLine::Flush)
171    }
172
173    /// Flushes the underlying writer.
174    pub fn flush(&mut self) -> Result<()> {
175        self.writer.flush()?;
176        Ok(())
177    }
178
179    /// Returns the inner writer.
180    pub fn into_inner(self) -> W {
181        self.writer
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use std::io::Cursor;
189
190    #[test]
191    fn test_pktline_encode() {
192        assert_eq!(PktLine::from_string("hello\n").encode(), b"000ahello\n");
193        assert_eq!(PktLine::Flush.encode(), b"0000");
194        assert_eq!(PktLine::Delimiter.encode(), b"0001");
195    }
196
197    #[test]
198    fn test_pktline_roundtrip() {
199        let packets = vec![
200            PktLine::from_string("hello\n"),
201            PktLine::from_string("world\n"),
202            PktLine::Flush,
203        ];
204
205        let mut buf = Vec::new();
206        {
207            let mut writer = PktLineWriter::new(&mut buf);
208            for pkt in &packets {
209                writer.write(pkt).unwrap();
210            }
211        }
212
213        let mut reader = PktLineReader::new(Cursor::new(buf));
214        assert_eq!(reader.read().unwrap(), Some(packets[0].clone()));
215        assert_eq!(reader.read().unwrap(), Some(packets[1].clone()));
216        assert_eq!(reader.read().unwrap(), Some(PktLine::Flush));
217    }
218
219    #[test]
220    fn test_pktline_response_end() {
221        assert_eq!(PktLine::ResponseEnd.encode(), b"0002");
222    }
223
224    #[test]
225    fn test_pktline_from_bytes() {
226        let pkt = PktLine::from_bytes(b"test data".to_vec());
227        assert_eq!(pkt.data(), Some(b"test data".as_slice()));
228    }
229
230    #[test]
231    fn test_pktline_is_flush() {
232        assert!(PktLine::Flush.is_flush());
233        assert!(!PktLine::from_string("test").is_flush());
234        assert!(!PktLine::Delimiter.is_flush());
235        assert!(!PktLine::ResponseEnd.is_flush());
236    }
237
238    #[test]
239    fn test_pktline_data() {
240        let pkt = PktLine::from_string("hello");
241        assert_eq!(pkt.data(), Some(b"hello".as_slice()));
242
243        assert!(PktLine::Flush.data().is_none());
244        assert!(PktLine::Delimiter.data().is_none());
245        assert!(PktLine::ResponseEnd.data().is_none());
246    }
247
248    #[test]
249    fn test_pktline_as_str() {
250        let pkt = PktLine::from_string("hello\n");
251        assert_eq!(pkt.as_str(), Some("hello"));
252
253        let pkt2 = PktLine::from_string("no newline");
254        assert_eq!(pkt2.as_str(), Some("no newline"));
255    }
256
257    #[test]
258    fn test_pktline_as_str_invalid_utf8() {
259        let pkt = PktLine::from_bytes(vec![0xff, 0xfe]);
260        assert!(pkt.as_str().is_none());
261    }
262
263    #[test]
264    fn test_pktline_reader_eof() {
265        let reader = PktLineReader::new(Cursor::new(Vec::<u8>::new()));
266        let result = reader.into_inner();
267        assert_eq!(result.position(), 0);
268    }
269
270    #[test]
271    fn test_pktline_read_until_flush() {
272        let mut buf = Vec::new();
273        {
274            let mut writer = PktLineWriter::new(&mut buf);
275            writer.write_line("line1").unwrap();
276            writer.write_line("line2").unwrap();
277            writer.flush_pkt().unwrap();
278            writer.write_line("line3").unwrap();
279        }
280
281        let mut reader = PktLineReader::new(Cursor::new(buf));
282        let packets = reader.read_until_flush().unwrap();
283        assert_eq!(packets.len(), 2);
284    }
285
286    #[test]
287    fn test_pktline_writer_write_line() {
288        let mut buf = Vec::new();
289        {
290            let mut writer = PktLineWriter::new(&mut buf);
291            writer.write_line("test").unwrap();
292        }
293        // "test\n" is 5 bytes, + 4 for length = 9, so hex "0009"
294        assert!(buf.starts_with(b"0009"));
295        assert!(buf.ends_with(b"test\n"));
296    }
297
298    #[test]
299    fn test_pktline_writer_write_line_with_newline() {
300        let mut buf = Vec::new();
301        {
302            let mut writer = PktLineWriter::new(&mut buf);
303            writer.write_line("test\n").unwrap();
304        }
305        // Should not double the newline
306        assert!(buf.ends_with(b"test\n"));
307        assert!(!buf.ends_with(b"test\n\n"));
308    }
309
310    #[test]
311    fn test_pktline_writer_write_data() {
312        let mut buf = Vec::new();
313        {
314            let mut writer = PktLineWriter::new(&mut buf);
315            writer.write_data(b"binary\x00data").unwrap();
316        }
317        assert!(buf.len() > 4); // At least the length prefix
318    }
319
320    #[test]
321    fn test_pktline_writer_flush() {
322        let mut buf = Vec::new();
323        {
324            let mut writer = PktLineWriter::new(&mut buf);
325            writer.write_line("test").unwrap();
326            writer.flush().unwrap();
327        }
328        // Should have been flushed to the buffer
329        assert!(!buf.is_empty());
330    }
331
332    #[test]
333    fn test_pktline_writer_into_inner() {
334        let buf = Vec::new();
335        let writer = PktLineWriter::new(buf);
336        let inner = writer.into_inner();
337        assert!(inner.is_empty());
338    }
339
340    #[test]
341    fn test_pktline_reader_inner_mut() {
342        let cursor = Cursor::new(Vec::<u8>::new());
343        let mut reader = PktLineReader::new(cursor);
344        let inner = reader.inner_mut();
345        assert_eq!(inner.position(), 0);
346    }
347
348    #[test]
349    fn test_pktline_read_delimiter() {
350        let mut buf = Vec::new();
351        buf.extend_from_slice(b"0001");
352
353        let mut reader = PktLineReader::new(Cursor::new(buf));
354        assert_eq!(reader.read().unwrap(), Some(PktLine::Delimiter));
355    }
356
357    #[test]
358    fn test_pktline_read_response_end() {
359        let mut buf = Vec::new();
360        buf.extend_from_slice(b"0002");
361
362        let mut reader = PktLineReader::new(Cursor::new(buf));
363        assert_eq!(reader.read().unwrap(), Some(PktLine::ResponseEnd));
364    }
365
366    #[test]
367    fn test_pktline_equality() {
368        assert_eq!(PktLine::Flush, PktLine::Flush);
369        assert_eq!(PktLine::Delimiter, PktLine::Delimiter);
370        assert_eq!(PktLine::ResponseEnd, PktLine::ResponseEnd);
371        assert_eq!(PktLine::from_string("test"), PktLine::from_string("test"));
372        assert_ne!(PktLine::Flush, PktLine::Delimiter);
373    }
374
375    #[test]
376    fn test_pktline_clone() {
377        let pkt = PktLine::from_string("test");
378        let cloned = pkt.clone();
379        assert_eq!(pkt, cloned);
380    }
381
382    #[test]
383    fn test_pktline_debug() {
384        let pkt = PktLine::Flush;
385        let debug = format!("{:?}", pkt);
386        assert!(debug.contains("Flush"));
387    }
388
389    #[test]
390    fn test_pktline_read_invalid_length() {
391        let mut buf = Vec::new();
392        buf.extend_from_slice(b"0003"); // Invalid: 3 is less than 4
393
394        let mut reader = PktLineReader::new(Cursor::new(buf));
395        let result = reader.read();
396        assert!(result.is_err());
397    }
398
399    #[test]
400    fn test_pktline_large_packet() {
401        let data = "x".repeat(1000);
402        let pkt = PktLine::from_string(&data);
403        let encoded = pkt.encode();
404
405        // Verify we can read it back
406        let mut reader = PktLineReader::new(Cursor::new(encoded));
407        let read_pkt = reader.read().unwrap().unwrap();
408        assert_eq!(read_pkt.data().unwrap().len(), 1000);
409    }
410
411    #[test]
412    fn test_pktline_empty_data() {
413        let pkt = PktLine::from_bytes(Vec::new());
414        let encoded = pkt.encode();
415        assert_eq!(&encoded[..4], b"0004"); // Just the length prefix
416    }
417
418    #[test]
419    fn test_pktline_read_eof_on_empty() {
420        let mut reader = PktLineReader::new(Cursor::new(Vec::<u8>::new()));
421        let result = reader.read().unwrap();
422        assert!(result.is_none());
423    }
424}