git_internal/protocol/
utils.rs1use bytes::{Buf, BufMut, Bytes, BytesMut};
5
6use super::types::{PKT_LINE_END_MARKER, TransportProtocol};
7
8pub fn read_pkt_line(bytes: &mut Bytes) -> (usize, Bytes) {
14 if bytes.is_empty() {
15 return (0, Bytes::new());
16 }
17
18 if bytes.len() < 4 {
20 return (0, Bytes::new());
21 }
22
23 let pkt_length = bytes.slice(0..4);
24 let pkt_length_str = match core::str::from_utf8(&pkt_length) {
25 Ok(s) => s,
26 Err(_) => {
27 tracing::warn!("Invalid UTF-8 in packet length: {:?}", pkt_length);
28 return (0, Bytes::new());
29 }
30 };
31
32 let pkt_length = match usize::from_str_radix(pkt_length_str, 16) {
33 Ok(len) => len,
34 Err(_) => {
35 tracing::warn!("Invalid hex packet length: {:?}", pkt_length_str);
36 return (0, Bytes::new());
37 }
38 };
39
40 if pkt_length == 0 {
41 bytes.advance(4);
42 return (4, Bytes::new()); }
44
45 if pkt_length < 4 {
46 tracing::warn!("Invalid packet length: {} (must be >= 4)", pkt_length);
47 return (0, Bytes::new());
48 }
49
50 if bytes.len() < pkt_length {
51 tracing::warn!(
52 "Insufficient data: need {} bytes, have {}",
53 pkt_length,
54 bytes.len()
55 );
56 return (0, Bytes::new());
57 }
58
59 bytes.advance(4);
61 let data_length = pkt_length - 4;
62 let pkt_line = bytes.copy_to_bytes(data_length);
63 tracing::debug!("pkt line: {:?}", pkt_line);
64
65 (pkt_length, pkt_line)
66}
67
68pub fn add_pkt_line_string(pkt_line_stream: &mut BytesMut, buf_str: String) {
72 let buf_str_length = buf_str.len() + 4;
73 pkt_line_stream.put(Bytes::from(format!("{buf_str_length:04x}")));
74 pkt_line_stream.put(buf_str.as_bytes());
75}
76
77pub fn read_until_white_space(bytes: &mut Bytes) -> String {
81 let mut buf = Vec::new();
82 while bytes.has_remaining() {
83 let c = bytes.get_u8();
84 if c.is_ascii_whitespace() || c == 0 {
85 break;
86 }
87 buf.push(c);
88 }
89 match String::from_utf8(buf) {
90 Ok(s) => s,
91 Err(e) => {
92 tracing::warn!("Invalid UTF-8 in protocol data: {}", e);
93 String::new() }
95 }
96}
97
98pub fn build_smart_reply(
102 transport_protocol: TransportProtocol,
103 ref_list: &[String],
104 service: String,
105) -> BytesMut {
106 let mut pkt_line_stream = BytesMut::new();
107 if transport_protocol == TransportProtocol::Http {
108 add_pkt_line_string(&mut pkt_line_stream, format!("# service={service}\n"));
109 pkt_line_stream.put(&PKT_LINE_END_MARKER[..]);
110 }
111
112 for ref_line in ref_list {
113 add_pkt_line_string(&mut pkt_line_stream, ref_line.to_string());
114 }
115 pkt_line_stream.put(&PKT_LINE_END_MARKER[..]);
116 pkt_line_stream
117}
118
119pub fn search_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
121 haystack
122 .windows(needle.len())
123 .position(|window| window == needle)
124}
125
126#[cfg(test)]
127mod tests {
128 use bytes::Bytes;
129
130 use super::*;
131
132 #[test]
134 fn read_pkt_line_incomplete_does_not_consume() {
135 let mut buf = Bytes::from_static(b"0009do");
136 let before = buf.len();
137 let (len, data) = read_pkt_line(&mut buf);
138 assert_eq!(len, 0);
139 assert!(data.is_empty());
140 assert_eq!(buf.len(), before);
141 }
142}