Skip to main content

image_rider/disk_format/apple/
nibble.rs

1//! Encoding and Decoding Nibble-based disk formats
2use std::collections::BTreeMap;
3use std::convert::TryInto;
4use std::fs::File;
5use std::io::Write;
6use std::path::PathBuf;
7
8use crate::config::Config;
9use log::{debug, error};
10
11use nom::{
12    bytes::streaming::{take, take_until},
13    character::complete::one_of,
14    multi::many0,
15    number::complete::le_u8,
16    IResult,
17};
18
19use crate::disk_format::image::DiskImageSaver;
20
21/// The different nibble encoding formats used for Apple disk images.
22/// These are required because of hardware requirements with Apple
23/// disk drives.  Not all 256 possible byte values could be written to
24/// disk.
25///
26/// The first requirement for hardware required that the high bit
27/// always be set.
28/// Second, at least two adjacent bits need to be set.
29/// Third, at most one pair of consecutive zero bits.
30///
31/// This leaves 34 valid disk bytes, in the range from AA to FF.
32/// Two of these bytes are reserved: 0xAA and 0xD5
33///
34/// This led to the "4 and 4" encoding format.  This
35/// splits the byte into two bytes, one containing the odd bytes and
36/// one containing the even bytes.
37/// Other encoding formats satisify these properties while allowing
38/// more efficient data usage.
39pub enum Format {
40    /// Four and four splits each data byte into two disk bytes,
41    /// containing the odd and even bits.
42    ///
43    /// b_7 b_6 b_5 b_4 b_3 b_2 b_1 b_0 -> 1 . . . b_7 b_5 b_3 b_1,
44    ///                                    1 . . . b_6 b_4 b_2 b_0
45    /// Used in earlier versions of DOS before DOS 3
46    FourAndFour,
47
48    /// 5 and 3 uses 5 bits of the data in the disk byte
49    /// Used in DOS versions from DOS 3 to DOS 3.2.1
50    FiveAndThree,
51
52    /// 6 and 2 uses 6 bits of the data in the disk byte
53    /// This was enabled by changes in the disk ROM that allowed two
54    /// consecutive zero bits.
55    /// Used in DOS 3.3
56    SixAndTwo,
57}
58
59/// The converstion table for writing nibble data
60#[allow(dead_code)]
61const NIBBLE_WRITE_TABLE_6_AND_2: [u8; 64] = [
62    0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
63    0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
64    0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
65    0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
66];
67
68/// The converstion table for reading nibble data
69/// It's the write table inverted
70const NIBBLE_READ_TABLE_6_AND_2: [u8; 256] = [
71    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
72    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
73    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
74    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
75    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
76    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
77    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
78    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
79    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
80    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x03, 0x00, 0x04, 0x05, 0x06,
81    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
82    0x00, 0x00, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
83    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x1C, 0x1D, 0x1E,
84    0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x21, 0x00, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
85    0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x2A, 0x2B, 0x00, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
86    0x00, 0x00, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x00, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
87];
88
89/// Parse a single byte encoded in 4 and 4 nibble format
90/// This is used to encode the volume, track, sector and checksum fields
91/// in the address field
92pub fn parse_nibble_byte_4_and_4(i: &[u8]) -> IResult<&[u8], u8> {
93    let (i, bytes) = take(2_usize)(i)?;
94
95    let byte = ((bytes[0] << 1) | 0x01) & bytes[1];
96
97    Ok((i, byte))
98}
99
100/// An address field identifies the data field that follows it
101pub struct AddressField {
102    /// The volume of the track
103    pub volume: u8,
104    /// The track of the track
105    pub track: u8,
106    /// The sector of the track
107    pub sector: u8,
108    /// The checksum of the address field
109    pub checksum: u8,
110}
111
112/// Finds the first sector address prologue in the data
113/// Returns the prologue as a slice of three bytes
114pub fn parse_prologue(i: &[u8]) -> IResult<&[u8], &[u8]> {
115    // Find 0xD5 0xAA, the start of the address prologue, then either 0x96 or 0xB5
116    // If another ending byte is found, keep searching from that point
117    let mut new_i = i;
118
119    loop {
120        let (i, _result) = take_until(&[0xD5, 0xAA][..])(new_i)?;
121
122        // Start the search at the character after DFAA
123        new_i = &i[2..];
124
125        let result = one_of::<&[u8], [u8; 2], crate::error::Error>([0x96_u8, 0xB5_u8])(new_i);
126        match result {
127            Ok(r) => {
128                debug!("Found an address field prologue");
129                return Ok((r.0, i));
130            }
131            // Should check for EOF error
132            Err(_e) => {}
133        }
134
135        // Next search includes the character after DFAA
136    }
137}
138
139/// Searches for an address prologue in the image
140/// If it's found, it returns the byte indicating the type of the prologue
141pub fn recognize_prologue(i: &[u8]) -> Option<u8> {
142    let result: IResult<&[u8], &[u8]> = parse_prologue(i);
143
144    match result {
145        Ok(r) => Some(r.1[2]),
146        Err(_) => None,
147    }
148}
149
150/// Find and parse an address field in the nibblized file
151pub fn find_and_parse_address_field(
152    config: &Config,
153) -> impl Fn(&[u8]) -> IResult<&[u8], AddressField> + '_ {
154    // Find the first field
155    // Read in the address field
156    // 3 byte prologue (D5 AA 96)
157    // 2 byte odd-even encoded volume:
158    //   odd (D_7 D_5 D_3 D_1) followed by even (D_6 D_4 D_2 D_0)
159    // 2 byte odd-even encoded track
160    // 2 byte odd-even encoded sector
161    // 2 byte odd-even encoded checksum
162    // Epilogue DE AA EB
163    // debug!("Searching 1");
164    move |i| {
165        let (i, _data) = take_until(&[0xD5, 0xAA, 0x96][..])(i)?;
166        let (i, _prologue) = take(3_usize)(i)?;
167        let (i, volume) = parse_nibble_byte_4_and_4(i)?;
168        let (i, track) = parse_nibble_byte_4_and_4(i)?;
169        let (i, sector) = parse_nibble_byte_4_and_4(i)?;
170        let (i, checksum) = parse_nibble_byte_4_and_4(i)?;
171        let (i, _epilogue) = take(3_usize)(i)?;
172
173        debug!(
174            "Found address field: volume: {}, track: {}, sector: {}, checksum: {}",
175            volume, track, sector, checksum
176        );
177
178        let computed_checksum = volume ^ track ^ sector;
179
180        let address_field = AddressField {
181            volume,
182            track,
183            sector,
184            checksum,
185        };
186
187        if computed_checksum != checksum {
188            error!(
189                "Address field computed checksum not equal to disk checksum: {} {}",
190                computed_checksum, checksum
191            );
192            if !config
193                .settings
194                .get_bool("ignore-checksums")
195                .unwrap_or(false)
196            {
197                panic!(
198                    "Address field computed checksum not equal to disk checksum: {} {}",
199                    computed_checksum, checksum
200                );
201            }
202        }
203
204        Ok((i, address_field))
205    }
206}
207
208/// A 6 and 2 encoded data field that follows an address field in a nibblized image
209pub struct DataField {
210    /// The DataField prologue, three bytes
211    _prologue: [u8; 3],
212    /// 342 bytes of data, encoded as 6 and 2
213    pub data: Vec<u8>,
214    /// The checksum of the data
215    pub checksum: u8,
216    /// The DataField epilogue, three bytes
217    _epilogue: [u8; 3],
218}
219
220/// Parse the data component of a data field
221pub fn parse_6_and_2_nibblized_data(i: &[u8]) -> Vec<u8> {
222    let data: Vec<u8> = i
223        .iter()
224        .map(|b| NIBBLE_READ_TABLE_6_AND_2[(*b & 0x7F) as usize])
225        .collect();
226
227    data
228}
229
230/// Find and parse a data field in the nibblized file
231pub fn find_and_parse_data_field(i: &[u8]) -> IResult<&[u8], DataField> {
232    // Find the next sequence of 0xD5 0xAA 0xAD that identifies a field
233    // let (i, find_tag) = tag([0xD5, 0xAA, 0xAD])(i)?;
234    // Find the first field
235    let (i, _data) = take_until(&[0xD5, 0xAA, 0xAD][..])(i)?;
236
237    // Read in the data field
238    // 3 byte prologue (D5 AA AD)
239    // 342 bytes data, 6 and 2 encoded
240    // 1 byte checksum
241    // Epilogue DE AA EB
242    let (i, prologue) = take(3_usize)(i)?;
243    let (i, data) = take(342_usize)(i)?;
244    let (i, checksum) = le_u8(i)?;
245    // let (i, _epilogue) = tag(&[0xDE, 0xAA, 0xEB][..])(i)?;
246    let (i, epilogue) = take(3_usize)(i)?;
247
248    Ok((
249        i,
250        DataField {
251            _prologue: prologue.try_into().unwrap(),
252            data: data.to_vec(),
253            checksum,
254            _epilogue: epilogue.try_into().unwrap(),
255        },
256    ))
257}
258
259/// A 256-byte 8-bit data structure computed from 6 and 2 data
260#[derive(Clone)]
261pub struct Sector {
262    /// The data
263    pub data: Vec<u8>,
264}
265
266/// Compute the checksum and transformed buffer for the data field
267pub fn data_field_build_buffer(data_field: &DataField) -> ([u8; 342], u8) {
268    // The data is split up into several different sections
269    // The first 0x56 bytes are the "auxiliary data buffer"
270    // Starting at offset 0x56 the 6 bit bytes are stored
271    let mut computed_checksum: u8 = 0;
272    let data_field_data_size = data_field.data.len();
273    let mut buffer = [0; 342];
274
275    for (index, byte) in data_field.data.iter().enumerate() {
276        computed_checksum ^= NIBBLE_READ_TABLE_6_AND_2[(*byte) as usize];
277        if index < 0x56 {
278            buffer[data_field_data_size - index - 1] = computed_checksum;
279        } else {
280            buffer[index - 0x56] = computed_checksum;
281        }
282    }
283    computed_checksum ^= NIBBLE_READ_TABLE_6_AND_2[data_field.checksum as usize];
284
285    (buffer, computed_checksum)
286}
287
288/// Transform a 6 and 2 data field to a 256-byte sector
289pub fn transform_data_field(config: &Config, data_field: &DataField) -> Sector {
290    // The data is split up into several different sections
291    // The first 0x56 bytes are the "auxiliary data buffer"
292    // Starting at offset 0x56 the 6 bit bytes are stored
293    // Two references that explain the encoding and decoding:
294    // Beneath Apple DOS and Beneath Apple ProDOS
295    // The source code for AppleCommander and apple2emu was invaluable
296    // in writing this code
297    let mut data = [0; 256];
298
299    let (buffer, computed_checksum) = data_field_build_buffer(data_field);
300
301    if computed_checksum != 0 {
302        error!(
303            "Invalid checksum on data: calculated: {}, disk: {}",
304            computed_checksum, data_field.checksum
305        );
306        if !config
307            .settings
308            .get_bool("ignore-checksums")
309            .unwrap_or(false)
310        {
311            panic!(
312                "Invalid checksum on data: calculated: {}, disk: {}",
313                computed_checksum, data_field.checksum
314            );
315        }
316    }
317
318    let reverse_values = [0x00, 0x02, 0x01, 0x03];
319    for i in 0..=255 {
320        let byte_1 = buffer[i];
321        let nibble_low = buffer.len() - (i % 0x56) - 1;
322        let byte_2 = buffer[nibble_low];
323        let shift_pairs = (i / 0x56) * 2;
324        let byte: u8 = (byte_1 << 2) | reverse_values[((byte_2 >> shift_pairs) & 0x03) as usize];
325        data[i] = byte;
326    }
327
328    Sector {
329        data: data.to_vec(),
330    }
331}
332
333// pub fn build_nibble_sector_5_and_3(data: &[u8]) -> DataField {
334// }
335
336/// Nibblize a sector
337/// This nibblizes a sector using the 6 and 2 algorithm
338/// This encodes data in the lower six bits.
339/// There are two reserved bytes, 0xAA and 0xD5
340///
341/// There are several ways this can be done.  The clearest is to split
342/// up the data into blocks that are multiples of six, since the
343/// encoding format uses the lower six bits.
344/// For u8 blocks of size six or 256 (the standard sector size) work.
345pub fn build_nibble_sector(data: &[u8]) -> DataField {
346    // The nibble data plus two bytes for the checksum
347    let mut nibble_data: [u8; 344] = [0; 344];
348
349    // The following was recommended from cargo-clippy, it's the equivalent of a
350    // loop over the entire data array:
351    // for i in 0..=255 {
352    //     nibble_data[i + 86] = data[i];
353    // }
354    // Copy a slice instead
355    nibble_data[86..(0xFF + 0x56 + 1)].copy_from_slice(&data[..(0xFF + 1)]);
356
357    let mut val: u8;
358
359    for (i, nibble_item) in nibble_data.iter_mut().enumerate().take(0x56) {
360        let ac_index: usize = (i + 0xAC) % 0x100;
361        let index_56: usize = (i + 0x56) % 0x100;
362        let index: usize = i % 0x100;
363        val = (((data[ac_index] & 0x1) << 1) | ((data[ac_index] & 0x2) >> 1)) << 6;
364        val |= (((data[index_56] & 0x1) << 1) | ((data[index_56] & 0x2) >> 1)) << 4;
365        val |= (((data[index] & 0x1) << 1) | ((data[index] & 0x2) >> 1)) << 2;
366        *nibble_item = val;
367    }
368
369    nibble_data[84] &= 0x3F;
370    nibble_data[85] &= 0x3F;
371
372    let mut checksum: u8 = 0;
373    let mut saved_data: u8;
374    for item in &mut nibble_data {
375        saved_data = *item;
376        *item ^= checksum;
377        checksum = saved_data;
378    }
379
380    let final_data: [u8; 342] = nibble_data[0..=341]
381        .iter()
382        .map(|d| NIBBLE_WRITE_TABLE_6_AND_2[(d >> 2) as usize])
383        .collect::<Vec<u8>>()
384        .try_into()
385        .unwrap();
386
387    DataField {
388        _prologue: [0xD5, 0xAA, 0xAD],
389        data: final_data.to_vec(),
390        checksum,
391        _epilogue: [0xDE, 0xAA, 0xEB],
392    }
393}
394
395/// Nibblize a slice of u8 data
396pub fn nibblize_data(data: &[u8]) -> Vec<u8> {
397    let mut output_data: Vec<u8> = Vec::new();
398
399    let mut i = 0;
400    debug!("Data length: {}", data.len());
401    while (i + 256) < data.len() {
402        let block = &data[i..=(i + 255)];
403        output_data.append(&mut build_nibble_sector(block).data);
404        i += 256;
405    }
406
407    if i > 0 {
408        i -= 256;
409    }
410    let block = &data[i..data.len()];
411    output_data.append(&mut build_nibble_sector(block).data);
412
413    output_data
414}
415
416/// A single track on the disk
417#[derive(Default)]
418pub struct Track {
419    /// The sectors on the disk
420    pub sectors: BTreeMap<u8, Sector>,
421}
422
423/// A single volume on the disk
424#[derive(Default)]
425pub struct Volume {
426    /// The tracks on the disk
427    pub tracks: BTreeMap<u8, Track>,
428}
429
430/// A Nibble encoded disk
431/// (although this is generic enough a module-wide data structure
432/// could be used)
433#[derive(Default)]
434pub struct NibbleDisk {
435    /// The sectors on the disk
436    pub volumes: BTreeMap<u8, Volume>,
437}
438
439// impl DiskImageParser for NibbleDisk {
440//     fn parse_disk_image<'a>(
441//         &self,
442//         config: &Config,
443//         filename: &str,
444//         // data: &'a [u8],
445//     ) -> IResult<&'a [u8], DiskImage<'a>> {
446//         let guess_option = format_from_filename_and_data(filename, data);
447
448//         match guess_option {
449//             Some(guess) => {
450//                 let (i, disk) = parse_nib_disk(config)(data)?;
451//                 Ok((
452//                     i,
453//                     DiskImage::Apple(AppleDisk {
454//                         encoding: guess.encoding,
455//                         format: guess.format,
456//                         data: AppleDiskData::Nibble(disk),
457//                     }),
458//                 ))
459//             }
460//             None => {
461//                 panic!("Invalid format");
462//             }
463//         }
464//     }
465// }
466
467impl DiskImageSaver for NibbleDisk {
468    fn save_disk_image(
469        &self,
470        _config: &Config,
471        _selected_filename: Option<&str>,
472        filename: &str,
473    ) -> std::result::Result<(), crate::error::Error> {
474        let filename = PathBuf::from(filename);
475        let file_result = File::create(filename);
476        match file_result {
477            Ok(mut file) => {
478                for volume in self.volumes.values() {
479                    for track in volume.tracks.values() {
480                        for sector in track.sectors.values() {
481                            file.write_all(&sector.data).unwrap();
482                        }
483                    }
484                }
485            }
486            Err(e) => error!("Error opening file: {}", e),
487        }
488        Ok(())
489    }
490}
491
492/// A field, containing both the data field and address field
493pub struct Field {
494    /// The address field, which contains volume, track and sector info indicating
495    /// where this sector is located on the disk
496    /// It also contains a checksum
497    pub address_field: AddressField,
498    /// The data field, which contains the data and checksum
499    pub data_field: DataField,
500}
501
502/// Parse an address field, data field and build a Sector
503pub fn parse_nib_sector(config: &Config) -> impl Fn(&[u8]) -> IResult<&[u8], Field> + '_ {
504    move |i| {
505        let (i, header) = find_and_parse_address_field(config)(i)?;
506        let (i, data_field) = find_and_parse_data_field(i)?;
507
508        Ok((
509            i,
510            Field {
511                address_field: header,
512                data_field,
513            },
514        ))
515    }
516}
517
518/// Parse an entire nibble encoded disk
519pub fn parse_nib_disk(config: &Config) -> impl Fn(&[u8]) -> IResult<&[u8], NibbleDisk> + '_ {
520    move |i| {
521        let (i, fields) = many0(parse_nib_sector(config))(i)?;
522
523        debug!("Found {} fields", fields.len());
524        let mut disk = NibbleDisk::default();
525
526        for field in &fields {
527            debug!("Parsing another field");
528            let volume = disk.volumes.entry(field.address_field.volume);
529            let track = volume.or_default().tracks.entry(field.address_field.track);
530            let sector = track.or_default().sectors.entry(field.address_field.sector);
531            sector.or_insert_with(|| transform_data_field(config, &field.data_field));
532        }
533
534        Ok((i, disk))
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::{
541        build_nibble_sector, data_field_build_buffer, find_and_parse_address_field,
542        parse_nibble_byte_4_and_4, parse_prologue, transform_data_field, DataField,
543        NIBBLE_WRITE_TABLE_6_AND_2,
544    };
545    use crate::config::{Config, Configuration};
546    use pretty_assertions::assert_eq;
547
548    /// Test nibble byte 4 and 4 parsing works
549    #[test]
550    fn parse_nibble_byte_4_and_4_works() {
551        let volume_data: [u8; 2] = [0xFF, 0xFE];
552        let track_data: [u8; 2] = [0xAB, 0xBF];
553        let sector_data: [u8; 2] = [0xAA, 0xAF];
554        let checksum_data: [u8; 2] = [0xFE, 0xEE];
555
556        let zero_data: [u8; 2] = [0x00, 0x00];
557        let one_data: [u8; 2] = [0x00, 0x01];
558
559        let volume_result = parse_nibble_byte_4_and_4(&volume_data);
560        match volume_result {
561            Ok(volume) => {
562                assert_eq!(volume.1, 0xFE);
563            }
564            Err(e) => {
565                panic!("Parser failed: {}", e);
566            }
567        }
568
569        let track_result = parse_nibble_byte_4_and_4(&track_data);
570        match track_result {
571            Ok(track) => {
572                assert_eq!(track.1, 0x17);
573            }
574            Err(e) => {
575                panic!("Parser failed: {}", e);
576            }
577        }
578
579        let sector_result = parse_nibble_byte_4_and_4(&sector_data);
580        match sector_result {
581            Ok(sector) => {
582                assert_eq!(sector.1, 0x05);
583            }
584            Err(e) => {
585                panic!("Parser failed: {}", e);
586            }
587        }
588
589        let checksum_result = parse_nibble_byte_4_and_4(&checksum_data);
590        match checksum_result {
591            Ok(checksum) => {
592                assert_eq!(checksum.1, 0xEC);
593            }
594            Err(e) => {
595                panic!("Parser failed: {}", e);
596            }
597        }
598
599        let zero_result = parse_nibble_byte_4_and_4(&zero_data);
600        match zero_result {
601            Ok(zero) => {
602                assert_eq!(zero.1, 0x00);
603            }
604            Err(e) => {
605                panic!("Parser failed: {}", e);
606            }
607        }
608
609        let one_result = parse_nibble_byte_4_and_4(&one_data);
610        match one_result {
611            Ok(one) => {
612                assert_eq!(one.1, 0x01);
613            }
614            Err(e) => {
615                panic!("Parser failed: {}", e);
616            }
617        }
618    }
619
620    /// Test find_and_parse_address_field with valid data
621    #[test]
622    fn find_and_parse_address_field_works() {
623        // volume: 254, track: 23, sector: 5
624        let address_field_data: [u8; 14] = [
625            0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0xAB, 0xBF, 0xAA, 0xAF, 0xFE, 0xEE, 0xDE, 0xAA, 0xEB,
626        ];
627
628        let config = Config::load(config::Config::default()).unwrap();
629        let address_field_result = find_and_parse_address_field(&config)(&address_field_data);
630
631        match address_field_result {
632            Ok(address_field) => {
633                assert_eq!(address_field.1.volume, 0xFE);
634                assert_eq!(address_field.1.track, 0x17);
635                assert_eq!(address_field.1.sector, 0x05);
636                assert_eq!(address_field.1.checksum, 0xEC);
637            }
638            Err(e) => {
639                panic!("Parsing error: {}", e);
640            }
641        }
642    }
643
644    /// Test that transform_data_field works
645    /// TODO: Build some known checksum values
646    #[test]
647    pub fn transform_data_field_works() {
648        let mut data: [u8; 342] = [0; 342];
649
650        for i in 0..=341 {
651            // data[i] = NIBBLE_WRITE_TABLE_6_AND_2[(i % 0x40) as usize];
652            data[i] = NIBBLE_WRITE_TABLE_6_AND_2[i % 0x40];
653        }
654
655        let data_field = DataField {
656            _prologue: [0xD5, 0xAA, 0xAD],
657            data: data.to_vec(),
658            checksum: 0x96,
659            _epilogue: [0xDE, 0xAA, 0xEB],
660        };
661
662        let (_buffer, checksum) = data_field_build_buffer(&data_field);
663
664        assert_eq!(checksum, 1);
665    }
666
667    /// Do a round-trip test of nibblizing a sector and denibblizing
668    /// it.  More work needs to be done on this.
669    ///
670    /// A full disk round-trip test may not be byte-for-byte equal.
671    /// The nibblized data may be out of order, which is why data
672    /// address headers are included.
673    #[test]
674    pub fn data_field_round_trip() {
675        let mut original_data: [u8; 256] = [0; 256];
676
677        for i in 0_u8..=255_u8 {
678            original_data[i as usize] = i;
679        }
680        original_data[255] = 1;
681
682        let data_field = build_nibble_sector(&original_data);
683
684        let config = Config::load(config::Config::default()).unwrap();
685        let sector = transform_data_field(&config, &data_field);
686
687        assert_eq!(sector.data, original_data);
688    }
689
690    /// Test find_and_parse_address_field with invalid checksum
691    #[test]
692    #[should_panic(expected = "Address field computed checksum not equal to disk checksum: 236 0")]
693    fn find_and_parse_address_field_panics_with_invalid_checksum() {
694        // volume: 254, track: 23, sector: 5
695        let address_field_data: [u8; 14] = [
696            0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0xAB, 0xBF, 0xAA, 0xAF, 0x00, 0x00, 0xDE, 0xAA, 0xEB,
697        ];
698
699        let config = Config::load(config::Config::default()).unwrap();
700        let address_field_result = find_and_parse_address_field(&config)(&address_field_data);
701
702        match address_field_result {
703            Ok(_address_field) => {
704                panic!("Should fail with checksum error");
705            }
706            Err(_e) => {
707                panic!("Should fail with checksum error");
708            }
709        }
710    }
711
712    // Test address field prologue parsing and identification
713
714    /// Test parsing a simple address field for a DOS 3.3 disk
715    #[test]
716    fn parse_prologue_dos_33_works() {
717        let data = [0xD5, 0xAA, 0x96];
718
719        let result = parse_prologue(&data);
720
721        match result {
722            Ok(r) => {
723                assert_eq!(r.1, &[0xD5, 0xAA, 0x96]);
724            }
725            Err(_) => {
726                panic!("Shouldn't fail parsing data");
727            }
728        }
729    }
730
731    /// Test parsing a simple address field for a DOS 3.3 disk
732    /// Where the first header is several bytes in the data
733    #[test]
734    fn parse_prologue_dos_33_skip_works() {
735        let data = [0x00, 0x00, 0xD5, 0xAA, 0x96];
736
737        let result = parse_prologue(&data);
738
739        match result {
740            Ok(r) => {
741                assert_eq!(r.1, &[0xD5, 0xAA, 0x96]);
742            }
743            Err(_) => {
744                panic!("Shouldn't fail parsing data");
745            }
746        }
747    }
748
749    /// Test parsing a simple address field for a DOS 3.2 disk
750    #[test]
751    fn parse_prologue_dos_32_works() {
752        let data = [0xD5, 0xAA, 0xB5];
753
754        let result = parse_prologue(&data);
755
756        match result {
757            Ok(r) => {
758                assert_eq!(r.1, &[0xD5, 0xAA, 0xB5]);
759            }
760            Err(_) => {
761                panic!("Shouldn't fail parsing data");
762            }
763        }
764    }
765
766    /// Test parsing a prologe on data without one
767    #[test]
768    fn parse_no_prologue_fails() {
769        let data = [0x00, 0x00, 0x00];
770
771        let result = parse_prologue(&data);
772
773        match result {
774            Ok(_) => {
775                panic!("Should fail parsing data");
776            }
777            Err(e) => match e {
778                nom::Err::Incomplete(nom::Needed::Unknown) => {
779                    assert_eq!("Parsing requires more data", e.to_string());
780                }
781                _ => {
782                    panic!("Wrong parsing error");
783                }
784            },
785        }
786    }
787
788    /// Test parsing a prologue on data that matches the first two
789    /// prologue bytes but not the last.
790    #[test]
791    fn parse_incomplete_prologue_fails() {
792        let data = [0xD5, 0xAA, 0x00];
793
794        let result = parse_prologue(&data);
795
796        match result {
797            Ok(_) => {
798                panic!("Should fail parsing data");
799            }
800            Err(e) => match e {
801                nom::Err::Incomplete(nom::Needed::Unknown) => {
802                    assert_eq!("Parsing requires more data", e.to_string());
803                }
804                _ => {
805                    panic!("Wrong parsing error");
806                }
807            },
808        }
809    }
810
811    /// Test parsing a prologue on data that matches the first two
812    /// prologue bytes with no data left fails.
813    #[test]
814    fn parse_incomplete_prologue_two_bytes_fails() {
815        let data = [0xD5, 0xAA];
816
817        let result = parse_prologue(&data);
818
819        match result {
820            Ok(_) => {
821                panic!("Should fail parsing data");
822            }
823            Err(e) => match e {
824                nom::Err::Incomplete(nom::Needed::Unknown) => {
825                    assert_eq!("Parsing requires more data", e.to_string());
826                }
827                _ => {
828                    panic!("Wrong parsing error");
829                }
830            },
831        }
832    }
833}