1use super::{Protocol, Reference, References};
3use git_zero_core::{
4 Error, ObjectFormat, ObjectId, Result,
5 io::{SliceWriter, Write},
6 pack,
7 pktline::{self, Packet},
8};
9
10pub struct SmartAdvertisement<'a> {
13 bytes: &'a [u8],
14 body_offset: usize,
15 head: Option<ObjectId>,
16 capabilities: &'a [u8],
17 format: Option<ObjectFormat>,
18}
19
20impl<'a> SmartAdvertisement<'a> {
21 pub fn parse(data: &'a [u8]) -> Result<Self> {
22 let body = advertisement_body(data)?;
23 let mut first = body;
24 let capabilities = match pktline::read(&mut first)? {
25 Some(Packet::Data(line)) => line.splitn(2, |&b| b == 0).nth(1).unwrap_or_default(),
26 _ => &[],
27 };
28 let mut head = None;
29 let mut refs = References {
30 protocol: Protocol::Smart,
31 bytes: body,
32 failed: false,
33 format: None,
34 };
35 for entry in &mut refs {
36 let entry = entry?;
37 if entry.name == "HEAD" && head.replace(entry.id).is_some() {
38 return Err(Error::InvalidReference);
39 }
40 }
41 Ok(Self {
42 bytes: data,
43 body_offset: data.len() - body.len(),
44 head,
45 capabilities,
46 format: refs.format,
47 })
48 }
49
50 pub fn head(&self) -> Result<ObjectId> {
51 self.head.ok_or(Error::ObjectNotFound)
52 }
53 pub fn resolve_reference(&self, name: &str) -> Result<ObjectId> {
55 if name == "HEAD" {
56 return self.head();
57 }
58 git_zero_core::repository::validate_ref_name(name)?;
59 let mut found = None;
60 for entry in self.references() {
61 let entry = entry?;
62 if entry.name == name && found.replace(entry.id).is_some() {
63 return Err(Error::InvalidReference);
64 }
65 }
66 found.ok_or(Error::ObjectNotFound)
67 }
68 pub fn object_format(&self) -> Option<ObjectFormat> {
69 self.format
70 }
71 pub fn supports_shallow(&self) -> bool {
72 self.supports("shallow")
73 }
74 pub fn supports(&self, capability: &str) -> bool {
76 self.capabilities
77 .split(|b| b.is_ascii_whitespace())
78 .any(|flag| flag == capability.as_bytes())
79 }
80 pub fn fetch_options(&self, shallow_depth: Option<u32>) -> FetchOptions {
83 FetchOptions {
84 shallow_depth: shallow_depth.filter(|_| self.supports_shallow()),
85 ofs_delta: self.supports("ofs-delta"),
86 no_progress: self.supports("no-progress"),
87 }
88 }
89 pub fn validate_want(&self, id: ObjectId) -> Result<()> {
93 if self.format.is_some_and(|format| format != id.format()) {
94 return Err(Error::InvalidRequest);
95 }
96 if self.supports("allow-tip-sha1-in-want") || self.supports("allow-reachable-sha1-in-want")
97 {
98 return Ok(());
99 }
100 for reference in self.references() {
101 if reference?.id == id {
102 return Ok(());
103 }
104 }
105 Err(Error::InvalidRequest)
106 }
107 pub fn references(&self) -> References<'a> {
108 References {
109 protocol: Protocol::Smart,
110 bytes: &self.bytes[self.body_offset..],
111 failed: false,
112 format: self.format,
113 }
114 }
115}
116
117pub(super) fn advertisement_body(mut data: &[u8]) -> Result<&[u8]> {
118 if !matches!(
119 pktline::read(&mut data)?,
120 Some(Packet::Data(b"# service=git-upload-pack\n"))
121 ) || !matches!(pktline::read(&mut data)?, Some(Packet::Flush))
122 {
123 return Err(Error::InvalidPacket);
124 }
125 Ok(data)
126}
127
128pub(super) fn next_reference<'a>(
129 bytes: &mut &'a [u8],
130 format: &mut Option<ObjectFormat>,
131) -> Result<Option<Reference<'a>>> {
132 loop {
133 match pktline::read(bytes)? {
134 Some(Packet::Data(line)) => {
135 let mut parser = References {
136 protocol: Protocol::Smart,
137 bytes: &[],
138 failed: false,
139 format: *format,
140 };
141 let reference = parser.parse(line)?;
142 *format = parser.format;
143 if reference.id.is_zero() && reference.name == "capabilities^{}" {
144 continue;
145 }
146 if reference.name != "HEAD" {
147 git_zero_core::repository::validate_ref_name(
148 reference.name.strip_suffix("^{}").unwrap_or(reference.name),
149 )?;
150 }
151 return Ok(Some(reference));
152 }
153 Some(Packet::Flush) if bytes.is_empty() => return Ok(None),
154 _ => return Err(Error::InvalidPacket),
155 }
156 }
157}
158
159pub fn read_smart_reference<'a>(
164 data: &'a [u8],
165 offset: &mut usize,
166) -> Result<Option<Reference<'a>>> {
167 if *offset == data.len() && *offset != 0 {
168 return Ok(None);
169 }
170 let body = advertisement_body(data)?;
171 let mut first = body;
172 let mut parser = References {
173 protocol: Protocol::Smart,
174 bytes: &[],
175 failed: false,
176 format: None,
177 };
178 if let Some(Packet::Data(line)) = pktline::read(&mut first)? {
179 parser.parse(line)?;
180 }
181 let mut bytes = if *offset == 0 {
182 body
183 } else {
184 data.get(*offset..).ok_or(Error::InvalidPacket)?
185 };
186 let entry = next_reference(&mut bytes, &mut parser.format)?;
187 *offset = data.len() - bytes.len();
188 Ok(entry)
189}
190
191#[derive(Clone, Copy, Debug, Default)]
192pub struct FetchOptions {
193 pub shallow_depth: Option<u32>,
196 pub ofs_delta: bool,
197 pub no_progress: bool,
198}
199
200pub fn write_fetch_request(
204 wants: &[ObjectId],
205 options: FetchOptions,
206 output: &mut impl Write,
207) -> Result<()> {
208 write_fetch_request_iter(wants.iter().copied(), options, output)
209}
210
211pub fn write_fetch_request_iter(
215 wants: impl Iterator<Item = ObjectId> + Clone,
216 options: FetchOptions,
217 output: &mut impl Write,
218) -> Result<()> {
219 let format = wants.clone().next().ok_or(Error::InvalidRequest)?.format();
220 if options.shallow_depth == Some(0) || wants.clone().any(|want| want.format() != format) {
221 return Err(Error::InvalidRequest);
222 }
223 for (position, want) in wants.clone().enumerate() {
224 if wants
225 .clone()
226 .take(position)
227 .any(|previous| previous == want)
228 {
229 continue;
230 }
231 let mut storage = [0; 128];
232 let mut line = SliceWriter::new(&mut storage);
233 line.write_all(b"want ")?;
234 line.write_all(&want.to_hex())?;
235 if position == 0 {
236 if format == ObjectFormat::Sha256 {
237 line.write_all(b" ")?;
238 line.write_all(format.capability())?;
239 }
240 if options.ofs_delta {
241 line.write_all(b" ofs-delta")?;
242 }
243 if options.no_progress {
244 line.write_all(b" no-progress")?;
245 }
246 if options.shallow_depth.is_some() {
247 line.write_all(b" shallow")?;
248 }
249 }
250 line.write_all(b"\n")?;
251 pktline::write(output, line.as_bytes())?;
252 }
253 if let Some(depth) = options.shallow_depth {
254 use core::fmt::Write as _;
255 let mut storage = [0; 32];
256 let mut line = SliceWriter::new(&mut storage);
257 writeln!(&mut line, "deepen {depth}").map_err(|_| Error::BufferTooSmall)?;
258 pktline::write(output, line.as_bytes())?;
259 }
260 output.write_all(b"0000")?;
261 pktline::write(output, b"done\n")
262}
263
264pub fn read_pack_response(bytes: &[u8]) -> Result<&[u8]> {
267 read_pack_response_with_format(bytes, ObjectFormat::Sha1)
268}
269
270pub fn read_pack_response_with_format(mut bytes: &[u8], format: ObjectFormat) -> Result<&[u8]> {
273 loop {
274 match pktline::read(&mut bytes)? {
275 Some(Packet::Data(b"NAK\n")) => break,
276 Some(Packet::Data(line)) if line.starts_with(b"shallow ") => {
277 if ObjectId::from_hex(&line.strip_suffix(b"\n").unwrap_or(line)[8..])?.format()
278 != format
279 {
280 return Err(Error::InvalidRequest);
281 }
282 }
283 Some(Packet::Flush) => {}
284 _ => return Err(Error::InvalidPacket),
285 }
286 }
287 pack::validate_with_format(bytes, format)?;
288 Ok(bytes)
289}