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