1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
use crate::fetch::command::Feature;
use git_object::owned;
use git_transport::{client, Protocol};
use quick_error::quick_error;
use std::io;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        Io(err: io::Error) {
            display("Failed to read from line reader")
            from()
            source(err)
        }
        Transport(err: client::Error) {
            display("An error occurred when decoding a line")
            from()
            source(err)
        }
        MissingServerCapability(feature: &'static str) {
            display("Currently we require feature '{}', which is not supported by the server", feature)
        }
        UnknownLineType(line: String) {
            display("Encountered an unknown line prefix in '{}'", line)
        }
        UnknownSectionHeader(header: String) {
            display("Unknown or unsupported header: '{}'", header)
        }
    }
}

#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Acknowledgement {
    Common(owned::Id),
    Ready,
    NAK,
}

#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum ShallowUpdate {
    Shallow(owned::Id),
    Unshallow(owned::Id),
}

impl ShallowUpdate {
    fn from_line(line: &str) -> Result<ShallowUpdate, Error> {
        let mut tokens = line.trim_end().splitn(2, ' ');
        match (tokens.next(), tokens.next()) {
            (Some(prefix), Some(id)) => {
                let id = owned::Id::from_40_bytes_in_hex(id.as_bytes())
                    .map_err(|_| Error::UnknownLineType(line.to_owned()))?;
                Ok(match prefix {
                    "shallow" => ShallowUpdate::Shallow(id),
                    "unshallow" => ShallowUpdate::Unshallow(id),
                    _ => return Err(Error::UnknownLineType(line.to_owned())),
                })
            }
            _ => unreachable!("cannot have an entirely empty line"),
        }
    }
}

impl Acknowledgement {
    fn from_line(line: &str) -> Result<Acknowledgement, Error> {
        let mut tokens = line.trim_end().splitn(3, ' ');
        Ok(match (tokens.next(), tokens.next(), tokens.next()) {
            (Some(first), id, description) => match first {
                "ready" => Acknowledgement::Ready, // V2
                "NAK" => Acknowledgement::NAK,     // V1
                "ACK" => {
                    let id = match id {
                        Some(id) => owned::Id::from_40_bytes_in_hex(id.as_bytes())
                            .map_err(|_| Error::UnknownLineType(line.to_owned()))?,
                        None => return Err(Error::UnknownLineType(line.to_owned())),
                    };
                    if let Some(description) = description {
                        match description {
                            "common" => {}
                            "ready" => return Ok(Acknowledgement::Ready),
                            _ => return Err(Error::UnknownLineType(line.to_owned())),
                        }
                    }
                    Acknowledgement::Common(id)
                }
                _ => return Err(Error::UnknownLineType(line.to_owned())),
            },
            (None, _, _) => unreachable!("cannot have an entirely empty line"),
        })
    }
    pub fn id(&self) -> Option<&owned::Id> {
        match self {
            Acknowledgement::Common(id) => Some(id),
            _ => None,
        }
    }
}

/// A representation of a complete fetch response
pub struct Response {
    acks: Vec<Acknowledgement>,
    shallows: Vec<ShallowUpdate>,
    has_pack: bool,
}

impl Response {
    pub fn has_pack(&self) -> bool {
        self.has_pack
    }
    pub fn check_required_features(version: Protocol, features: &[Feature]) -> Result<(), Error> {
        match version {
            Protocol::V1 => {
                let has = |name: &str| features.iter().any(|f| f.0 == name);
                // Let's focus on V2 standards, and simply not support old servers to keep our code simpler
                if !has("multi_ack_detailed") {
                    return Err(Error::MissingServerCapability("multi_ack_detailed"));
                }
                // It's easy to NOT do sideband for us, but then again, everyone supports it.
                // CORRECTION: If side-band is off, it would send the packfile without packet line encoding,
                // which is nothing we ever want to deal with (despite it being more efficient). In V2, this
                // is not even an option anymore, sidebands are always present.
                if !has("side-band") && !has("side-band-64k") {
                    return Err(Error::MissingServerCapability("side-band OR side-band-64k"));
                }
            }
            Protocol::V2 => {}
        }
        Ok(())
    }
    pub fn from_line_reader(version: Protocol, reader: &mut impl client::ExtendedBufRead) -> Result<Response, Error> {
        match version {
            Protocol::V1 => {
                let mut line = String::new();
                let mut acks = Vec::<Acknowledgement>::new();
                let mut shallows = Vec::<ShallowUpdate>::new();
                let has_pack = 'lines: loop {
                    line.clear();
                    let peeked_line = match reader.peek_data_line() {
                        Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
                        // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes
                        // Note that this might block forever in stateful connections as there it's not really clear
                        // if something will be following or not by just looking at the response. Instead you have to know
                        // the arguments sent to the server and count response lines based on intricate knowledge on how the
                        // server works.
                        // For now this is acceptable, as V2 can be used as a workaround, which also is the default.
                        Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false,
                        Some(Err(err)) => return Err(err.into()),
                        Some(Ok(Err(err))) => return Err(err.into()),
                        None => {
                            // maybe we saw a shallow flush packet, let's reset and retry
                            reader.read_line(&mut line)?;
                            reader.reset(Protocol::V1);
                            match reader.peek_data_line() {
                                Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
                                Some(Err(err)) => return Err(err.into()),
                                Some(Ok(Err(err))) => return Err(err.into()),
                                None => break 'lines false, // EOF
                            }
                        }
                    };

                    // with a friendly server, we just assume that a non-ack line is a pack line
                    // which is our hint to stop here.
                    match Acknowledgement::from_line(&peeked_line) {
                        Ok(ack) => match ack.id() {
                            Some(id) => {
                                if !acks.iter().any(|a| a.id() == Some(id)) {
                                    acks.push(ack);
                                }
                            }
                            None => acks.push(ack),
                        },
                        Err(_) => match ShallowUpdate::from_line(&peeked_line) {
                            Ok(shallow) => {
                                shallows.push(shallow);
                            }
                            Err(_) => break 'lines true,
                        },
                    };
                    assert_ne!(reader.read_line(&mut line)?, 0, "consuming a peeked line works");
                };
                Ok(Response {
                    acks,
                    has_pack,
                    shallows,
                })
            }
            Protocol::V2 => {
                // NOTE: We only read acknowledgements and scrub to the pack file, until we have use for the other features
                let mut line = String::new();
                reader.reset(Protocol::V2);
                let mut acks = Vec::<Acknowledgement>::new();
                let mut shallows = Vec::<ShallowUpdate>::new();
                let has_pack = 'section: loop {
                    line.clear();
                    if reader.read_line(&mut line)? == 0 {
                        return Err(Error::Io(io::Error::new(
                            io::ErrorKind::UnexpectedEof,
                            "Could not read message headline",
                        )));
                    };

                    match line.trim_end() {
                        "acknowledgments" => {
                            if parse_section(&mut line, reader, &mut acks, Acknowledgement::from_line)? {
                                break 'section false;
                            }
                        }
                        "shallow-info" => {
                            if parse_section(&mut line, reader, &mut shallows, ShallowUpdate::from_line)? {
                                break 'section false;
                            }
                        }
                        "packfile" => {
                            // what follows is the packfile itself, which can be read with a sideband enabled reader
                            break 'section true;
                        }
                        _ => return Err(Error::UnknownSectionHeader(line)),
                    }
                };
                Ok(Response {
                    acks,
                    has_pack,
                    shallows,
                })
            }
        }
    }

    pub fn acknowledgements(&self) -> &[Acknowledgement] {
        &self.acks
    }

    pub fn shallow_updates(&self) -> &[ShallowUpdate] {
        &self.shallows
    }
}

fn parse_section<T>(
    line: &mut String,
    reader: &mut impl client::ExtendedBufRead,
    res: &mut Vec<T>,
    parse: impl Fn(&str) -> Result<T, Error>,
) -> Result<bool, Error> {
    line.clear();
    while reader.read_line(line)? != 0 {
        res.push(parse(line)?);
        line.clear();
    }
    // End of message, or end of section?
    Ok(if reader.stopped_at() == Some(client::MessageKind::Delimiter) {
        // try reading more sections
        reader.reset(Protocol::V2);
        false
    } else {
        // we are done, there is no pack
        true
    })
}