gix_protocol/fetch/response/io.rs
1use std::io;
2
3#[crate::bisync::only_async]
4use crate::transport::client::async_io::ExtendedBufRead;
5#[crate::bisync::only_sync]
6use crate::transport::client::blocking_io::ExtendedBufRead;
7use gix_transport::{Protocol, client::MessageKind};
8
9use crate::fetch::{
10 Response, response,
11 response::{Acknowledgement, ShallowUpdate, WantedRef, shallow_update_from_line},
12};
13
14#[crate::bisync::bisync]
15async fn parse_v2_section<'a, T>(
16 line: &mut String,
17 reader: &mut impl ExtendedBufRead<'a>,
18 res: &mut Vec<T>,
19 parse: impl Fn(&str) -> Result<T, response::Error>,
20) -> Result<bool, response::Error> {
21 line.clear();
22 while reader.readline_str(line).await? != 0 {
23 res.push(parse(line)?);
24 line.clear();
25 }
26 // End of message, or end of section?
27 Ok(if reader.stopped_at() == Some(MessageKind::Delimiter) {
28 // try reading more sections
29 reader.reset(Protocol::V2);
30 false
31 } else {
32 // we are done, there is no pack
33 true
34 })
35}
36
37impl Response {
38 /// Parse a response of the given `version` of the protocol from `reader`.
39 ///
40 /// `client_expects_pack` is only relevant for V1 stateful connections, and if `false`, causes us to stop parsing when seeing `NAK`,
41 /// and if `true` we will keep parsing until we get a pack as the client already signalled to the server that it's done.
42 /// This way of doing things allows us to exploit knowledge about more recent versions of the protocol, which keeps code easier
43 /// and more localized without having to support all the cruft that there is.
44 ///
45 /// `wants_to_negotiate` should be `false` for clones which is when we don't have sent any haves. The reason for this flag to exist
46 /// is to predict how to parse V1 output only, and neither `client_expects_pack` nor `wants_to_negotiate` are relevant for V2.
47 /// This ugliness is in place to avoid having to resort to an [an even more complex ugliness](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594)
48 /// that `git` has to use to predict how many acks are supposed to be read. We also genuinely hope that this covers it all….
49 #[crate::bisync::bisync]
50 pub async fn from_line_reader<'a>(
51 version: Protocol,
52 reader: &mut impl ExtendedBufRead<'a>,
53 client_expects_pack: bool,
54 wants_to_negotiate: bool,
55 ) -> Result<Response, response::Error> {
56 match version {
57 Protocol::V0 | Protocol::V1 => {
58 let mut line = String::new();
59 let mut acks = Vec::<Acknowledgement>::new();
60 let mut shallows = Vec::<ShallowUpdate>::new();
61 let mut saw_ready = false;
62 let has_pack = 'lines: loop {
63 line.clear();
64 let peeked_line = match reader.peek_data_line().await {
65 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
66 // This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes
67 // Note that this might block forever in stateful connections as there it's not really clear
68 // if something will be following or not by just looking at the response. Instead you have to know
69 // [a lot](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594)
70 // to deal with this correctly.
71 // For now this is acceptable, as V2 can be used as a workaround, which also is the default.
72 Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false,
73 Some(Err(err)) => return Err(err.into()),
74 Some(Ok(Err(err))) => return Err(err.into()),
75 None => {
76 // maybe we saw a shallow flush packet, let's reset and retry
77 debug_assert_eq!(
78 reader.stopped_at(),
79 Some(MessageKind::Flush),
80 "If this isn't a flush packet, we don't know what's going on"
81 );
82 reader.readline_str(&mut line).await?;
83 reader.reset(Protocol::V1);
84 match reader.peek_data_line().await {
85 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
86 Some(Err(err)) => return Err(err.into()),
87 Some(Ok(Err(err))) => return Err(err.into()),
88 None => break 'lines false, // EOF
89 }
90 }
91 };
92
93 if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
94 break 'lines true;
95 }
96 assert_ne!(
97 reader.readline_str(&mut line).await?,
98 0,
99 "consuming a peeked line works"
100 );
101 // When the server sends ready, we know there is going to be a pack so no need to stop early.
102 saw_ready |= matches!(acks.last(), Some(Acknowledgement::Ready));
103 if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack || !saw_ready) {
104 if !wants_to_negotiate {
105 continue;
106 }
107 break 'lines false;
108 }
109 };
110 Ok(Response {
111 acks,
112 shallows,
113 wanted_refs: vec![],
114 has_pack,
115 })
116 }
117 Protocol::V2 => {
118 // NOTE: We only read acknowledgements and scrub to the pack file, until we have use for the other features
119 let mut line = String::new();
120 reader.reset(Protocol::V2);
121 let mut acks = Vec::<Acknowledgement>::new();
122 let mut shallows = Vec::<ShallowUpdate>::new();
123 let mut wanted_refs = Vec::<WantedRef>::new();
124 let has_pack = 'section: loop {
125 line.clear();
126 if reader.readline_str(&mut line).await? == 0 {
127 return Err(response::Error::Io(io::Error::new(
128 io::ErrorKind::UnexpectedEof,
129 "Could not read message headline",
130 )));
131 }
132
133 match line.trim_end() {
134 "acknowledgments" => {
135 if parse_v2_section(&mut line, reader, &mut acks, Acknowledgement::from_line).await? {
136 break 'section false;
137 }
138 }
139 "shallow-info" => {
140 if parse_v2_section(&mut line, reader, &mut shallows, shallow_update_from_line).await? {
141 break 'section false;
142 }
143 }
144 "wanted-refs" => {
145 if parse_v2_section(&mut line, reader, &mut wanted_refs, WantedRef::from_line).await? {
146 break 'section false;
147 }
148 }
149 "packfile" => {
150 // what follows is the packfile itself, which can be read with a sideband enabled reader
151 break 'section true;
152 }
153 _ => return Err(response::Error::UnknownSectionHeader { header: line }),
154 }
155 };
156 Ok(Response {
157 acks,
158 shallows,
159 wanted_refs,
160 has_pack,
161 })
162 }
163 }
164 }
165}