1
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![warn(clippy::cognitive_complexity)]
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![deny(clippy::print_stderr)]
28
#![deny(clippy::print_stdout)]
29
#![warn(clippy::rc_buffer)]
30
#![deny(clippy::ref_option_ref)]
31
#![warn(clippy::semicolon_if_nothing_returned)]
32
#![warn(clippy::trait_duplication_in_bounds)]
33
#![deny(clippy::unchecked_duration_subtraction)]
34
#![deny(clippy::unnecessary_wraps)]
35
#![warn(clippy::unseparated_literal_suffix)]
36
#![deny(clippy::unwrap_used)]
37
#![deny(clippy::mod_module_files)]
38
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39
#![allow(clippy::uninlined_format_args)]
40
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43
#![allow(clippy::needless_lifetimes)] // See arti#1765
44
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
45

            
46
use std::fmt::{Display, Formatter};
47
use std::num::NonZeroUsize;
48
use std::str::FromStr;
49

            
50
mod err;
51
pub use err::Error;
52

            
53
/// Result type used by this crate
54
type Result<T> = std::result::Result<T, Error>;
55

            
56
/// Return true if `s` looks more like a consensus diff than some other kind
57
/// of document.
58
124
pub fn looks_like_diff(s: &str) -> bool {
59
124
    s.starts_with("network-status-diff-version")
60
124
}
61

            
62
/// Apply a given diff to an input text, and return the result from applying
63
/// that diff.
64
///
65
/// This is a slow version, for testing and correctness checking.  It uses
66
/// an O(n) operation to apply diffs, and therefore runs in O(n^2) time.
67
#[cfg(any(test, feature = "slow-diff-apply"))]
68
2
pub fn apply_diff_trivial<'a>(input: &'a str, diff: &'a str) -> Result<DiffResult<'a>> {
69
2
    let mut diff_lines = diff.lines();
70
2
    let (_, d2) = parse_diff_header(&mut diff_lines)?;
71

            
72
2
    let mut diffable = DiffResult::from_str(input, d2);
73

            
74
24
    for command in DiffCommandIter::new(diff_lines) {
75
24
        command?.apply_to(&mut diffable)?;
76
    }
77

            
78
2
    Ok(diffable)
79
2
}
80

            
81
/// Apply a given diff to an input text, and return the result from applying
82
/// that diff.
83
///
84
/// If `check_digest_in` is provided, require the diff to say that it
85
/// applies to a document with the provided digest.
86
93
pub fn apply_diff<'a>(
87
93
    input: &'a str,
88
93
    diff: &'a str,
89
93
    check_digest_in: Option<[u8; 32]>,
90
93
) -> Result<DiffResult<'a>> {
91
93
    let mut input = DiffResult::from_str(input, [0; 32]);
92
93

            
93
93
    let mut diff_lines = diff.lines();
94
93
    let (d1, d2) = parse_diff_header(&mut diff_lines)?;
95
93
    if let Some(d_want) = check_digest_in {
96
62
        if d1 != d_want {
97
            return Err(Error::CantApply("listed digest does not match document"));
98
62
        }
99
31
    }
100

            
101
93
    let mut output = DiffResult::new(d2);
102

            
103
434
    for command in DiffCommandIter::new(diff_lines) {
104
434
        command?.apply_transformation(&mut input, &mut output)?;
105
    }
106

            
107
93
    output.push_reversed(&input.lines[..]);
108
93

            
109
93
    output.lines.reverse();
110
93
    Ok(output)
111
93
}
112

            
113
/// Given a line iterator, check to make sure the first two lines are
114
/// a valid diff header as specified in dir-spec.txt.
115
115
fn parse_diff_header<'a, I>(iter: &mut I) -> Result<([u8; 32], [u8; 32])>
116
115
where
117
115
    I: Iterator<Item = &'a str>,
118
115
{
119
115
    let line1 = iter.next();
120
115
    if line1 != Some("network-status-diff-version 1") {
121
6
        return Err(Error::BadDiff("unrecognized or missing header"));
122
109
    }
123
109
    let line2 = iter.next().ok_or(Error::BadDiff("header truncated"))?;
124
107
    if !line2.starts_with("hash ") {
125
2
        return Err(Error::BadDiff("missing 'hash' line"));
126
105
    }
127
105
    let elts: Vec<_> = line2.split_ascii_whitespace().collect();
128
105
    if elts.len() != 3 {
129
2
        return Err(Error::BadDiff("invalid 'hash' line"));
130
103
    }
131
103
    let d1 = hex::decode(elts[1])?;
132
99
    let d2 = hex::decode(elts[2])?;
133
99
    match (d1.try_into(), d2.try_into()) {
134
97
        (Ok(a), Ok(b)) => Ok((a, b)),
135
2
        _ => Err(Error::BadDiff("wrong digest lengths on 'hash' line")),
136
    }
137
115
}
138

            
139
/// A command that can appear in a diff.  Each command tells us to
140
/// remove zero or more lines, and insert zero or more lines in their
141
/// place.
142
///
143
/// Commands refer to lines by 1-indexed line number.
144
#[derive(Clone, Debug)]
145
enum DiffCommand<'a> {
146
    /// Remove the lines from low through high, inclusive.
147
    Delete {
148
        /// The first line to remove
149
        low: usize,
150
        /// The last line to remove
151
        high: usize,
152
    },
153
    /// Remove the lines from low through the end of the file, inclusive.
154
    DeleteToEnd {
155
        /// The first line to remove
156
        low: usize,
157
    },
158
    /// Replace the lines from low through high, inclusive, with the
159
    /// lines in 'lines'.
160
    Replace {
161
        /// The first line to replace
162
        low: usize,
163
        /// The last line to replace
164
        high: usize,
165
        /// The text to insert instead
166
        lines: Vec<&'a str>,
167
    },
168
    /// Insert the provided 'lines' after the line with index 'pos'.
169
    Insert {
170
        /// The position after which to insert the text
171
        pos: usize,
172
        /// The text to insert
173
        lines: Vec<&'a str>,
174
    },
175
}
176

            
177
/// The result of applying one or more diff commands to an input string.
178
///
179
/// It refers to lines from the diff and the input by reference, to
180
/// avoid copying.
181
#[derive(Clone, Debug)]
182
pub struct DiffResult<'a> {
183
    /// An expected digest of the output, after it has been assembled.
184
    d_post: [u8; 32],
185
    /// The lines in the output.
186
    lines: Vec<&'a str>,
187
}
188

            
189
/// A possible value for the end of a range.  It can be either a line number,
190
/// or a dollar sign indicating "end of file".
191
#[derive(Clone, Copy, Debug)]
192
enum RangeEnd {
193
    /// A line number in the file.
194
    Num(NonZeroUsize),
195
    /// A dollar sign, indicating "end of file" in a delete command.
196
    DollarSign,
197
}
198

            
199
impl FromStr for RangeEnd {
200
    type Err = Error;
201
296
    fn from_str(s: &str) -> Result<RangeEnd> {
202
296
        if s == "$" {
203
37
            Ok(RangeEnd::DollarSign)
204
        } else {
205
259
            let v: NonZeroUsize = s.parse()?;
206
257
            if v.get() == usize::MAX {
207
2
                return Err(Error::BadDiff("range cannot end at usize::MAX"));
208
255
            }
209
255
            Ok(RangeEnd::Num(v))
210
        }
211
296
    }
212
}
213

            
214
impl<'a> DiffCommand<'a> {
215
    /// Transform 'target' according to the this command.
216
    ///
217
    /// Because DiffResult internally uses a vector of line, this
218
    /// implementation is potentially O(n) in the size of the input.
219
    #[cfg(any(test, feature = "slow-diff-apply"))]
220
32
    fn apply_to(&self, target: &mut DiffResult<'a>) -> Result<()> {
221
32
        match self {
222
8
            Self::Delete { low, high } => {
223
8
                target.remove_lines(*low, *high)?;
224
            }
225
4
            Self::DeleteToEnd { low } => {
226
4
                target.remove_lines(*low, target.lines.len())?;
227
            }
228
16
            Self::Replace { low, high, lines } => {
229
16
                target.remove_lines(*low, *high)?;
230
16
                target.insert_at(*low, lines)?;
231
            }
232
4
            Self::Insert { pos, lines } => {
233
4
                // This '+1' seems off, but it's what the spec says. I wonder
234
4
                // if the spec is wrong.
235
4
                target.insert_at(*pos + 1, lines)?;
236
            }
237
        };
238
32
        Ok(())
239
32
    }
240

            
241
    /// Apply this command to 'input', moving lines into 'output'.
242
    ///
243
    /// This is a more efficient algorithm, but it requires that the
244
    /// diff commands are sorted in reverse order by line
245
    /// number. (Fortunately, the Tor ed diff format guarantees this.)
246
    ///
247
    /// Before calling this method, input and output must contain the
248
    /// results of having applied the previous command in the diff.
249
    /// (When no commands have been applied, input starts out as the
250
    /// original text, and output starts out empty.)
251
    ///
252
    /// This method applies the command by copying unaffected lines
253
    /// from the _end_ of input into output, adding any lines inserted
254
    /// by this command, and finally deleting any affected lines from
255
    /// input.
256
    ///
257
    /// We build the `output` value in reverse order, and then put it
258
    /// back to normal before giving it to the user.
259
454
    fn apply_transformation(
260
454
        &self,
261
454
        input: &mut DiffResult<'a>,
262
454
        output: &mut DiffResult<'a>,
263
454
    ) -> Result<()> {
264
454
        if let Some(succ) = self.following_lines() {
265
417
            if let Some(subslice) = input.lines.get(succ - 1..) {
266
413
                // Lines from `succ` onwards are unaffected.  Copy them.
267
413
                output.push_reversed(subslice);
268
413
            } else {
269
                // Oops, dubious line number.
270
4
                return Err(Error::CantApply(
271
4
                    "ending line number didn't correspond to document",
272
4
                ));
273
            }
274
37
        }
275

            
276
450
        if let Some(lines) = self.lines() {
277
316
            // These are the lines we're inserting.
278
316
            output.push_reversed(lines);
279
320
        }
280

            
281
450
        let remove = self.first_removed_line();
282
450
        if remove == 0 || (!self.is_insert() && remove > input.lines.len()) {
283
4
            return Err(Error::CantApply(
284
4
                "starting line number didn't correspond to document",
285
4
            ));
286
446
        }
287
446
        input.lines.truncate(remove - 1);
288
446

            
289
446
        Ok(())
290
454
    }
291

            
292
    /// Return the lines that we should add to the output
293
458
    fn lines(&self) -> Option<&[&'a str]> {
294
458
        match self {
295
324
            Self::Replace { lines, .. } | Self::Insert { lines, .. } => Some(lines.as_slice()),
296
134
            _ => None,
297
        }
298
458
    }
299

            
300
    /// Return a mutable reference to the vector of lines we should
301
    /// add to the output.
302
488
    fn linebuf_mut(&mut self) -> Option<&mut Vec<&'a str>> {
303
488
        match self {
304
334
            Self::Replace { ref mut lines, .. } | Self::Insert { ref mut lines, .. } => Some(lines),
305
154
            _ => None,
306
        }
307
488
    }
308

            
309
    /// Return the (1-indexed) line number of the first line in the
310
    /// input that comes _after_ this command, and is not affected by it.
311
    ///
312
    /// We use this line number to know which lines we should copy.
313
928
    fn following_lines(&self) -> Option<usize> {
314
928
        match self {
315
790
            Self::Delete { high, .. } | Self::Replace { high, .. } => Some(high + 1),
316
70
            Self::DeleteToEnd { .. } => None,
317
68
            Self::Insert { pos, .. } => Some(pos + 1),
318
        }
319
928
    }
320

            
321
    /// Return the (1-indexed) line number of the first line that we
322
    /// should clear from the input when processing this command.
323
    ///
324
    /// This can be the same as following_lines(), if we shouldn't
325
    /// actually remove any lines.
326
918
    fn first_removed_line(&self) -> usize {
327
918
        match self {
328
206
            Self::Delete { low, .. } => *low,
329
70
            Self::DeleteToEnd { low } => *low,
330
574
            Self::Replace { low, .. } => *low,
331
68
            Self::Insert { pos, .. } => *pos + 1,
332
        }
333
918
    }
334

            
335
    /// Return true if this is an Insert command.
336
448
    fn is_insert(&self) -> bool {
337
448
        matches!(self, Self::Insert { .. })
338
448
    }
339

            
340
    /// Extract a single command from a line iterator that yields lines
341
    /// of the diffs.  Return None if we're at the end of the iterator.
342
625
    fn from_line_iterator<I>(iter: &mut I) -> Result<Option<Self>>
343
625
    where
344
625
        I: Iterator<Item = &'a str>,
345
625
    {
346
625
        let command = match iter.next() {
347
514
            Some(s) => s,
348
111
            None => return Ok(None),
349
        };
350

            
351
        // `command` can be of these forms: `Rc`, `Rd`, `N,$d`, and `Na`,
352
        // where R is a range of form `N,N`, and where N is a line number.
353

            
354
514
        if command.len() < 2 || !command.is_ascii() {
355
6
            return Err(Error::BadDiff("command too short"));
356
508
        }
357
508

            
358
508
        let (range, command) = command.split_at(command.len() - 1);
359
508
        let (low, high) = if let Some(comma_pos) = range.find(',') {
360
            (
361
298
                range[..comma_pos].parse::<usize>()?,
362
296
                Some(range[comma_pos + 1..].parse::<RangeEnd>()?),
363
            )
364
        } else {
365
210
            (range.parse::<usize>()?, None)
366
        };
367

            
368
496
        if low == usize::MAX {
369
2
            return Err(Error::BadDiff("range cannot begin at usize::MAX"));
370
494
        }
371
494

            
372
494
        match (low, high) {
373
255
            (lo, Some(RangeEnd::Num(hi))) if lo > hi.into() => {
374
2
                return Err(Error::BadDiff("mis-ordered lines in range"))
375
            }
376
492
            (_, _) => (),
377
        }
378

            
379
492
        let mut cmd = match (command, low, high) {
380
492
            ("d", low, None) => Self::Delete { low, high: low },
381
117
            ("d", low, Some(RangeEnd::Num(high))) => Self::Delete {
382
117
                low,
383
117
                high: high.into(),
384
117
            },
385
35
            ("d", low, Some(RangeEnd::DollarSign)) => Self::DeleteToEnd { low },
386
338
            ("c", low, None) => Self::Replace {
387
163
                low,
388
163
                high: low,
389
163
                lines: Vec::new(),
390
163
            },
391
134
            ("c", low, Some(RangeEnd::Num(high))) => Self::Replace {
392
134
                low,
393
134
                high: high.into(),
394
134
                lines: Vec::new(),
395
134
            },
396
39
            ("a", low, None) => Self::Insert {
397
37
                pos: low,
398
37
                lines: Vec::new(),
399
37
            },
400
4
            (_, _, _) => return Err(Error::BadDiff("can't parse command line")),
401
        };
402

            
403
488
        if let Some(ref mut linebuf) = cmd.linebuf_mut() {
404
            // The 'c' and 'a' commands take a series of lines followed by a
405
            // line containing a period.
406
            loop {
407
1666
                match iter.next() {
408
                    None => return Err(Error::BadDiff("unterminated block to insert")),
409
1666
                    Some(".") => break,
410
1332
                    Some(line) => linebuf.push(line),
411
                }
412
            }
413
154
        }
414

            
415
488
        Ok(Some(cmd))
416
625
    }
417
}
418

            
419
/// Iterator that wraps a line iterator and returns a sequence of
420
/// `Result<DiffCommand>`.
421
///
422
/// This iterator forces the commands to affect the file in reverse order,
423
/// so that we can use the O(n) algorithm for applying these diffs.
424
struct DiffCommandIter<'a, I>
425
where
426
    I: Iterator<Item = &'a str>,
427
{
428
    /// The underlying iterator.
429
    iter: I,
430

            
431
    /// The 'first removed line' of the last-parsed command; used to ensure
432
    /// that commands appear in reverse order.
433
    last_cmd_first_removed: Option<usize>,
434
}
435

            
436
impl<'a, I> DiffCommandIter<'a, I>
437
where
438
    I: Iterator<Item = &'a str>,
439
{
440
    /// Construct a new DiffCommandIter wrapping `iter`.
441
103
    fn new(iter: I) -> Self {
442
103
        DiffCommandIter {
443
103
            iter,
444
103
            last_cmd_first_removed: None,
445
103
        }
446
103
    }
447
}
448

            
449
impl<'a, I> Iterator for DiffCommandIter<'a, I>
450
where
451
    I: Iterator<Item = &'a str>,
452
{
453
    type Item = Result<DiffCommand<'a>>;
454
571
    fn next(&mut self) -> Option<Result<DiffCommand<'a>>> {
455
571
        match DiffCommand::from_line_iterator(&mut self.iter) {
456
            Err(e) => Some(Err(e)),
457
97
            Ok(None) => None,
458
474
            Ok(Some(c)) => match (self.last_cmd_first_removed, c.following_lines()) {
459
                (Some(_), None) => Some(Err(Error::BadDiff("misordered commands"))),
460
371
                (Some(a), Some(b)) if a < b => Some(Err(Error::BadDiff("misordered commands"))),
461
                (_, _) => {
462
468
                    self.last_cmd_first_removed = Some(c.first_removed_line());
463
468
                    Some(Ok(c))
464
                }
465
            },
466
        }
467
571
    }
468
}
469

            
470
impl<'a> DiffResult<'a> {
471
    /// Construct a new DiffResult containing the provided string
472
    /// split into lines, and an expected post-transformation digest.
473
103
    fn from_str(s: &'a str, d_post: [u8; 32]) -> Self {
474
103
        // I'd like to use str::split_inclusive here, but that isn't stable yet
475
103
        // as of rust 1.48.
476
103

            
477
103
        let lines: Vec<_> = s.lines().collect();
478
103

            
479
103
        DiffResult { d_post, lines }
480
103
    }
481

            
482
    /// Return a new empty DiffResult with an expected
483
    /// post-transformation digests
484
97
    fn new(d_post: [u8; 32]) -> Self {
485
97
        DiffResult {
486
97
            d_post,
487
97
            lines: Vec::new(),
488
97
        }
489
97
    }
490

            
491
    /// Put every member of `lines` at the end of this DiffResult, in
492
    /// reverse order.
493
826
    fn push_reversed(&mut self, lines: &[&'a str]) {
494
826
        self.lines.extend(lines.iter().rev());
495
826
    }
496

            
497
    /// Remove the 1-indexed lines from `first` through `last` inclusive.
498
    ///
499
    /// This has to move elements around within the vector, and so it
500
    /// is potentially O(n) in its length.
501
    #[cfg(any(test, feature = "slow-diff-apply"))]
502
40
    fn remove_lines(&mut self, first: usize, last: usize) -> Result<()> {
503
40
        if first > self.lines.len() || last > self.lines.len() || first == 0 || last == 0 {
504
4
            Err(Error::CantApply("line out of range"))
505
        } else {
506
36
            let n_to_remove = last - first + 1;
507
36
            if last != self.lines.len() {
508
28
                self.lines[..].copy_within((last).., first - 1);
509
28
            }
510
36
            self.lines.truncate(self.lines.len() - n_to_remove);
511
36
            Ok(())
512
        }
513
40
    }
514

            
515
    /// Insert the provided `lines` so that they appear at 1-indexed
516
    /// position `pos`.
517
    ///
518
    /// This has to move elements around within the vector, and so it
519
    /// is potentially O(n) in its length.
520
    #[cfg(any(test, feature = "slow-diff-apply"))]
521
28
    fn insert_at(&mut self, pos: usize, lines: &[&'a str]) -> Result<()> {
522
28
        if pos > self.lines.len() + 1 || pos == 0 {
523
4
            Err(Error::CantApply("position out of range"))
524
        } else {
525
24
            let orig_len = self.lines.len();
526
24
            self.lines.resize(self.lines.len() + lines.len(), "");
527
24
            self.lines
528
24
                .copy_within(pos - 1..orig_len, pos - 1 + lines.len());
529
24
            self.lines[(pos - 1)..(pos + lines.len() - 1)].copy_from_slice(lines);
530
24
            Ok(())
531
        }
532
28
    }
533

            
534
    /// See whether the output of this diff matches the target digest.
535
    ///
536
    /// If not, return an error.
537
64
    pub fn check_digest(&self) -> Result<()> {
538
        use digest::Digest;
539
        use tor_llcrypto::d::Sha3_256;
540
64
        let mut d = Sha3_256::new();
541
390
        for line in &self.lines {
542
326
            d.update(line.as_bytes());
543
326
            d.update(b"\n");
544
326
        }
545
64
        if d.finalize() == self.d_post.into() {
546
33
            Ok(())
547
        } else {
548
31
            Err(Error::CantApply("Wrong digest after applying diff"))
549
        }
550
64
    }
551
}
552

            
553
impl<'a> Display for DiffResult<'a> {
554
116
    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
555
2773
        for elt in &self.lines {
556
2657
            writeln!(f, "{}", elt)?;
557
        }
558
116
        Ok(())
559
116
    }
560
}
561

            
562
#[cfg(test)]
563
mod test {
564
    // @@ begin test lint list maintained by maint/add_warning @@
565
    #![allow(clippy::bool_assert_comparison)]
566
    #![allow(clippy::clone_on_copy)]
567
    #![allow(clippy::dbg_macro)]
568
    #![allow(clippy::mixed_attributes_style)]
569
    #![allow(clippy::print_stderr)]
570
    #![allow(clippy::print_stdout)]
571
    #![allow(clippy::single_char_pattern)]
572
    #![allow(clippy::unwrap_used)]
573
    #![allow(clippy::unchecked_duration_subtraction)]
574
    #![allow(clippy::useless_vec)]
575
    #![allow(clippy::needless_pass_by_value)]
576
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
577
    use super::*;
578

            
579
    #[test]
580
    fn remove() -> Result<()> {
581
        let example = DiffResult::from_str("1\n2\n3\n4\n5\n6\n7\n8\n9\n", [0; 32]);
582

            
583
        let mut d = example.clone();
584
        d.remove_lines(5, 7)?;
585
        assert_eq!(d.to_string(), "1\n2\n3\n4\n8\n9\n");
586

            
587
        let mut d = example.clone();
588
        d.remove_lines(1, 9)?;
589
        assert_eq!(d.to_string(), "");
590

            
591
        let mut d = example.clone();
592
        d.remove_lines(1, 1)?;
593
        assert_eq!(d.to_string(), "2\n3\n4\n5\n6\n7\n8\n9\n");
594

            
595
        let mut d = example.clone();
596
        d.remove_lines(6, 9)?;
597
        assert_eq!(d.to_string(), "1\n2\n3\n4\n5\n");
598

            
599
        let mut d = example.clone();
600
        assert!(d.remove_lines(6, 10).is_err());
601
        assert!(d.remove_lines(0, 1).is_err());
602
        assert_eq!(d.to_string(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n");
603

            
604
        Ok(())
605
    }
606

            
607
    #[test]
608
    fn insert() -> Result<()> {
609
        let example = DiffResult::from_str("1\n2\n3\n4\n5\n", [0; 32]);
610
        let mut d = example.clone();
611
        d.insert_at(3, &["hello", "world"])?;
612
        assert_eq!(d.to_string(), "1\n2\nhello\nworld\n3\n4\n5\n");
613

            
614
        let mut d = example.clone();
615
        d.insert_at(6, &["hello", "world"])?;
616
        assert_eq!(d.to_string(), "1\n2\n3\n4\n5\nhello\nworld\n");
617

            
618
        let mut d = example.clone();
619
        assert!(d.insert_at(0, &["hello", "world"]).is_err());
620
        assert!(d.insert_at(7, &["hello", "world"]).is_err());
621
        Ok(())
622
    }
623

            
624
    #[test]
625
    fn push_reversed() {
626
        let mut d = DiffResult::new([0; 32]);
627
        d.push_reversed(&["7", "8", "9"]);
628
        assert_eq!(d.to_string(), "9\n8\n7\n");
629
        d.push_reversed(&["world", "hello", ""]);
630
        assert_eq!(d.to_string(), "9\n8\n7\n\nhello\nworld\n");
631
    }
632

            
633
    #[test]
634
    fn apply_command_simple() {
635
        let example = DiffResult::from_str("a\nb\nc\nd\ne\nf\n", [0; 32]);
636

            
637
        let mut d = example.clone();
638
        assert_eq!(d.to_string(), "a\nb\nc\nd\ne\nf\n".to_string());
639
        assert!(DiffCommand::DeleteToEnd { low: 5 }.apply_to(&mut d).is_ok());
640
        assert_eq!(d.to_string(), "a\nb\nc\nd\n".to_string());
641

            
642
        let mut d = example.clone();
643
        assert!(DiffCommand::Delete { low: 3, high: 5 }
644
            .apply_to(&mut d)
645
            .is_ok());
646
        assert_eq!(d.to_string(), "a\nb\nf\n".to_string());
647

            
648
        let mut d = example.clone();
649
        assert!(DiffCommand::Replace {
650
            low: 3,
651
            high: 5,
652
            lines: vec!["hello", "world"]
653
        }
654
        .apply_to(&mut d)
655
        .is_ok());
656
        assert_eq!(d.to_string(), "a\nb\nhello\nworld\nf\n".to_string());
657

            
658
        let mut d = example.clone();
659
        assert!(DiffCommand::Insert {
660
            pos: 3,
661
            lines: vec!["hello", "world"]
662
        }
663
        .apply_to(&mut d)
664
        .is_ok());
665
        assert_eq!(
666
            d.to_string(),
667
            "a\nb\nc\nhello\nworld\nd\ne\nf\n".to_string()
668
        );
669
    }
670

            
671
    #[test]
672
    fn parse_command() -> Result<()> {
673
        fn parse(s: &str) -> Result<DiffCommand<'_>> {
674
            let mut iter = s.lines();
675
            let cmd = DiffCommand::from_line_iterator(&mut iter)?;
676
            let cmd2 = DiffCommand::from_line_iterator(&mut iter)?;
677
            if cmd2.is_some() {
678
                panic!("Unexpected second command");
679
            }
680
            Ok(cmd.unwrap())
681
        }
682

            
683
        fn parse_err(s: &str) {
684
            let mut iter = s.lines();
685
            let cmd = DiffCommand::from_line_iterator(&mut iter);
686
            assert!(matches!(cmd, Err(Error::BadDiff(_))));
687
        }
688

            
689
        let p = parse("3,8d\n")?;
690
        assert!(matches!(p, DiffCommand::Delete { low: 3, high: 8 }));
691
        let p = parse("3d\n")?;
692
        assert!(matches!(p, DiffCommand::Delete { low: 3, high: 3 }));
693
        let p = parse("100,$d\n")?;
694
        assert!(matches!(p, DiffCommand::DeleteToEnd { low: 100 }));
695

            
696
        let p = parse("30,40c\nHello\nWorld\n.\n")?;
697
        assert!(matches!(
698
            p,
699
            DiffCommand::Replace {
700
                low: 30,
701
                high: 40,
702
                ..
703
            }
704
        ));
705
        assert_eq!(p.lines(), Some(&["Hello", "World"][..]));
706
        let p = parse("30c\nHello\nWorld\n.\n")?;
707
        assert!(matches!(
708
            p,
709
            DiffCommand::Replace {
710
                low: 30,
711
                high: 30,
712
                ..
713
            }
714
        ));
715
        assert_eq!(p.lines(), Some(&["Hello", "World"][..]));
716

            
717
        let p = parse("999a\nHello\nWorld\n.\n")?;
718
        assert!(matches!(p, DiffCommand::Insert { pos: 999, .. }));
719
        assert_eq!(p.lines(), Some(&["Hello", "World"][..]));
720
        let p = parse("0a\nHello\nWorld\n.\n")?;
721
        assert!(matches!(p, DiffCommand::Insert { pos: 0, .. }));
722
        assert_eq!(p.lines(), Some(&["Hello", "World"][..]));
723

            
724
        parse_err("hello world");
725
        parse_err("\n\n");
726
        parse_err("$,5d");
727
        parse_err("5,6,8d");
728
        parse_err("8,5d");
729
        parse_err("6");
730
        parse_err("d");
731
        parse_err("-10d");
732
        parse_err("4,$c\na\n.");
733
        parse_err("foo");
734
        parse_err("5,10p");
735
        parse_err("18446744073709551615a");
736
        parse_err("1,18446744073709551615d");
737

            
738
        Ok(())
739
    }
740

            
741
    #[test]
742
    fn apply_transformation() -> Result<()> {
743
        let example = DiffResult::from_str("1\n2\n3\n4\n5\n6\n7\n8\n9\n", [0; 32]);
744
        let empty = DiffResult::new([1; 32]);
745

            
746
        let mut inp = example.clone();
747
        let mut out = empty.clone();
748
        DiffCommand::DeleteToEnd { low: 5 }.apply_transformation(&mut inp, &mut out)?;
749
        assert_eq!(inp.to_string(), "1\n2\n3\n4\n");
750
        assert_eq!(out.to_string(), "");
751

            
752
        let mut inp = example.clone();
753
        let mut out = empty.clone();
754
        DiffCommand::DeleteToEnd { low: 9 }.apply_transformation(&mut inp, &mut out)?;
755
        assert_eq!(inp.to_string(), "1\n2\n3\n4\n5\n6\n7\n8\n");
756
        assert_eq!(out.to_string(), "");
757

            
758
        let mut inp = example.clone();
759
        let mut out = empty.clone();
760
        DiffCommand::Delete { low: 3, high: 5 }.apply_transformation(&mut inp, &mut out)?;
761
        assert_eq!(inp.to_string(), "1\n2\n");
762
        assert_eq!(out.to_string(), "9\n8\n7\n6\n");
763

            
764
        let mut inp = example.clone();
765
        let mut out = empty.clone();
766
        DiffCommand::Replace {
767
            low: 5,
768
            high: 6,
769
            lines: vec!["oh hey", "there"],
770
        }
771
        .apply_transformation(&mut inp, &mut out)?;
772
        assert_eq!(inp.to_string(), "1\n2\n3\n4\n");
773
        assert_eq!(out.to_string(), "9\n8\n7\nthere\noh hey\n");
774

            
775
        let mut inp = example.clone();
776
        let mut out = empty.clone();
777
        DiffCommand::Insert {
778
            pos: 3,
779
            lines: vec!["oh hey", "there"],
780
        }
781
        .apply_transformation(&mut inp, &mut out)?;
782
        assert_eq!(inp.to_string(), "1\n2\n3\n");
783
        assert_eq!(out.to_string(), "9\n8\n7\n6\n5\n4\nthere\noh hey\n");
784
        DiffCommand::Insert {
785
            pos: 0,
786
            lines: vec!["boom!"],
787
        }
788
        .apply_transformation(&mut inp, &mut out)?;
789
        assert_eq!(inp.to_string(), "");
790
        assert_eq!(
791
            out.to_string(),
792
            "9\n8\n7\n6\n5\n4\nthere\noh hey\n3\n2\n1\nboom!\n"
793
        );
794

            
795
        let mut inp = example.clone();
796
        let mut out = empty.clone();
797
        let r = DiffCommand::Delete {
798
            low: 100,
799
            high: 200,
800
        }
801
        .apply_transformation(&mut inp, &mut out);
802
        assert!(r.is_err());
803
        let r = DiffCommand::Delete { low: 5, high: 200 }.apply_transformation(&mut inp, &mut out);
804
        assert!(r.is_err());
805
        let r = DiffCommand::Delete { low: 0, high: 1 }.apply_transformation(&mut inp, &mut out);
806
        assert!(r.is_err());
807
        let r = DiffCommand::DeleteToEnd { low: 10 }.apply_transformation(&mut inp, &mut out);
808
        assert!(r.is_err());
809
        Ok(())
810
    }
811

            
812
    #[test]
813
    fn header() -> Result<()> {
814
        fn header_from(s: &str) -> Result<([u8; 32], [u8; 32])> {
815
            let mut iter = s.lines();
816
            parse_diff_header(&mut iter)
817
        }
818

            
819
        let (a,b) = header_from(
820
            "network-status-diff-version 1
821
hash B03DA3ACA1D3C1D083E3FF97873002416EBD81A058B406D5C5946EAB53A79663 F6789F35B6B3BA58BB23D29E53A8ED6CBB995543DBE075DD5671481C4BA677FB"
822
        )?;
823

            
824
        assert_eq!(
825
            &a[..],
826
            hex::decode("B03DA3ACA1D3C1D083E3FF97873002416EBD81A058B406D5C5946EAB53A79663")?
827
        );
828
        assert_eq!(
829
            &b[..],
830
            hex::decode("F6789F35B6B3BA58BB23D29E53A8ED6CBB995543DBE075DD5671481C4BA677FB")?
831
        );
832

            
833
        assert!(header_from("network-status-diff-version 2\n").is_err());
834
        assert!(header_from("").is_err());
835
        assert!(header_from("5,$d\n1,2d\n").is_err());
836
        assert!(header_from("network-status-diff-version 1\n").is_err());
837
        assert!(header_from(
838
            "network-status-diff-version 1
839
hash x y
840
5,5d"
841
        )
842
        .is_err());
843
        assert!(header_from(
844
            "network-status-diff-version 1
845
hash x y
846
5,5d"
847
        )
848
        .is_err());
849
        assert!(header_from(
850
            "network-status-diff-version 1
851
hash AA BB
852
5,5d"
853
        )
854
        .is_err());
855
        assert!(header_from(
856
            "network-status-diff-version 1
857
oh hello there
858
5,5d"
859
        )
860
        .is_err());
861
        assert!(header_from("network-status-diff-version 1
862
hash B03DA3ACA1D3C1D083E3FF97873002416EBD81A058B406D5C5946EAB53A79663 F6789F35B6B3BA58BB23D29E53A8ED6CBB995543DBE075DD5671481C4BA677FB extra").is_err());
863

            
864
        Ok(())
865
    }
866

            
867
    #[test]
868
    fn apply_simple() {
869
        let pre = include_str!("../testdata/consensus1.txt");
870
        let diff = include_str!("../testdata/diff1.txt");
871
        let post = include_str!("../testdata/consensus2.txt");
872

            
873
        let result = apply_diff_trivial(pre, diff).unwrap();
874
        assert!(result.check_digest().is_ok());
875
        assert_eq!(result.to_string(), post);
876
    }
877

            
878
    #[test]
879
    fn sort_order() -> Result<()> {
880
        fn cmds(s: &str) -> Result<Vec<DiffCommand<'_>>> {
881
            let mut out = Vec::new();
882
            for cmd in DiffCommandIter::new(s.lines()) {
883
                out.push(cmd?);
884
            }
885
            Ok(out)
886
        }
887

            
888
        let _ = cmds("6,9d\n5,5d\n")?;
889
        assert!(cmds("5,5d\n6,9d\n").is_err());
890
        assert!(cmds("5,5d\n6,6d\n").is_err());
891
        assert!(cmds("5,5d\n5,6d\n").is_err());
892

            
893
        Ok(())
894
    }
895
}