1use core::mem;
53use core::convert::{TryInto, TryFrom};
54use core::borrow::Borrow;
55use core::fmt;
56use core::iter::Cycle;
57use core::slice;
58use std::borrow::Cow;
59use std::io::{self, Read, Write, Seek};
60use std::collections::{HashMap, HashSet};
61
62use crate::ReadExactEx;
63use super::tap::{
64    BlockType, Header, TapChunkWriter, TapChunkReader, TapChunkRead, TapChunkInfo,
65    DATA_BLOCK_FLAG, array_name
66};
67pub use spectrusty_peripherals::storage::microdrives::{
68    MAX_USABLE_SECTORS, HEAD_SIZE,
69    Sector, MicroCartridge, MicroCartridgeIdSecIter};
70
71pub fn checksum<I: IntoIterator<Item=B>, B: Borrow<u8>>(iter: I) -> u8 {
73    iter.into_iter().fold(0, |acc, x| {
74        let (mut acc, carry) = acc.overflowing_add(*x.borrow());
75        acc = acc.wrapping_add(carry as u8);
76        if acc == u8::max_value() { 0 } else { acc }
77    })
78}
79
80pub trait MicroCartridgeExt: Sized {
82    fn file_info<S: AsRef<[u8]>>(&self, file_name: S) -> Result<Option<CatFile>, MdrValidationError>;
84    fn file_type<S: AsRef<[u8]>>(&self, file_name: S) -> Result<Option<CatFileType>, MdrValidationError>;
86    fn retrieve_file<S: AsRef<[u8]>, W: Write>(&self, file_name: S, wr: W) -> io::Result<Option<(CatFileType, usize)>>;
92    fn store_file<S: AsRef<[u8]>, R: Read>(&mut self, file_name: S, is_save: bool, rd: R) -> io::Result<u8>;
105    fn erase_file<S: AsRef<[u8]>>(&mut self, file_name: S) -> u8;
108    fn file_to_tap_writer<S: AsRef<[u8]>, W: Write + Seek>(&self, file_name: S, wr: &mut TapChunkWriter<W>) -> io::Result<bool>;
114    fn file_from_tap_reader<R: Read + Seek>(&mut self, rd: &mut TapChunkReader<R>) -> io::Result<u8>;
126    fn file_sector_ids_unordered<S: AsRef<[u8]>>(&self, file_name: S) -> FileSectorIdsUnordIter<'_>;
130    fn file_sectors<S: AsRef<[u8]>>(&self, file_name: S) -> FileSectorIter<'_>;
134    fn catalog(&self) -> Result<Option<Catalog>, MdrValidationError>;
140    fn catalog_name(&self) -> Result<Option<Cow<'_, str>>, MdrValidationError>;
147    fn validate_sectors(&self) -> Result<usize, MdrValidationError>;
149    fn count_sectors_in_use(&self) -> usize;
151    fn from_mdr<R: Read>(rd: R, max_sectors: usize) -> io::Result<Self>;
162    fn write_mdr<W: Write>(&self, wr: W) -> io::Result<usize>;
170    fn new_formatted<S: AsRef<[u8]>>(max_sectors: usize, catalog_name: S) -> Self;
177}
178
179pub trait SectorExt {
181    fn is_free(&self) -> bool;
183    fn catalog_name(&self) -> &[u8;10];
185    fn file_name(&self) -> &[u8;10];
187    fn file_name_matches(&self, name: &[u8]) -> bool;
189    fn file_type(&self) -> Result<Option<CatFileType>, &'static str>;
191    fn sector_seq(&self) -> u8;
193    fn file_block_seq(&self) -> u8;
195    fn file_block_len(&self) -> u16;
197    fn is_last_file_block(&self) -> bool;
199    fn is_save_file(&self) -> bool;
201    fn data(&self) -> &[u8];
203    fn data_mut(&mut self) -> &mut [u8];
205    fn data_record(&self) -> &[u8];
207    fn set_file_name(&mut self, file_name: &[u8]);
209    fn set_file_block_seq(&mut self, block_seq: u8);
211    fn set_file_block_len(&mut self, len: u16);
213    fn set_last_file_block(&mut self, eof: bool);
215    fn set_save_file_flag(&mut self, is_save: bool);
217    fn update_block_checksums(&mut self);
219    fn erase(&mut self);
221    fn format(&mut self, seq: u8, catalog_name: &[u8]);
226    fn validate(&self) -> Result<(), &'static str>;
228    fn file_header(&self) -> Result<Option<&MdrFileHeader>, &'static str>;
230    fn tap_header(&self) -> Result<Option<Header>, &'static str>;
232}
233
234
235#[derive(Clone, Copy, Debug, PartialEq, Eq)]
237pub struct MdrValidationError {
238    pub index: u8,
240    pub description: &'static str
242}
243
244#[derive(Clone, Debug)]
246pub struct Catalog {
247    pub name: String,
249    pub files: HashMap<[u8;10], CatFile>,
251    pub sectors_free: u8
253}
254
255#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
257pub struct CatFile {
258    pub size: u32,
260    pub blocks: u8,
262    pub copies: u8,
264    pub file_type: CatFileType
266}
267
268#[derive(Clone, Copy, Debug, PartialEq, Eq)]
270pub enum CatFileType {
271    Data,
273    File(BlockType)
275}
276
277pub struct FileSectorIter<'a> {
281    counter: u8,
282    stop: u8,
283    block_seq: u8,
284    name: [u8; 10],
285    iter: Cycle<MicroCartridgeIdSecIter<'a>>
286}
287
288pub struct FileSectorIdsUnordIter<'a> {
290    name: [u8; 10],
291    iter: MicroCartridgeIdSecIter<'a>
292}
293
294#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
296pub struct SectorBlock {
297    pub index: u8,
299    pub block_seq: u8
301}
302
303#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
309#[repr(C,packed)]
310pub struct MdrFileHeader {
311    pub block_type: BlockType,
313    pub length: [u8;2],
315    pub start: [u8;2],
317    pub prog_length: [u8;2],
319    pub line: [u8;2]
321}
322
323#[repr(C)]
324union MdrFileHeaderArray {
325    header: MdrFileHeader,
326    array: [u8; mem::size_of::<MdrFileHeader>()],
327}
328
329impl MdrFileHeader {
330    pub fn array_name(&self) -> char {
331        array_name(self.prog_length[0])
332    }
333
334    pub fn length(&self) -> u16 {
335        u16::from_le_bytes(self.length)
336    }
337
338    pub fn start(&self) -> u16 {
339        u16::from_le_bytes(self.start)
340    }
341
342    pub fn prog_length(&self) -> u16 {
343        u16::from_le_bytes(self.prog_length)
344    }
345
346    pub fn line(&self) -> u16 {
347        u16::from_le_bytes(self.line)
348    }
349
350    pub fn into_array(self) -> [u8; mem::size_of::<MdrFileHeader>()] {
351        unsafe { MdrFileHeaderArray { header: self }.array }
352    }
353}
354
355impl From<&'_ Header> for MdrFileHeader {
356    fn from(header: &Header) -> Self {
357        let block_type = header.block_type;
358        let length = header.length.to_le_bytes();
359        let mut start = header.par1;
360        let mut prog_length = [0, 0];
361        let mut line = [0, 0];
362        match block_type {
363            BlockType::Program => {
364                start = 0x5CCBu16.to_le_bytes();
365                line = header.par1;
366                prog_length = header.par2;
367            }
368            BlockType::NumberArray|BlockType::CharArray => {
369                start = 0x5CCBu16.to_le_bytes();
370                prog_length[0] = header.par1[1];
371            }
372            BlockType::Code => {}
373        }
374        MdrFileHeader { block_type, length, start, prog_length, line }
375    }
376}
377
378impl From<&'_ MdrFileHeader> for Header {
379    fn from(fbh: &MdrFileHeader) -> Self {
380        let length = u16::from_le_bytes(fbh.length);
381        match fbh.block_type {
382            BlockType::Program => {
383                let vars = u16::from_le_bytes(fbh.prog_length);
384                let line = u16::from_le_bytes(fbh.line);
385                Header::new_program(length).with_start(line).with_vars(vars)
386            }
387            BlockType::NumberArray => {
388                Header::new_number_array(length).with_array_name(fbh.array_name())
389            }
390            BlockType::CharArray => {
391                Header::new_char_array(length).with_array_name(fbh.array_name())
392            }
393            BlockType::Code => {
394                let start = u16::from_le_bytes(fbh.start);
395                Header::new_code(length).with_start(start)
396            }
397        }
398    }
399}
400
401impl TryFrom<&[u8]> for &MdrFileHeader {
402    type Error = &'static str;
403    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
404        let data: &[u8; mem::size_of::<MdrFileHeader>()] = data.try_into()
405                            .map_err(|_| "wrong size of slice for MdrFileHeader")?;
406        BlockType::try_from(data[0]).map_err(|_| "file type not recognized" )?;
407        let ptr = data as *const _ as  *const MdrFileHeader;
408        unsafe { Ok(&*ptr) }
409    }
410}
411
412const HDFLAG:  usize =   0; const HDNUMB:  usize =   1; const HDNAME:  usize =   4; const HDCHK:   usize =  14; const RECFLG:  usize =   0; const RECNUM:  usize =   1; const RECLEN:  usize =   2; const RECNAM:  usize =   4; const DESCHK:  usize =  14; const HD_00:   usize =  15; const HD_SIZE: usize =   9; const DCHK:    usize = 527; const RECFLG_EOF:  u8 = 2;
431const RECFLG_SAVE: u8 = 4;
432
433const BLOCK_DATA_MAX: u16 = 512;
434
435fn copy_name(target: &mut [u8;10], name: &[u8]) {
436    let name_len = name.len().min(10);
437    target[..name_len].copy_from_slice(&name[..name_len]);
438    target[name_len..].iter_mut().for_each(|p| *p=b' ');
439}
440
441impl SectorExt for Sector {
442    fn is_free(&self) -> bool {
443        !self.is_last_file_block() && self.file_block_len() < BLOCK_DATA_MAX
444    }
445
446    fn catalog_name(&self) -> &[u8;10] {
447        self.head[HDNAME..HDNAME + 10].try_into().unwrap()
448    }
449
450    fn file_name(&self) -> &[u8;10] {
451        self.data[RECNAM..RECNAM + 10].try_into().unwrap()
452    }
453
454    fn file_name_matches(&self, name: &[u8]) -> bool {
455        let name = &name[0..name.len().min(10)];
456        if &self.data[RECNAM..RECNAM + name.len()] == name {
457            return self.data[RECNAM + name.len()..RECNAM + 10].iter().all(|p| *p==b' ')
458        }
459        false
460    }
461
462    fn file_type(&self) -> Result<Option<CatFileType>, &'static str> {
463        if self.is_free() {
464            return Ok(None)
465        }
466        Ok(Some(if self.is_save_file() {
467            CatFileType::File(BlockType::try_from(self.data[HD_00])
468                .map_err(|_| "file type not recognized" )?)
469        }
470        else {
471            CatFileType::Data
472        }))
473    }
474
475    fn sector_seq(&self) -> u8 {
476        self.head[HDNUMB]
477    }
478
479    fn file_block_seq(&self) -> u8 {
480        self.data[RECNUM]
481    }
482
483    fn file_block_len(&self) -> u16 {
484        let reclen = &self.data[RECLEN..RECLEN + 2];
485        u16::from_le_bytes(<[u8;2]>::try_from(reclen).unwrap())
486    }
487
488    fn is_last_file_block(&self) -> bool {
489        self.data[RECFLG] & RECFLG_EOF != 0
490    }
491
492    fn is_save_file(&self) -> bool {
493        self.data[RECFLG] & RECFLG_SAVE != 0
494    }
495
496    fn data(&self) -> &[u8] {
497        &self.data[DESCHK + 1..DCHK]
498    }
499
500    fn data_mut(&mut self) -> &mut [u8] {
501        &mut self.data[DESCHK + 1..DCHK]
502    }
503
504    fn data_record(&self) -> &[u8] {
505        let reclen = self.file_block_len() as usize;
506        &self.data[DESCHK + 1..DESCHK + 1 + reclen]
507    }
508
509    fn set_file_name(&mut self, file_name: &[u8]) {
510        let block_name = &mut self.data[RECNAM..RECNAM + 10];
511        copy_name(block_name.try_into().unwrap(), file_name);
512    }
513
514    fn set_file_block_seq(&mut self, block_seq: u8) {
515        self.data[RECNUM] = block_seq;
516    }
517
518    fn set_file_block_len(&mut self, len: u16) {
519        if len > BLOCK_DATA_MAX {
520            panic!("RECLEN must be less than 512");
521        }
522        let reclen = len.to_le_bytes();
523        self.data[RECLEN..RECLEN + 2].copy_from_slice(&reclen);
524    }
525
526    fn set_last_file_block(&mut self, eof: bool) {
527        if eof {
528            self.data[RECFLG] |= RECFLG_EOF;
529        }
530        else {
531            self.data[RECFLG] &= !RECFLG_EOF;
532        }
533    }
534
535    fn set_save_file_flag(&mut self, is_save: bool) {
536        if is_save {
537            self.data[RECFLG] |= RECFLG_SAVE;
538        }
539        else {
540            self.data[RECFLG] &= !RECFLG_SAVE;
541        }
542    }
543
544    fn update_block_checksums(&mut self) {
545        self.data[DESCHK] = checksum(&self.data[0..DESCHK]);
546        self.data[DCHK]   = checksum(&self.data[DESCHK + 1..DCHK])
547    }
548
549    fn erase(&mut self) {
550        self.data[RECFLG] = 0;
551        self.data[RECLEN..RECLEN + 2].iter_mut().for_each(|p| *p=0);
552        self.data[DESCHK] = checksum(&self.data[0..DESCHK]);
553    }
554
555    fn format(&mut self, seq: u8, catalog_name: &[u8]) {
556        if !(1..=245).contains(&seq) {
557            panic!("HDNUMB must be between 1 and 254");
558        }
559        self.head[HDFLAG] = 1;
560        self.head[HDNUMB] = seq;
561        let head_name = &mut self.head[HDNAME..HDNAME + 10];
562        copy_name(head_name.try_into().unwrap(), catalog_name);
563        self.head[HDCHK] = checksum(&self.head[0..HDCHK]);
564        self.erase();
565    }
566
567    fn validate(&self) -> Result<(), &'static str> {
568        if self.head[HDFLAG] & 1 != 1 {
569            return Err("bad sector header: HDFLAG bit 0 is reset");
570        }
571        if self.data[RECFLG] & 1 != 0 {
572            return Err("bad data block: RECFLG bit 0 is set");
573        }
574        if !(1..=254).contains(&self.head[HDNUMB]) {
575            return Err("bad sector header: HDNUMB is outside the allowed range: [1-254]");
576        }
577        if self.file_block_len() > BLOCK_DATA_MAX {
578            return Err("bad data block: RECLEN is larger than the sector size: 512");
579        }
580        if self.head[HDCHK] != checksum(&self.head[0..HDCHK]) {
581            return Err("bad sector header: HDCHK invalid checksum");
582        }
583        if self.data[DESCHK] != checksum(&self.data[0..DESCHK]) {
584            return Err("bad data block: DESCHK invalid header checksum");
585        }
586        if !self.is_free() &&
587           self.data[DCHK] != checksum(&self.data[DESCHK+1..DCHK])
588        {
589            return Err("bad data block: DESCHK invalid data checksum");
590        }
591        Ok(())
592    }
593
594    fn file_header(&self) -> Result<Option<&MdrFileHeader>, &'static str> {
595        if !self.is_free() && self.is_save_file() && self.file_block_seq() == 0 {
596            let mdrhd = <&MdrFileHeader>::try_from(&self.data[HD_00..HD_00 + HD_SIZE])?;
597            Ok(Some(mdrhd))
598        }
599        else {
600            Ok(None)
601        }
602
603    }
604
605    fn tap_header(&self) -> Result<Option<Header>, &'static str> {
606        self.file_header().map(|m|
607            m.map(|hd| Header::from(hd).with_name(&self.data[RECNAM..RECNAM + 10]))
608        )
609    }
610}
611
612impl Default for CatFileType {
613    fn default() -> Self {
614        CatFileType::Data
615    }
616}
617
618impl fmt::Display for Catalog {
619    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620        writeln!(f, "{:10}:", self.name)?;
621        for (name, file) in self.files.iter() {
622            writeln!(f, "  {:10} {}", String::from_utf8_lossy(name), file)?;
623        }
624        writeln!(f, "free: {} sec. {} bytes",
625                        self.sectors_free,
626                        self.sectors_free as u32 * BLOCK_DATA_MAX as u32)
627    }
628}
629
630impl fmt::Display for CatFile {
631    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632        write!(f, "{} {:5} bytes {:2} blk.", self.file_type, self.size, self.blocks)?;
633        if self.copies != 1 {
634            write!(f, " {} copies", self.copies)?;
635        }
636        Ok(())
637    }
638}
639
640impl fmt::Display for CatFileType {
641    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642        match self {
643            CatFileType::Data => f.write_str("Data"),
644            CatFileType::File(bt) => bt.fmt(f),
645        }
646    }
647}
648
649impl std::error::Error for MdrValidationError {}
650
651impl fmt::Display for MdrValidationError {
652    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653        write!(f, "MDR validation error in sector #{}: {}", self.index, self.description)
654    }
655}
656
657impl<'a> Iterator for FileSectorIter<'a> {
658    type Item = Result<&'a Sector, MdrValidationError>;
659
660    fn next(&mut self) -> Option<Self::Item> {
661        if self.counter == 0 {
662            return None
663        }
664        for (index, sector) in self.iter.by_ref() {
665            if self.stop == index {
666                self.counter = 0;
667            }
668            else {
669                self.counter -= 1;
670                if !sector.is_free() && sector.file_name() == &self.name
671                   && sector.file_block_seq() == self.block_seq
672                {
673                    if sector.is_last_file_block() {
674                        self.counter = 0;
675                    }
676                    else {
677                        self.stop = index;
678                        self.counter = !0;
679                        self.block_seq += 1;
680                    }
681                    return Some(Ok(sector))
682                }
683            }
684            if self.counter == 0 {
685                if self.block_seq != 0 {
686                    return Some(Err(MdrValidationError { index, description:
687                                    "missing end of file block" }))
688                }
689                break
690            }
691        }
692        None
693    }
694}
695
696impl<'a> Iterator for FileSectorIdsUnordIter<'a> {
697    type Item = SectorBlock;
698
699    fn next(&mut self) -> Option<Self::Item> {
700        for (index, sector) in self.iter.by_ref() {
701            if !sector.is_free() && sector.file_name() == &self.name {
702                let block_seq = sector.file_block_seq();
703                return Some(SectorBlock { index, block_seq })
704            }
705        }
706        None
707    }
708}
709
710impl MicroCartridgeExt for MicroCartridge {
711    fn file_info<S: AsRef<[u8]>>(
712            &self,
713            file_name: S
714        ) -> Result<Option<CatFile>, MdrValidationError>
715    {
716        let file_name = file_name.as_ref();
717        let mut meta = CatFile::default();
718        for (index, sector) in self.iter_with_indices() {
719            if !sector.is_free() && sector.file_name_matches(file_name) {
720                if sector.file_block_seq() == 0 {
721                    meta.copies += 1;
722                    meta.file_type = sector.file_type()
723                        .map_err(|e| MdrValidationError { index, description: e })?
724                        .unwrap();
725                }
726                meta.blocks += 1;
727                meta.size += sector.file_block_len() as u32;
728            }
729        }
730        if meta.copies != 0 {
731            meta.size /= meta.copies as u32;
732            Ok(Some(meta))
733        }
734        else {
735            Ok(None)
736        }
737    }
738
739    fn file_type<S: AsRef<[u8]>>(
740            &self,
741            file_name: S
742        ) -> Result<Option<CatFileType>, MdrValidationError>
743    {
744        let file_name = file_name.as_ref();
745        for (index, sector) in self.iter_with_indices() {
746            if !sector.is_free() && sector.file_block_seq() == 0 && sector.file_name_matches(file_name) {
747                return sector.file_type().map_err(|e| MdrValidationError { index, description: e })
748            }
749        }
750        Ok(None)
751    }
752
753    fn file_to_tap_writer<S: AsRef<[u8]>, W: Write + Seek>(
754            &self,
755            file_name: S,
756            wr: &mut TapChunkWriter<W>
757        ) -> io::Result<bool>
758    {
759        let mut sector_iter = self.file_sectors(file_name);
760        #[allow(clippy::never_loop)]
761        let mut tran = loop {
762            if let Some(sector) = sector_iter.next() {
763                let sector = sector.unwrap();
764                if let Some(header) = sector.tap_header()
765                                      .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
766                {
767                    wr.write_header(&header)?;
768                    let mut tran = wr.begin()?;
769                    tran.write_all(slice::from_ref(&DATA_BLOCK_FLAG))?;
770                    tran.write_all(§or.data_record()[HD_SIZE..])?;
771                    break tran
772                }
773            }
774            return Ok(false)
775        };
776        for sector in sector_iter {
777            let sector = sector.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
778            let record = sector.data_record();
779            tran.write_all(record)?;
780        }
781        tran.commit(true)?;
782        Ok(true)
783    }
784
785    fn file_from_tap_reader<R: Read + Seek>(&mut self, rd: &mut TapChunkReader<R>) -> io::Result<u8> {
786        if rd.chunk_limit() == 0 {
787            rd.next_chunk()?;
788        }
789        let header = if let TapChunkInfo::Head(header) = TapChunkInfo::try_from(rd.get_mut())? {
790            header
791        }
792        else {
793            return Err(io::Error::new(io::ErrorKind::InvalidData, "not a TAP header"))
794        };
795        if let Some(chunk_size) = rd.next_chunk()? {
796            if chunk_size < 2 || header.length != chunk_size - 2 {
797                return Err(io::Error::new(io::ErrorKind::InvalidData, "not a TAP block"))
798            }
799            let mut flag = 0u8;
800            rd.read_exact(slice::from_mut(&mut flag))?;
801            if flag != DATA_BLOCK_FLAG {
802                return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid TAP block flag"))
803            }
804        }
805        else {
806            return Err(io::Error::new(io::ErrorKind::InvalidData, "missing TAP chunk"))
807        }
808        let fbheader = MdrFileHeader::from(&header).into_array();
809        let hdrd = io::Cursor::new(fbheader).chain(rd.take(header.length as u64));
810        let res = self.store_file(header.name, true, hdrd)?;
811        {
812            let mut checksum = 0u8;
813            let res = rd.read_exact(slice::from_mut(&mut checksum));
814            if res.is_err() || rd.checksum != 0 {
815                self.erase_file(header.name);
816                return Err(io::Error::new(io::ErrorKind::InvalidData, "TAP block checksum error"))
817            }
818        }
819        Ok(res)
820    }
821
822    fn retrieve_file<S: AsRef<[u8]>, W: Write>(
823            &self,
824            file_name: S,
825            mut wr: W
826        ) -> io::Result<Option<(CatFileType, usize)>>
827    {
828        let mut len = 0;
829        let mut file_type = None;
830        for sector in self.file_sectors(file_name) {
831            let sector = sector.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
832            if sector.file_block_seq() == 0 {
833                file_type = sector.file_type().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
834            }
835            let record = sector.data_record();
836            wr.write_all(record)?;
837            len += record.len();
838        }
839        Ok(file_type.map(|ft| (ft, len)))
840    }
841
842    fn store_file<S: AsRef<[u8]>, R: Read>(
843            &mut self,
844            file_name: S,
845            is_save: bool,
846            mut rd: R
847        ) -> io::Result<u8>
848    {
849        let file_name = file_name.as_ref();
850        if let Some((index, _)) = self.iter_with_indices().find(|(_,s)|
851                                    !s.is_free() && s.file_name_matches(file_name)) {
852            return Err(io::Error::new(io::ErrorKind::AlreadyExists,
853                        MdrValidationError { index, description: "file name already exists" }))
854        }
855        let mut block_seq = 0;
856        let mut first_byte = 0u8;
857        if !rd.read_exact_or_none(slice::from_mut(&mut first_byte))? {
858            return Ok(0);
859        }
860        for sector in self.into_iter() {
861            if sector.is_free() {
862                let buf = sector.data_mut();
863                buf[0] = first_byte;
864                let len = 1 + rd.read_exact_or_to_end(&mut buf[1..])?;
865                sector.set_file_name(file_name);
866                sector.set_file_block_seq(block_seq);
867                block_seq += 1;
868                sector.set_file_block_len(len.try_into().unwrap());
869                sector.set_save_file_flag(is_save);
870                let is_last = !rd.read_exact_or_none(slice::from_mut(&mut first_byte))?;
871                sector.set_last_file_block(is_last);
872                sector.update_block_checksums();
873                if is_last {
874                    return Ok(block_seq);
875                }
876            }
877        }
878        Err(io::Error::new(io::ErrorKind::WriteZero, "not enough free sectors to fit the whole file"))
879    }
880
881    fn erase_file<S: AsRef<[u8]>>(&mut self, file_name: S) -> u8 {
882        let file_name = file_name.as_ref();
883        let mut block_seq = 0;
884        for sector in self.into_iter() {
885            if !sector.is_free() && sector.file_name_matches(file_name) {
886                sector.erase();
887                block_seq += 1;
888            }
889        }
890        block_seq
891    }
892
893    fn file_sector_ids_unordered<S: AsRef<[u8]>>(
894            &self,
895            file_name: S
896        ) -> FileSectorIdsUnordIter<'_>
897    {
898        let file_name = file_name.as_ref();
899        let mut name = [b' '; 10];
900        copy_name(&mut name, file_name);
901        FileSectorIdsUnordIter {
902            name,
903            iter: self.iter_with_indices()
904        }
905    }
906
907    fn file_sectors<S: AsRef<[u8]>>(
908            &self,
909            file_name: S
910        ) -> FileSectorIter<'_>
911    {
912        let file_name = file_name.as_ref();
913        let mut name = [b' '; 10];
914        copy_name(&mut name, file_name);
915        let counter = self.count_formatted().try_into().unwrap();
916        FileSectorIter {
917            counter,
918            stop: !0,
919            block_seq: 0,
920            name,
921            iter: self.iter_with_indices().cycle()
922        }
923    }
924
925    fn catalog(&self) -> Result<Option<Catalog>, MdrValidationError> {
926        let name = if let Some(name) = self.catalog_name()? {
927            name.to_string()
928        }
929        else {
930            return Ok(None)
931        };
932        let mut files: HashMap<[u8;10], CatFile> = HashMap::new();
933        let mut sectors_free = 0u8;
934        for (index, sector) in self.iter_with_indices() {
935            if sector.is_free() {
936                sectors_free += 1;
937            }
938            else {
939                let meta = files.entry(*sector.file_name()).or_default();
940                if sector.file_block_seq() == 0 {
941                    meta.copies += 1;
942                    meta.file_type = sector.file_type()
943                        .map_err(|e| MdrValidationError { index, description: e })?
944                        .unwrap();
945                }
946                meta.blocks += 1;
947                meta.size += sector.file_block_len() as u32;
948            }
949        }
950        for meta in files.values_mut() {
951            if meta.copies == 0 {
952                return Err(MdrValidationError { index: u8::max_value(), description: "block 0 is missing" })
953            }
954            meta.size /= meta.copies as u32;
955        }
956        Ok(Some(Catalog {
957            name, files, sectors_free
958        }))
959    }
960
961    fn catalog_name(&self) -> Result<Option<Cow<'_, str>>, MdrValidationError> {
962        let mut header: Option<&[u8]> = None;
963        let mut seqs: HashSet<u8> = HashSet::new();
964        for (index, sector) in self.iter_with_indices() {
965            sector.validate().map_err(|description| MdrValidationError { index, description })?;
966            if let Some(name) = header {
967                if name != sector.catalog_name() {
968                    return Err(MdrValidationError { index, description:
969                        "catalog name is inconsistent" })
970                }
971            }
972            else {
973                header = Some(sector.catalog_name());
974            }
975            if !seqs.insert(sector.sector_seq()) {
976                return Err(MdrValidationError { index, description:
977                    "at least two sectors have the same identification number" })
978            }
979        }
980        Ok(header.map(String::from_utf8_lossy))
981    }
982
983    fn validate_sectors(&self) -> Result<usize, MdrValidationError> {
984        let mut count = 0usize;
985        for (index, sector) in self.iter_with_indices() {
986            sector.validate().map_err(|description| MdrValidationError { index, description })?;
987            count += 1;
988        }
989        Ok(count)
990    }
991
992    fn count_sectors_in_use(&self) -> usize {
993        self.into_iter().filter(|sec| !sec.is_free()).count()
994    }
995
996    fn from_mdr<R: Read>(mut rd: R, max_sectors: usize) -> io::Result<Self> {
997        let mut sectors: Vec<Sector> = Vec::with_capacity(max_sectors);
998        let mut write_protect = false;
999        'sectors: loop {
1000            let mut sector = Sector::default();
1001            loop {
1002                match rd.read_exact_or_to_end(&mut sector.head) {
1003                    Ok(0) => break 'sectors,
1004                    Ok(1) => {
1005                        write_protect = sector.head[0] != 0;
1006                        break 'sectors
1007                    }
1008                    Ok(HEAD_SIZE) => break,
1009                    Ok(..) => return Err(io::Error::new(io::ErrorKind::UnexpectedEof,
1010                                                "failed to fill whole sector header")),
1011                    Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
1012                    Err(e) => return Err(e),
1013                }
1014            }
1015            if sectors.len() == MAX_USABLE_SECTORS {
1016                return Err(io::Error::new(io::ErrorKind::InvalidData,
1017                                            "only 254 sectors are supported"));
1018            }
1019            rd.read_exact(&mut sector.data)?;
1020            sectors.push(sector);
1021        }
1022        if sectors.is_empty() {
1023            return Err(io::Error::new(io::ErrorKind::InvalidData, "no sectors read"));
1024        }
1025        let max_sectors = max_sectors.max(sectors.len());
1026        Ok(MicroCartridge::new_with_sectors(sectors, write_protect, max_sectors))
1027    }
1028
1029    fn write_mdr<W: Write>(&self, mut wr: W) -> io::Result<usize> {
1030        let mut bytes: usize = 0;
1031        for sector in self.into_iter() {
1032            wr.write_all(§or.head)?;
1033            wr.write_all(§or.data)?;
1034            bytes += sector.head.len() + sector.data.len();
1035        }
1036        if bytes == 0 {
1037            return Ok(0)
1038        }
1039        wr.write_all(slice::from_ref(&self.is_write_protected().into()))?;
1040        Ok(bytes + 1)
1041    }
1042
1043    fn new_formatted<S: AsRef<[u8]>>(max_sectors: usize, catalog_name: S) -> Self {
1044        let mut sectors = vec![Sector::default(); max_sectors.min(254)];
1045        for (sector, seq) in sectors.iter_mut().rev().zip(1..254) {
1046            sector.format(seq, catalog_name.as_ref());
1047        }
1048        MicroCartridge::new_with_sectors(sectors, false, max_sectors)
1049    }
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054    use super::*;
1055    use std::fs::File;
1056    use rand::prelude::*;
1057    use crate::tap;
1058
1059    #[test]
1060    fn mdr_works() {
1061        let mdr = MicroCartridge::default();
1062        assert!(mdr.catalog_name().unwrap().is_none());
1063        assert!(mdr.catalog().unwrap().is_none());
1064        assert!(mdr.file_type("hello world").unwrap().is_none());
1065        assert!(mdr.file_info("hello world").unwrap().is_none());
1066        assert_eq!(mdr.count_sectors_in_use(), 0);
1067        assert_eq!(mdr.max_sectors(), 256);
1068        assert_eq!(mdr.count_formatted(), 0);
1069        let mut mdr = MicroCartridge::new_formatted(10, "testing");
1070        for i in 0..10 {
1071            assert_eq!(mdr[i].is_free(), true);
1072            assert_eq!(mdr[i].sector_seq(), 10 - i);
1073            assert_eq!(mdr[i].catalog_name(), b"testing   ");
1074            assert_eq!(mdr[i].validate().unwrap(), ());
1075        }
1076        assert_eq!(mdr.count_sectors_in_use(), 0);
1077        assert_eq!(mdr.max_sectors(), 10);
1078        assert_eq!(mdr.count_formatted(), 10);
1079        assert_eq!(mdr.catalog_name().unwrap().unwrap(), "testing   ");
1080        let catalog = mdr.catalog().unwrap().unwrap();
1081        assert_eq!(catalog.name, "testing   ");
1082        assert_eq!(catalog.files, HashMap::<[u8;10],CatFile>::new());
1083        assert_eq!(catalog.sectors_free, 10);
1084        assert_eq!(mdr.store_file("hello world", false, io::Cursor::new([1,2,3,4,5])).unwrap(), 1);
1085        assert_eq!(mdr.file_type("hello world").unwrap().unwrap(), CatFileType::Data);
1086        assert_eq!(mdr.file_info("hello world").unwrap().unwrap(),
1087                CatFile { size: 5, blocks: 1, copies: 1, file_type: CatFileType::Data});
1088        assert_eq!(mdr.count_sectors_in_use(), 1);
1089        assert_eq!(mdr.max_sectors(), 10);
1090        assert_eq!(mdr.count_formatted(), 10);
1091        let catalog = mdr.catalog().unwrap().unwrap();
1092        assert_eq!(catalog.name, "testing   ");
1093        assert_eq!(catalog.files.len(), 1);
1094        assert_eq!(catalog.files.get(b"hello worl").unwrap(), &CatFile {
1095            size: 5,
1096            blocks: 1,
1097            copies: 1,
1098            file_type: CatFileType::Data
1099        });
1100        assert_eq!(catalog.sectors_free, 9);
1101        let mut wr = io::Cursor::new(Vec::new());
1102        assert_eq!(mdr.retrieve_file("hello world", &mut wr).unwrap().unwrap(), (CatFileType::Data, 5));
1103        assert_eq!(wr.get_ref(), &vec![1u8,2,3,4,5]);
1104        assert_eq!(mdr.erase_file("hello world"), 1);
1105        let catalog = mdr.catalog().unwrap().unwrap();
1106        assert_eq!(catalog.name, "testing   ");
1107        assert_eq!(catalog.files.len(), 0);
1108        assert_eq!(catalog.sectors_free, 10);
1109        let mut big_file = vec![0u8;5000];
1110        thread_rng().fill(&mut big_file[..]);
1111        assert_eq!(mdr.store_file("big file", false, io::Cursor::new(&big_file[..])).unwrap(), 10);
1112        let catalog = mdr.catalog().unwrap().unwrap();
1113        assert_eq!(catalog.name, "testing   ");
1114        assert_eq!(catalog.files.len(), 1);
1115        assert_eq!(catalog.files.get(b"big file  ").unwrap(), &CatFile {
1116            size: 5000,
1117            blocks: 10,
1118            copies: 1,
1119            file_type: CatFileType::Data
1120        });
1121        assert_eq!(catalog.sectors_free, 0);
1122        assert_eq!(mdr.file_type("big file").unwrap().unwrap(), CatFileType::Data);
1123        assert_eq!(mdr.file_info("big file").unwrap().unwrap(),
1124                CatFile { size: 5000, blocks: 10, copies: 1, file_type: CatFileType::Data});
1125        let err = mdr.store_file("hello world", false, io::Cursor::new([1]));
1126        assert!(err.is_err());
1127        let err = err.err().unwrap();
1128        assert_eq!(err.kind(), io::ErrorKind::WriteZero);
1129        assert_eq!(err.to_string(), "not enough free sectors to fit the whole file");
1130        let mut wr = io::Cursor::new(Vec::new());
1131        assert_eq!(mdr.retrieve_file("big file", &mut wr).unwrap().unwrap(), (CatFileType::Data, 5000));
1132        assert_eq!(wr.get_ref(), &big_file);
1133        assert_eq!(mdr.erase_file("hello world"), 0);
1134        assert_eq!(mdr.erase_file("big file"), 10);
1135        let catalog = mdr.catalog().unwrap().unwrap();
1136        assert_eq!(catalog.name, "testing   ");
1137        assert_eq!(catalog.files.len(), 0);
1138        assert_eq!(catalog.sectors_free, 10);
1139
1140        let file = File::open("../resources/read_tap_test.tap").unwrap();
1141        let mut tap_reader = tap::read_tap(file);
1142        assert_eq!(mdr.file_from_tap_reader(tap_reader.by_ref()).unwrap(), 1);
1143        assert_eq!(mdr.file_from_tap_reader(tap_reader.by_ref()).unwrap(), 1);
1144        assert_eq!(mdr.file_from_tap_reader(tap_reader.by_ref()).unwrap(), 1);
1145        let catalog = mdr.catalog().unwrap().unwrap();
1146        assert_eq!(catalog.name, "testing   ");
1147        assert_eq!(catalog.files.len(), 3);
1148        assert_eq!(catalog.sectors_free, 7);
1149        assert_eq!(catalog.files.get(b"HelloWorld").unwrap(), &CatFile {
1150            size: 9+6, blocks: 1, copies: 1, file_type: CatFileType::File(BlockType::Program)
1151        });
1152        assert_eq!(mdr.file_type("HelloWorld").unwrap().unwrap(), CatFileType::File(BlockType::Program));
1153        assert_eq!(mdr.file_info("HelloWorld").unwrap().unwrap(),
1154                CatFile { size: 9+6, blocks: 1, copies: 1, file_type: CatFileType::File(BlockType::Program)});
1155        assert_eq!(catalog.files.get(b"a(10)     ").unwrap(), &CatFile {
1156            size: 9+53, blocks: 1, copies: 1, file_type: CatFileType::File(BlockType::NumberArray)
1157        });
1158        assert_eq!(mdr.file_type("a(10)").unwrap().unwrap(), CatFileType::File(BlockType::NumberArray));
1159        assert_eq!(mdr.file_info("a(10)").unwrap().unwrap(),
1160                CatFile { size: 9+53, blocks: 1, copies: 1, file_type: CatFileType::File(BlockType::NumberArray)});
1161        assert_eq!(catalog.files.get(b"weekdays  ").unwrap(), &CatFile {
1162            size: 9+26, blocks: 1, copies: 1, file_type: CatFileType::File(BlockType::CharArray)
1163        });
1164        assert_eq!(mdr.file_type("weekdays").unwrap().unwrap(), CatFileType::File(BlockType::CharArray));
1165        assert_eq!(mdr.file_info("weekdays").unwrap().unwrap(),
1166                CatFile { size: 9+26, blocks: 1, copies: 1, file_type: CatFileType::File(BlockType::CharArray)});
1167        let tap_data = io::Cursor::new(Vec::new());
1168        let mut writer = tap::write_tap(tap_data).unwrap();
1169        assert_eq!(mdr.file_to_tap_writer("HelloWorld", &mut writer).unwrap(), true);
1170        assert_eq!(mdr.file_to_tap_writer("a(10)", &mut writer).unwrap(), true);
1171        assert_eq!(mdr.file_to_tap_writer("weekdays", &mut writer).unwrap(), true);
1172        let tap_file = std::fs::read("../resources/read_tap_test.tap").unwrap();
1173        assert_eq!(tap_file, writer.into_inner().into_inner().into_inner());
1174    }
1175}