1#![no_implicit_prelude]
21
22extern crate core;
23extern crate rpsp;
24
25use core::cmp::PartialEq;
26use core::convert::{AsRef, Into, TryInto};
27use core::ops::{Deref, Drop};
28use core::option::Option::{None, Some};
29use core::result::Result::{self, Err, Ok};
30use core::{cmp, matches, mem};
31
32use rpsp::ignore_error;
33use rpsp::io::{Read, Seek, SeekFrom, Write};
34use rpsp::time::{Time, Weekday};
35
36use crate::fs::volume::LongNamePtr;
37use crate::fs::{Block, BlockCache, BlockDevice, Cluster, DIR_SIZE, DeviceError, DirectoryIndex, Error, FatVersion, LongName, ShortName, Storage, Volume, le_u16, le_u32, to_le_u16, to_le_u32};
38
39const FILE_MAX_SIZE: u32 = 0xFFFFFFFFu32;
40
41pub enum Mode {}
42
43pub struct DirEntry {
44 name: ShortName,
45 lfn: u8,
46 size: u32,
47 attrs: u8,
48 block: u32,
49 offset: u32,
50 created: Time,
51 cluster: Cluster,
52 modified: Time,
53}
54pub struct DirEntryFull {
55 lfn: LongNamePtr,
56 sum: u8,
57 entry: DirEntry,
58}
59
60pub struct File<'a, B: BlockDevice> {
61 vol: &'a Volume<'a, B>,
62 pos: u32,
63 file: DirEntry,
64 last: u32,
65 mode: u8,
66 short: u32,
67}
68pub struct Directory<'a, B: BlockDevice> {
69 vol: &'a Volume<'a, B>,
70 dir: DirEntry,
71}
72
73impl Mode {
74 pub const READ: u8 = 0x0u8;
75 pub const WRITE: u8 = 0x1u8;
76 pub const CREATE: u8 = 0x2u8;
77 pub const APPEND: u8 = 0x4u8;
78 pub const TRUNCATE: u8 = 0x8u8;
79
80 #[inline(always)]
81 pub(super) fn is_create(m: u8) -> bool {
82 m & Mode::CREATE != 0 && (m & Mode::WRITE != 0 || m & Mode::APPEND != 0)
83 }
84 #[inline(always)]
85 pub(super) fn is_mode_valid(m: u8) -> bool {
86 if (m >> 4) > 0 || m & (Mode::TRUNCATE | Mode::APPEND) == 0x12 || m == Mode::CREATE {
87 false
88 } else {
89 true
90 }
91 }
92}
93impl DirEntry {
94 #[inline]
95 pub(super) fn new_root() -> DirEntry {
96 DirEntry {
97 lfn: 0,
98 name: ShortName::empty(),
99 size: 0u32,
100 block: 0u32,
101 attrs: 0x10u8,
102 offset: 0u32,
103 created: Time::empty(),
104 cluster: None,
105 modified: Time::empty(),
106 }
107 }
108 #[inline]
109 pub(super) fn new(attrs: u8, lfn: u8) -> DirEntry {
110 DirEntry {
111 lfn,
112 attrs,
113 name: ShortName::empty(),
114 size: 0u32,
115 block: 0u32,
116 offset: 0u32,
117 created: Time::empty(),
118 cluster: Some(0),
119 modified: Time::empty(),
120 }
121 }
122 #[inline]
123 pub(super) fn new_self(parent: &DirEntry, block: u32) -> DirEntry {
124 DirEntry {
125 block,
126 lfn: 0u8,
127 name: ShortName::SELF,
128 size: 0u32,
129 attrs: 0x10u8,
130 offset: 0u32,
131 created: Time::empty(),
132 cluster: parent.cluster,
133 modified: Time::empty(),
134 }
135 }
136 #[inline]
137 pub(super) fn new_parent(cluster: Cluster, block: u32) -> DirEntry {
138 DirEntry {
139 block,
140 cluster,
141 lfn: 0u8,
142 name: ShortName::PARENT,
143 size: 0u32,
144 attrs: 0x10u8,
145 offset: 0x20u32,
146 created: Time::empty(),
147 modified: Time::empty(),
148 }
149 }
150
151 #[inline(always)]
152 pub fn size(&self) -> u32 {
153 self.size
154 }
155 #[inline(always)]
156 pub fn name(&self) -> &str {
157 self.name.as_str()
158 }
159 #[inline(always)]
160 pub fn offset(&self) -> u32 {
161 self.offset
162 }
163 #[inline(always)]
164 pub fn is_file(&self) -> bool {
165 !self.is_directory()
166 }
167 #[inline(always)]
168 pub fn created(&self) -> &Time {
169 &self.created
170 }
171 #[inline(always)]
172 pub fn attributes(&self) -> u8 {
173 self.attrs
174 }
175 #[inline(always)]
176 pub fn modified(&self) -> &Time {
177 &self.modified
178 }
179 #[inline(always)]
180 pub fn cluster(&self) -> Cluster {
181 self.cluster
182 }
183 #[inline(always)]
184 pub fn is_directory(&self) -> bool {
185 self.attrs & 0x10 == 0x10
186 }
187 #[inline(always)]
188 pub fn filename(&self) -> &ShortName {
189 &self.name
190 }
191 #[inline(always)]
192 pub fn set_created(&mut self, t: Time) {
193 self.created = t
194 }
195 #[inline(always)]
196 pub fn set_modified(&mut self, t: Time) {
197 self.modified = t
198 }
199 #[inline(always)]
200 pub fn set_attributes(&mut self, a: u8) {
201 self.attrs = a
202 }
203 #[inline]
204 pub fn into_dir<'a, B: BlockDevice>(self, vol: &'a Volume<B>) -> Result<Directory<'a, B>, DeviceError> {
205 if !self.is_directory() {
206 return Err(DeviceError::NotADirectory);
207 }
208 Ok(Directory::new(self, vol))
209 }
210 #[inline]
211 pub fn into_file<'a, B: BlockDevice>(self, vol: &'a Volume<B>, mode: u8) -> Result<File<'a, B>, DeviceError> {
212 if !Mode::is_mode_valid(mode) {
213 return Err(DeviceError::InvalidOptions);
214 }
215 if !self.is_file() {
216 return Err(DeviceError::NotAFile);
217 }
218 Ok(File::new(self, mode, vol))
219 }
220
221 #[inline(always)]
222 pub(super) fn fill(&mut self, v: &[u8]) {
223 self.name.fill(v);
224 }
225 #[inline(always)]
226 pub(super) fn cluster_abs(&self) -> u32 {
227 self.cluster.unwrap_or(0xFFFFFFFC)
228 }
229 #[inline]
230 pub(super) fn is_root_or_parent(&self) -> bool {
231 match self.cluster {
232 Some(v) if v == 0xFFFFFFFC => return true,
233 None => return true,
234 _ => (),
235 }
236 self.name.0.is_empty() || self.name.is_self() || self.name.is_parent()
237 }
238 #[inline(always)]
239 pub(super) fn write_prep(&mut self, block: u32, offset: usize) {
240 self.block = block;
241 self.offset = offset as u32;
242 }
243 pub(super) fn write_entry(&self, f: &FatVersion, b: &mut [u8]) {
244 b[0..ShortName::SIZE].copy_from_slice(&self.name.as_bytes());
245 b[11] = self.attrs;
246 b[12] = 0;
247 b[13] = 0;
248 time_write(&self.created, &mut b[14..]);
249 let c = self.cluster_abs();
250 match f {
251 FatVersion::Fat16(_) => (b[20], b[21]) = (0, 0),
252 FatVersion::Fat32(_) => to_le_u16(((c >> 16) & 0xFFFF) as u16, &mut b[20..]),
253 }
254 time_write(&self.modified, &mut b[22..]);
255 to_le_u16((c & 0xFFFF) as u16, &mut b[26..]);
256 to_le_u32(self.size, &mut b[28..])
257 }
258 pub(super) fn write_lfn_entry(&self, lfn: &LongName, pos: u8, s: u8, b: &mut [u8]) {
259 let (n, c) = (lfn.len(), self.name.checksum());
260 b[0..DIR_SIZE].fill(0);
261 b[0] = if pos == 0 { 0x40 } else { 0 } | (s - pos) as u8;
262 b[0xB] = 0xF;
263 b[0xD] = c;
264 let (s, mut p) = ((s - 1 - pos) as usize * 0xD, 0);
265 for v in s..cmp::min(s + 0xD, n) {
266 b[LongName::pos_to_lfn(p)] = lfn.0[v];
267 p += 1;
268 }
269 if p < 0xC {
271 b[LongName::pos_to_lfn(p + 1)] = 0;
272 p += 1;
273 }
274 if p < 0xC {
276 for x in LongName::pos_to_lfn(p)..DIR_SIZE {
277 match x {
278 0 | 0xB | 0xC | 0xD | 0x1A | 0x1B => continue,
279 _ => (),
280 }
281 b[x] = 0xFF
282 }
283 }
284 }
285 pub(super) fn delete(&self, vol: &Volume<impl BlockDevice>, scratch: &mut Block) -> Result<(), DeviceError> {
286 vol.dev.read_single(scratch, self.block)?;
287 if self.offset as usize > Block::SIZE {
288 return Err(DeviceError::BadData);
289 }
290 scratch[self.offset as usize] = 0xE5;
291 if self.lfn == 0 {
292 return vol.dev.write_single(scratch, self.block);
293 }
294 let n = self.lfn as u32 * DIR_SIZE as u32;
297 if n > self.offset {
298 return vol.dev.write_single(scratch, self.block);
299 }
300 let mut i = self.offset.saturating_sub(n) as usize;
301 while i < self.offset as usize {
302 scratch[i..i + DIR_SIZE].fill(0);
303 i += DIR_SIZE;
304 }
305 vol.dev.write_single(scratch, self.block)?;
306 if let Some(v) = self.cluster {
307 vol.man.cluster_truncate(vol.dev, scratch, v)?;
308 }
309 Ok(())
310 }
311 #[inline(always)]
312 pub(super) fn allocate(&mut self, vol: &Volume<impl BlockDevice>, scratch: &mut Block) -> Result<(), DeviceError> {
313 self.cluster = Some(vol.man.cluster_allocate(vol.dev, scratch, None, false)?);
314 Ok(())
315 }
316 #[inline]
317 pub(super) fn sync(&self, dev: &Storage<impl BlockDevice>, scratch: &mut Block, f: &FatVersion) -> Result<(), DeviceError> {
318 dev.read_single(scratch, self.block)?;
319 if self.offset as usize > Block::SIZE {
320 return Err(DeviceError::BadData);
321 }
322 self.write_entry(f, &mut scratch[self.offset as usize..]);
323 dev.write_single(scratch, self.block)
324 }
325}
326impl DirEntryFull {
327 #[inline]
328 pub(super) fn new() -> DirEntryFull {
329 DirEntryFull {
330 lfn: LongNamePtr::new(),
331 sum: 0u8,
332 entry: DirEntry::new(0, 0),
333 }
334 }
335
336 #[inline]
337 pub fn name(&self) -> &str {
338 if self.entry.lfn == 0 || self.lfn.is_empty() {
339 self.entry.name.as_str()
340 } else {
341 self.lfn.as_str()
342 }
343 }
344 #[inline(always)]
345 pub fn longname(&self) -> &LongName {
346 &self.lfn
347 }
348 #[inline(always)]
349 pub fn is_name(&self, v: &str) -> bool {
350 self.eq(v)
351 }
352
353 #[inline]
354 pub(super) fn reset(&mut self) {
355 self.lfn.reset();
356 self.sum = 0;
357 self.entry.lfn = 0;
358 }
359 #[inline(always)]
360 pub(super) fn fill(&mut self, b: &[u8]) {
361 self.sum = self.lfn.fill_lfn(b)
362 }
363 #[inline]
364 pub(super) fn entry(&mut self) -> DirEntry {
365 let mut n = DirEntry::new(0, 0);
366 mem::swap(&mut self.entry, &mut n);
367 n
368 }
369 pub(super) fn load(&mut self, f: &FatVersion, b: &[u8], block: u32, offset: u32) {
370 let v = if matches!(f, FatVersion::Fat32(_)) {
371 (le_u16(&b[20..]) as u32) << 16 | le_u16(&b[26..]) as u32
372 } else {
373 le_u16(&b[26..]) as u32
374 };
375 self.entry.lfn = self.lfn.lfn_size();
376 self.entry.name.fill_inner(b);
377 if self.sum != self.entry.name.checksum() {
378 self.lfn.reset();
379 self.entry.lfn = 0;
380 }
381 self.entry.block = block;
382 self.entry.offset = offset;
383 self.entry.size = le_u32(&b[28..]);
384 self.entry.attrs = b[11];
385 self.entry.created = time_read(le_u16(&b[16..]), le_u16(&b[14..]));
386 self.entry.cluster = if v == 0 && b[11] & 0x10 == 0x10 { None } else { Some(v) };
387 self.entry.modified = time_read(le_u16(&b[24..]), le_u16(&b[22..]));
388 }
389}
390impl<'a, B: BlockDevice> File<'a, B> {
391 #[inline]
392 pub(super) fn new(file: DirEntry, mode: u8, vol: &'a Volume<'a, B>) -> File<'a, B> {
393 File {
394 last: file.cluster_abs(),
395 vol,
396 file,
397 mode,
398 pos: 0u32,
399 short: 0u32,
400 }
401 }
402
403 #[inline(always)]
404 pub fn cursor(&self) -> usize {
405 self.pos as usize
406 }
407 #[inline(always)]
408 pub fn is_dirty(&self) -> bool {
409 self.mode & 0x80 != 0
410 }
411 #[inline(always)]
412 pub fn available(&self) -> usize {
413 self.file.size.saturating_sub(self.pos) as usize
414 }
415 #[inline(always)]
416 pub fn is_readable(&self) -> bool {
417 self.mode >> 4 == 0 || self.mode & Mode::READ != 0
418 }
419 #[inline(always)]
420 pub fn is_writeable(&self) -> bool {
421 self.mode & Mode::WRITE != 0
422 }
423 #[inline(always)]
424 pub fn is_allocated(&self) -> bool {
425 self.file.cluster.is_some_and(|v| v > 2)
426 }
427 #[inline(always)]
428 pub fn volume(&self) -> &Volume<'a, B> {
429 self.vol
430 }
431 #[inline]
432 pub fn delete(self) -> Result<(), DeviceError> {
433 let mut b = Block::new();
434 self.file.delete(self.vol, &mut b)
435 }
436 #[inline(always)]
437 pub fn close(mut self) -> Result<(), DeviceError> {
438 self.flush()
439 }
440 #[inline]
441 pub fn flush(&mut self) -> Result<(), DeviceError> {
442 if !self.is_dirty() {
443 return Ok(());
444 }
445 let mut b = Block::new();
446 self.vol.man.sync(self.vol.dev, &mut b)?;
447 self.file.sync(self.vol.dev, &mut b, &self.vol.man.ver)
448 }
449 pub fn write(&mut self, b: &[u8]) -> Result<usize, DeviceError> {
450 if !self.is_writeable() {
451 return Err(DeviceError::NotWritable);
452 }
453 if b.is_empty() {
454 return Ok(0);
455 }
456 self.mode |= 0x80;
457 let mut d = Block::new();
458 if !self.is_allocated() {
459 self.file.cluster = Some(self.vol.man.cluster_allocate(self.vol.dev, &mut d, None, false)?);
460 d.clear();
461 }
462 let c = self.file.cluster.ok_or(DeviceError::WriteError)?;
463 if self.last < c {
464 self.last = c;
465 self.short = 0;
466 }
467 let t = cmp::min(b.len(), (FILE_MAX_SIZE - self.pos) as usize);
468 let (mut c, mut p) = (BlockCache::new(), 0);
469 while p < t {
470 let (i, o, a) = match self.data(&mut d, &mut c) {
471 Ok(v) => v,
472 Err(DeviceError::EndOfFile) => {
473 self.vol
474 .man
475 .cluster_allocate(self.vol.dev, &mut d, self.cluster(), false)
476 .map_err(|_| DeviceError::NoSpace)?;
477 self.data(&mut d, &mut c).map_err(|_| DeviceError::WriteError)?
478 },
479 Err(e) => return Err(e),
480 };
481 let n = cmp::min(a, t - p);
482 if n == 0 {
483 break;
484 }
485 if o != 0 {
486 self.vol.dev.read_single(&mut d, i)?;
487 }
488 d[o..o + n].copy_from_slice(&b[p..p + n]);
489 self.vol.dev.write_single(&d, i)?;
490 self.file.size = self.file.size.saturating_add(n as u32);
491 self.pos = self.pos.saturating_add(n as u32);
492 p += n;
493 }
494 self.file.attrs |= 0x20;
495 Ok(p)
496 }
497 pub fn truncate(&mut self, pos: usize) -> Result<(), DeviceError> {
500 let i = pos.try_into().map_err(|_| DeviceError::Overflow)?;
501 if i > self.file.size {
502 return Err(DeviceError::InvalidIndex);
503 }
504 self.file.size = i;
505 if self.file.size > self.pos {
506 self.pos = i;
507 }
508 Ok(())
509 }
510 pub fn read(&mut self, b: &mut [u8]) -> Result<usize, DeviceError> {
511 if !self.is_readable() {
512 return Err(DeviceError::NotReadable);
513 }
514 if b.is_empty() {
515 return Ok(0);
516 }
517 let (mut p, t) = (0, b.len());
518 let (mut d, mut c) = (Block::new(), BlockCache::new());
519 while p < t && self.pos < self.file.size {
520 let (i, o, a) = match self.data(&mut d, &mut c) {
521 Err(DeviceError::EndOfFile) => return Ok(p),
522 Err(e) => return Err(e),
523 Ok(v) => v,
524 };
525 self.vol.dev.read_single(&mut d, i)?;
526 let n = cmp::min(cmp::min(a, t - p), self.available());
527 if n == 0 {
528 break;
529 }
530 b[p..p + n].copy_from_slice(&d[o..o + n]);
531 self.pos = self.pos.saturating_add(n as u32);
532 p += n;
533 }
534 Ok(p)
535 }
536
537 #[inline(always)]
538 pub(super) fn zero(&mut self) {
539 self.file.size = 0
540 }
541 #[inline(always)]
542 pub(super) fn seek_to_end(&mut self) {
543 self.pos = self.file.size
544 }
545
546 fn data(&mut self, scratch: &mut Block, cache: &mut BlockCache) -> Result<(u32, usize, usize), DeviceError> {
547 if self.pos < self.short {
548 (self.short, self.last) = (0, self.cluster_abs());
549 }
550 let c = self.vol.man.blocks.bytes_per_cluster();
551 let n = self.pos.saturating_sub(self.short);
552 cache.clear();
553 for _ in 0..(n / c) {
554 self.last = self
555 .vol
556 .man
557 .cluster_next(self.vol.dev, scratch, cache, self.last)?
558 .ok_or_else(|| DeviceError::EndOfFile)?;
559 self.short += c;
560 }
561 let i = self.vol.man.block_pos_at(self.last) + (self.pos.saturating_sub(self.short) / Block::SIZE as u32);
562 let o = self.pos as usize % Block::SIZE;
563 Ok((i, o, Block::SIZE - o))
564 }
565}
566impl<'a, B: BlockDevice> Directory<'a, B> {
567 #[inline(always)]
568 pub(super) fn new(dir: DirEntry, vol: &'a Volume<'a, B>) -> Directory<'a, B> {
569 Directory { vol, dir }
570 }
571
572 #[inline(always)]
573 pub fn volume(&self) -> &Volume<'a, B> {
574 self.vol
575 }
576 #[inline]
577 pub fn delete(self, force: bool) -> Result<(), DeviceError> {
578 let mut b = Block::new();
579 Directory::delete_inner(self.vol, &self.dir, &mut b, force)
580 }
581 #[inline(always)]
582 pub fn list(&self) -> Result<DirectoryIndex<'a, B>, DeviceError> {
583 unsafe { self.vol.list_entry(Some(&self.dir)) }
585 }
586 #[inline]
587 pub fn file(&'a self, name: impl AsRef<str>, mode: u8) -> Result<File<'a, B>, DeviceError> {
588 unsafe { self.vol.file_entry(name, Some(&self.dir), mode) }
590 }
591 #[inline]
592 pub fn dir(&'a self, name: impl AsRef<str>, create: bool) -> Result<Directory<'a, B>, DeviceError> {
593 unsafe { self.vol.dir_entry(name, Some(&self.dir), create) }
595 }
596
597 #[inline(always)]
598 pub(super) fn entry(&self) -> &DirEntry {
599 &self.dir
600 }
601 #[inline(always)]
602 pub(super) fn cluster(&self) -> Cluster {
603 self.dir.cluster
604 }
605
606 fn delete_inner(vol: &'a Volume<'a, B>, dir: &DirEntry, scratch: &mut Block, force: bool) -> Result<(), DeviceError> {
607 for e in unsafe { vol.list_entry(Some(dir))? } {
611 if !force {
612 return Err(DeviceError::NonEmptyDirectory);
613 }
614 let v = e?;
615 if v.is_root_or_parent() {
616 break;
620 }
621 if v.is_directory() {
622 Directory::delete_inner(vol, &v, scratch, force)?;
623 } else {
624 v.delete(vol, scratch)?;
625 }
626 }
627 dir.delete(vol, scratch)?;
628 vol.man.cluster_truncate(vol.dev, scratch, dir.cluster_abs())
629 }
630}
631
632impl<B: BlockDevice> Drop for File<'_, B> {
633 #[inline(always)]
634 fn drop(&mut self) {
635 ignore_error!(self.flush());
636 }
637}
638impl<B: BlockDevice> Deref for File<'_, B> {
639 type Target = DirEntry;
640
641 #[inline(always)]
642 fn deref(&self) -> &DirEntry {
643 &self.file
644 }
645}
646impl<B: BlockDevice> Seek<DeviceError> for File<'_, B> {
647 fn seek(&mut self, s: SeekFrom) -> Result<u64, Error> {
648 let r = match s {
649 SeekFrom::End(v) => {
650 if v > 0 {
651 return Err(Error::InvalidIndex);
652 }
653 self.pos
654 .saturating_sub(v.unsigned_abs().try_into().map_err(|_| DeviceError::Overflow)?)
655 },
656 SeekFrom::Start(v) => v.try_into().map_err(|_| DeviceError::Overflow)?,
657 SeekFrom::Current(v) if v > 0 => self.pos.saturating_add(v.try_into().map_err(|_| DeviceError::Overflow)?),
658 SeekFrom::Current(v) => self
659 .pos
660 .saturating_sub(v.unsigned_abs().try_into().map_err(|_| DeviceError::Overflow)?),
661 };
662 if r > self.file.size {
663 return Err(Error::InvalidIndex);
664 }
665 self.pos = r;
666 Ok(self.pos as u64)
667 }
668}
669impl<B: BlockDevice> Read<DeviceError> for File<'_, B> {
670 #[inline(always)]
671 fn read(&mut self, b: &mut [u8]) -> Result<usize, Error> {
672 Ok(self.read(b)?)
673 }
674}
675impl<B: BlockDevice> Write<DeviceError> for File<'_, B> {
676 #[inline(always)]
677 fn flush(&mut self) -> Result<(), Error> {
678 Ok(self.flush()?)
679 }
680 #[inline(always)]
681 fn write(&mut self, b: &[u8]) -> Result<usize, Error> {
682 Ok(self.write(b)?)
683 }
684}
685
686impl PartialEq<str> for DirEntry {
687 #[inline(always)]
688 fn eq(&self, other: &str) -> bool {
689 self.eq(other.as_bytes())
690 }
691}
692impl PartialEq<[u8]> for DirEntry {
693 #[inline(always)]
694 fn eq(&self, other: &[u8]) -> bool {
695 self.name.eq(other)
696 }
697}
698
699impl Deref for DirEntryFull {
700 type Target = DirEntry;
701
702 #[inline(always)]
703 fn deref(&self) -> &DirEntry {
704 &self.entry
705 }
706}
707impl PartialEq<str> for DirEntryFull {
708 #[inline(always)]
709 fn eq(&self, other: &str) -> bool {
710 self.eq(other.as_bytes())
711 }
712}
713impl PartialEq<[u8]> for DirEntryFull {
714 #[inline]
715 fn eq(&self, other: &[u8]) -> bool {
716 if self.entry.lfn == 0 { self.entry.name.eq(other) } else { self.lfn.eq(other) }
717 }
718}
719
720impl<B: BlockDevice> Deref for Directory<'_, B> {
721 type Target = DirEntry;
722
723 #[inline(always)]
724 fn deref(&self) -> &DirEntry {
725 &self.dir
726 }
727}
728
729#[inline]
730fn time_read(a: u16, b: u16) -> Time {
731 Time {
732 year: ((a >> 0x9) + 0xA) + 0x7B2u16,
733 month: (((a >> 0x5) & 0xF) as u8 + 1).into(),
734 day: (a & 0x1F) as u8,
735 hours: ((b >> 0xB) & 0x1F) as u8,
736 mins: ((b >> 0x5) & 0x3F) as u8,
737 secs: ((b << 0x1) & 0x3F) as u8,
738 weekday: Weekday::None,
739 }
740}
741#[inline]
742fn time_write(t: &Time, b: &mut [u8]) {
743 to_le_u16(
744 (((t.hours as u16) << 11) & 0xF800) | (((t.mins as u16) << 5) & 0x7E0) | (((t.secs as u16) / 2) & 0x1F),
745 b,
746 );
747 to_le_u16(
748 (((t.year.saturating_sub(0x7B2)).saturating_sub(10) << 9) & 0xFE00) | (((t.month as u16 + 1) << 5) & 0x1E0) | ((t.day as u16 + 1) & 0x1F),
749 &mut b[2..],
750 )
751}