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}