• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

actioninja / refpack-rs / 6689328355

30 Oct 2023 06:54AM UTC coverage: 89.345% (-3.0%) from 92.374%
6689328355

Pull #6

github

web-flow
Merge 6f3e6b894 into f55f4d390
Pull Request #6: Demoded controls

294 of 294 new or added lines in 7 files covered. (100.0%)

914 of 1023 relevant lines covered (89.35%)

154499.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.72
/src/data/control/mod.rs
1
////////////////////////////////////////////////////////////////////////////////
2
// This Source Code Form is subject to the terms of the Mozilla Public         /
3
// License, v. 2.0. If a copy of the MPL was not distributed with this         /
4
// file, You can obtain one at https://mozilla.org/MPL/2.0/.                   /
5
//                                                                             /
6
////////////////////////////////////////////////////////////////////////////////
7

8
//! control codes utilized by compression and decompression
9

10
#[cfg(test)]
11
mod iterator;
12

13
use std::io::{Read, Seek, Write};
14

15
use byteorder::{ReadBytesExt, WriteBytesExt};
16
#[cfg(test)]
17
use proptest::collection::{size_range, vec};
18
#[cfg(test)]
19
use proptest::prelude::*;
20

21
use crate::{RefPackError, RefPackResult};
22

23
/// minimum value of the literal length in a literal command
24
pub const LITERAL_MIN: u8 = 4;
25

26
/// maximum value of the literal length in a literal command
27
pub const LITERAL_MAX: u8 = 112;
28

29
/// "Real" maximum of literal value in a literal command once encoded
30
///
31
/// Literal commands encode their value in a a special limit precision
32
/// format
33
///
34
/// Equivalent to `0`, written as an expression to convey the relation
35
pub const LITERAL_EFFECTIVE_MIN: u8 = (LITERAL_MIN - 4) / 4;
36

37
/// "Real" maximum of literal value in a literal command once encoded
38
///
39
/// Literal commands encode their value in a a special limit precision
40
/// format
41
///
42
/// Equivalent to `27`, written as an expression to convey the relation
43
pub const LITERAL_EFFECTIVE_MAX: u8 = (LITERAL_MAX - 4) / 4;
44

45
/// minimum value of the literal length in a non-literal command
46
pub const COPY_LITERAL_MIN: u8 = 0;
47

48
/// maximum value of the literal length in a non-literal command
49
pub const COPY_LITERAL_MAX: u8 = 3;
50

51
/// minimum offset distance for a short command
52
pub const SHORT_OFFSET_MIN: u16 = 1;
53

54
/// maximum offset distance for a short command
55
pub const SHORT_OFFSET_MAX: u16 = 1_024;
56

57
/// minimum length for a short command
58
pub const SHORT_LENGTH_MIN: u8 = 3;
59

60
/// maximum length for a short command
61
pub const SHORT_LENGTH_MAX: u8 = 10;
62

63
/// minimum offset distance for a medium command
64
pub const MEDIUM_OFFSET_MIN: u16 = 1;
65

66
/// maximum offset distance for a medium command
67
pub const MEDIUM_OFFSET_MAX: u16 = 16_384;
68

69
/// minimum length for a medium command
70
pub const MEDIUM_LENGTH_MIN: u8 = 4;
71

72
/// maximum length for a medium command
73
pub const MEDIUM_LENGTH_MAX: u8 = 67;
74

75
/// minimum offset distance for a long command
76
pub const LONG_OFFSET_MIN: u32 = 1;
77

78
/// maximum offset distance for a long command
79
pub const LONG_OFFSET_MAX: u32 = 131_072;
80

81
/// minimum length for a long command
82
pub const LONG_LENGTH_MIN: u16 = 5;
83

84
/// maximum length for a long command
85
pub const LONG_LENGTH_MAX: u16 = 1_028;
86

87
/// Possible actual control code values
88
///
89
/// ## Split Numbers
90
/// Numbers are always "smashed" together into as small of a space as possible
91
/// EX: Getting the position from "`0PPL-LLBB--PPPP-PPPP`"
92
/// 1. mask first byte: `(byte0 & 0b0110_0000)` = `0PP0-0000`
93
/// 2. shift left by 3: `(0PP0-0000 << 3)` = `0000-00PP--0000-0000`
94
/// 3. OR with second:  `(0000-00PP--0000-0000 | 0000-0000--PPPP-PPPP)` =
95
/// `0000-00PP--PPPP-PPPP` Another way to do this would be to first shift right
96
/// by 5 and so on
97
///
98
/// ## Key for description:
99
/// - Len: Length of the command in bytes
100
/// - Literal: Possible range of number of literal bytes to copy
101
/// - Length: Possible range of copy length
102
/// - Position Range: Possible range of positions
103
/// - Layout: Bit layout of the command bytes
104
///
105
/// ### Key for layout
106
/// - `0` or `1`: header
107
/// - `P`: Position
108
/// - `L`: Length
109
/// - `B`: Literal bytes Length
110
/// - `-`: Nibble Separator
111
/// - `:`: Byte Separator
112
///
113
/// ## Commands
114
///
115
/// | Command | Len | Literal      | Length        | Position        | Layout                                    |
116
/// |---------|-----|--------------|---------------|-----------------|-------------------------------------------|
117
/// | Short   | 2   | (0..=3) +0   | (3..=10) +3   | (1..=1024) +1   | `0PPL-LLBB:PPPP-PPPP`                     |
118
/// | Medium  | 3   | (0..=3) +0   | (4..=67) +4   | (1..=16384) +1  | `10LL-LLLL:BBPP-PPPP:PPPP-PPPP`           |
119
/// | Long    | 4   | (0..=3) +0   | (5..=1028) +5 | (1..=131072) +1 | `110P-LLBB:PPPP-PPPP:PPPP-PPPP:LLLL-LLLL` |
120
/// | Literal | 1   | (4..=112) +4 | 0             | 0               | `111B-BBBB`                               |
121
/// | Stop    | 1   | (0..=3) +0   | 0             | 0               | `1111-11BB`                               |
122
///
123
/// ### Extra Note on Literal Commands
124
///
125
/// Literal is a special command that has differently encoded values.
126
///
127
/// While the practical range is 4-112, literal values must always be an even
128
/// multiple of 4. Before being encoded, the value is first decreased by 4 then
129
/// shifted right by 2
130
///
131
/// #### Why
132
///
133
/// Because all other codes can have an up to 3 byte literal payload, this means
134
/// that the number of literals can be stored as (length / 4) + (length % 4).
135
/// When a number is an even multiple of a power of 2, it can be encoded in less
136
/// bits by bitshifting it before encoding and decoding. This lets an effective
137
/// range of 0-112 for the length of literal commands in only 5 bits of data,
138
/// since the first 3 bits are the huffman header.
139
///
140
/// If this is unclear, here's the process written out:
141
///
142
/// We want to encode a literal length of 97
143
///
144
/// 1. take `97 % 4` to get the "leftover" length - this will be used in next
145
/// command following the literal
146
/// 2. take `(97 - 4) >> 2` to get the value to encode into the literal value
147
/// 3. create a literal command with the result from 2, take that number of
148
/// literals from the current literal buffer and write to stream
149
/// 4. in the next command, encode the leftover literal value from 1
150
///
151
/// One extra unusual detail is that despite that it seems like te cap from the
152
/// bitshift should be 128, in practice it's limited to 112. The way the
153
/// original reference implementation worked was to read the huffman encoded
154
/// headers via just checking if the first byte read with within certain decimal
155
/// ranges. `refpack` implements this similarly for maximum compatibility. If
156
/// the first byte read is within `252..=255`, it's interpreted as a stopcode.
157
/// The highest allowed values of 112 is encoded as `0b1111_1011` which is `251`
158
/// exactly. Any higher of a value would start seeping in to the stopcode range.
159
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203,468✔
160
pub enum Command {
161
    /// Represents a two byte copy command
162
    Short {
163
        literal: u8,
164
        length: u8,
165
        offset: u16,
166
    },
167
    /// Represents a three byte copy command
168
    Medium {
169
        literal: u8,
170
        length: u8,
171
        offset: u16,
172
    },
173
    /// Represents a four byte copy command
174
    Long {
175
        literal: u8,
176
        length: u16,
177
        offset: u32,
178
    },
179
    /// Represents exclusively writing literal bytes from the stream
180
    ///
181
    /// u8: number of literal bytes following the command to write to the stream
182
    Literal(u8),
183
    /// Represents an end of stream, when this command is encountered during
184
    /// decompression it's evaluated and then decompression halts
185
    ///
186
    /// u8: Number of literal bytes to write to the stream before ending
187
    /// decompression
188
    Stop(u8),
189
}
190

191
impl Command {
192
    /// Create a new copy type `Command` struct.
193
    /// # Panics
194
    /// Panics if you attempt to create an invalid Command in some way
195
    #[must_use]
196
    pub fn new(offset: usize, length: usize, literal: usize) -> Self {
301✔
197
        assert!(
301✔
198
            literal <= COPY_LITERAL_MAX as usize,
301✔
199
            "Literal length must be less than or equal to {COPY_LITERAL_MAX} for commands \
1✔
200
             ({literal})"
1✔
201
        );
202

203
        if offset > LONG_OFFSET_MAX as usize || length > LONG_LENGTH_MAX as usize {
300✔
204
            panic!(
2✔
205
                "Invalid offset or length (Maximum offset {LONG_OFFSET_MAX}, got {offset}) \
2✔
206
                 (Maximum length {LONG_LENGTH_MAX}, got {length})"
2✔
207
            );
2✔
208
        } else if offset > MEDIUM_OFFSET_MAX as usize || length > MEDIUM_LENGTH_MAX as usize {
298✔
209
            assert!(
254✔
210
                length >= LONG_LENGTH_MIN as usize,
254✔
211
                "Length must be greater than or equal to {LONG_LENGTH_MIN} for long commands \
×
212
                 (Length: {length}) (Offset: {offset})"
×
213
            );
214
            Self::Long {
254✔
215
                offset: offset as u32,
254✔
216
                length: length as u16,
254✔
217
                literal: literal as u8,
254✔
218
            }
254✔
219
        } else if offset > SHORT_OFFSET_MAX as usize || length > SHORT_LENGTH_MAX as usize {
44✔
220
            assert!(
2✔
221
                length >= MEDIUM_LENGTH_MIN as usize,
2✔
222
                "Length must be greater than or equal to {MEDIUM_LENGTH_MIN} for medium commands \
×
223
                 (Length: {length}) (Offset: {offset})"
×
224
            );
225
            Self::Medium {
2✔
226
                offset: offset as u16,
2✔
227
                length: length as u8,
2✔
228
                literal: literal as u8,
2✔
229
            }
2✔
230
        } else {
231
            Self::Short {
42✔
232
                offset: offset as u16,
42✔
233
                length: length as u8,
42✔
234
                literal: literal as u8,
42✔
235
            }
42✔
236
        }
237
    }
298✔
238

239
    /// Creates a new literal command block
240
    /// # Panics
241
    /// Panics if you attempt to create too long of a literal command. This
242
    /// depends on control mode used.
243
    #[must_use]
244
    pub fn new_literal(length: usize) -> Self {
105,649✔
245
        assert!(
105,649✔
246
            length <= LITERAL_MAX as usize,
105,649✔
247
            "Literal received too long of a literal length (max {LITERAL_MAX}, got {length})"
1✔
248
        );
249
        Self::Literal(length as u8)
105,648✔
250
    }
105,648✔
251

252
    /// Creates a new stopcode command block
253
    /// # Panics
254
    /// Panics if you attempt to create too long of a stop code. This depends on
255
    /// control mode used.
256
    #[must_use]
257
    pub fn new_stop(literal_length: usize) -> Self {
101,295✔
258
        debug_assert!(
259
            literal_length <= 3,
101,295✔
260
            "Stopcode recieved too long of a literal length (max {COPY_LITERAL_MAX}, got \
1✔
261
             {literal_length})"
1✔
262
        );
263
        Self::Stop(literal_length as u8)
101,294✔
264
    }
101,294✔
265

266
    /// Get number of literal bytes on the command, if they have any
267
    /// Returns `None` if the length is 0
268
    #[must_use]
269
    pub fn num_of_literal(self) -> Option<usize> {
135,816✔
270
        let num = match self {
135,816✔
271
            Command::Short { literal, .. }
33,428✔
272
            | Command::Medium { literal, .. }
34,410✔
273
            | Command::Long { literal, .. }
33,724✔
274
            | Command::Literal(literal)
33,742✔
275
            | Command::Stop(literal) => literal,
135,816✔
276
        };
135,816✔
277
        if num == 0 {
135,816✔
278
            None
25,538✔
279
        } else {
280
            Some(num as usize)
110,278✔
281
        }
282
    }
135,816✔
283

284
    /// Get the offset and length of a copy command as a `(usize, usize)` tuple.
285
    ///
286
    /// Returns `None` if `self` is not a copy command.
287
    #[must_use]
288
    pub fn offset_copy(self) -> Option<(usize, usize)> {
×
289
        match self {
×
290
            Command::Short { offset, length, .. } | Command::Medium { offset, length, .. } => {
×
291
                Some((offset as usize, length as usize))
×
292
            }
293
            Command::Long { offset, length, .. } => Some((offset as usize, length as usize)),
×
294
            _ => None,
×
295
        }
296
    }
×
297

298
    /// Returns true if the command is a stopcode, false if it is not.
299
    #[must_use]
300
    pub fn is_stop(self) -> bool {
67,652✔
301
        matches!(self, Command::Stop(_))
67,652✔
302
    }
67,652✔
303

304
    /// Reference read implementation of short copy commands. See structure
305
    /// definition for documentation
306
    ///
307
    /// # Errors
308
    /// - [RefPackError::Io]: Failed to get remaining single byte from reader
309
    #[inline(always)]
310
    pub fn read_short(first: u8, reader: &mut (impl Read + Seek)) -> RefPackResult<Command> {
16,827✔
311
        let byte1 = first as usize;
16,827✔
312
        let byte2: usize = reader.read_u8()?.into();
16,827✔
313

16,827✔
314
        let offset = ((((byte1 & 0b0110_0000) << 3) | byte2) + 1) as u16;
16,827✔
315
        let length = (((byte1 & 0b0001_1100) >> 2) + 3) as u8;
16,827✔
316
        let literal = (byte1 & 0b0000_0011) as u8;
16,827✔
317

16,827✔
318
        Ok(Command::Short {
16,827✔
319
            offset,
16,827✔
320
            length,
16,827✔
321
            literal,
16,827✔
322
        })
16,827✔
323
    }
16,827✔
324

325
    /// Reference read implementation of medium copy commands. See struct
326
    /// definition for documentation
327
    ///
328
    /// # Errors
329
    /// - [RefPackError::Io]: Failed to get remaining two bytes from reader
330
    #[inline(always)]
331
    pub fn read_medium(first: u8, reader: &mut (impl Read + Seek)) -> RefPackResult<Command> {
17,267✔
332
        let byte1: usize = first as usize;
17,267✔
333
        let byte2: usize = reader.read_u8()?.into();
17,267✔
334
        let byte3: usize = reader.read_u8()?.into();
17,267✔
335

17,267✔
336
        let offset = ((((byte2 & 0b0011_1111) << 8) | byte3) + 1) as u16;
17,267✔
337
        let length = ((byte1 & 0b0011_1111) + 4) as u8;
17,267✔
338
        let literal = ((byte2 & 0b1100_0000) >> 6) as u8;
17,267✔
339

17,267✔
340
        Ok(Command::Medium {
17,267✔
341
            offset,
17,267✔
342
            length,
17,267✔
343
            literal,
17,267✔
344
        })
17,267✔
345
    }
17,267✔
346

347
    /// Reference read implementation of long commands. See struct definition
348
    /// for documentation
349
    ///
350
    /// # Errors
351
    /// - [RefPackError::Io]: Failed to get remaining three bytes from the reader
352
    #[inline(always)]
353
    pub fn read_long(first: u8, reader: &mut (impl Read + Seek)) -> RefPackResult<Command> {
17,186✔
354
        let byte1: usize = first as usize;
17,186✔
355
        let byte2: usize = reader.read_u8()?.into();
17,186✔
356
        let byte3: usize = reader.read_u8()?.into();
17,186✔
357
        let byte4: usize = reader.read_u8()?.into();
17,186✔
358

17,186✔
359
        let offset = ((((byte1 & 0b0001_0000) << 12) | (byte2 << 8) | byte3) + 1) as u32;
17,186✔
360
        let length = ((((byte1 & 0b0000_1100) << 6) | byte4) + 5) as u16;
17,186✔
361

17,186✔
362
        let literal = (byte1 & 0b0000_0011) as u8;
17,186✔
363

17,186✔
364
        Ok(Command::Long {
17,186✔
365
            offset,
17,186✔
366
            length,
17,186✔
367
            literal,
17,186✔
368
        })
17,186✔
369
    }
17,186✔
370

371
    /// Reference read implementation of literal commands. See struct definition
372
    /// for documentation
373
    #[inline(always)]
374
    #[must_use]
375
    pub fn read_literal(first: u8) -> Command {
122,574✔
376
        Command::Literal(((first & 0b0001_1111) << 2) + 4)
122,574✔
377
    }
122,574✔
378

379
    /// Reference read implementation of literal commands. See struct definition
380
    /// for documentation
381
    #[inline(always)]
382
    #[must_use]
383
    pub fn read_stop(first: u8) -> Command {
101,550✔
384
        Command::Stop(first & 0b0000_0011)
101,550✔
385
    }
101,550✔
386

387
    /// Reads and decodes a command from a `Read + Seek` reader.
388
    /// # Errors
389
    /// - [RefPackError::Io]: Generic IO error occurred while attempting to read
390
    ///   data
391
    #[inline(always)]
392
    pub fn read(reader: &mut (impl Read + Seek)) -> RefPackResult<Self> {
275,404✔
393
        let first = reader.read_u8()?;
275,404✔
394

395
        match first {
275,404✔
396
            0x00..=0x7F => Self::read_short(first, reader),
275,404✔
397
            0x80..=0xBF => Self::read_medium(first, reader),
258,577✔
398
            0xC0..=0xDF => Self::read_long(first, reader),
241,310✔
399
            0xE0..=0xFB => Ok(Self::read_literal(first)),
224,124✔
400
            0xFC..=0xFF => Ok(Self::read_stop(first)),
101,550✔
401
        }
402
    }
275,404✔
403

404
    /// Reference write implementation of short copy commands. See struct
405
    /// definition for specification
406
    ///
407
    /// # Errors
408
    /// - [RefPackError::Io]: Generic IO error occurred while attempting to
409
    ///   write data
410
    #[inline]
411
    pub fn write_short(
16,827✔
412
        offset: u16,
16,827✔
413
        length: u8,
16,827✔
414
        literal: u8,
16,827✔
415
        writer: &mut (impl Write + Seek),
16,827✔
416
    ) -> RefPackResult<()> {
16,827✔
417
        let length_adjusted = length - 3;
16,827✔
418
        let offset_adjusted = offset - 1;
16,827✔
419

16,827✔
420
        let first = ((offset_adjusted & 0b0000_0011_0000_0000) >> 3) as u8
16,827✔
421
            | (length_adjusted & 0b0000_0111) << 2
16,827✔
422
            | literal & 0b0000_0011;
16,827✔
423
        let second = (offset_adjusted & 0b0000_0000_1111_1111) as u8;
16,827✔
424

16,827✔
425
        writer.write_u8(first)?;
16,827✔
426
        writer.write_u8(second)?;
16,827✔
427
        Ok(())
16,827✔
428
    }
16,827✔
429

430
    /// Reference write implementation of medium copy commands. See struct
431
    /// definition for specification
432
    ///
433
    /// # Errors
434
    /// - [RefPackError::Io]: Generic IO error occurred while attempting to
435
    ///   write data
436
    #[inline]
437
    pub fn write_medium(
17,267✔
438
        offset: u16,
17,267✔
439
        length: u8,
17,267✔
440
        literal: u8,
17,267✔
441
        writer: &mut (impl Write + Seek),
17,267✔
442
    ) -> RefPackResult<()> {
17,267✔
443
        let length_adjusted = length - 4;
17,267✔
444
        let offset_adjusted = offset - 1;
17,267✔
445

17,267✔
446
        let first = 0b1000_0000 | length_adjusted & 0b0011_1111;
17,267✔
447
        let second = (literal & 0b0000_0011) << 6 | (offset_adjusted >> 8) as u8;
17,267✔
448
        let third = (offset_adjusted & 0b0000_0000_1111_1111) as u8;
17,267✔
449

17,267✔
450
        writer.write_u8(first)?;
17,267✔
451
        writer.write_u8(second)?;
17,267✔
452
        writer.write_u8(third)?;
17,267✔
453

454
        Ok(())
17,267✔
455
    }
17,267✔
456

457
    /// Reference write implementation of long copy commands. See struct
458
    /// definition for specification
459
    ///
460
    /// # Errors
461
    /// - [RefPackError::Io]: Generic IO error occurred while attempting to
462
    ///   write data
463
    #[inline]
464
    pub fn write_long(
17,186✔
465
        offset: u32,
17,186✔
466
        length: u16,
17,186✔
467
        literal: u8,
17,186✔
468
        writer: &mut (impl Write + Seek),
17,186✔
469
    ) -> RefPackResult<()> {
17,186✔
470
        let length_adjusted = length - 5;
17,186✔
471
        let offset_adjusted = offset - 1;
17,186✔
472

17,186✔
473
        let first = 0b1100_0000u8
17,186✔
474
            | ((offset_adjusted >> 12) & 0b0001_0000) as u8
17,186✔
475
            | ((length_adjusted >> 6) & 0b0000_1100) as u8
17,186✔
476
            | literal & 0b0000_0011;
17,186✔
477
        let second = ((offset_adjusted >> 8) & 0b1111_1111) as u8;
17,186✔
478
        let third = (offset_adjusted & 0b1111_1111) as u8;
17,186✔
479
        let fourth = (length_adjusted & 0b1111_1111) as u8;
17,186✔
480

17,186✔
481
        writer.write_u8(first)?;
17,186✔
482
        writer.write_u8(second)?;
17,186✔
483
        writer.write_u8(third)?;
17,186✔
484
        writer.write_u8(fourth)?;
17,186✔
485

486
        Ok(())
17,186✔
487
    }
17,186✔
488

489
    /// Reference write implementation of literal commands. See struct
490
    /// definition for specification
491
    ///
492
    /// # Errors
493
    /// - [RefPackError::Io]: Generic IO error occurred while attempting to
494
    ///   write data
495
    #[inline]
496
    pub fn write_literal(literal: u8, writer: &mut (impl Write + Seek)) -> RefPackResult<()> {
122,574✔
497
        let adjusted = (literal - 4) >> 2;
122,574✔
498
        let out = 0b1110_0000 | (adjusted & 0b0001_1111);
122,574✔
499
        writer.write_u8(out)?;
122,574✔
500
        Ok(())
122,574✔
501
    }
122,574✔
502

503
    /// Reference write implementation of stopcode. See struct definition for
504
    /// specification
505
    ///
506
    /// # Errors
507
    /// - [RefPackError::Io]: Generic IO error occurred while attempting to
508
    ///   write data
509
    #[inline]
510
    pub fn write_stop(number: u8, writer: &mut (impl Write + Seek)) -> RefPackResult<()> {
101,550✔
511
        let out = 0b1111_1100 | (number & 0b0000_0011);
101,550✔
512
        writer.write_u8(out)?;
101,550✔
513
        Ok(())
101,550✔
514
    }
101,550✔
515

516
    /// Encodes and writes a command to a `Write + Seek` writer
517
    ///
518
    /// # Errors
519
    /// - [RefPackError::Io]: Generic IO error occurred while attempting to
520
    ///   write data
521
    pub fn write(self, writer: &mut (impl Write + Seek)) -> RefPackResult<()> {
275,404✔
522
        match self {
275,404✔
523
            Command::Short {
524
                offset,
16,827✔
525
                length,
16,827✔
526
                literal,
16,827✔
527
            } => Self::write_short(offset, length, literal, writer),
16,827✔
528
            Command::Medium {
529
                offset,
17,267✔
530
                length,
17,267✔
531
                literal,
17,267✔
532
            } => Self::write_medium(offset, length, literal, writer),
17,267✔
533
            Command::Long {
534
                offset,
17,186✔
535
                length,
17,186✔
536
                literal,
17,186✔
537
            } => Self::write_long(offset, length, literal, writer),
17,186✔
538
            Command::Literal(literal) => Self::write_literal(literal, writer),
122,574✔
539
            Command::Stop(literal) => Self::write_stop(literal, writer),
101,550✔
540
        }
541
    }
275,404✔
542
}
543

544
#[cfg(test)]
545
prop_compose! {
×
546
    fn bytes_strategy(
×
547
        length: usize,
×
548
    )(
×
549
        vec in vec(any::<u8>(), size_range(length)),
×
550
    ) -> Vec<u8> {
×
551
        vec
×
552
    }
×
553
}
×
554

555
/// Full control block of command + literal bytes following it
556
#[derive(Debug, Clone, PartialEq, Eq)]
67,908✔
557
pub struct Control {
558
    /// The command code
559
    pub command: Command,
560
    /// the literal bytes to write to the stream
561
    pub bytes: Vec<u8>,
562
}
563

564
impl Control {
565
    /// Create a new Control given a command and bytes
566
    #[must_use]
567
    pub fn new(command: Command, bytes: Vec<u8>) -> Self {
42✔
568
        Self { command, bytes }
42✔
569
    }
42✔
570

571
    /// Create a new literal block given a slice of bytes.
572
    /// the `Command` is automatically generated from the length of the byte
573
    /// slice.
574
    #[must_use]
575
    pub fn new_literal_block(bytes: &[u8]) -> Self {
105,392✔
576
        Self {
105,392✔
577
            command: Command::new_literal(bytes.len()),
105,392✔
578
            bytes: bytes.to_vec(),
105,392✔
579
        }
105,392✔
580
    }
105,392✔
581

582
    /// Create a new stop control block given a slice of bytes
583
    /// the `Command` is automatically generated from the length of the byte
584
    /// slice.
585
    #[must_use]
586
    pub fn new_stop(bytes: &[u8]) -> Self {
101,038✔
587
        Self {
101,038✔
588
            command: Command::new_stop(bytes.len()),
101,038✔
589
            bytes: bytes.to_vec(),
101,038✔
590
        }
101,038✔
591
    }
101,038✔
592

593
    /// Reads and decodes a control block from a `Read + Seek` reader
594
    /// # Errors
595
    /// - [RefPackError::Io]: Generic IO error occurred while attempting to read
596
    ///   data
597
    pub fn read(reader: &mut (impl Read + Seek)) -> Result<Self, RefPackError> {
67,908✔
598
        let command = Command::read(reader)?;
67,908✔
599
        let mut buf = vec![0u8; command.num_of_literal().unwrap_or(0)];
67,908✔
600
        reader.read_exact(&mut buf)?;
67,908✔
601
        Ok(Control {
67,908✔
602
            command,
67,908✔
603
            bytes: buf,
67,908✔
604
        })
67,908✔
605
    }
67,908✔
606

607
    /// Encodes and writes a control block to a `Write + Seek` writer
608
    /// # Errors
609
    /// - [RefPackError::Io]: Generic IO Error occurred while attempting to
610
    ///   write data
611
    pub fn write(&self, writer: &mut (impl Write + Seek)) -> Result<(), RefPackError> {
612
        self.command.write(writer)?;
274,380✔
613
        writer.write_all(&self.bytes)?;
274,380✔
614
        Ok(())
274,380✔
615
    }
274,380✔
616
}
617

618
#[cfg(test)]
619
pub(crate) mod tests {
620
    use std::io::{Cursor, SeekFrom};
621

622
    use test_strategy::proptest;
623

624
    use super::*;
625

626
    pub fn generate_random_valid_command() -> BoxedStrategy<Command> {
3✔
627
        let short_copy_strat = (
3✔
628
            SHORT_OFFSET_MIN..=SHORT_OFFSET_MAX,
3✔
629
            SHORT_LENGTH_MIN..=SHORT_LENGTH_MAX,
3✔
630
            COPY_LITERAL_MIN..=COPY_LITERAL_MAX,
3✔
631
        )
3✔
632
            .prop_map(|(offset, length, literal)| {
16,785✔
633
                Command::Short {
16,785✔
634
                    offset,
16,785✔
635
                    length,
16,785✔
636
                    literal,
16,785✔
637
                }
16,785✔
638
            });
16,785✔
639

3✔
640
        let medium_copy_strat = (
3✔
641
            MEDIUM_OFFSET_MIN..=MEDIUM_OFFSET_MAX,
3✔
642
            MEDIUM_LENGTH_MIN..=MEDIUM_LENGTH_MAX,
3✔
643
            COPY_LITERAL_MIN..=COPY_LITERAL_MAX,
3✔
644
        )
3✔
645
            .prop_map(|(offset, length, literal)| {
17,265✔
646
                Command::Medium {
17,265✔
647
                    offset,
17,265✔
648
                    length,
17,265✔
649
                    literal,
17,265✔
650
                }
17,265✔
651
            });
17,265✔
652

3✔
653
        let long_copy_strat = (
3✔
654
            LONG_OFFSET_MIN..=LONG_OFFSET_MAX,
3✔
655
            LONG_LENGTH_MIN..=LONG_LENGTH_MAX,
3✔
656
            COPY_LITERAL_MIN..=COPY_LITERAL_MAX,
3✔
657
        )
3✔
658
            .prop_map(|(offset, length, literal)| {
16,932✔
659
                Command::Long {
16,932✔
660
                    offset,
16,932✔
661
                    length,
16,932✔
662
                    literal,
16,932✔
663
                }
16,932✔
664
            });
16,932✔
665

3✔
666
        let literal_strat = LITERAL_EFFECTIVE_MIN..=LITERAL_EFFECTIVE_MAX;
3✔
667
        let literal =
3✔
668
            Strategy::prop_map(literal_strat, |literal| Command::Literal((literal * 4) + 4));
16,926✔
669
        prop_oneof![
3✔
670
            short_copy_strat,
3✔
671
            medium_copy_strat,
3✔
672
            long_copy_strat,
3✔
673
            literal
3✔
674
        ]
3✔
675
        .boxed()
3✔
676
    }
3✔
677

678
    pub fn generate_stopcode() -> BoxedStrategy<Command> {
1✔
679
        (COPY_LITERAL_MIN..=COPY_LITERAL_MAX)
1✔
680
            .prop_map(Command::Stop)
1✔
681
            .boxed()
1✔
682
    }
1✔
683

684
    pub fn generate_control() -> BoxedStrategy<Control> {
2✔
685
        generate_random_valid_command()
2✔
686
            .prop_flat_map(|command| {
67,652✔
687
                (
67,652✔
688
                    Just(command),
67,652✔
689
                    vec(any::<u8>(), command.num_of_literal().unwrap_or(0)),
67,652✔
690
                )
67,652✔
691
            })
67,652✔
692
            .prop_map(|(command, bytes)| Control { command, bytes })
67,652✔
693
            .boxed()
2✔
694
    }
2✔
695

696
    pub fn generate_stop_control() -> BoxedStrategy<Control> {
1✔
697
        generate_stopcode()
1✔
698
            .prop_flat_map(|command| {
256✔
699
                (
256✔
700
                    Just(command),
256✔
701
                    vec(any::<u8>(), command.num_of_literal().unwrap_or(0)),
256✔
702
                )
256✔
703
            })
256✔
704
            .prop_map(|(command, bytes)| Control { command, bytes })
256✔
705
            .boxed()
1✔
706
    }
1✔
707

708
    pub fn generate_valid_control_sequence(max_length: usize) -> BoxedStrategy<Vec<Control>> {
1✔
709
        (
1✔
710
            vec(generate_control(), 0..(max_length - 1)),
1✔
711
            generate_stop_control(),
1✔
712
        )
1✔
713
            .prop_map(|(vec, stopcode)| {
256✔
714
                let mut vec = vec;
256✔
715
                vec.push(stopcode);
256✔
716
                vec
256✔
717
            })
256✔
718
            .boxed()
1✔
719
    }
1✔
720

721
    #[proptest]
1✔
722
    fn symmetrical_command_copy(
723
        #[strategy(1..=131_071_usize)] offset: usize,
1✔
724
        #[strategy(5..=1028_usize)] length: usize,
1✔
725
        #[strategy(0..=3_usize)] literal: usize,
1✔
726
    ) {
727
        let expected = Command::new(offset, length, literal);
728
        let mut buf = Cursor::new(vec![]);
729
        expected.write(&mut buf).unwrap();
730
        buf.seek(SeekFrom::Start(0)).unwrap();
731
        let out: Command = Command::read(&mut buf).unwrap();
732

733
        prop_assert_eq!(out, expected);
734
    }
735

736
    #[proptest]
1✔
737
    fn symmetrical_command_literal(#[strategy(0..=27_usize)] literal: usize) {
1✔
738
        let real_length = (literal * 4) + 4;
739

740
        let expected = Command::new_literal(real_length);
741
        let mut buf = Cursor::new(vec![]);
742
        expected.write(&mut buf).unwrap();
743
        buf.seek(SeekFrom::Start(0)).unwrap();
744
        let out: Command = Command::read(&mut buf).unwrap();
745

746
        prop_assert_eq!(out, expected);
747
    }
748

749
    #[proptest]
1✔
750
    fn symmetrical_command_stop(#[strategy(0..=3_usize)] input: usize) {
1✔
751
        let expected = Command::new_stop(input);
752
        let mut buf = Cursor::new(vec![]);
753
        expected.write(&mut buf).unwrap();
754
        buf.seek(SeekFrom::Start(0)).unwrap();
755
        let out: Command = Command::read(&mut buf).unwrap();
756

757
        prop_assert_eq!(out, expected);
758
    }
759

760
    #[proptest]
1✔
761
    fn symmetrical_any_command(#[strategy(generate_random_valid_command())] input: Command) {
1✔
762
        let expected = input;
763
        let mut buf = Cursor::new(vec![]);
764
        expected.write(&mut buf).unwrap();
765
        buf.seek(SeekFrom::Start(0)).unwrap();
766
        let out: Command = Command::read(&mut buf).unwrap();
767

768
        prop_assert_eq!(out, expected);
769
    }
770

771
    #[test]
1✔
772
    #[should_panic]
773
    fn command_reject_new_stop_invalid() {
1✔
774
        let _invalid = Command::new_stop(8000);
1✔
775
    }
1✔
776

777
    #[test]
1✔
778
    #[should_panic]
779
    fn command_reject_new_literal_invalid() {
1✔
780
        let _invalid = Command::new_literal(8000);
1✔
781
    }
1✔
782

783
    #[test]
1✔
784
    #[should_panic]
785
    fn command_reject_new_invalid_high_offset() {
1✔
786
        let _invalid = Command::new(500_000, 0, 0);
1✔
787
    }
1✔
788

789
    #[test]
1✔
790
    #[should_panic]
791
    fn command_reject_new_invalid_high_length() {
1✔
792
        let _invalid = Command::new(0, 500_000, 0);
1✔
793
    }
1✔
794

795
    #[test]
1✔
796
    #[should_panic]
797
    fn command_reject_new_invalid_high_literal() {
1✔
798
        let _invalid = Command::new(0, 0, 6000);
1✔
799
    }
1✔
800

801
    #[proptest]
1✔
802
    fn symmetrical_control(#[strategy(generate_control())] input: Control) {
1✔
803
        let expected = input;
804
        let mut buf = Cursor::new(vec![]);
805
        expected.write(&mut buf).unwrap();
806
        buf.seek(SeekFrom::Start(0)).unwrap();
807
        let out: Control = Control::read(&mut buf).unwrap();
808

809
        prop_assert_eq!(out, expected);
810
    }
811
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc