1use crate::{
11 errors::NetworkParseError,
12 mion::{
13 errors::MIONAPIError,
14 proto::control::{MIONControlProtocolError, MionCommandByte},
15 },
16};
17use bytes::{BufMut, Bytes, BytesMut};
18use mac_address::MacAddress;
19use std::{
20 fmt::{Display, Formatter, Result as FmtResult},
21 net::Ipv4Addr,
22};
23use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
24
25const ANNOUNCEMENT_MESSAGE: &str = "MULTI_I/O_NETWORK_BOARD";
27const DETAIL_FLAG_MESSAGE: &str = "enumV1";
29
30#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Valuable)]
35pub struct MionIdentityAnnouncement {
36 detailed: bool,
40}
41impl MionIdentityAnnouncement {
42 #[must_use]
43 pub const fn new(is_detailed: bool) -> Self {
44 Self {
45 detailed: is_detailed,
46 }
47 }
48
49 #[must_use]
52 pub const fn is_detailed(&self) -> bool {
53 self.detailed
54 }
55}
56impl Display for MionIdentityAnnouncement {
57 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
58 write!(
59 fmt,
60 "{}",
61 if self.detailed {
62 "DetailedMionIdentityAnnouncement"
63 } else {
64 "MionIdentityAnnouncement"
65 }
66 )
67 }
68}
69impl TryFrom<Bytes> for MionIdentityAnnouncement {
70 type Error = NetworkParseError;
71
72 fn try_from(packet: Bytes) -> Result<Self, Self::Error> {
73 if packet.len() < 25 {
74 return Err(NetworkParseError::NotEnoughData(
75 "MionIdentityAnnouncement",
76 25,
77 packet.len(),
78 packet,
79 ));
80 }
81 if packet.len() > 33 {
82 return Err(NetworkParseError::UnexpectedTrailer(
83 "MionIdentityAnnouncement",
84 packet.slice(33..),
85 ));
86 }
87 let is_detailed = packet.len() > 25;
88
89 if packet[0] != u8::from(MionCommandByte::AnnounceYourselves) {
90 return Err(MIONControlProtocolError::UnknownCommand(packet[0]).into());
91 }
92
93 if &packet[1..24] != ANNOUNCEMENT_MESSAGE.as_bytes() {
94 return Err(NetworkParseError::FieldEncodedIncorrectly(
95 "MionIdentityAnnouncement",
96 "buff",
97 "Must start with static message: `MULTI_I/O_NETWORK_BOARD` with a NUL Terminator",
98 ));
99 }
100 if packet[24] != 0 {
101 return Err(NetworkParseError::FieldEncodedIncorrectly(
102 "MionIdentityAnnouncement",
103 "buff",
104 "Must start with static message: `MULTI_I/O_NETWORK_BOARD` with a NUL Terminator",
105 ));
106 }
107 if is_detailed && &packet[25..] != b"enumV1\0\0" {
108 return Err(
109 NetworkParseError::FieldEncodedIncorrectly(
110 "MionIdentityAnnouncement",
111 "buff",
112 "Only the static string `enumV1` followed by two NUL Terminators is allowed after `MULTI_I/O_NETWORK_BOARD`.",
113 ),
114 );
115 }
116
117 Ok(Self {
118 detailed: is_detailed,
119 })
120 }
121}
122impl From<&MionIdentityAnnouncement> for Bytes {
123 fn from(this: &MionIdentityAnnouncement) -> Self {
124 let mut buff = BytesMut::with_capacity(if this.detailed { 33 } else { 25 });
125 buff.put_u8(u8::from(MionCommandByte::AnnounceYourselves));
126 buff.extend_from_slice(ANNOUNCEMENT_MESSAGE.as_bytes());
127 buff.put_u8(0);
128 if this.detailed {
129 buff.extend_from_slice(DETAIL_FLAG_MESSAGE.as_bytes());
130 buff.put_u16(0_u16);
131 }
132 buff.freeze()
133 }
134}
135impl From<MionIdentityAnnouncement> for Bytes {
136 fn from(value: MionIdentityAnnouncement) -> Self {
137 Self::from(&value)
138 }
139}
140
141#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Valuable)]
143pub enum MIONBootType {
144 NAND,
146 PCFS,
148 DUAL,
151 Unk(u8),
153}
154impl Display for MIONBootType {
155 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
156 match *self {
157 Self::NAND => write!(fmt, "NAND"),
158 Self::PCFS => write!(fmt, "PCFS"),
159 Self::DUAL => write!(fmt, "DUAL"),
160 Self::Unk(val) => write!(fmt, "Unk({val})"),
161 }
162 }
163}
164impl From<u8> for MIONBootType {
165 fn from(value: u8) -> Self {
166 match value {
167 0x1 => MIONBootType::NAND,
168 0x2 => MIONBootType::PCFS,
169 0x3 => MIONBootType::DUAL,
170 num => MIONBootType::Unk(num),
171 }
172 }
173}
174impl From<MIONBootType> for u8 {
175 fn from(value: MIONBootType) -> u8 {
176 match value {
177 MIONBootType::NAND => 0x1,
178 MIONBootType::PCFS => 0x2,
179 MIONBootType::DUAL => 0x3,
180 MIONBootType::Unk(num) => num,
181 }
182 }
183}
184
185#[derive(Clone, Debug, Hash, PartialEq, Eq)]
187pub struct MionIdentity {
188 detailed_all: Option<Bytes>,
191 firmware_version: [u8; 4],
192 fpga_version: [u8; 4],
193 ip_address: Ipv4Addr,
194 mac: MacAddress,
195 name: String,
196}
197impl MionIdentity {
198 pub fn new(
206 detailed_data: Option<Bytes>,
207 firmware_version: [u8; 4],
208 fpga_version: [u8; 4],
209 ip_address: Ipv4Addr,
210 mac: MacAddress,
211 name: String,
212 ) -> Result<Self, MIONAPIError> {
213 if !name.is_ascii() {
214 return Err(MIONAPIError::DeviceNameMustBeAscii);
215 }
216 if name.len() > 255 {
217 return Err(MIONAPIError::DeviceNameTooLong(name.len()));
218 }
219 if name.is_empty() {
220 return Err(MIONAPIError::DeviceNameCannotBeEmpty);
221 }
222
223 Ok(Self {
224 detailed_all: detailed_data,
225 firmware_version,
226 fpga_version,
227 ip_address,
228 mac,
229 name,
230 })
231 }
232
233 #[must_use]
236 pub fn firmware_version(&self) -> String {
237 format!(
238 "0.{}.{}.{}",
239 self.firmware_version[0], self.firmware_version[1], self.firmware_version[2],
240 )
241 }
242 #[must_use]
247 pub const fn raw_firmware_version(&self) -> [u8; 4] {
248 self.firmware_version
249 }
250
251 #[must_use]
254 pub fn fpga_version(&self) -> String {
255 let mut fpga_version = String::new();
256 for byte in [
257 self.fpga_version[3],
258 self.fpga_version[2],
259 self.fpga_version[1],
260 self.fpga_version[0],
261 ] {
262 fpga_version.push_str(&format!("{byte:x}"));
263 }
264 fpga_version
265 }
266 #[must_use]
269 pub fn detailed_fpga_version(&self) -> String {
270 let mut fpga_version = String::new();
271 for byte in [
272 self.fpga_version[3],
273 self.fpga_version[2],
274 self.fpga_version[1],
275 self.fpga_version[0],
276 ] {
277 fpga_version.push_str(&format!("{byte:02x}"));
278 }
279 fpga_version
280 }
281 #[must_use]
286 pub const fn raw_fpga_version(&self) -> [u8; 4] {
287 self.fpga_version
288 }
289
290 #[must_use]
292 pub const fn ip_address(&self) -> Ipv4Addr {
293 self.ip_address
294 }
295
296 #[must_use]
298 pub const fn mac_address(&self) -> MacAddress {
299 self.mac
300 }
301
302 #[must_use]
304 pub fn name(&self) -> &str {
305 &self.name
306 }
307
308 #[must_use]
314 pub const fn is_detailed(&self) -> bool {
315 self.detailed_all.is_some()
316 }
317
318 #[must_use]
321 pub fn detailed_sdk_version(&self) -> Option<String> {
322 self.detailed_all.as_ref().map(|extra_data| {
323 let bytes = [
324 extra_data[227],
325 extra_data[228],
326 extra_data[229],
327 extra_data[230],
328 ];
329
330 if bytes[3] == 0 {
332 format!("{}.{}.{}", bytes[0], bytes[1], bytes[2])
333 } else {
334 format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3])
335 }
336 })
337 }
338 #[must_use]
345 pub fn detailed_raw_sdk_version(&self) -> Option<[u8; 4]> {
346 self.detailed_all.as_ref().map(|extra_data| {
347 [
348 extra_data[227],
349 extra_data[228],
350 extra_data[229],
351 extra_data[230],
352 ]
353 })
354 }
355
356 #[must_use]
359 pub fn detailed_boot_type(&self) -> Option<MIONBootType> {
360 self.detailed_all
361 .as_ref()
362 .map(|extra_data| MIONBootType::from(extra_data[232]))
363 }
364
365 #[must_use]
368 pub fn detailed_is_cafe_on(&self) -> Option<bool> {
369 self.detailed_all
370 .as_ref()
371 .map(|extra_data| extra_data[233] > 0)
372 }
373}
374impl Display for MionIdentity {
375 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
376 if let Some(detailed) = self.detailed_all.as_ref() {
377 write!(
378 fmt,
379 "{} (aka {}) @ {} fpga-v{}.{}.{}.{} fw-v{}.{}.{}.{} sdk-v{}.{}.{}.{} boot-type:{} cafe:{}",
380 self.name,
381 self.ip_address,
382 self.mac,
383 self.fpga_version[0],
384 self.fpga_version[1],
385 self.fpga_version[2],
386 self.fpga_version[3],
387 self.firmware_version[0],
388 self.firmware_version[1],
389 self.firmware_version[2],
390 self.firmware_version[3],
391 detailed[227],
392 detailed[228],
393 detailed[229],
394 detailed[230],
395 MIONBootType::from(detailed[232]),
396 detailed[233],
397 )
398 } else {
399 write!(
400 fmt,
401 "{} (aka {}) @ {} fpga-v{}.{}.{}.{} fw-v{}.{}.{}.{}",
402 self.name,
403 self.ip_address,
404 self.mac,
405 self.fpga_version[0],
406 self.fpga_version[1],
407 self.fpga_version[2],
408 self.fpga_version[3],
409 self.firmware_version[0],
410 self.firmware_version[1],
411 self.firmware_version[2],
412 self.firmware_version[3],
413 )
414 }
415 }
416}
417impl TryFrom<(Ipv4Addr, Bytes)> for MionIdentity {
418 type Error = NetworkParseError;
419
420 fn try_from((from_address, packet): (Ipv4Addr, Bytes)) -> Result<Self, Self::Error> {
421 if packet.len() < 17 {
425 return Err(NetworkParseError::NotEnoughData(
426 "MionIdentity",
427 17,
428 packet.len(),
429 packet,
430 ));
431 }
432
433 if packet[0] != u8::from(MionCommandByte::AcknowledgeAnnouncement) {
434 return Err(MIONControlProtocolError::UnknownCommand(packet[0]).into());
435 }
436 let name_length = usize::from(packet[7]);
440 if packet.len() < 16 + name_length {
441 return Err(NetworkParseError::NotEnoughData(
442 "MionIdentity",
443 16 + name_length,
444 packet.len(),
445 packet,
446 ));
447 }
448 if name_length < 1 {
449 return Err(NetworkParseError::FieldNotLongEnough(
450 "MionIdentity",
451 "name",
452 1,
453 name_length,
454 packet,
455 ));
456 }
457 if packet.len() > 16 + name_length + 239 {
458 return Err(NetworkParseError::UnexpectedTrailer(
459 "MionIdentity",
460 packet.slice(16 + name_length + 239..),
461 ));
462 }
463 if packet.len() != 16 + name_length && packet.len() != 16 + name_length + 239 {
464 return Err(NetworkParseError::UnexpectedTrailer(
465 "MionIdentity",
466 packet.slice(16 + name_length..),
467 ));
468 }
469 let is_detailed = packet.len() > 16 + name_length;
470
471 let mac = MacAddress::new([
472 packet[1], packet[2], packet[3], packet[4], packet[5], packet[6],
473 ]);
474 let fpga_version = [packet[8], packet[9], packet[10], packet[11]];
475 let firmware_version = [packet[12], packet[13], packet[14], packet[15]];
476 let Ok(name) = String::from_utf8(Vec::from(&packet[16..16 + name_length])) else {
477 return Err(NetworkParseError::FieldEncodedIncorrectly(
478 "MionIdentity",
479 "name",
480 "ASCII",
481 ));
482 };
483 if !name.is_ascii() {
484 return Err(NetworkParseError::FieldEncodedIncorrectly(
485 "MionIdentity",
486 "name",
487 "ASCII",
488 ));
489 }
490
491 let detailed_all = if is_detailed {
492 Some(packet.slice(16 + name_length..))
493 } else {
494 None
495 };
496
497 Ok(Self {
498 detailed_all,
499 firmware_version,
500 fpga_version,
501 ip_address: from_address,
502 mac,
503 name,
504 })
505 }
506}
507impl From<&MionIdentity> for Bytes {
508 fn from(value: &MionIdentity) -> Self {
509 let mut buff = BytesMut::with_capacity(16 + value.name.len());
510 buff.put_u8(u8::from(MionCommandByte::AcknowledgeAnnouncement));
511 buff.extend_from_slice(&value.mac.bytes());
512 buff.put_u8(u8::try_from(value.name.len()).unwrap_or(u8::MAX));
513 buff.extend_from_slice(&[
514 value.fpga_version[0],
515 value.fpga_version[1],
516 value.fpga_version[2],
517 value.fpga_version[3],
518 ]);
519 buff.extend_from_slice(&[
520 value.firmware_version[0],
521 value.firmware_version[1],
522 value.firmware_version[2],
523 value.firmware_version[3],
524 ]);
525 buff.extend_from_slice(value.name.as_bytes());
526 buff.freeze()
527 }
528}
529impl From<MionIdentity> for Bytes {
530 fn from(value: MionIdentity) -> Self {
531 Self::from(&value)
532 }
533}
534
535const MION_IDENTITY_FIELDS: &[NamedField<'static>] = &[
536 NamedField::new("name"),
537 NamedField::new("ip_address"),
538 NamedField::new("mac"),
539 NamedField::new("fpga_version"),
540 NamedField::new("firmware_version"),
541 NamedField::new("detailed_sdk_version"),
542 NamedField::new("detailed_boot_mode"),
543 NamedField::new("detailed_power_status"),
544];
545impl Structable for MionIdentity {
546 fn definition(&self) -> StructDef<'_> {
547 StructDef::new_static("MionIdentity", Fields::Named(MION_IDENTITY_FIELDS))
548 }
549}
550impl Valuable for MionIdentity {
551 fn as_value(&self) -> Value<'_> {
552 Value::Structable(self)
553 }
554
555 fn visit(&self, visitor: &mut dyn Visit) {
556 let detailed_sdk_version = self
557 .detailed_sdk_version()
558 .unwrap_or("<missing data>".to_owned());
559 let detailed_boot_mode = self
560 .detailed_boot_type()
561 .map_or("<missing data>".to_owned(), |bt| format!("{bt}"));
562 let detailed_power_status = self
563 .detailed_sdk_version()
564 .unwrap_or("<missing data>".to_owned());
565
566 visitor.visit_named_fields(&NamedValues::new(
567 MION_IDENTITY_FIELDS,
568 &[
569 Valuable::as_value(&self.name),
570 Valuable::as_value(&format!("{}", self.ip_address)),
571 Valuable::as_value(&format!("{}", self.mac)),
572 Valuable::as_value(&self.detailed_fpga_version()),
573 Valuable::as_value(&self.firmware_version()),
574 Valuable::as_value(&detailed_sdk_version),
575 Valuable::as_value(&detailed_boot_mode),
576 Valuable::as_value(&detailed_power_status),
577 ],
578 ));
579 }
580}
581
582#[cfg(test)]
583mod unit_tests {
584 use super::*;
585 use crate::mion::errors::MIONProtocolError;
586
587 #[test]
588 pub fn mion_command_byte_conversions() {
589 for command_byte in vec![
590 MionCommandByte::Search,
591 MionCommandByte::Broadcast,
592 MionCommandByte::AnnounceYourselves,
593 MionCommandByte::AcknowledgeAnnouncement,
594 ] {
595 assert_eq!(
596 MionCommandByte::try_from(u8::from(command_byte))
597 .expect("Failed to turn command byte -> u8 -> command byte"),
598 command_byte,
599 "Mion Command Byte when serialized & deserialized was not the same: {}",
600 command_byte,
601 );
602 }
603 }
604
605 #[test]
606 pub fn mion_identity_construction_tests() {
607 assert_eq!(
608 MionIdentity::new(
609 None,
610 [0, 0, 0, 0],
611 [0, 0, 0, 0],
612 Ipv4Addr::LOCALHOST,
613 MacAddress::new([0, 0, 0, 0, 0, 0]),
614 "Ƙ".to_owned()
616 ),
617 Err(MIONAPIError::DeviceNameMustBeAscii),
618 );
619 assert_eq!(
620 MionIdentity::new(
621 None,
622 [0, 0, 0, 0],
623 [0, 0, 0, 0],
624 Ipv4Addr::LOCALHOST,
625 MacAddress::new([0, 0, 0, 0, 0, 0]),
626 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned()
628 ),
629 Err(MIONAPIError::DeviceNameTooLong(300)),
630 );
631 assert_eq!(
632 MionIdentity::new(
633 None,
634 [0, 0, 0, 0],
635 [0, 0, 0, 0],
636 Ipv4Addr::LOCALHOST,
637 MacAddress::new([0, 0, 0, 0, 0, 0]),
638 String::new(),
640 ),
641 Err(MIONAPIError::DeviceNameCannotBeEmpty),
642 );
643 assert!(MionIdentity::new(
645 None,
646 [0, 0, 0, 0],
647 [0, 0, 0, 0],
648 Ipv4Addr::LOCALHOST,
649 MacAddress::new([0, 0, 0, 0, 0, 0]),
650 "00-00-00-00-00-00".to_owned(),
651 )
652 .is_ok());
653 }
654
655 #[test]
656 pub fn mion_identity_deser() {
657 {
659 let identity = MionIdentity::new(
660 None,
661 [1, 2, 3, 4],
662 [5, 6, 7, 8],
663 Ipv4Addr::new(9, 10, 11, 12),
664 MacAddress::new([13, 14, 15, 16, 17, 18]),
665 "Apples".to_owned(),
666 )
667 .expect("Failed to create identity to serialize & deserialize.");
668
669 assert_eq!(
670 identity,
671 MionIdentity::try_from((Ipv4Addr::new(9, 10, 11, 12), Bytes::from(&identity)))
672 .expect("Failed to deserialize MION Identity")
673 );
674 }
675
676 {
678 let buff = Bytes::from(vec![
679 u8::from(MionCommandByte::AcknowledgeAnnouncement),
681 0x1,
683 0x2,
684 0x3,
685 0x4,
686 0x5,
687 0x6,
688 0x1,
690 0x1,
692 0x2,
693 0x3,
694 0x4,
695 0x1,
697 0x2,
698 0x3,
699 0x4,
700 ]);
701
702 assert!(matches!(
703 MionIdentity::try_from((Ipv4Addr::LOCALHOST, buff.clone())),
704 Err(NetworkParseError::NotEnoughData("MionIdentity", 17, 16, _)),
705 ));
706 }
707
708 {
710 let buff = Bytes::from(vec![
711 u8::from(MionCommandByte::Search),
713 0x1,
715 0x2,
716 0x3,
717 0x4,
718 0x5,
719 0x6,
720 0x1,
722 0x1,
724 0x2,
725 0x3,
726 0x4,
727 0x1,
729 0x2,
730 0x3,
731 0x4,
732 101,
734 ]);
735
736 assert_eq!(
737 MionIdentity::try_from((Ipv4Addr::LOCALHOST, buff.clone())),
738 Err(NetworkParseError::MION(MIONProtocolError::Control(
739 MIONControlProtocolError::UnknownCommand(0x3F)
740 ))),
741 );
742 }
743
744 {
746 let buff = Bytes::from(vec![
747 u8::from(MionCommandByte::AcknowledgeAnnouncement),
749 0x1,
751 0x2,
752 0x3,
753 0x4,
754 0x5,
755 0x6,
756 0x0,
758 0x1,
760 0x2,
761 0x3,
762 0x4,
763 0x1,
765 0x2,
766 0x3,
767 0x4,
768 101,
770 ]);
771
772 assert_eq!(
773 MionIdentity::try_from((Ipv4Addr::LOCALHOST, buff.clone())),
774 Err(NetworkParseError::FieldNotLongEnough(
775 "MionIdentity",
776 "name",
777 1,
778 0,
779 buff
780 )),
781 );
782 }
783
784 {
786 let buff = Bytes::from(vec![
787 u8::from(MionCommandByte::AcknowledgeAnnouncement),
789 0x1,
791 0x2,
792 0x3,
793 0x4,
794 0x5,
795 0x6,
796 0x6,
798 0x1,
800 0x2,
801 0x3,
802 0x4,
803 0x1,
805 0x2,
806 0x3,
807 0x4,
808 0xFF,
810 0xFF,
811 0xFF,
812 0xFF,
813 0xFF,
814 0xFF,
815 ]);
816
817 assert_eq!(
818 MionIdentity::try_from((Ipv4Addr::LOCALHOST, buff.clone())),
819 Err(NetworkParseError::FieldEncodedIncorrectly(
820 "MionIdentity",
821 "name",
822 "ASCII"
823 )),
824 );
825 }
826
827 {
829 let buff = Bytes::from(vec![
830 u8::from(MionCommandByte::AcknowledgeAnnouncement),
832 0x1,
834 0x2,
835 0x3,
836 0x4,
837 0x5,
838 0x6,
839 0x2,
841 0x1,
843 0x2,
844 0x3,
845 0x4,
846 0x1,
848 0x2,
849 0x3,
850 0x4,
851 0xC6,
853 0x98,
854 ]);
855
856 assert_eq!(
857 MionIdentity::try_from((Ipv4Addr::LOCALHOST, buff.clone())),
858 Err(NetworkParseError::FieldEncodedIncorrectly(
859 "MionIdentity",
860 "name",
861 "ASCII"
862 )),
863 );
864 }
865
866 {
868 let mut buff = BytesMut::new();
869 buff.extend_from_slice(&[
870 u8::from(MionCommandByte::AcknowledgeAnnouncement),
872 0x1,
874 0x2,
875 0x3,
876 0x4,
877 0x5,
878 0x6,
879 0x2,
881 0x1,
883 0x2,
884 0x3,
885 0x4,
886 0x1,
888 0x2,
889 0x3,
890 0x4,
891 0x61,
893 0x61,
894 ]);
895 buff.extend_from_slice(b"abcd");
897
898 assert_eq!(
899 MionIdentity::try_from((Ipv4Addr::LOCALHOST, buff.freeze())),
900 Err(NetworkParseError::UnexpectedTrailer(
901 "MionIdentity",
902 Bytes::from(b"abcd".iter().cloned().collect::<Vec<u8>>())
903 )),
904 );
905 }
906
907 {
909 let mut buff = BytesMut::new();
910 buff.extend_from_slice(&[
911 u8::from(MionCommandByte::AcknowledgeAnnouncement),
913 0x1,
915 0x2,
916 0x3,
917 0x4,
918 0x5,
919 0x6,
920 0x2,
922 0x1,
924 0x2,
925 0x3,
926 0x4,
927 0x1,
929 0x2,
930 0x3,
931 0x4,
932 0x61,
934 0x61,
935 ]);
936 buff.extend_from_slice(&[0x0; 239]);
938 buff.extend_from_slice(b"abcd");
940
941 assert_eq!(
942 MionIdentity::try_from((Ipv4Addr::LOCALHOST, buff.freeze())),
943 Err(NetworkParseError::UnexpectedTrailer(
944 "MionIdentity",
945 Bytes::from(b"abcd".iter().cloned().collect::<Vec<u8>>())
946 )),
947 );
948 }
949 }
950
951 #[test]
952 pub fn test_real_life_detailed_announcements() {
953 const OFF_ANNOUNCEMENT: [u8; 272] = [
954 0x20, 0x00, 0x25, 0x5c, 0xba, 0x5a, 0x00, 0x11, 0x71, 0x20, 0x05, 0x13, 0x00, 0x0e,
955 0x50, 0x01, 0x30, 0x30, 0x2d, 0x32, 0x35, 0x2d, 0x35, 0x43, 0x2d, 0x42, 0x41, 0x2d,
956 0x35, 0x41, 0x2d, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
957 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
958 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
959 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
960 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
961 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
962 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
963 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
964 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
965 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
966 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
967 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
968 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
969 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
970 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
971 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
972 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0c, 0x0d, 0x00, 0x01, 0x02,
973 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
974 ];
975 const ON_ANNOUNCEMENT: [u8; 272] = [
976 0x20, 0x00, 0x25, 0x5c, 0xba, 0x5a, 0x00, 0x11, 0x71, 0x20, 0x05, 0x13, 0x00, 0x0e,
977 0x50, 0x01, 0x30, 0x30, 0x2d, 0x32, 0x35, 0x2d, 0x35, 0x43, 0x2d, 0x42, 0x41, 0x2d,
978 0x35, 0x41, 0x2d, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
979 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
980 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
981 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
982 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
983 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
984 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
985 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
986 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
987 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
988 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
989 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
990 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
991 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
992 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
993 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
994 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0c, 0x0d, 0x00, 0x01, 0x02,
995 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
996 ];
997
998 let off_identity = MionIdentity::try_from((
999 Ipv4Addr::LOCALHOST,
1000 Bytes::from(Vec::from(OFF_ANNOUNCEMENT)),
1001 ))
1002 .expect("Failed to parse `OFF_ANNOUNCEMENT` from an actual data packet. Parser is broken.");
1003 let on_identity =
1004 MionIdentity::try_from((Ipv4Addr::LOCALHOST, Bytes::from(Vec::from(ON_ANNOUNCEMENT))))
1005 .expect(
1006 "Failed to parse `ON_ANNOUNCEMENT` from an actual data packet. Parser is broken.",
1007 );
1008
1009 assert_eq!(
1010 off_identity.detailed_sdk_version(),
1011 Some("2.12.13".to_owned())
1012 );
1013 assert_eq!(off_identity.detailed_boot_type(), Some(MIONBootType::PCFS));
1014 assert_eq!(off_identity.detailed_is_cafe_on(), Some(false));
1015
1016 assert_eq!(
1017 on_identity.detailed_sdk_version(),
1018 Some("2.12.13".to_owned())
1019 );
1020 assert_eq!(on_identity.detailed_boot_type(), Some(MIONBootType::PCFS));
1021 assert_eq!(on_identity.detailed_is_cafe_on(), Some(true));
1022 }
1023
1024 #[test]
1025 pub fn mion_announcement_ser_deser() {
1026 {
1028 let announcement = MionIdentityAnnouncement { detailed: false };
1029 let serialized = Bytes::from(&announcement);
1030 let deser = MionIdentityAnnouncement::try_from(serialized);
1031 assert!(
1032 deser.is_ok(),
1033 "Failed to deserialize serialized MionIdentityAnnouncement!"
1034 );
1035 assert_eq!(
1036 announcement,
1037 deser.unwrap(),
1038 "MionIdentityAnnouncement was not the same after being serialized, and deserialized!",
1039 );
1040 }
1041 {
1042 let announcement = MionIdentityAnnouncement { detailed: true };
1043 let serialized = Bytes::from(&announcement);
1044 let deser = MionIdentityAnnouncement::try_from(serialized);
1045 assert!(
1046 deser.is_ok(),
1047 "Failed to deserialize serialized MionIdentityAnnouncement!"
1048 );
1049 assert_eq!(
1050 announcement,
1051 deser.unwrap(),
1052 "MionIdentityAnnouncement was not the same after being serialized, and deserialized!",
1053 );
1054 }
1055
1056 {
1058 let packet = Bytes::from(vec![
1059 u8::from(MionCommandByte::AnnounceYourselves),
1061 0xA,
1063 0x0,
1065 ]);
1066
1067 assert_eq!(
1068 MionIdentity::try_from((Ipv4Addr::LOCALHOST, packet.clone())),
1069 Err(NetworkParseError::NotEnoughData(
1070 "MionIdentity",
1071 17,
1072 3,
1073 packet
1074 )),
1075 );
1076 }
1077
1078 {
1080 let packet = Bytes::from(vec![
1081 u8::from(MionCommandByte::AnnounceYourselves),
1083 0xA,
1085 0xA,
1086 0xA,
1087 0xA,
1088 0xA,
1089 0xA,
1090 0xA,
1091 0xA,
1092 0xA,
1093 0xA,
1094 0xA,
1095 0xA,
1096 0xA,
1097 0xA,
1098 0xA,
1099 0xA,
1100 0xA,
1101 0xA,
1102 0xA,
1103 0xA,
1104 0xA,
1105 0xA,
1106 0xA,
1107 0xA,
1108 0xA,
1109 0xA,
1110 0xA,
1111 0xA,
1112 0xA,
1113 0xA,
1114 0xA,
1115 0xA,
1116 0xA,
1117 0xA,
1118 0xA,
1119 0xA,
1120 0xA,
1121 0xA,
1122 0xA,
1123 0xA,
1124 0xA,
1125 0x0,
1127 ]);
1128
1129 assert_eq!(
1130 MionIdentityAnnouncement::try_from(packet.clone()),
1131 Err(NetworkParseError::UnexpectedTrailer(
1132 "MionIdentityAnnouncement",
1133 packet.slice(33..),
1134 )),
1135 );
1136 }
1137
1138 {
1140 let mut buff = Vec::new();
1141 buff.push(u8::from(MionCommandByte::Search));
1143 buff.extend_from_slice(ANNOUNCEMENT_MESSAGE.as_bytes());
1144 buff.push(0x0);
1145 let packet = Bytes::from(buff);
1146
1147 assert_eq!(
1148 MionIdentityAnnouncement::try_from(packet),
1149 Err(NetworkParseError::MION(MIONProtocolError::Control(
1150 MIONControlProtocolError::UnknownCommand(u8::from(MionCommandByte::Search))
1151 ))),
1152 );
1153 }
1154
1155 {
1157 let packet = Bytes::from(vec![
1158 u8::from(MionCommandByte::AnnounceYourselves),
1160 0xA,
1162 0xA,
1163 0xA,
1164 0xA,
1165 0xA,
1166 0xA,
1167 0xA,
1168 0xA,
1169 0xA,
1170 0xA,
1171 0xA,
1172 0xA,
1173 0xA,
1174 0xA,
1175 0xA,
1176 0xA,
1177 0xA,
1178 0xA,
1179 0xA,
1180 0xA,
1181 0xA,
1182 0xA,
1183 0xA,
1184 0x0,
1186 ]);
1187
1188 assert_eq!(
1189 MionIdentityAnnouncement::try_from(packet),
1190 Err(NetworkParseError::FieldEncodedIncorrectly(
1191 "MionIdentityAnnouncement",
1192 "buff",
1193 "Must start with static message: `MULTI_I/O_NETWORK_BOARD` with a NUL Terminator",
1194 )),
1195 );
1196 }
1197
1198 {
1200 let mut buff = Vec::new();
1201 buff.push(u8::from(MionCommandByte::AnnounceYourselves));
1203 buff.extend_from_slice(ANNOUNCEMENT_MESSAGE.as_bytes());
1204 buff.push(0x1);
1206 let packet = Bytes::from(buff);
1207
1208 assert_eq!(
1209 MionIdentityAnnouncement::try_from(packet),
1210 Err(NetworkParseError::FieldEncodedIncorrectly(
1211 "MionIdentityAnnouncement",
1212 "buff",
1213 "Must start with static message: `MULTI_I/O_NETWORK_BOARD` with a NUL Terminator",
1214 )),
1215 );
1216 }
1217
1218 {
1220 let mut buff = Vec::new();
1221 buff.push(u8::from(MionCommandByte::AnnounceYourselves));
1223 buff.extend_from_slice(ANNOUNCEMENT_MESSAGE.as_bytes());
1224 buff.push(0x0);
1225 buff.extend_from_slice(DETAIL_FLAG_MESSAGE.as_bytes());
1226 buff.push(0x1);
1228 buff.push(0x2);
1229 let packet = Bytes::from(buff);
1230
1231 assert_eq!(
1232 MionIdentityAnnouncement::try_from(packet),
1233 Err(NetworkParseError::FieldEncodedIncorrectly(
1234 "MionIdentityAnnouncement",
1235 "buff",
1236 "Only the static string `enumV1` followed by two NUL Terminators is allowed after `MULTI_I/O_NETWORK_BOARD`.",
1237 )),
1238 );
1239 }
1240 }
1241
1242 #[test]
1243 pub fn conversion_boot_type() {
1244 for boot_type in vec![
1245 MIONBootType::NAND,
1246 MIONBootType::PCFS,
1247 MIONBootType::DUAL,
1248 MIONBootType::Unk(0),
1249 ] {
1250 assert_eq!(
1251 MIONBootType::from(u8::from(boot_type)),
1252 boot_type,
1253 "`MIONBootType` : {boot_type} was not converted successfully!"
1254 );
1255 }
1256 }
1257}