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