1use crate::protocol::programming::{
23 self, CHANNEL_RECORD_SIZE, CHANNELS_PER_MEMGROUP, ChannelFlag, FLAG_EMPTY, FLAG_RECORD_SIZE,
24 MEMGROUP_COUNT, NAME_ENTRY_SIZE, PAGE_SIZE,
25};
26use crate::sdcard::config::ChannelEntry;
27use crate::types::channel::FlashChannel;
28
29use super::MemoryError;
30
31const FLAGS_OFFSET: usize = 0x2000;
37
38const DATA_OFFSET: usize = 0x4000;
40
41const NAMES_OFFSET: usize = 0x10000;
43
44const MAX_REGULAR_CHANNEL: u16 = 999;
46
47const TOTAL_ENTRIES: usize = programming::TOTAL_CHANNEL_ENTRIES; #[allow(clippy::cast_possible_truncation)]
53const MAX_ENTRY_INDEX: u16 = (TOTAL_ENTRIES - 1) as u16;
54
55#[derive(Debug)]
65pub struct ChannelAccess<'a> {
66 image: &'a [u8],
67}
68
69impl<'a> ChannelAccess<'a> {
70 pub(crate) const fn new(image: &'a [u8]) -> Self {
72 Self { image }
73 }
74
75 #[must_use]
77 pub fn count(&self) -> usize {
78 (0..=MAX_REGULAR_CHANNEL)
79 .filter(|&ch| self.is_used(ch))
80 .count()
81 }
82
83 #[must_use]
87 pub fn is_used(&self, number: u16) -> bool {
88 let number_usize = number as usize;
89 if number_usize >= TOTAL_ENTRIES {
90 return false;
91 }
92 let offset = FLAGS_OFFSET + number_usize * FLAG_RECORD_SIZE;
93 if offset >= self.image.len() {
94 return false;
95 }
96 self.image[offset] != FLAG_EMPTY
97 }
98
99 #[must_use]
104 pub fn get(&self, number: u16) -> Option<ChannelEntry> {
105 let number_usize = number as usize;
106 if number_usize >= TOTAL_ENTRIES {
107 return None;
108 }
109
110 let flag = self.flag(number)?;
111 let used = flag.used != FLAG_EMPTY;
112 let flash = self.flash(number)?;
113 let name = self.name(number);
114
115 Some(ChannelEntry {
116 number,
117 name,
118 flash,
119 used,
120 lockout: flag.lockout,
121 })
122 }
123
124 #[must_use]
129 pub fn all(&self) -> Vec<ChannelEntry> {
130 (0..=MAX_REGULAR_CHANNEL)
131 .filter_map(|ch| {
132 let entry = self.get(ch)?;
133 if entry.used { Some(entry) } else { None }
134 })
135 .collect()
136 }
137
138 #[must_use]
140 pub fn all_slots(&self) -> Vec<ChannelEntry> {
141 (0..=MAX_REGULAR_CHANNEL)
142 .filter_map(|ch| self.get(ch))
143 .collect()
144 }
145
146 #[must_use]
151 pub fn name(&self, number: u16) -> String {
152 let number_usize = number as usize;
153 if number_usize >= TOTAL_ENTRIES {
154 return String::new();
155 }
156 let offset = NAMES_OFFSET + number_usize * NAME_ENTRY_SIZE;
157 if offset + NAME_ENTRY_SIZE > self.image.len() {
158 return String::new();
159 }
160 programming::extract_name(&self.image[offset..offset + NAME_ENTRY_SIZE])
161 }
162
163 #[must_use]
167 pub fn flag(&self, number: u16) -> Option<ChannelFlag> {
168 let number_usize = number as usize;
169 if number_usize >= TOTAL_ENTRIES {
170 return None;
171 }
172 let offset = FLAGS_OFFSET + number_usize * FLAG_RECORD_SIZE;
173 if offset + FLAG_RECORD_SIZE > self.image.len() {
174 return None;
175 }
176 programming::parse_channel_flag(&self.image[offset..])
177 }
178
179 #[must_use]
185 pub fn flash(&self, number: u16) -> Option<FlashChannel> {
186 let number_usize = number as usize;
187 if number_usize >= TOTAL_ENTRIES {
188 return None;
189 }
190
191 let memgroup = number_usize / CHANNELS_PER_MEMGROUP;
194 let slot = number_usize % CHANNELS_PER_MEMGROUP;
195
196 if memgroup >= MEMGROUP_COUNT {
197 return None;
198 }
199
200 let offset = DATA_OFFSET + memgroup * PAGE_SIZE + slot * CHANNEL_RECORD_SIZE;
201 if offset + CHANNEL_RECORD_SIZE > self.image.len() {
202 return None;
203 }
204 FlashChannel::from_bytes(&self.image[offset..]).ok()
205 }
206
207 #[must_use]
211 pub fn names(&self) -> Vec<String> {
212 (0..=MAX_REGULAR_CHANNEL).map(|ch| self.name(ch)).collect()
213 }
214
215 #[must_use]
219 pub fn group_name(&self, group: u8) -> String {
220 if group >= 30 {
221 return String::new();
222 }
223 let name_index = 1152 + group as usize;
224 let offset = NAMES_OFFSET + name_index * NAME_ENTRY_SIZE;
225 if offset + NAME_ENTRY_SIZE > self.image.len() {
226 return String::new();
227 }
228 programming::extract_name(&self.image[offset..offset + NAME_ENTRY_SIZE])
229 }
230
231 #[must_use]
233 pub fn group_names(&self) -> Vec<String> {
234 (0..30).map(|g| self.group_name(g)).collect()
235 }
236}
237
238#[derive(Debug)]
246pub struct ChannelWriter<'a> {
247 image: &'a mut [u8],
248}
249
250impl<'a> ChannelWriter<'a> {
251 pub(crate) const fn new(image: &'a mut [u8]) -> Self {
253 Self { image }
254 }
255
256 pub fn set(&mut self, entry: &ChannelEntry) -> Result<(), MemoryError> {
266 let number = entry.number as usize;
267 if number >= TOTAL_ENTRIES {
268 return Err(MemoryError::ChannelOutOfRange {
269 channel: entry.number,
270 max: MAX_ENTRY_INDEX,
271 });
272 }
273
274 self.set_flag(entry.number, entry.used, entry.lockout)?;
276
277 self.set_flash(entry.number, &entry.flash)?;
279
280 self.set_name(entry.number, &entry.name)?;
282
283 Ok(())
284 }
285
286 fn set_flag(&mut self, number: u16, used: bool, lockout: bool) -> Result<(), MemoryError> {
288 let number_usize = number as usize;
289 let offset = FLAGS_OFFSET + number_usize * FLAG_RECORD_SIZE;
290 if offset + FLAG_RECORD_SIZE > self.image.len() {
291 return Err(MemoryError::ChannelOutOfRange {
292 channel: number,
293 max: MAX_ENTRY_INDEX,
294 });
295 }
296
297 if used {
298 if self.image[offset] == FLAG_EMPTY {
301 self.image[offset] = 0x00; }
303 } else {
304 self.image[offset] = FLAG_EMPTY;
305 }
306
307 if lockout {
309 self.image[offset + 1] |= 0x01;
310 } else {
311 self.image[offset + 1] &= !0x01;
312 }
313
314 Ok(())
315 }
316
317 fn set_flash(&mut self, number: u16, memory: &FlashChannel) -> Result<(), MemoryError> {
319 let number_usize = number as usize;
320 let memgroup = number_usize / CHANNELS_PER_MEMGROUP;
321 let slot = number_usize % CHANNELS_PER_MEMGROUP;
322
323 if memgroup >= MEMGROUP_COUNT {
324 return Err(MemoryError::ChannelOutOfRange {
325 channel: number,
326 max: MAX_ENTRY_INDEX,
327 });
328 }
329
330 let offset = DATA_OFFSET + memgroup * PAGE_SIZE + slot * CHANNEL_RECORD_SIZE;
331 if offset + CHANNEL_RECORD_SIZE > self.image.len() {
332 return Err(MemoryError::ChannelOutOfRange {
333 channel: number,
334 max: MAX_ENTRY_INDEX,
335 });
336 }
337
338 let bytes = memory.to_bytes();
339 self.image[offset..offset + CHANNEL_RECORD_SIZE].copy_from_slice(&bytes);
340 Ok(())
341 }
342
343 fn set_name(&mut self, number: u16, name: &str) -> Result<(), MemoryError> {
345 let number_usize = number as usize;
346 let offset = NAMES_OFFSET + number_usize * NAME_ENTRY_SIZE;
347 if offset + NAME_ENTRY_SIZE > self.image.len() {
348 return Err(MemoryError::ChannelOutOfRange {
349 channel: number,
350 max: MAX_ENTRY_INDEX,
351 });
352 }
353
354 let mut buf = [0u8; NAME_ENTRY_SIZE];
355 let src = name.as_bytes();
356 let copy_len = src.len().min(NAME_ENTRY_SIZE);
357 buf[..copy_len].copy_from_slice(&src[..copy_len]);
358 self.image[offset..offset + NAME_ENTRY_SIZE].copy_from_slice(&buf);
359 Ok(())
360 }
361
362 pub fn set_group_name(&mut self, group: u8, name: &str) -> Result<(), MemoryError> {
371 if group >= 30 {
372 return Err(MemoryError::ChannelOutOfRange {
373 channel: u16::from(group),
374 max: 29,
375 });
376 }
377 let name_index = 1152 + group as usize;
378 let offset = NAMES_OFFSET + name_index * NAME_ENTRY_SIZE;
379 if offset + NAME_ENTRY_SIZE > self.image.len() {
380 return Err(MemoryError::ChannelOutOfRange {
381 channel: u16::from(group),
382 max: 29,
383 });
384 }
385
386 let mut buf = [0u8; NAME_ENTRY_SIZE];
387 let src = name.as_bytes();
388 let copy_len = src.len().min(NAME_ENTRY_SIZE);
389 buf[..copy_len].copy_from_slice(&src[..copy_len]);
390 self.image[offset..offset + NAME_ENTRY_SIZE].copy_from_slice(&buf);
391 Ok(())
392 }
393}
394
395#[cfg(test)]
400mod tests {
401 use super::*;
402 use crate::protocol::programming::TOTAL_SIZE;
403 use crate::types::Frequency;
404
405 fn make_test_image() -> Vec<u8> {
407 let mut image = vec![0xFF_u8; TOTAL_SIZE];
408
409 let names_end = NAMES_OFFSET + TOTAL_ENTRIES * NAME_ENTRY_SIZE;
411 image[NAMES_OFFSET..names_end].fill(0x00);
412
413 image[0x2000] = 0x00; image[0x2001] = 0x00; image[0x2002] = 0x00; image[0x2003] = 0xFF;
419
420 let freq: u32 = 146_520_000;
423 let freq_bytes = freq.to_le_bytes();
424 image[0x4000..0x4004].copy_from_slice(&freq_bytes);
425 image[0x4004..0x4008].copy_from_slice(&[0, 0, 0, 0]);
427 image[0x4008] = 0x00;
429 image[0x4009] = 0x00;
431 image[0x400A] = 0x00;
433 image[0x400B] = 0x00;
435 image[0x400C] = 0x00;
436 image[0x400D] = 0x00;
437 image[0x400E] = 0x00;
439 image[0x4027] = 0x00;
442
443 let name = b"2M CALL\0\0\0\0\0\0\0\0\0";
445 image[0x10000..0x10010].copy_from_slice(name);
446
447 image[0x2000 + 5 * 4] = 0x02; image[0x2000 + 5 * 4 + 1] = 0x01; image[0x2000 + 5 * 4 + 2] = 0x03; image[0x2000 + 5 * 4 + 3] = 0xFF;
455
456 let ch5_freq: u32 = 446_000_000;
458 let ch5_freq_bytes = ch5_freq.to_le_bytes();
459 image[0x40C8..0x40CC].copy_from_slice(&ch5_freq_bytes);
460 image[0x40CC..0x40D0].copy_from_slice(&[0, 0, 0, 0]);
461 image[0x40D0] = 0x00;
462 image[0x40D1] = 0x00;
463 image[0x40D2] = 0x00;
464 image[0x40D3] = 0x00;
465 image[0x40D4] = 0x00;
466 image[0x40D5] = 0x00;
467 image[0x40D6] = 0x00;
468 image[0x40EF] = 0x00;
469
470 let name5 = b"UHF CHAN\0\0\0\0\0\0\0\0";
472 image[0x10000 + 5 * 16..0x10000 + 5 * 16 + 16].copy_from_slice(name5);
473
474 image
475 }
476
477 #[test]
478 fn from_raw_valid_size() {
479 let image = vec![0u8; TOTAL_SIZE];
480 assert!(super::super::MemoryImage::from_raw(image).is_ok());
481 }
482
483 #[test]
484 fn from_raw_invalid_size() {
485 let image = vec![0u8; 1000];
486 let err = super::super::MemoryImage::from_raw(image).unwrap_err();
487 assert!(matches!(err, MemoryError::InvalidSize { .. }));
488 }
489
490 #[test]
491 fn channel_is_used() {
492 let image = make_test_image();
493 let mi = super::super::MemoryImage::from_raw(image).unwrap();
494 let ch = mi.channels();
495 assert!(ch.is_used(0));
496 assert!(!ch.is_used(1));
497 assert!(ch.is_used(5));
498 }
499
500 #[test]
501 fn channel_count() {
502 let image = make_test_image();
503 let mi = super::super::MemoryImage::from_raw(image).unwrap();
504 let ch = mi.channels();
505 assert_eq!(ch.count(), 2); }
507
508 #[test]
509 fn channel_get_name() {
510 let image = make_test_image();
511 let mi = super::super::MemoryImage::from_raw(image).unwrap();
512 let ch = mi.channels();
513 assert_eq!(ch.name(0), "2M CALL");
514 assert_eq!(ch.name(5), "UHF CHAN");
515 assert_eq!(ch.name(1), ""); }
517
518 #[test]
519 fn channel_get_entry() {
520 let image = make_test_image();
521 let mi = super::super::MemoryImage::from_raw(image).unwrap();
522 let ch = mi.channels();
523
524 let entry0 = ch.get(0).unwrap();
525 assert!(entry0.used);
526 assert!(!entry0.lockout);
527 assert_eq!(entry0.name, "2M CALL");
528 assert_eq!(entry0.flash.rx_frequency.as_hz(), 146_520_000);
529
530 let entry5 = ch.get(5).unwrap();
531 assert!(entry5.used);
532 assert!(entry5.lockout);
533 assert_eq!(entry5.name, "UHF CHAN");
534 assert_eq!(entry5.flash.rx_frequency.as_hz(), 446_000_000);
535 }
536
537 #[test]
538 fn channel_get_out_of_range() {
539 let image = make_test_image();
540 let mi = super::super::MemoryImage::from_raw(image).unwrap();
541 let ch = mi.channels();
542 assert!(ch.get(1200).is_none());
543 }
544
545 #[test]
546 fn channel_all_returns_only_used() {
547 let image = make_test_image();
548 let mi = super::super::MemoryImage::from_raw(image).unwrap();
549 let ch = mi.channels();
550 let all = ch.all();
551 assert_eq!(all.len(), 2);
552 assert_eq!(all[0].number, 0);
553 assert_eq!(all[1].number, 5);
554 }
555
556 #[test]
557 fn channel_flag() {
558 let image = make_test_image();
559 let mi = super::super::MemoryImage::from_raw(image).unwrap();
560 let ch = mi.channels();
561
562 let flag0 = ch.flag(0).unwrap();
563 assert_eq!(flag0.used, 0x00); assert!(!flag0.lockout);
565 assert_eq!(flag0.group, 0);
566
567 let flag5 = ch.flag(5).unwrap();
568 assert_eq!(flag5.used, 0x02); assert!(flag5.lockout);
570 assert_eq!(flag5.group, 3);
571 }
572
573 #[test]
574 fn channel_group_names() {
575 let mut image = make_test_image();
576 let name = b"Ham Radio\0\0\0\0\0\0\0";
578 let offset = 0x10000 + 1152 * 16;
579 image[offset..offset + 16].copy_from_slice(name);
580
581 let mi = super::super::MemoryImage::from_raw(image).unwrap();
582 let ch = mi.channels();
583 assert_eq!(ch.group_name(0), "Ham Radio");
584 assert_eq!(ch.group_name(1), ""); }
586
587 #[test]
588 fn channel_writer_set() {
589 let image = make_test_image();
590 let mut mi = super::super::MemoryImage::from_raw(image).unwrap();
591
592 let entry = ChannelEntry {
593 number: 10,
594 name: "TEST CH".to_owned(),
595 flash: FlashChannel {
596 rx_frequency: Frequency::new(145_000_000),
597 ..FlashChannel::default()
598 },
599 used: true,
600 lockout: false,
601 };
602
603 {
604 let mut writer = ChannelWriter::new(mi.as_raw_mut());
605 writer.set(&entry).unwrap();
606 }
607
608 let ch = mi.channels();
609 assert!(ch.is_used(10));
610 let read_back = ch.get(10).unwrap();
611 assert!(read_back.used);
612 assert_eq!(read_back.name, "TEST CH");
613 assert_eq!(read_back.flash.rx_frequency.as_hz(), 145_000_000);
614 }
615
616 #[test]
617 fn channel_writer_group_name() {
618 let image = make_test_image();
619 let mut mi = super::super::MemoryImage::from_raw(image).unwrap();
620
621 {
622 let mut writer = ChannelWriter::new(mi.as_raw_mut());
623 writer.set_group_name(0, "My Group").unwrap();
624 }
625
626 let ch = mi.channels();
627 assert_eq!(ch.group_name(0), "My Group");
628 }
629
630 #[test]
631 fn channel_writer_out_of_range() {
632 let image = make_test_image();
633 let mut mi = super::super::MemoryImage::from_raw(image).unwrap();
634
635 let entry = ChannelEntry {
636 number: 1200,
637 name: String::new(),
638 flash: FlashChannel::default(),
639 used: false,
640 lockout: false,
641 };
642
643 let mut writer = ChannelWriter::new(mi.as_raw_mut());
644 let result = writer.set(&entry);
645 assert!(result.is_err());
646 }
647}