1use alloc::vec::Vec;
11
12use crate::err::{Error, Result};
13use crate::oid::Oid;
14use crate::pkt::{self, Pkt, PktReader};
15
16#[derive(Debug, Default, Clone, PartialEq, Eq)]
18pub struct Advertisement {
19 pub ls_refs: bool,
20 pub fetch: bool,
21 pub fetch_shallow: bool,
23}
24
25pub fn parse_advertisement(body: &[u8]) -> Result<Advertisement> {
27 let mut r = PktReader::new(body);
28
29 let mut first = r.expect_next()?;
31 if let Pkt::Data(data) = first
32 && pkt::trim_line(data).starts_with(b"# service=")
33 {
34 loop {
35 match r.expect_next()? {
36 Pkt::Flush => break,
37 Pkt::Data(_) => {}
38 _ => return Err(Error::Corrupt("service announcement")),
39 }
40 }
41 first = r.expect_next()?;
42 }
43
44 match first {
45 Pkt::Data(data) if pkt::trim_line(data) == b"version 2" => {}
46 _ => return Err(Error::Unsupported("protocol version (v2 required)")),
47 }
48
49 let mut adv = Advertisement::default();
50 loop {
51 match r.expect_next()? {
52 Pkt::Flush => return Ok(adv),
53 Pkt::Data(data) => {
54 let line = pkt::trim_line(data);
55 let (name, value) = match line.iter().position(|&b| b == b'=') {
56 Some(eq) => (&line[..eq], &line[eq + 1..]),
57 None => (line, &b""[..]),
58 };
59 match name {
60 b"ls-refs" => adv.ls_refs = true,
61 b"fetch" => {
62 adv.fetch = true;
63 adv.fetch_shallow = value.split(|&b| b == b' ').any(|f| f == b"shallow");
64 }
65 _ => {}
66 }
67 }
68 _ => return Err(Error::Corrupt("capability advertisement")),
69 }
70 }
71}
72
73pub fn ls_refs_request(prefixes: &[&[u8]]) -> Vec<u8> {
78 let mut out = Vec::new();
79 pkt::write_line(&mut out, b"command=ls-refs");
80 pkt::write_delim(&mut out);
81 pkt::write_line(&mut out, b"peel");
82 pkt::write_line(&mut out, b"symrefs");
83 for prefix in prefixes {
84 let mut line = Vec::with_capacity(11 + prefix.len());
85 line.extend_from_slice(b"ref-prefix ");
86 line.extend_from_slice(prefix);
87 pkt::write_line(&mut out, &line);
88 }
89 pkt::write_flush(&mut out);
90 out
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct RefEntry {
95 pub name: Vec<u8>,
96 pub oid: Oid,
97 pub peeled: Option<Oid>,
99 pub symref_target: Option<Vec<u8>>,
101}
102
103pub fn parse_ls_refs(body: &[u8]) -> Result<Vec<RefEntry>> {
105 let mut r = PktReader::new(body);
106 let mut refs = Vec::new();
107 loop {
108 match r.expect_next()? {
109 Pkt::Flush | Pkt::ResponseEnd => return Ok(refs),
110 Pkt::Data(data) => {
111 let line = pkt::trim_line(data);
112 let mut fields = line.split(|&b| b == b' ');
113 let oid_hex = fields.next().ok_or(Error::Corrupt("ls-refs line"))?;
114 let name = fields.next().ok_or(Error::Corrupt("ls-refs line"))?;
115 let mut entry = RefEntry {
116 name: name.to_vec(),
117 oid: Oid::from_hex(oid_hex)?,
118 peeled: None,
119 symref_target: None,
120 };
121 for attr in fields {
122 if let Some(v) = attr.strip_prefix(b"peeled:") {
123 entry.peeled = Some(Oid::from_hex(v)?);
124 } else if let Some(v) = attr.strip_prefix(b"symref-target:") {
125 entry.symref_target = Some(v.to_vec());
126 }
127 }
128 refs.push(entry);
129 }
130 _ => return Err(Error::Corrupt("ls-refs response")),
131 }
132 }
133}
134
135pub fn fetch_request(wants: &[Oid], depth: Option<u32>) -> Vec<u8> {
141 let mut out = Vec::new();
142 pkt::write_line(&mut out, b"command=fetch");
143 pkt::write_delim(&mut out);
144 pkt::write_line(&mut out, b"no-progress");
145 pkt::write_line(&mut out, b"ofs-delta");
146 if let Some(depth) = depth {
147 let mut line = Vec::new();
148 line.extend_from_slice(b"deepen ");
149 push_decimal(&mut line, depth);
150 pkt::write_line(&mut out, &line);
151 }
152 for want in wants {
153 let mut line = Vec::with_capacity(45);
154 line.extend_from_slice(b"want ");
155 push_hex(&mut line, want);
156 pkt::write_line(&mut out, &line);
157 }
158 pkt::write_line(&mut out, b"done");
159 pkt::write_flush(&mut out);
160 out
161}
162
163#[derive(Debug, Default, PartialEq, Eq)]
164pub struct FetchResponse {
165 pub shallow: Vec<Oid>,
167 pub pack: Vec<u8>,
168}
169
170pub fn parse_fetch_response(body: &[u8]) -> Result<FetchResponse> {
172 let mut r = PktReader::new(body);
173 let mut resp = FetchResponse::default();
174
175 loop {
177 match r.expect_next()? {
178 Pkt::Flush | Pkt::ResponseEnd => {
179 if resp.pack.is_empty() {
180 return Err(Error::Corrupt("fetch response without packfile"));
181 }
182 return Ok(resp);
183 }
184 Pkt::Data(data) => match pkt::trim_line(data) {
185 b"shallow-info" => parse_shallow_info(&mut r, &mut resp)?,
186 b"acknowledgments" => skip_section(&mut r)?,
187 b"packfile" => {
188 parse_packfile_section(&mut r, &mut resp)?;
189 return Ok(resp);
190 }
191 _ => return Err(Error::Unsupported("fetch response section")),
192 },
193 _ => return Err(Error::Corrupt("fetch response")),
194 }
195 }
196}
197
198fn parse_shallow_info(r: &mut PktReader<'_>, resp: &mut FetchResponse) -> Result<()> {
199 loop {
200 match r.expect_next()? {
201 Pkt::Delim => return Ok(()),
202 Pkt::Flush | Pkt::ResponseEnd => return Err(Error::Corrupt("shallow-info section")),
203 Pkt::Data(data) => {
204 let line = pkt::trim_line(data);
205 if let Some(v) = line.strip_prefix(b"shallow ") {
206 resp.shallow.push(Oid::from_hex(v)?);
207 }
208 }
210 }
211 }
212}
213
214fn skip_section(r: &mut PktReader<'_>) -> Result<()> {
215 loop {
216 match r.expect_next()? {
217 Pkt::Delim => return Ok(()),
218 Pkt::Flush | Pkt::ResponseEnd => return Err(Error::Corrupt("truncated section")),
219 _ => {}
220 }
221 }
222}
223
224fn parse_packfile_section(r: &mut PktReader<'_>, resp: &mut FetchResponse) -> Result<()> {
225 loop {
226 match r.expect_next()? {
227 Pkt::Flush | Pkt::ResponseEnd => {
228 if resp.pack.is_empty() {
229 return Err(Error::Corrupt("empty packfile section"));
230 }
231 return Ok(());
232 }
233 Pkt::Data(data) => {
234 let (&band, payload) = data.split_first().ok_or(Error::Corrupt("sideband pkt"))?;
235 match band {
236 1 => resp.pack.extend_from_slice(payload),
237 2 => {} 3 => return Err(Error::Corrupt("remote error (sideband 3)")),
239 _ => return Err(Error::Corrupt("sideband channel")),
240 }
241 }
242 _ => return Err(Error::Corrupt("packfile section")),
243 }
244 }
245}
246
247fn push_decimal(out: &mut Vec<u8>, mut value: u32) {
248 let mut digits = [0u8; 10];
249 let mut n = 0;
250 loop {
251 digits[n] = b'0' + (value % 10) as u8;
252 value /= 10;
253 n += 1;
254 if value == 0 {
255 break;
256 }
257 }
258 for i in (0..n).rev() {
259 out.push(digits[i]);
260 }
261}
262
263fn push_hex(out: &mut Vec<u8>, oid: &Oid) {
264 const HEX: &[u8; 16] = b"0123456789abcdef";
265 for &b in oid.as_bytes() {
266 out.push(HEX[usize::from(b >> 4)]);
267 out.push(HEX[usize::from(b & 0xf)]);
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 fn oid(n: u8) -> Oid {
276 Oid::from_bytes([n; 20])
277 }
278
279 fn adv_body(with_service: bool, caps: &[&[u8]]) -> Vec<u8> {
280 let mut b = Vec::new();
281 if with_service {
282 pkt::write_line(&mut b, b"# service=git-upload-pack");
283 pkt::write_flush(&mut b);
284 }
285 pkt::write_line(&mut b, b"version 2");
286 for c in caps {
287 pkt::write_line(&mut b, c);
288 }
289 pkt::write_flush(&mut b);
290 b
291 }
292
293 #[test]
294 fn advertisement_with_service_prefix() {
295 let body = adv_body(
296 true,
297 &[
298 b"agent=git/2.54.0",
299 b"ls-refs=unborn",
300 b"fetch=shallow wait-for-done",
301 ],
302 );
303 let adv = parse_advertisement(&body).unwrap();
304 assert!(adv.ls_refs && adv.fetch && adv.fetch_shallow);
305 }
306
307 #[test]
308 fn advertisement_without_service_prefix() {
309 let body = adv_body(false, &[b"ls-refs", b"fetch"]);
310 let adv = parse_advertisement(&body).unwrap();
311 assert!(adv.ls_refs && adv.fetch && !adv.fetch_shallow);
312 }
313
314 #[test]
315 fn advertisement_v1_rejected() {
316 let mut body = Vec::new();
317 pkt::write_line(&mut body, b"version 1");
318 pkt::write_flush(&mut body);
319 assert!(parse_advertisement(&body).is_err());
320 }
321
322 #[test]
323 fn ls_refs_request_encoding() {
324 let body = ls_refs_request(&[b"refs/heads/"]);
325 let expected =
326 b"0014command=ls-refs\n00010009peel\n000csymrefs\n001bref-prefix refs/heads/\n0000";
327 assert_eq!(body, expected);
328 }
329
330 #[test]
331 fn ls_refs_response_parse() {
332 let hex1 = "1111111111111111111111111111111111111111";
333 let hex2 = "2222222222222222222222222222222222222222";
334 let mut body = Vec::new();
335 pkt::write_line(
336 &mut body,
337 format!("{hex1} HEAD symref-target:refs/heads/main").as_bytes(),
338 );
339 pkt::write_line(
340 &mut body,
341 format!("{hex1} refs/tags/v1 peeled:{hex2}").as_bytes(),
342 );
343 pkt::write_flush(&mut body);
344
345 let refs = parse_ls_refs(&body).unwrap();
346 assert_eq!(refs.len(), 2);
347 assert_eq!(refs[0].name, b"HEAD");
348 assert_eq!(
349 refs[0].symref_target.as_deref(),
350 Some(&b"refs/heads/main"[..])
351 );
352 assert_eq!(refs[1].oid, oid(0x11));
353 assert_eq!(refs[1].peeled, Some(oid(0x22)));
354 }
355
356 #[test]
357 fn fetch_request_contents() {
358 let body = fetch_request(&[oid(0xab)], Some(1));
359 let text = String::from_utf8_lossy(&body);
360 assert!(text.contains("command=fetch"));
361 assert!(text.contains("deepen 1"));
362 assert!(text.contains(&format!("want {}", oid(0xab))));
363 assert!(text.contains("done"));
364 }
365
366 #[test]
367 fn fetch_response_sideband() {
368 let mut body = Vec::new();
369 pkt::write_line(&mut body, b"shallow-info");
370 pkt::write_line(
371 &mut body,
372 b"shallow 1111111111111111111111111111111111111111",
373 );
374 pkt::write_delim(&mut body);
375 pkt::write_line(&mut body, b"packfile");
376 pkt::write_data(&mut body, &[1, b'P', b'A']);
377 pkt::write_data(&mut body, &[2, b'p', b'r', b'o', b'g']);
378 pkt::write_data(&mut body, &[1, b'C', b'K']);
379 pkt::write_flush(&mut body);
380
381 let resp = parse_fetch_response(&body).unwrap();
382 assert_eq!(resp.shallow, alloc::vec![oid(0x11)]);
383 assert_eq!(resp.pack, b"PACK");
384 }
385
386 #[test]
387 fn fetch_response_error_band() {
388 let mut body = Vec::new();
389 pkt::write_line(&mut body, b"packfile");
390 pkt::write_data(&mut body, &[3, b'n', b'g']);
391 pkt::write_flush(&mut body);
392 assert!(parse_fetch_response(&body).is_err());
393 }
394}