use crate::{
SV2_JOB_DECLARATION_PROTOCOL_DISCRIMINANT, SV2_MINING_PROTOCOL_DISCRIMINANT,
SV2_TEMPLATE_DISTRIBUTION_PROTOCOL_DISCRIMINANT,
};
use alloc::{fmt, vec::Vec};
use binary_sv2::{
self,
decodable::{DecodableField, FieldMarker},
Deserialize, GetSize, Serialize, Str0255,
};
use core::convert::{TryFrom, TryInto};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct SetupConnection<'decoder> {
pub protocol: Protocol,
pub min_version: u16,
pub max_version: u16,
pub flags: u32,
pub endpoint_host: Str0255<'decoder>,
pub endpoint_port: u16,
pub vendor: Str0255<'decoder>,
pub hardware_version: Str0255<'decoder>,
pub firmware: Str0255<'decoder>,
pub device_id: Str0255<'decoder>,
}
impl fmt::Display for SetupConnection<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SetupConnection(protocol: {}, min_version: {}, max_version: {}, flags: 0x{:08x}, endpoint_host: {}, endpoint_port: {}, vendor: {}, hardware_version: {}, firmware: {}, device_id: {})",
self.protocol as u8,
self.min_version,
self.max_version,
self.flags,
self.endpoint_host.as_utf8_or_hex(),
self.endpoint_port,
self.vendor.as_utf8_or_hex(),
self.hardware_version.as_utf8_or_hex(),
self.firmware.as_utf8_or_hex(),
self.device_id.as_utf8_or_hex()
)
}
}
impl SetupConnection<'_> {
pub fn set_requires_standard_job(&mut self) {
self.flags |= 0b_0000_0000_0000_0000_0000_0000_0000_0001;
}
pub fn allow_full_template_mode(&mut self) {
self.flags |= 0b_0000_0000_0000_0000_0000_0000_0000_0001;
}
pub fn check_flags(protocol: Protocol, available_flags: u32, required_flags: u32) -> bool {
match protocol {
Protocol::MiningProtocol => {
let available = available_flags.reverse_bits();
let required_flags = required_flags.reverse_bits();
let requires_work_selection_passed = required_flags >> 30 > 0;
let requires_version_rolling_passed = required_flags >> 29 > 0;
let requires_work_selection_self = available >> 30 > 0;
let requires_version_rolling_self = available >> 29 > 0;
let work_selection =
!requires_work_selection_self || requires_work_selection_passed;
let version_rolling =
!requires_version_rolling_self || requires_version_rolling_passed;
work_selection && version_rolling
}
Protocol::JobDeclarationProtocol => {
let available = available_flags.reverse_bits();
let required = required_flags.reverse_bits();
let allow_full_teamplate_mode_passed = (required >> 31) & 1 > 0;
let full_template_mode_required_self = (available >> 31) & 1 > 0;
match (
full_template_mode_required_self,
allow_full_teamplate_mode_passed,
) {
(true, true) => true,
(true, false) => false,
(false, true) => true,
(false, false) => true,
}
}
Protocol::TemplateDistributionProtocol => {
false
}
}
}
pub fn get_version(&self, min_version: u16, max_version: u16) -> Option<u16> {
if self.min_version > max_version || min_version > self.max_version {
None
} else {
Some(self.max_version.min(max_version))
}
}
pub fn requires_standard_job(&self) -> bool {
has_requires_std_job(self.flags)
}
}
pub fn has_requires_std_job(flags: u32) -> bool {
let flags = flags.reverse_bits();
let flag = flags >> 31;
flag != 0
}
pub fn has_version_rolling(flags: u32) -> bool {
let flags = flags.reverse_bits();
let flags = flags << 2;
let flag = flags >> 31;
flag != 0
}
pub fn has_work_selection(flags: u32) -> bool {
let flags = flags.reverse_bits();
let flags = flags << 1;
let flag = flags >> 31;
flag != 0
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy)]
pub struct SetupConnectionSuccess {
pub used_version: u16,
pub flags: u32,
}
impl fmt::Display for SetupConnectionSuccess {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SetupConnectionSuccess(used_version: {}, flags: 0x{:08x})",
self.used_version, self.flags
)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct SetupConnectionError<'decoder> {
pub flags: u32,
pub error_code: Str0255<'decoder>,
}
impl fmt::Display for SetupConnectionError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SetupConnectionError(flags: 0x{:08x}, error_code: {})",
self.flags,
self.error_code.as_utf8_or_hex()
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
#[allow(clippy::enum_variant_names)]
pub enum Protocol {
MiningProtocol = SV2_MINING_PROTOCOL_DISCRIMINANT,
JobDeclarationProtocol = SV2_JOB_DECLARATION_PROTOCOL_DISCRIMINANT,
TemplateDistributionProtocol = SV2_TEMPLATE_DISTRIBUTION_PROTOCOL_DISCRIMINANT,
}
impl From<Protocol> for binary_sv2::encodable::EncodableField<'_> {
fn from(v: Protocol) -> Self {
let val = v as u8;
val.into()
}
}
impl<'decoder> binary_sv2::Decodable<'decoder> for Protocol {
fn get_structure(
_: &[u8],
) -> core::result::Result<alloc::vec::Vec<FieldMarker>, binary_sv2::Error> {
let field: FieldMarker = (0_u8).into();
Ok(alloc::vec![field])
}
fn from_decoded_fields(
mut v: alloc::vec::Vec<DecodableField<'decoder>>,
) -> core::result::Result<Self, binary_sv2::Error> {
let val = v.pop().ok_or(binary_sv2::Error::NoDecodableFieldPassed)?;
let val: u8 = val.try_into()?;
val.try_into()
.map_err(|_| binary_sv2::Error::ValueIsNotAValidProtocol(val))
}
}
impl TryFrom<u8> for Protocol {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
SV2_MINING_PROTOCOL_DISCRIMINANT => Ok(Protocol::MiningProtocol),
SV2_JOB_DECLARATION_PROTOCOL_DISCRIMINANT => Ok(Protocol::JobDeclarationProtocol),
SV2_TEMPLATE_DISTRIBUTION_PROTOCOL_DISCRIMINANT => {
Ok(Protocol::TemplateDistributionProtocol)
}
_ => Err(()),
}
}
}
impl GetSize for Protocol {
fn get_size(&self) -> usize {
1
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::alloc::string::ToString;
use alloc::vec;
use core::convert::TryInto;
#[test]
fn test_parsing_an_empty_bytes_vec() {
let mut data = vec![];
let result = SetupConnection::from_bytes(&mut data);
assert!(result.is_err());
match result {
Err(binary_sv2::Error::OutOfBound) => (),
Err(e) => panic!("Expected OutOfBounds error, got {:?}", e),
Ok(_) => panic!("Expected error, got Ok"),
}
}
#[test]
fn test_check_flag() {
let protocol = crate::Protocol::MiningProtocol;
let flag_available = 0b_0000_0000_0000_0000_0000_0000_0000_0000;
let flag_required = 0b_0000_0000_0000_0000_0000_0000_0000_0001;
assert!(SetupConnection::check_flags(
protocol,
flag_available,
flag_required
));
let protocol = crate::Protocol::JobDeclarationProtocol;
let available_flags = 0b_1000_0000_0000_0000_0000_0000_0000_0000;
let required_flags = 0b_1000_0000_0000_0000_0000_0000_0000_0000;
assert!(SetupConnection::check_flags(
protocol,
available_flags,
required_flags
));
}
#[test]
fn test_has_requires_std_job() {
let flags = 0b_0000_0000_0000_0000_0000_0000_0000_0001;
assert!(has_requires_std_job(flags));
let flags = 0b_0000_0000_0000_0000_0000_0000_0000_0010;
assert!(!has_requires_std_job(flags));
}
#[test]
fn test_has_version_rolling() {
let flags = 0b_0000_0000_0000_0000_0000_0000_0000_0100;
assert!(has_version_rolling(flags));
let flags = 0b_0000_0000_0000_0000_0000_0000_0000_0001;
assert!(!has_version_rolling(flags));
}
#[test]
fn test_has_work_selection() {
let flags = 0b_0000_0000_0000_0000_0000_0000_0000_0010;
assert!(has_work_selection(flags));
let flags = 0b_0000_0000_0000_0000_0000_0000_0000_0001;
assert!(!has_work_selection(flags));
}
fn create_setup_connection() -> SetupConnection<'static> {
SetupConnection {
protocol: Protocol::MiningProtocol,
min_version: 1,
max_version: 4,
flags: 0,
endpoint_host: "0.0.0.0".to_string().into_bytes().try_into().unwrap(),
endpoint_port: 0,
vendor: "vendor".to_string().into_bytes().try_into().unwrap(),
hardware_version: "hw_version".to_string().into_bytes().try_into().unwrap(),
firmware: "firmware".to_string().into_bytes().try_into().unwrap(),
device_id: "device_id".to_string().into_bytes().try_into().unwrap(),
}
}
#[test]
fn test_get_version() {
let setup_conn = create_setup_connection();
assert_eq!(setup_conn.get_version(1, 5).unwrap(), 4);
assert_eq!(setup_conn.get_version(6, 6), None);
}
#[test]
fn test_set_requires_std_job() {
let mut setup_conn = create_setup_connection();
assert!(!setup_conn.requires_standard_job());
setup_conn.set_requires_standard_job();
assert!(setup_conn.requires_standard_job());
}
}