git_internal/protocol/
utils.rs

1use bytes::{Buf, BufMut, Bytes, BytesMut};
2
3use super::types::{PKT_LINE_END_MARKER, TransportProtocol};
4
5/// Read a packet line from the given bytes buffer
6///
7/// Returns a tuple of (bytes_consumed, packet_data)
8///
9/// This is the original simple implementation from ceres
10pub fn read_pkt_line(bytes: &mut Bytes) -> (usize, Bytes) {
11    if bytes.is_empty() {
12        return (0, Bytes::new());
13    }
14
15    // Ensure we have at least 4 bytes for the length prefix
16    if bytes.len() < 4 {
17        return (0, Bytes::new());
18    }
19
20    let pkt_length = bytes.copy_to_bytes(4);
21    let pkt_length_str = match core::str::from_utf8(&pkt_length) {
22        Ok(s) => s,
23        Err(_) => {
24            tracing::warn!("Invalid UTF-8 in packet length: {:?}", pkt_length);
25            return (0, Bytes::new());
26        }
27    };
28
29    let pkt_length = match usize::from_str_radix(pkt_length_str, 16) {
30        Ok(len) => len,
31        Err(_) => {
32            tracing::warn!("Invalid hex packet length: {:?}", pkt_length_str);
33            return (0, Bytes::new());
34        }
35    };
36
37    if pkt_length == 0 {
38        return (4, Bytes::new()); // Consumed 4 bytes for the "0000" marker
39    }
40
41    if pkt_length < 4 {
42        tracing::warn!("Invalid packet length: {} (must be >= 4)", pkt_length);
43        return (0, Bytes::new());
44    }
45
46    let data_length = pkt_length - 4;
47    if bytes.len() < data_length {
48        tracing::warn!(
49            "Insufficient data: need {} bytes, have {}",
50            data_length,
51            bytes.len()
52        );
53        return (0, Bytes::new());
54    }
55
56    // this operation will change the original bytes
57    let pkt_line = bytes.copy_to_bytes(data_length);
58    tracing::debug!("pkt line: {:?}", pkt_line);
59
60    (pkt_length, pkt_line)
61}
62
63/// Add a packet line string to the buffer with proper length prefix
64///
65/// This is the original simple implementation from ceres
66pub fn add_pkt_line_string(pkt_line_stream: &mut BytesMut, buf_str: String) {
67    let buf_str_length = buf_str.len() + 4;
68    pkt_line_stream.put(Bytes::from(format!("{buf_str_length:04x}")));
69    pkt_line_stream.put(buf_str.as_bytes());
70}
71
72/// Read until whitespace and return the extracted string
73///
74/// This is the original implementation from ceres
75pub fn read_until_white_space(bytes: &mut Bytes) -> String {
76    let mut buf = Vec::new();
77    while bytes.has_remaining() {
78        let c = bytes.get_u8();
79        if c.is_ascii_whitespace() || c == 0 {
80            break;
81        }
82        buf.push(c);
83    }
84    match String::from_utf8(buf) {
85        Ok(s) => s,
86        Err(e) => {
87            tracing::warn!("Invalid UTF-8 in protocol data: {}", e);
88            String::new() // Return empty string on invalid UTF-8
89        }
90    }
91}
92
93pub fn build_smart_reply(
94    transport_protocol: TransportProtocol,
95    ref_list: &[String],
96    service: String,
97) -> BytesMut {
98    let mut pkt_line_stream = BytesMut::new();
99    if transport_protocol == TransportProtocol::Http {
100        add_pkt_line_string(&mut pkt_line_stream, format!("# service={service}\n"));
101        pkt_line_stream.put(&PKT_LINE_END_MARKER[..]);
102    }
103
104    for ref_line in ref_list {
105        add_pkt_line_string(&mut pkt_line_stream, ref_line.to_string());
106    }
107    pkt_line_stream.put(&PKT_LINE_END_MARKER[..]);
108    pkt_line_stream
109}
110
111/// Search for a subsequence in a byte slice
112pub fn search_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
113    haystack
114        .windows(needle.len())
115        .position(|window| window == needle)
116}