1pub mod dsk_d13;
57pub mod dsk_do;
58pub mod dsk_po;
59pub mod dsk_img;
60pub mod dot2mg;
61pub mod nib;
62pub mod woz;
63pub mod woz1;
64pub mod woz2;
65pub mod imd;
66pub mod td0;
67pub mod names;
68pub mod meta;
69pub mod tracks;
70
71use std::str::FromStr;
72use std::fmt;
73use log::{info,error};
74use crate::bios::Block;
75use crate::{STDRESULT,DYNERR};
76use tracks::DiskFormat;
77
78use a2kit_macro::DiskStructError;
79
80#[derive(thiserror::Error,Debug)]
82pub enum Error {
83 #[error("unknown kind of disk")]
84 UnknownDiskKind,
85 #[error("unknown image type")]
86 UnknownImageType,
87 #[error("unknown format")]
88 UnknownFormat,
89 #[error("invalid kind of disk")]
90 DiskKindMismatch,
91 #[error("geometric coordinate out of range")]
92 GeometryMismatch,
93 #[error("image size did not match the request")]
94 ImageSizeMismatch,
95 #[error("image type not compatible with request")]
96 ImageTypeMismatch,
97 #[error("error while accessing internal structures")]
98 InternalStructureAccess,
99 #[error("unable to access sector")]
100 SectorAccess,
101 #[error("sector not found")]
102 SectorNotFound,
103 #[error("unable to access track")]
104 TrackAccess,
105 #[error("track request out of range")]
106 TrackNotFound,
107 #[error("track is unformatted")]
108 BlankTrack,
109 #[error("metadata mismatch")]
110 MetadataMismatch,
111 #[error("wrong context for this request")]
112 BadContext,
113 #[error("invalid byte while decoding")]
114 InvalidByte,
115 #[error("bad checksum found in a sector")]
116 BadChecksum,
117 #[error("could not find bit pattern")]
118 BitPatternNotFound,
119 #[error("nibble type appeared in wrong context")]
120 NibbleType,
121 #[error("track lies outside expected zones")]
122 UnexpectedZone
123}
124
125#[derive(Clone,Copy,PartialEq)]
127pub enum Track {
128 Num(usize),
130 CH((usize, usize)),
132 Motor((usize, usize)),
134}
135
136pub struct SectorHood {
139 vol: u8,
140 cyl: u8,
141 head: u8,
142 aux: u8
143}
144
145#[derive(Clone,PartialEq)]
149pub enum Sector {
150 Num(usize),
152 Addr((usize,Vec<u8>))
155}
156
157#[derive(PartialEq,Eq,Clone,Copy)]
159pub enum FluxCode {
160 None,
161 FM,
162 GCR,
163 MFM
164}
165
166#[derive(PartialEq,Eq,Clone,Copy)]
170pub enum FieldCode {
171 None,
172 WOZ((usize,usize)),
173 G64((usize,usize)),
174 IBM((usize,usize))
175}
176
177#[derive(PartialEq,Eq,Clone,Copy)]
178pub struct BlockLayout {
179 block_size: usize,
180 block_count: usize
181}
182
183pub struct SolvedTrack {
186 flux_code: FluxCode,
187 addr_code: FieldCode,
188 data_code: FieldCode,
189 speed_kbps: usize,
191 density: Option<f64>,
194 addr_type: String,
196 addr_mask: [u8;6],
198 addr_map: Vec<[u8;6]>,
200 size_map: Vec<usize>
201}
202
203pub enum TrackSolution {
207 Blank,
208 Unsolved,
209 Solved(SolvedTrack)
210}
211
212#[derive(PartialEq,Eq,Clone,Copy)]
218pub struct TrackLayout {
219 cylinders: [usize;5],
220 sides: [usize;5],
221 sectors: [usize;5],
222 sector_size: [usize;5],
223 flux_code: [FluxCode;5],
224 addr_code: [FieldCode;5],
225 data_code: [FieldCode;5],
226 speed_kbps: [usize;5]
227}
228
229#[derive(PartialEq,Eq,Clone,Copy)]
234pub enum DiskKind {
235 Unknown,
236 LogicalBlocks(BlockLayout),
237 LogicalSectors(TrackLayout),
238 D3(TrackLayout),
239 D35(TrackLayout),
240 D525(TrackLayout),
241 D8(TrackLayout)
242}
243
244#[derive(PartialEq,Clone,Copy)]
245pub enum DiskImageType {
246 D13,
247 DO,
248 PO,
249 IMG,
250 WOZ1,
251 WOZ2,
252 IMD,
253 DOT2MG,
254 NIB,
255 TD0,
256 DOT86F,
258 D64,
260 G64,
262 MFI,
264 MFM,
266 HFE,
268}
269
270impl TrackLayout {
271 pub fn track_count(&self) -> usize {
272 let mut ans = 0;
273 for i in 0..5 {
274 ans += self.cylinders[i] * self.sides[i];
275 }
276 ans
277 }
278 pub fn sides(&self) -> usize {
279 *self.sides.iter().max().unwrap()
280 }
281 pub fn zones(&self) -> usize {
282 for i in 0..5 {
283 if self.cylinders[i]==0 {
284 return i;
285 }
286 }
287 5
288 }
289 pub fn zone(&self,track_num: usize) -> usize {
290 let mut tcount: [usize;5] = [0;5];
291 tcount[0] = self.cylinders[0] * self.sides[0];
292 for i in 1..5 {
293 tcount[i] = tcount[i-1] + self.cylinders[i] * self.sides[i];
294 }
295 match track_num {
296 n if n < tcount[0] => 0,
297 n if n < tcount[1] => 1,
298 n if n < tcount[2] => 2,
299 n if n < tcount[3] => 3,
300 _ => 4
301 }
302 }
303 pub fn byte_capacity(&self) -> usize {
304 let mut ans = 0;
305 for i in 0..5 {
306 ans += self.cylinders[i] * self.sides[i] * self.sectors[i] * self.sector_size[i];
307 }
308 ans
309 }
310 }
315
316impl fmt::Display for TrackLayout {
319 fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
320 let mut cyls = 0;
321 for c in self.cylinders {
322 cyls += c;
323 }
324 write!(f,"{}/{}/{}/{}",cyls,self.sides(),self.sectors[0],self.sector_size[0])
325 }
326}
327
328impl fmt::Display for DiskKind {
331 fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 match *self {
333 DiskKind::LogicalBlocks(lay) => write!(f,"Logical disk, {} blocks",lay.block_count),
334 DiskKind::LogicalSectors(lay) => write!(f,"Logical disk, {}",lay),
335 names::A2_400_KIND => write!(f,"Apple 3.5 inch 400K"),
336 names::A2_800_KIND => write!(f,"Apple 3.5 inch 800K"),
337 names::A2_DOS32_KIND => write!(f,"Apple 5.25 inch 13 sector"),
338 names::A2_DOS33_KIND => write!(f,"Apple 5.25 inch 16 sector"),
339 DiskKind::D3(lay) => write!(f,"3.0 inch {}",lay),
340 DiskKind::D35(lay) => write!(f,"3.5 inch {}",lay),
341 DiskKind::D525(lay) => write!(f,"5.25 inch {}",lay),
342 DiskKind::D8(lay) => write!(f,"8 inch {}",lay),
343 DiskKind::Unknown => write!(f,"unknown")
344 }
345 }
346}
347
348impl fmt::Display for FieldCode {
349 fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 match *self {
351 FieldCode::WOZ((x,y)) => write!(f,"{}&{}",x,y),
352 FieldCode::G64((x,y)) => write!(f,"G64-{}:{}",x,y),
353 FieldCode::IBM((x,y)) => write!(f,"IBM-{}:{}",x,y),
354 FieldCode::None => write!(f,"none")
355 }
356 }
357}
358
359impl FromStr for FieldCode {
360 type Err = Error;
361 fn from_str(s: &str) -> Result<Self,Self::Err> {
362 match s {
363 "4&4" => Ok(FieldCode::WOZ((4,4))),
364 "5&3" => Ok(FieldCode::WOZ((5,3))),
365 "6&2" => Ok(FieldCode::WOZ((6,2))),
366 "G64-5:4" => Ok(FieldCode::G64((5,4))),
367 "IBM-5:4" => Ok(FieldCode::IBM((5,4))),
368 "none" => Ok(FieldCode::None),
369 _ => Err(Error::MetadataMismatch)
370 }
371 }
372}
373
374impl FromStr for FluxCode {
375 type Err = Error;
376 fn from_str(s: &str) -> Result<Self,Self::Err> {
377 match s {
378 "FM" => Ok(FluxCode::FM),
379 "MFM" => Ok(FluxCode::MFM),
380 "GCR" => Ok(FluxCode::GCR),
381 "none" => Ok(FluxCode::None),
382 _ => Err(Error::MetadataMismatch)
383 }
384 }
385}
386
387impl fmt::Display for FluxCode {
388 fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
389 match *self {
390 FluxCode::FM => write!(f,"FM"),
391 FluxCode::MFM => write!(f,"MFM"),
392 FluxCode::GCR => write!(f,"GCR"),
393 FluxCode::None => write!(f,"none")
394 }
395 }
396}
397
398impl FromStr for DiskKind {
400 type Err = Error;
401 fn from_str(s: &str) -> Result<Self,Self::Err> {
402 match s {
403 "8in-ibm-sssd" => Ok(names::IBM_CPM1_KIND),
404 "8in-trs80-ssdd" => Ok(names::TRS80_M2_CPM_KIND),
405 "8in-nabu-dsdd" => Ok(names::NABU_CPM_KIND),
406 "5.25in-ibm-ssdd8" => Ok(Self::D525(names::IBM_SSDD_8)),
407 "5.25in-ibm-ssdd9" => Ok(Self::D525(names::IBM_SSDD_9)),
408 "5.25in-ibm-dsdd8" => Ok(Self::D525(names::IBM_DSDD_8)),
409 "5.25in-ibm-dsdd9" => Ok(Self::D525(names::IBM_DSDD_9)),
410 "5.25in-ibm-ssqd" => Ok(Self::D525(names::IBM_SSQD)),
411 "5.25in-ibm-dsqd" => Ok(Self::D525(names::IBM_DSQD)),
412 "5.25in-ibm-dshd" => Ok(Self::D525(names::IBM_DSHD)),
413 "5.25in-osb-sssd" => Ok(names::OSBORNE1_SD_KIND),
414 "5.25in-osb-ssdd" => Ok(names::OSBORNE1_DD_KIND),
415 "5.25in-kay-ssdd" => Ok(names::KAYPROII_KIND),
416 "5.25in-kay-dsdd" => Ok(names::KAYPRO4_KIND),
417 "5.25in-apple-13" => Ok(names::A2_DOS32_KIND),
418 "5.25in-apple-16" => Ok(names::A2_DOS33_KIND),
419 "3.5in-apple-400" => Ok(names::A2_400_KIND),
420 "3.5in-apple-800" => Ok(names::A2_800_KIND),
421 "3.5in-ibm-720" => Ok(Self::D35(names::IBM_720)),
422 "3.5in-ibm-1440" => Ok(Self::D35(names::IBM_1440)),
423 "3.5in-ibm-2880" => Ok(Self::D35(names::IBM_2880)),
424 "3in-amstrad-ssdd" => Ok(names::AMSTRAD_SS_KIND),
425 "hdmax" => Ok(names::A2_HD_MAX),
426 _ => Err(Error::UnknownDiskKind)
427 }
428 }
429}
430
431impl FromStr for DiskImageType {
432 type Err = Error;
433 fn from_str(s: &str) -> Result<Self,Self::Err> {
434 match s {
435 "d13" => Ok(Self::D13),
436 "do" => Ok(Self::DO),
437 "po" => Ok(Self::PO),
438 "img" => Ok(Self::IMG),
439 "woz1" => Ok(Self::WOZ1),
440 "woz2" => Ok(Self::WOZ2),
441 "imd" => Ok(Self::IMD),
442 "2mg" => Ok(Self::DOT2MG),
443 "2img" => Ok(Self::DOT2MG),
444 "nib" => Ok(Self::NIB),
445 "td0" => Ok(Self::TD0),
446 _ => Err(Error::UnknownImageType)
447 }
448 }
449}
450
451impl fmt::Display for DiskImageType {
452 fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
453 match self {
454 Self::D13 => write!(f,"d13"),
455 Self::DO => write!(f,"do"),
456 Self::PO => write!(f,"po"),
457 Self::IMG => write!(f,"img"),
458 Self::WOZ1 => write!(f,"woz1"),
459 Self::WOZ2 => write!(f,"woz2"),
460 Self::IMD => write!(f,"imd"),
461 Self::DOT2MG => write!(f,"2mg"),
462 Self::NIB => write!(f,"nib"),
463 Self::TD0 => write!(f,"td0"),
464 Self::D64 => write!(f,"d64"),
465 Self::DOT86F => write!(f,"86f"),
466 Self::G64 => write!(f,"g64"),
467 Self::HFE => write!(f,"hfe"),
468 Self::MFM => write!(f,"mfm"),
469 Self::MFI => write!(f,"mfi")
470 }
471 }
472}
473
474pub trait DiskImage {
479 fn track_count(&self) -> usize;
481 fn end_track(&self) -> usize;
483 fn num_heads(&self) -> usize;
484 fn motor_steps_per_cyl(&self) ->usize {
485 1
486 }
487 fn get_rz(&self,trk: Track) -> Result<[usize;2],DYNERR> {
490 let msc = self.motor_steps_per_cyl();
491 let ans = match trk {
492 Track::Num(t) => [t/self.num_heads(),t%self.num_heads()],
493 Track::CH((c,h)) => [c,h],
494 Track::Motor((m,h)) => [(m+msc/4)/msc,h]
495 };
496 Ok(ans)
497 }
498 fn get_track(&self,trk: Track) -> Result<usize,DYNERR> {
501 let msc = self.motor_steps_per_cyl();
502 let ans = match trk {
503 Track::Num(t) => t,
504 Track::CH((c,h)) => c*self.num_heads() + h,
505 Track::Motor((m,h)) => ((m+msc/4)/msc)*self.num_heads() + h
506 };
507 Ok(ans)
508 }
509 fn get_rzq(&self,trk: Track,sec: Sector) -> Result<[usize;3],DYNERR> {
513 let [c,h] = self.get_rz(trk)?;
514 let s = match sec {
515 Sector::Num(s) => s,
516 Sector::Addr((_,addr)) => {
517 match self.kind() {
518 names::A2_400_KIND | names::A2_800_KIND => addr[1] as usize,
519 _ => addr[2] as usize
520 }
521 }
522 };
523 Ok([c,h,s])
524 }
525 fn nominal_capacity(&self) -> Option<usize>;
528 fn actual_capacity(&mut self) -> Result<usize,DYNERR>;
531 fn what_am_i(&self) -> DiskImageType;
532 fn file_extensions(&self) -> Vec<String>;
533 fn kind(&self) -> DiskKind;
534 fn change_kind(&mut self,kind: DiskKind);
536 fn change_format(&mut self,_fmt: DiskFormat) -> STDRESULT {
538 Err(Box::new(Error::ImageTypeMismatch))
539 }
540 fn change_method(&mut self,_method: tracks::Method) {
544 }
545 fn from_bytes(buf: &[u8]) -> Result<Self,DiskStructError> where Self: Sized;
546 fn to_bytes(&mut self) -> Vec<u8>;
547 fn read_block(&mut self,addr: Block) -> Result<Vec<u8>,DYNERR>;
549 fn write_block(&mut self, addr: Block, dat: &[u8]) -> STDRESULT;
551 fn read_sector(&mut self,trk: Track,sec: Sector) -> Result<Vec<u8>,DYNERR>;
553 fn write_sector(&mut self,trk: Track,sec: Sector,dat: &[u8]) -> STDRESULT;
555 fn get_track_buf(&mut self,trk: Track) -> Result<Vec<u8>,DYNERR>;
557 fn set_track_buf(&mut self,trk: Track,dat: &[u8]) -> STDRESULT;
559 fn get_track_solution(&mut self,trk: Track) -> Result<TrackSolution,DYNERR>;
563 fn get_track_nibbles(&mut self,trk: Track) -> Result<Vec<u8>,DYNERR>;
565 fn display_track(&self,bytes: &[u8]) -> String;
568 fn get_metadata(&self,indent: Option<u16>) -> String {
571 let mut root = json::JsonValue::new_object();
572 let typ = self.what_am_i().to_string();
573 root[typ] = json::JsonValue::new_object();
574 if let Some(spaces) = indent {
575 json::stringify_pretty(root,spaces)
576 } else {
577 json::stringify(root)
578 }
579 }
580 fn put_metadata(&mut self,key_path: &Vec<String>, _val: &json::JsonValue) -> STDRESULT {
588 meta::test_metadata(key_path,self.what_am_i())
589 }
590 fn export_geometry(&mut self,indent: Option<u16>) -> Result<String,DYNERR> {
592 let pkg = package_string(&self.kind());
593 let mut track_sols = Vec::new();
594 for trk in 0..self.end_track() {
595 log::trace!("solve track {}",trk);
596 let sol = self.get_track_solution(Track::Num(trk))?;
597 let [c,h] = self.get_rz(Track::Num(trk))?;
598 track_sols.push((c as f64,h,sol));
599 }
600 geometry_json(pkg,track_sols,self.end_track(),self.num_heads(),self.motor_steps_per_cyl(),indent)
601 }
602 fn export_format(&self,_indent: Option<u16>) -> Result<String,DYNERR> {
604 Err(Box::new(Error::UnknownFormat))
605 }
606}
607
608fn solved_track_json(sol: SolvedTrack) -> Result<json::JsonValue,DYNERR> {
609 let mut ans = json::JsonValue::new_object();
610 ans["flux_code"] = match sol.flux_code {
611 FluxCode::None => json::JsonValue::Null,
612 f => json::JsonValue::String(f.to_string())
613 };
614 ans["addr_code"] = match sol.addr_code {
615 FieldCode::None => json::JsonValue::Null,
616 n => json::JsonValue::String(n.to_string())
617 };
618 ans["nibble_code"] = match sol.data_code {
619 FieldCode::None => json::JsonValue::Null,
620 n => json::JsonValue::String(n.to_string())
621 };
622 ans["speed_kbps"] = json::JsonValue::Number(sol.speed_kbps.into());
623 ans["density"] = match sol.density {
624 Some(val) => json::JsonValue::Number(val.into()),
625 None => json::JsonValue::Null
626 };
627 ans["addr_map"] = json::JsonValue::new_array();
628 for addr in sol.addr_map {
629 ans["addr_map"].push(json::JsonValue::String(hex::encode_upper(&addr[0..sol.addr_type.len()])))?;
630 }
631 ans["size_map"] = json::JsonValue::new_array();
632 for size in sol.size_map {
633 ans["size_map"].push(size)?;
634 }
635 ans["addr_type"] = json::JsonValue::String(sol.addr_type);
636 ans["addr_mask"] = json::JsonValue::new_array();
637 for by in sol.addr_mask {
638 ans["addr_mask"].push(by)?;
639 }
640 Ok(ans)
641}
642
643fn geometry_json(pkg: String,desc: Vec<(f64,usize,TrackSolution)>,cylinders: usize,heads: usize,width: usize,indent: Option<u16>) -> Result<String,DYNERR> {
646 let mut root = json::JsonValue::new_object();
647 root["package"] = json::JsonValue::String(pkg);
648 let mut trk_ary = json::JsonValue::new_array();
649 let mut blank_track_count = 0;
650 let mut solved_track_count = 0;
651 let mut unsolved_track_count = 0;
652 let mut last_blank_track: Option<usize> = None;
653 let mut last_solved_track: Option<usize> = None;
654 let mut last_unsolved_track: Option<usize> = None;
655 let mut idx = 0;
656 for (fcyl,head,sol) in desc {
657 let mut trk_obj = json::JsonValue::new_object();
658 trk_obj["cylinder"] = json::JsonValue::Number(fcyl.into());
659 trk_obj["head"] = json::JsonValue::Number(head.into());
660 let ignore = match sol {
661 TrackSolution::Blank => {
662 if fcyl < cylinders as f64 {
663 blank_track_count += 1;
664 last_blank_track = Some(idx);
665 }
666 fcyl >= cylinders as f64
667 },
668 TrackSolution::Unsolved => {
669 unsolved_track_count += 1;
670 last_unsolved_track = Some(idx);
671 false
672 },
673 TrackSolution::Solved(_) => {
674 solved_track_count += 1;
675 last_solved_track = Some(idx);
676 false
677 }
678 };
679 trk_obj["solution"] = match sol {
680 TrackSolution::Blank => json::JsonValue::String("blank".to_string()),
681 TrackSolution::Unsolved => json::JsonValue::String("unsolved".to_string()),
682 TrackSolution::Solved(sol) => solved_track_json(sol)?
683 };
684 if !ignore {
685 trk_ary.push(trk_obj)?;
686 }
687 idx += 1;
688 }
689
690 root["summary"] = json::JsonValue::new_object();
691 root["summary"]["cylinders"] = json::JsonValue::Number(cylinders.into());
692 root["summary"]["heads"] = json::JsonValue::Number(heads.into());
693 root["summary"]["blank_tracks"] = json::JsonValue::Number(blank_track_count.into());
694 root["summary"]["solved_tracks"] = json::JsonValue::Number(solved_track_count.into());
695 root["summary"]["unsolved_tracks"] = json::JsonValue::Number(unsolved_track_count.into());
696 root["summary"]["last_blank_track"] = match last_blank_track {
697 Some(t) => json::JsonValue::Number(t.into()),
698 None => json::JsonValue::Null
699 };
700 root["summary"]["last_solved_track"] = match last_solved_track {
701 Some(t) => json::JsonValue::Number(t.into()),
702 None => json::JsonValue::Null
703 };
704 root["summary"]["last_unsolved_track"] = match last_unsolved_track {
705 Some(t) => json::JsonValue::Number(t.into()),
706 None => json::JsonValue::Null
707 };
708 root["summary"]["steps_per_cyl"] = json::JsonValue::Number(width.into());
709
710 if trk_ary.len()==0 {
711 root["tracks"] = json::JsonValue::Null;
712 } else {
713 root["tracks"] = trk_ary;
714 }
715 if let Some(spaces) = indent {
716 Ok(json::stringify_pretty(root,spaces))
717 } else {
718 Ok(json::stringify(root))
719 }
720}
721
722pub fn is_dos_size(dsk: &Vec<u8>,allowed_track_counts: &Vec<usize>,sectors: usize) -> STDRESULT {
724 let bytes = dsk.len();
725 for tracks in allowed_track_counts {
726 if bytes==tracks*sectors*256 {
727 return Ok(());
728 }
729 }
730 info!("image size was {}",bytes);
731 return Err(Box::new(Error::ImageSizeMismatch));
732}
733
734pub fn quantize_block(src: &[u8],quantum: usize) -> Vec<u8> {
737 let mut padded: Vec<u8> = Vec::new();
738 for i in 0..quantum {
739 if i<src.len() {
740 padded.push(src[i])
741 } else {
742 padded.push(0);
743 }
744 }
745 return padded;
746}
747
748pub fn package_string(kind: &DiskKind) -> String {
750 match kind {
751 DiskKind::D3(_) => "3".to_string(),
752 DiskKind::D35(_) => "3.5".to_string(),
753 DiskKind::D525(_) => "5.25".to_string(),
754 DiskKind::D8(_) => "8".to_string(),
755 DiskKind::LogicalBlocks(_) => "logical".to_string(),
756 DiskKind::LogicalSectors(_) => "logical".to_string(),
757 DiskKind::Unknown => "unknown".to_string()
758 }
759}
760
761fn highest_bit(mut val: usize) -> u8 {
762 let mut ans = 0;
763 while val > 0 {
764 ans += 1;
765 val = val >> 1;
766 }
767 ans
768}
769
770pub fn append_ibm_crc(addr: [u8;4],maybe_sync: Option<[u8;4]>) -> [u8;6]
773{
774 let mut buf = vec![];
775 match maybe_sync {
776 Some(sync) => buf.append(&mut sync.to_vec()),
777 None => buf.append(&mut vec![0xa1,0xa1,0xa1,0xfe])
778 };
779 buf.append(&mut addr.to_vec());
780 let buf = [[0xa1,0xa1,0xa1,0xfe],[addr[0],addr[1],addr[2],addr[3]]].concat();
781 let mut crc: u16 = 0xffff;
782 for i in 0..buf.len() {
783 crc ^= (buf[i] as u16) << 8;
784 for _bit in 0..8 {
785 crc = (crc << 1) ^ match crc & 0x8000 { 0 => 0, _ => 0x1021 };
786 }
787 }
788 let be = u16::to_be_bytes(crc);
789 [addr[0],addr[1],addr[2],addr[3],be[0],be[1]]
790}