use std::io::Cursor;
use chia_sha2::Sha256;
use chia_traits::{Error, Result, Streamable};
#[repr(u8)]
#[derive(Hash, Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub enum NodeType {
FullNode = 1,
Harvester = 2,
Farmer = 3,
Timelord = 4,
Introducer = 5,
Wallet = 6,
DataLayer = 7,
}
impl NodeType {
pub const ALL: [Self; 7] = [
Self::FullNode,
Self::Harvester,
Self::Farmer,
Self::Timelord,
Self::Introducer,
Self::Wallet,
Self::DataLayer,
];
#[must_use]
pub fn to_byte(self) -> u8 {
self as u8
}
#[must_use]
pub fn from_byte(byte: u8) -> Option<Self> {
Self::ALL.into_iter().find(|role| role.to_byte() == byte)
}
}
impl TryFrom<u8> for NodeType {
type Error = UnknownNodeType;
fn try_from(byte: u8) -> std::result::Result<Self, UnknownNodeType> {
Self::from_byte(byte).ok_or(UnknownNodeType(byte))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("{0} is not a known node type")]
pub struct UnknownNodeType(pub u8);
impl Streamable for NodeType {
fn update_digest(&self, digest: &mut Sha256) {
digest.update([self.to_byte()]);
}
fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
out.push(self.to_byte());
Ok(())
}
fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
let byte = u8::parse::<TRUSTED>(input)?;
Self::from_byte(byte).ok_or(Error::InvalidEnum)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn discriminants_are_frozen_at_their_wire_values() {
assert_eq!(NodeType::FullNode.to_byte(), 1);
assert_eq!(NodeType::Harvester.to_byte(), 2);
assert_eq!(NodeType::Farmer.to_byte(), 3);
assert_eq!(NodeType::Timelord.to_byte(), 4);
assert_eq!(NodeType::Introducer.to_byte(), 5);
assert_eq!(NodeType::Wallet.to_byte(), 6);
assert_eq!(NodeType::DataLayer.to_byte(), 7);
}
#[test]
fn streams_as_exactly_one_byte_for_every_variant() {
for role in NodeType::ALL {
assert_eq!(role.to_bytes().expect("encode"), vec![role.to_byte()]);
}
}
#[test]
fn every_variant_round_trips_through_parse() {
for role in NodeType::ALL {
let decoded = NodeType::from_bytes(&role.to_bytes().expect("encode")).expect("decode");
assert_eq!(decoded, role);
}
}
#[test]
fn a_byte_naming_no_role_is_refused_rather_than_defaulted() {
for byte in 0..=u8::MAX {
let is_known = (1..=7).contains(&byte);
assert_eq!(
NodeType::from_byte(byte).is_some(),
is_known,
"byte {byte} disagreed with the known-role set"
);
assert_eq!(NodeType::from_bytes(&[byte]).is_ok(), is_known);
}
}
#[test]
fn try_from_reports_the_offending_byte() {
assert_eq!(NodeType::try_from(5), Ok(NodeType::Introducer));
assert_eq!(NodeType::try_from(0), Err(UnknownNodeType(0)));
assert_eq!(NodeType::try_from(8), Err(UnknownNodeType(8)));
assert_eq!(UnknownNodeType(9).to_string(), "9 is not a known node type");
}
#[test]
fn all_covers_the_whole_enum() {
assert_eq!(NodeType::ALL.len(), 7);
let mut bytes: Vec<u8> = NodeType::ALL.iter().map(|r| r.to_byte()).collect();
bytes.sort_unstable();
bytes.dedup();
assert_eq!(bytes, vec![1, 2, 3, 4, 5, 6, 7]);
}
}