1use bstr::{BStr, BString, ByteSlice};
2
3#[cfg(any(feature = "blocking-client", feature = "async-client"))]
4use crate::client;
5use crate::Protocol;
6
7#[derive(Debug, thiserror::Error)]
9#[allow(missing_docs)]
10pub enum Error {
11 #[error("Capabilities were missing entirely as there was no 0 byte")]
12 MissingDelimitingNullByte,
13 #[error("there was not a single capability behind the delimiter")]
14 NoCapabilities,
15 #[error("a version line was expected, but none was retrieved")]
16 MissingVersionLine,
17 #[error("expected 'version X', got {0:?}")]
18 MalformattedVersionLine(BString),
19 #[error("Got unsupported version {actual:?}, expected {}", *desired as u8)]
20 UnsupportedVersion { desired: Protocol, actual: BString },
21 #[error("An IO error occurred while reading V2 lines")]
22 Io(#[from] std::io::Error),
23}
24
25#[derive(Debug, Clone)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct Capabilities {
34 data: BString,
35 value_sep: u8,
36}
37
38impl Default for Capabilities {
43 fn default() -> Self {
44 Capabilities::from_lines("version 2\nmulti_ack_detailed\nside-band-64k\n".into())
45 .expect("valid format, known at compile time")
46 }
47}
48
49pub struct Capability<'a>(&'a BStr);
51
52impl<'a> Capability<'a> {
53 pub fn name(&self) -> &'a BStr {
57 self.0
58 .splitn(2, |b| *b == b'=')
59 .next()
60 .expect("there is always a single item")
61 .as_bstr()
62 }
63 pub fn value(&self) -> Option<&'a BStr> {
68 self.0.splitn(2, |b| *b == b'=').nth(1).map(ByteSlice::as_bstr)
69 }
70 pub fn values(&self) -> Option<impl Iterator<Item = &'a BStr>> {
72 self.value().map(|v| v.split(|b| *b == b' ').map(ByteSlice::as_bstr))
73 }
74 pub fn supports(&self, want: impl Into<&'a BStr>) -> Option<bool> {
76 let want = want.into();
77 self.values().map(|mut iter| iter.any(|v| v == want))
78 }
79}
80
81impl Capabilities {
82 pub fn from_bytes(bytes: &[u8]) -> Result<(Capabilities, usize), Error> {
86 let delimiter_pos = bytes.find_byte(0).ok_or(Error::MissingDelimitingNullByte)?;
87 if delimiter_pos + 1 == bytes.len() {
88 return Err(Error::NoCapabilities);
89 }
90 let capabilities = &bytes[delimiter_pos + 1..];
91 Ok((
92 Capabilities {
93 data: capabilities.as_bstr().to_owned(),
94 value_sep: b' ',
95 },
96 delimiter_pos,
97 ))
98 }
99
100 pub fn from_lines(lines_buf: BString) -> Result<Capabilities, Error> {
107 let mut lines = <_ as bstr::ByteSlice>::lines(lines_buf.as_slice().trim());
108 let version_line = lines.next().ok_or(Error::MissingVersionLine)?;
109 let (name, value) = version_line.split_at(
110 version_line
111 .find(b" ")
112 .ok_or_else(|| Error::MalformattedVersionLine(version_line.to_owned().into()))?,
113 );
114 if name != b"version" {
115 return Err(Error::MalformattedVersionLine(version_line.to_owned().into()));
116 }
117 if value != b" 2" {
118 return Err(Error::UnsupportedVersion {
119 desired: Protocol::V2,
120 actual: value.to_owned().into(),
121 });
122 }
123 Ok(Capabilities {
124 value_sep: b'\n',
125 data: lines.as_bytes().into(),
126 })
127 }
128
129 pub fn contains(&self, feature: &str) -> bool {
131 self.capability(feature).is_some()
132 }
133
134 pub fn capability(&self, name: &str) -> Option<Capability<'_>> {
136 self.iter().find(|c| c.name() == name.as_bytes().as_bstr())
137 }
138
139 pub fn iter(&self) -> impl Iterator<Item = Capability<'_>> {
141 self.data
142 .split(move |b| *b == self.value_sep)
143 .map(|c| Capability(c.as_bstr()))
144 }
145}
146
147#[cfg(any(feature = "blocking-client", feature = "async-client"))]
149impl Capabilities {
150 fn extract_protocol(capabilities_or_version: gix_packetline::TextRef<'_>) -> Result<Protocol, client::Error> {
151 let line = capabilities_or_version.as_bstr();
152 let version = if line.starts_with_str("version ") {
153 if line.len() != "version X".len() {
154 return Err(client::Error::UnsupportedProtocolVersion(line.as_bstr().into()));
155 }
156 match line {
157 line if line.ends_with_str("1") => Protocol::V1,
158 line if line.ends_with_str("2") => Protocol::V2,
159 _ => return Err(client::Error::UnsupportedProtocolVersion(line.as_bstr().into())),
160 }
161 } else {
162 Protocol::V1
163 };
164 Ok(version)
165 }
166}
167
168#[cfg(feature = "blocking-client")]
170pub mod blocking_recv {
171 use std::io;
172
173 use bstr::ByteVec;
174
175 use crate::{
176 client::{self, blocking_io::ReadlineBufRead, Capabilities},
177 packetline::blocking_io::StreamingPeekableIter,
178 Protocol,
179 };
180
181 pub struct Handshake<'a> {
183 pub capabilities: Capabilities,
185 pub refs: Option<Box<dyn ReadlineBufRead + 'a>>,
190 pub protocol: Protocol,
192 }
193
194 impl Handshake<'_> {
195 pub fn from_lines_with_version_detection<T: io::Read>(
200 rd: &mut StreamingPeekableIter<T>,
201 ) -> Result<Handshake<'_>, client::Error> {
202 rd.fail_on_err_lines(true);
207
208 Ok(match rd.peek_line() {
209 Some(line) => {
210 let line = line??.as_text().ok_or(client::Error::ExpectedLine("text"))?;
211 let version = Capabilities::extract_protocol(line)?;
212 match version {
213 Protocol::V0 => unreachable!("already handled in `None` case"),
214 Protocol::V1 => {
215 let (capabilities, delimiter_position) = Capabilities::from_bytes(line.0)?;
216 rd.peek_buffer_replace_and_truncate(delimiter_position, b'\n');
217 Handshake {
218 capabilities,
219 refs: Some(Box::new(rd.as_read())),
220 protocol: Protocol::V1,
221 }
222 }
223 Protocol::V2 => Handshake {
224 capabilities: {
225 let mut rd = rd.as_read();
226 let mut buf = Vec::new();
227 while let Some(line) = rd.read_data_line() {
228 let line = line??;
229 match line.as_bstr() {
230 Some(line) => {
231 buf.push_str(line);
232 if buf.last() != Some(&b'\n') {
233 buf.push(b'\n');
234 }
235 }
236 None => break,
237 }
238 }
239 Capabilities::from_lines(buf.into())?
240 },
241 refs: None,
242 protocol: Protocol::V2,
243 },
244 }
245 }
246 None => Handshake {
247 capabilities: Capabilities::default(),
248 refs: Some(Box::new(rd.as_read())),
249 protocol: Protocol::V0,
250 },
251 })
252 }
253 }
254}
255
256#[cfg(feature = "async-client")]
258#[allow(missing_docs)]
259pub mod async_recv {
260 use bstr::ByteVec;
261 use futures_io::AsyncRead;
262
263 use crate::{
264 client::{self, async_io::ReadlineBufRead, Capabilities},
265 packetline::async_io::StreamingPeekableIter,
266 Protocol,
267 };
268
269 pub struct Handshake<'a> {
271 pub capabilities: Capabilities,
273 pub refs: Option<Box<dyn ReadlineBufRead + Unpin + 'a>>,
278 pub protocol: Protocol,
280 }
281
282 impl Handshake<'_> {
283 pub async fn from_lines_with_version_detection<T: AsyncRead + Unpin>(
288 rd: &mut StreamingPeekableIter<T>,
289 ) -> Result<Handshake<'_>, client::Error> {
290 rd.fail_on_err_lines(true);
295
296 Ok(match rd.peek_line().await {
297 Some(line) => {
298 let line = line??.as_text().ok_or(client::Error::ExpectedLine("text"))?;
299 let version = Capabilities::extract_protocol(line)?;
300 match version {
301 Protocol::V0 => unreachable!("already handled in `None` case"),
302 Protocol::V1 => {
303 let (capabilities, delimiter_position) = Capabilities::from_bytes(line.0)?;
304 rd.peek_buffer_replace_and_truncate(delimiter_position, b'\n');
305 Handshake {
306 capabilities,
307 refs: Some(Box::new(rd.as_read())),
308 protocol: Protocol::V1,
309 }
310 }
311 Protocol::V2 => Handshake {
312 capabilities: {
313 let mut rd = rd.as_read();
314 let mut buf = Vec::new();
315 while let Some(line) = rd.read_data_line().await {
316 let line = line??;
317 match line.as_bstr() {
318 Some(line) => {
319 buf.push_str(line);
320 if buf.last() != Some(&b'\n') {
321 buf.push(b'\n');
322 }
323 }
324 None => break,
325 }
326 }
327 Capabilities::from_lines(buf.into())?
328 },
329 refs: None,
330 protocol: Protocol::V2,
331 },
332 }
333 }
334 None => Handshake {
335 capabilities: Capabilities::default(),
336 refs: Some(Box::new(rd.as_read())),
337 protocol: Protocol::V0,
338 },
339 })
340 }
341 }
342}