diff --git a/src/rdm/mod.rs b/src/rdm/mod.rs index eafb581..0d54110 100644 --- a/src/rdm/mod.rs +++ b/src/rdm/mod.rs @@ -1,6 +1,8 @@ //! Data types and functionality for encoding and decoding RDM packets pub mod error; +#[macro_use] +pub mod utils; pub mod parameter; pub mod request; pub mod response; diff --git a/src/rdm/parameter.rs b/src/rdm/parameter.rs index 08e6190..62ded7b 100644 --- a/src/rdm/parameter.rs +++ b/src/rdm/parameter.rs @@ -35,7 +35,7 @@ pub fn decode_string_bytes(bytes: &[u8]) -> Result, Rd #[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq)] pub enum ParameterId { - // E1.20 + // E1.20 2025 Table A-3 DiscUniqueBranch, DiscMute, DiscUnMute, @@ -88,7 +88,7 @@ pub enum ParameterId { SelfTestDescription, CapturePreset, PresetPlayback, - // E1.37-1 + // E1.37-1 2012r2022 Table A-1 DmxBlockAddress, DmxFailMode, DmxStartupMode, @@ -110,7 +110,7 @@ pub enum ParameterId { PresetStatus, PresetMergeMode, PowerOnSelfTest, - // E1.37-2 + // E1.37-2 2015r2021 Table A-1 ListInterfaces, InterfaceLabel, InterfaceHardwareAddressType1, @@ -125,7 +125,7 @@ pub enum ParameterId { DnsIpV4NameServer, DnsHostName, DnsDomainName, - // E1.37-7 + // E1.37-7 2019 Table A-1 EndpointList, EndpointListChange, IdentifyEndpoint, @@ -142,7 +142,7 @@ pub enum ParameterId { BindingControlFields, BackgroundQueuedStatusPolicy, BackgroundQueuedStatusPolicyDescription, - // E1.33 + // E1.33 2019 Table A-15 ComponentScope, SearchDomain, TcpCommsStatus, @@ -395,8 +395,8 @@ impl From for u16 { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ProtocolVersion { - major: u8, - minor: u8, + pub major: u8, + pub minor: u8, } impl ProtocolVersion { @@ -417,6 +417,7 @@ impl fmt::Display for ProtocolVersion { } } +// E1.20 2025 Table A-6 #[derive(Copy, Clone, Debug, PartialEq)] pub enum ProductDetail { NotDeclared, @@ -451,7 +452,7 @@ pub enum ProductDetail { Bubble, FlamePropane, FlameOther, - OlefactoryStimulator, + OlfactoryStimulator, Snow, WaterJet, Wind, @@ -466,7 +467,7 @@ pub enum ProductDetail { HfHvNeonBallast, HfHvEl, MhrBallast, - BitangleModulation, + BitAngleModulation, FrequencyModulation, HighFrequency12V, RelayMechanical, @@ -538,7 +539,7 @@ impl From for ProductDetail { 0x0305 => Self::Bubble, 0x0306 => Self::FlamePropane, 0x0307 => Self::FlameOther, - 0x0308 => Self::OlefactoryStimulator, + 0x0308 => Self::OlfactoryStimulator, 0x0309 => Self::Snow, 0x030a => Self::WaterJet, 0x030b => Self::Wind, @@ -553,7 +554,7 @@ impl From for ProductDetail { 0x0406 => Self::HfHvNeonBallast, 0x0407 => Self::HfHvEl, 0x0408 => Self::MhrBallast, - 0x0409 => Self::BitangleModulation, + 0x0409 => Self::BitAngleModulation, 0x040a => Self::FrequencyModulation, 0x040b => Self::HighFrequency12V, 0x040c => Self::RelayMechanical, @@ -627,7 +628,7 @@ impl From for u16 { ProductDetail::Bubble => 0x0305, ProductDetail::FlamePropane => 0x0306, ProductDetail::FlameOther => 0x0307, - ProductDetail::OlefactoryStimulator => 0x0308, + ProductDetail::OlfactoryStimulator => 0x0308, ProductDetail::Snow => 0x0309, ProductDetail::WaterJet => 0x030a, ProductDetail::Wind => 0x030b, @@ -642,7 +643,7 @@ impl From for u16 { ProductDetail::HfHvNeonBallast => 0x0406, ProductDetail::HfHvEl => 0x0407, ProductDetail::MhrBallast => 0x0408, - ProductDetail::BitangleModulation => 0x0409, + ProductDetail::BitAngleModulation => 0x0409, ProductDetail::FrequencyModulation => 0x040a, ProductDetail::HighFrequency12V => 0x040b, ProductDetail::RelayMechanical => 0x040c, @@ -681,6 +682,7 @@ impl From for u16 { } } +// E1.20 2025 Table A-16 #[derive(Copy, Clone, Debug, PartialEq)] pub enum ImplementedCommandClass { Get = 0x01, @@ -701,6 +703,7 @@ impl TryFrom for ImplementedCommandClass { } } +// E1.20 2025 Table A-15 #[derive(Copy, Clone, Debug, PartialEq)] pub enum ParameterDataType { NotDefined, @@ -752,12 +755,8 @@ impl From for u8 { } } +// E1.20 2025 Section 10.4.2 pub enum ConvertedParameterValue { - BitField(u8), - Ascii( - #[cfg(feature = "alloc")] String, - #[cfg(not(feature = "alloc"))] String<4>, - ), UnsignedByte(u8), SignedByte(i8), UnsignedWord(u16), @@ -790,10 +789,6 @@ impl ParameterDescription { value: [u8; 4], ) -> Result { match parameter_data_type { - ParameterDataType::BitField => Ok(ConvertedParameterValue::BitField(value[3])), - ParameterDataType::Ascii => { - Ok(ConvertedParameterValue::Ascii(decode_string_bytes(&value)?)) - } ParameterDataType::UnsignedByte => Ok(ConvertedParameterValue::UnsignedByte(value[3])), ParameterDataType::SignedByte => { Ok(ConvertedParameterValue::SignedByte(value[3] as i8)) @@ -814,6 +809,7 @@ impl ParameterDescription { ParameterDataType::SignedDWord => Ok(ConvertedParameterValue::SignedDWord( i32::from_be_bytes(value), )), + ParameterDataType::BitField | ParameterDataType::Ascii | ParameterDataType::NotDefined | ParameterDataType::ManufacturerSpecific(..) => { Ok(ConvertedParameterValue::Raw(value)) } @@ -831,6 +827,7 @@ impl ParameterDescription { } } +// E1.20 2025 Table A-4 #[derive(Copy, Clone, Debug, PartialEq)] pub enum StatusType { None = 0x00, @@ -861,7 +858,7 @@ impl TryFrom for StatusType { } } -// Product Categories - Page 105 RDM Spec +// E1.20 2025 Table A-5 #[derive(Copy, Clone, Debug, PartialEq)] pub enum ProductCategory { NotDeclared, @@ -876,7 +873,7 @@ pub enum ProductCategory { FixtureAccessoryMirror, FixtureAccessoryEffect, FixtureAccessoryBeam, - AccessoryOther, + FixtureAccessoryOther, Projector, ProjectorFixed, ProjectorMovingYoke, @@ -944,7 +941,7 @@ impl From for ProductCategory { 0x0203 => Self::FixtureAccessoryMirror, 0x0204 => Self::FixtureAccessoryEffect, 0x0205 => Self::FixtureAccessoryBeam, - 0x02ff => Self::AccessoryOther, + 0x02ff => Self::FixtureAccessoryOther, 0x0300 => Self::Projector, 0x0301 => Self::ProjectorFixed, 0x0302 => Self::ProjectorMovingYoke, @@ -1014,7 +1011,7 @@ impl From for u16 { ProductCategory::FixtureAccessoryMirror => 0x0203, ProductCategory::FixtureAccessoryEffect => 0x0204, ProductCategory::FixtureAccessoryBeam => 0x0205, - ProductCategory::AccessoryOther => 0x02ff, + ProductCategory::FixtureAccessoryOther => 0x02ff, ProductCategory::Projector => 0x0300, ProductCategory::ProjectorFixed => 0x0301, ProductCategory::ProjectorMovingYoke => 0x0302, @@ -1069,6 +1066,7 @@ impl From for u16 { } } +// E1.20 2025 Table A-8 #[derive(Copy, Clone, Debug, PartialEq)] pub enum LampState { LampOff, @@ -1111,6 +1109,7 @@ impl From for u8 { } } +// E1.20 2025 Table A-9 #[derive(Copy, Clone, Debug, PartialEq)] pub enum LampOnMode { OffMode, @@ -1147,6 +1146,7 @@ impl From for u8 { } } +// E1.20 2025 Table A-11 #[derive(Copy, Clone, Debug, PartialEq)] pub enum PowerState { FullOff = 0x00, @@ -1163,12 +1163,13 @@ impl TryFrom for PowerState { 0x00 => Ok(Self::FullOff), 0x01 => Ok(Self::Shutdown), 0x02 => Ok(Self::Standby), - 0x03 => Ok(Self::Normal), + 0xff => Ok(Self::Normal), _ => Err(RdmError::InvalidPowerState(value)), } } } + #[derive(Copy, Clone, Debug, PartialEq)] pub enum OnOffStates { Off = 0x00, @@ -1187,6 +1188,7 @@ impl TryFrom for OnOffStates { } } +// E1.20 2025 Section 10.9.1 #[derive(Copy, Clone, Debug, PartialEq)] pub enum DisplayInvertMode { Off = 0x00, @@ -1207,6 +1209,7 @@ impl TryFrom for DisplayInvertMode { } } +// E1.20 2025 Section 10.11.2 #[derive(Copy, Clone, Debug, PartialEq)] pub enum ResetDeviceMode { Warm = 0x01, @@ -1225,6 +1228,7 @@ impl TryFrom for ResetDeviceMode { } } +// E1.20 2025 Table A-10 #[derive(Copy, Clone, Debug, PartialEq)] pub enum SelfTest { Off, @@ -1252,6 +1256,7 @@ impl From for u8 { } } +// E1.20 2025 Table A-7 #[derive(Copy, Clone, Debug, PartialEq)] pub enum PresetPlaybackMode { Off, @@ -1286,6 +1291,7 @@ pub struct FadeTimes { pub wait_time: u16, } +// E1.20 2025 Table B-2 #[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq)] pub enum StatusMessageIdDefinition { @@ -1589,8 +1595,9 @@ impl StatusMessage { } } -#[derive(Copy, Clone, Debug, PartialEq)] +// E1.20 2025 Table C-1 #[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum SlotType { Primary, SecondaryFine, @@ -1655,6 +1662,7 @@ impl SlotInfo { } } +// E1.20 2025 Table C-2 #[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq)] pub enum SlotIdDefinition { @@ -1833,8 +1841,9 @@ impl DefaultSlotValue { } } -#[derive(Copy, Clone, Debug, PartialEq)] +// E1.20 2025 Table A-12 #[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum SensorType { Temperature, Voltage, @@ -1959,8 +1968,9 @@ impl From for u8 { } } -#[derive(Copy, Clone, Debug, PartialEq)] +// E1.20 2025 Table A-13 #[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum SensorUnit { None, Centigrade, @@ -2071,6 +2081,7 @@ impl From for u8 { } } +// E1.20 2025 Table A-14 #[derive(Copy, Clone, Debug, PartialEq)] pub enum SensorUnitPrefix { None = 0x00, @@ -2172,6 +2183,7 @@ impl SensorValue { } } +// E1.31-1 2012r2022 Section 3.2 #[derive(Copy, Clone, Debug, PartialEq)] pub enum IdentifyMode { Quiet = 0x00, @@ -2190,6 +2202,7 @@ impl TryFrom for IdentifyMode { } } +// E1.37-1 2012r2022 Table A-2 #[derive(Copy, Clone, Debug, PartialEq)] pub enum PresetProgrammed { NotProgrammed = 0x00, @@ -2210,6 +2223,7 @@ impl TryFrom for PresetProgrammed { } } +// E1.37-1 2012r2022 Table A-3 #[derive(Copy, Clone, Debug, PartialEq)] pub enum MergeMode { Default = 0x00, @@ -2249,6 +2263,7 @@ impl TryFrom for PinCode { } } +// E1.37-1 2012r2022 Section 5.2 #[derive(Copy, Clone, Debug, PartialEq)] pub enum SupportedTimes { NotSupported, @@ -2273,6 +2288,7 @@ impl From for u16 { } } +// E1.37-1 2012r2022 Section 3.4, 3.5 #[derive(Copy, Clone, Debug, PartialEq)] pub enum TimeMode { Infinite, @@ -2297,6 +2313,7 @@ impl From for u16 { } } +// E1.37-2 2015r2021 Table A-3 #[derive(Copy, Clone, Debug, PartialEq)] pub enum DhcpMode { Inactive = 0x00, @@ -2417,6 +2434,7 @@ impl From for u128 { } } +// E1.37-2 2015r2021 Section 4.11 #[derive(Copy, Clone, Debug, PartialEq)] pub enum Ipv4Route { NoDefault, @@ -2619,6 +2637,7 @@ pub struct NetworkInterface { pub hardware_type: HardwareType, } +// E1.33 2019 Table A-17 #[derive(Copy, Clone, Debug, PartialEq)] pub enum StaticConfigType { NoStaticConfig = 0x00, @@ -2639,6 +2658,7 @@ impl TryFrom for StaticConfigType { } } +// E1.33 2019 Table A-18 #[derive(Copy, Clone, Debug, PartialEq)] pub enum BrokerState { Disabled = 0x00, @@ -2659,6 +2679,7 @@ impl TryFrom for BrokerState { } } +// E1.37-7 2019 Table A-2 #[derive(Copy, Clone, Debug, PartialEq)] pub enum DiscoveryState { Incomplete, @@ -2695,6 +2716,7 @@ impl From for u8 { } } +// E1.37-7 2019 Table A-3 #[derive(Copy, Clone, Debug, PartialEq)] pub enum DiscoveryCountStatus { Incomplete, @@ -2722,6 +2744,7 @@ impl From for u16 { } } +// E1.37-7 2019 Table A-4 #[derive(Copy, Clone, Debug, PartialEq)] pub enum EndpointMode { Disabled = 0x00, // Does not pass any DMX512-A/RDM traffic on a local RDM Command Port or DMX512-A Data Link @@ -2742,6 +2765,7 @@ impl TryFrom for EndpointMode { } } +// E1.33 2019 #[derive(Copy, Clone, Debug, PartialEq)] pub enum EndpointId { Null, @@ -2772,6 +2796,7 @@ impl From for u16 { } } +// E1.37-7 2019 Table A-5 #[derive(Copy, Clone, Debug, PartialEq)] pub enum EndpointType { Virtual = 0x00, @@ -2790,6 +2815,31 @@ impl TryFrom for EndpointType { } } +// E1.37-5 2024 Section 4.1 +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum IdentifyTimeout { + Disabled, + Seconds(u16) +} + +impl From for IdentifyTimeout { + fn from(value: u16) -> Self { + match value { + 0 => Self::Disabled, + _ => Self::Seconds(value) + } + } +} + +impl From for u16 { + fn from(value: IdentifyTimeout) -> Self { + match value { + IdentifyTimeout::Disabled => 0, + IdentifyTimeout::Seconds(s) => s, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/rdm/request.rs b/src/rdm/request.rs index 476a1f5..2e13cc8 100644 --- a/src/rdm/request.rs +++ b/src/rdm/request.rs @@ -44,7 +44,7 @@ use super::{ error::RdmError, parameter::{ decode_string_bytes, BrokerState, DiscoveryState, DisplayInvertMode, EndpointId, - EndpointMode, FadeTimes, Ipv4Address, Ipv4Route, Ipv6Address, LampOnMode, LampState, + EndpointMode, FadeTimes, IdentifyMode, Ipv4Address, Ipv4Route, Ipv6Address, LampOnMode, LampState, MergeMode, ParameterId, PinCode, PowerState, PresetPlaybackMode, ResetDeviceMode, SelfTest, StaticConfigType, StatusType, TimeMode, }, @@ -64,6 +64,8 @@ pub enum RequestParameter { lower_bound_uid: DeviceUID, upper_bound_uid: DeviceUID, }, + GetProxiedDeviceCount, + GetProxiedDevices, GetCommsStatus, SetCommsStatus, GetQueuedMessage { @@ -103,7 +105,7 @@ pub enum RequestParameter { #[cfg(feature = "alloc")] language: String, #[cfg(not(feature = "alloc"))] - language: String<32>, + language: String<2>, }, GetSoftwareVersionLabel, GetBootSoftwareVersionId, @@ -204,19 +206,23 @@ pub enum RequestParameter { SetPerformSelfTest { self_test_id: SelfTest, }, + GetSelfTestDescription { + self_test_id: SelfTest, + }, SetCapturePreset { scene_id: u16, fade_times: Option, }, - GetSelfTestDescription { - self_test_id: SelfTest, - }, GetPresetPlayback, SetPresetPlayback { mode: PresetPlaybackMode, level: u8, }, // E1.37-1 + GetIdentifyMode, + SetIdentifyMode { + identify_mode: IdentifyMode + }, GetDmxBlockAddress, SetDmxBlockAddress { dmx_block_address: u16, @@ -235,6 +241,27 @@ pub enum RequestParameter { hold_time: TimeMode, level: u8, }, + GetPowerOnSelfTest, + SetPowerOnSelfTest { + perform_test: bool + }, + GetLockState, + SetLockState { + pin_code: PinCode, + lock_state: u8, + }, + GetLockStateDescription { + lock_state: u8 + }, + GetLockPin, + SetLockPin { + new_pin_code: PinCode, + current_pin_code: PinCode, + }, + GetBurnIn, + SetBurnIn { + hours: u8, + }, GetDimmerInfo, GetMinimumLevel, SetMinimumLevel { @@ -267,37 +294,10 @@ pub enum RequestParameter { GetModulationFrequencyDescription { modulation_frequency_id: u8, }, - GetPowerOnSelfTest, - SetPowerOnSelfTest { - self_test_id: SelfTest, - }, - GetLockState, - SetLockState { - pin_code: PinCode, - lock_state: bool, - }, - GetLockStateDescription, - GetLockPin, - SetLockPin { - new_pin_code: PinCode, - current_pin_code: PinCode, - }, - GetBurnIn, - SetBurnIn { - hours: u8, - }, - GetIdentifyMode, - SetIdentifyMode { - identify_mode: u8, - }, GetPresetInfo, GetPresetStatus { scene_id: u16, }, - GetPresetMergeMode, - SetPresetMergeMode { - merge_mode: MergeMode, - }, SetPresetStatus { scene_id: u16, up_fade_time: u16, @@ -305,6 +305,10 @@ pub enum RequestParameter { wait_time: u16, clear_preset: bool, }, + GetPresetMergeMode, + SetPresetMergeMode { + merge_mode: MergeMode, + }, // E1.37-2 GetListInterfaces, GetInterfaceLabel { @@ -511,7 +515,9 @@ impl RequestParameter { CommandClass::DiscoveryCommand } // E1.20 - Self::GetCommsStatus + Self::GetProxiedDeviceCount + | Self::GetProxiedDevices + | Self::GetCommsStatus | Self::GetQueuedMessage { .. } | Self::GetStatusMessages { .. } | Self::GetStatusIdDescription { .. } @@ -555,9 +561,15 @@ impl RequestParameter { | Self::GetSelfTestDescription { .. } | Self::GetPresetPlayback // E1.37-1 + | Self::GetIdentifyMode | Self::GetDmxBlockAddress | Self::GetDmxFailMode | Self::GetDmxStartupMode + | Self::GetPowerOnSelfTest + | Self::GetLockState + | Self::GetLockStateDescription { .. } + | Self::GetLockPin + | Self::GetBurnIn | Self::GetDimmerInfo | Self::GetMinimumLevel | Self::GetMaximumLevel @@ -567,12 +579,6 @@ impl RequestParameter { | Self::GetOutputResponseTimeDescription { .. } | Self::GetModulationFrequency | Self::GetModulationFrequencyDescription { .. } - | Self::GetPowerOnSelfTest - | Self::GetLockState - | Self::GetLockStateDescription - | Self::GetLockPin - | Self::GetBurnIn - | Self::GetIdentifyMode | Self::GetPresetInfo | Self::GetPresetStatus { .. } | Self::GetPresetMergeMode @@ -641,21 +647,21 @@ impl RequestParameter { | Self::SetCapturePreset { .. } | Self::SetPresetPlayback { .. } // E1.37-1 + | Self::SetIdentifyMode { .. } | Self::SetDmxBlockAddress { .. } | Self::SetDmxFailMode { .. } | Self::SetDmxStartupMode { .. } + | Self::SetPowerOnSelfTest { .. } + | Self::SetLockState { .. } + | Self::SetLockPin { .. } + | Self::SetBurnIn { .. } | Self::SetMinimumLevel { .. } | Self::SetMaximumLevel { .. } | Self::SetCurve { .. } | Self::SetOutputResponseTime { .. } | Self::SetModulationFrequency { .. } - | Self::SetPowerOnSelfTest { .. } - | Self::SetLockState { .. } - | Self::SetLockPin { .. } - | Self::SetBurnIn { .. } - | Self::SetIdentifyMode { .. } - | Self::SetPresetMergeMode { .. } | Self::SetPresetStatus { .. } + | Self::SetPresetMergeMode { .. } // E1.37-2 | Self::SetIpV4DhcpMode { .. } | Self::SetIpV4ZeroConfMode { .. } @@ -694,6 +700,8 @@ impl RequestParameter { Self::DiscMute => ParameterId::DiscMute, Self::DiscUnMute => ParameterId::DiscUnMute, Self::DiscUniqueBranch { .. } => ParameterId::DiscUniqueBranch, + Self::GetProxiedDeviceCount => ParameterId::ProxiedDeviceCount, + Self::GetProxiedDevices => ParameterId::ProxiedDevices, Self::GetCommsStatus | Self::SetCommsStatus => ParameterId::CommsStatus, Self::GetQueuedMessage { .. } => ParameterId::QueuedMessage, Self::GetStatusMessages { .. } => ParameterId::StatusMessages, @@ -747,15 +755,23 @@ impl RequestParameter { Self::GetPerformSelfTest | Self::SetPerformSelfTest { .. } => { ParameterId::PerformSelfTest } - Self::SetCapturePreset { .. } => ParameterId::CapturePreset, Self::GetSelfTestDescription { .. } => ParameterId::SelfTestDescription, + Self::SetCapturePreset { .. } => ParameterId::CapturePreset, Self::GetPresetPlayback | Self::SetPresetPlayback { .. } => ParameterId::PresetPlayback, // E1.37-1 + Self::GetIdentifyMode | Self::SetIdentifyMode { .. } => ParameterId::IdentifyMode, Self::GetDmxBlockAddress | Self::SetDmxBlockAddress { .. } => { ParameterId::DmxBlockAddress } Self::GetDmxFailMode | Self::SetDmxFailMode { .. } => ParameterId::DmxFailMode, Self::GetDmxStartupMode | Self::SetDmxStartupMode { .. } => ParameterId::DmxStartupMode, + Self::GetPowerOnSelfTest | Self::SetPowerOnSelfTest { .. } => { + ParameterId::PowerOnSelfTest + } + Self::GetLockState | Self::SetLockState { .. } => ParameterId::LockState, + Self::GetLockStateDescription { .. } => ParameterId::LockStateDescription, + Self::GetLockPin | Self::SetLockPin { .. } => ParameterId::LockPin, + Self::GetBurnIn | Self::SetBurnIn { .. } => ParameterId::BurnIn, Self::GetDimmerInfo => ParameterId::DimmerInfo, Self::GetMinimumLevel | Self::SetMinimumLevel { .. } => ParameterId::MinimumLevel, Self::GetMaximumLevel | Self::SetMaximumLevel { .. } => ParameterId::MaximumLevel, @@ -773,14 +789,6 @@ impl RequestParameter { Self::GetModulationFrequencyDescription { .. } => { ParameterId::ModulationFrequencyDescription } - Self::GetPowerOnSelfTest | Self::SetPowerOnSelfTest { .. } => { - ParameterId::PowerOnSelfTest - } - Self::GetLockState | Self::SetLockState { .. } => ParameterId::LockState, - Self::GetLockStateDescription => ParameterId::LockStateDescription, - Self::GetLockPin | Self::SetLockPin { .. } => ParameterId::LockPin, - Self::GetBurnIn | Self::SetBurnIn { .. } => ParameterId::BurnIn, - Self::GetIdentifyMode | Self::SetIdentifyMode { .. } => ParameterId::IdentifyMode, Self::GetPresetInfo => ParameterId::PresetInfo, Self::GetPresetStatus { .. } | Self::SetPresetStatus { .. } => { ParameterId::PresetStatus @@ -890,6 +898,8 @@ impl RequestParameter { buf.extend(upper_bound_uid.manufacturer_id.to_be_bytes()); buf.extend(upper_bound_uid.device_id.to_be_bytes()); } + Self::GetProxiedDeviceCount => {} + Self::GetProxiedDevices => {} Self::GetCommsStatus => {} Self::SetCommsStatus => {} Self::GetQueuedMessage { status_type } => { @@ -1202,6 +1212,15 @@ impl RequestParameter { #[cfg(not(feature = "alloc"))] buf.push((*self_test_id).into()).unwrap(); } + Self::GetSelfTestDescription { self_test_id } => { + #[cfg(feature = "alloc")] + buf.reserve(0x01); + + #[cfg(feature = "alloc")] + buf.push((*self_test_id).into()); + #[cfg(not(feature = "alloc"))] + buf.push((*self_test_id).into()).unwrap(); + } Self::SetCapturePreset { scene_id, fade_times, @@ -1217,15 +1236,6 @@ impl RequestParameter { buf.extend((fade_times.wait_time).to_be_bytes()); } } - Self::GetSelfTestDescription { self_test_id } => { - #[cfg(feature = "alloc")] - buf.reserve(0x01); - - #[cfg(feature = "alloc")] - buf.push((*self_test_id).into()); - #[cfg(not(feature = "alloc"))] - buf.push((*self_test_id).into()).unwrap(); - } Self::GetPresetPlayback => {} Self::SetPresetPlayback { mode, level } => { #[cfg(feature = "alloc")] @@ -1239,6 +1249,16 @@ impl RequestParameter { buf.push(*level).unwrap(); } // E1.37-1 + Self::GetIdentifyMode => {} + Self::SetIdentifyMode { identify_mode } => { + #[cfg(feature = "alloc")] + buf.reserve(0x01); + + #[cfg(feature = "alloc")] + buf.push(*identify_mode as u8); + #[cfg(not(feature = "alloc"))] + buf.push(*identify_mode as u8).unwrap(); + } Self::GetDmxBlockAddress => {} Self::SetDmxBlockAddress { dmx_block_address } => { #[cfg(feature = "alloc")] @@ -1284,6 +1304,61 @@ impl RequestParameter { #[cfg(not(feature = "alloc"))] buf.push(*level).unwrap(); } + Self::GetPowerOnSelfTest => {} + Self::SetPowerOnSelfTest { perform_test } => { + #[cfg(feature = "alloc")] + buf.reserve(0x01); + + #[cfg(feature = "alloc")] + buf.push(*perform_test as u8); + #[cfg(not(feature = "alloc"))] + buf.push(*perform_test as u8).unwrap(); + } + Self::GetLockState => {} + Self::SetLockState { + pin_code, + lock_state, + } => { + #[cfg(feature = "alloc")] + buf.reserve(0x03); + + buf.extend((pin_code.0).to_be_bytes()); + + #[cfg(feature = "alloc")] + buf.push(*lock_state as u8); + #[cfg(not(feature = "alloc"))] + buf.push(*lock_state as u8).unwrap(); + } + Self::GetLockStateDescription { lock_state } => { + #[cfg(feature = "alloc")] + buf.reserve(0x01); + + #[cfg(feature = "alloc")] + buf.push(*lock_state as u8); + #[cfg(not(feature = "alloc"))] + buf.push(*lock_state as u8).unwrap(); + } + Self::GetLockPin => {} + Self::SetLockPin { + new_pin_code, + current_pin_code, + } => { + #[cfg(feature = "alloc")] + buf.reserve(0x04); + + buf.extend((new_pin_code.0).to_be_bytes()); + buf.extend((current_pin_code.0).to_be_bytes()); + } + Self::GetBurnIn => {} + Self::SetBurnIn { hours } => { + #[cfg(feature = "alloc")] + buf.reserve(0x01); + + #[cfg(feature = "alloc")] + buf.push(*hours); + #[cfg(not(feature = "alloc"))] + buf.push(*hours).unwrap(); + } Self::GetDimmerInfo => {} Self::GetMinimumLevel => {} Self::SetMinimumLevel { @@ -1374,63 +1449,6 @@ impl RequestParameter { #[cfg(not(feature = "alloc"))] buf.push(*modulation_frequency_id).unwrap(); } - Self::GetPowerOnSelfTest => {} - Self::SetPowerOnSelfTest { self_test_id } => { - #[cfg(feature = "alloc")] - buf.reserve(0x01); - - #[cfg(feature = "alloc")] - buf.push((*self_test_id).into()); - #[cfg(not(feature = "alloc"))] - buf.push((*self_test_id).into()).unwrap(); - } - Self::GetLockState => {} - Self::SetLockState { - pin_code, - lock_state, - } => { - #[cfg(feature = "alloc")] - buf.reserve(0x03); - - buf.extend((pin_code.0).to_be_bytes()); - - #[cfg(feature = "alloc")] - buf.push(*lock_state as u8); - #[cfg(not(feature = "alloc"))] - buf.push(*lock_state as u8).unwrap(); - } - Self::GetLockStateDescription => {} - Self::GetLockPin => {} - Self::SetLockPin { - new_pin_code, - current_pin_code, - } => { - #[cfg(feature = "alloc")] - buf.reserve(0x04); - - buf.extend((new_pin_code.0).to_be_bytes()); - buf.extend((current_pin_code.0).to_be_bytes()); - } - Self::GetBurnIn => {} - Self::SetBurnIn { hours } => { - #[cfg(feature = "alloc")] - buf.reserve(0x01); - - #[cfg(feature = "alloc")] - buf.push(*hours); - #[cfg(not(feature = "alloc"))] - buf.push(*hours).unwrap(); - } - Self::GetIdentifyMode => {} - Self::SetIdentifyMode { identify_mode } => { - #[cfg(feature = "alloc")] - buf.reserve(0x01); - - #[cfg(feature = "alloc")] - buf.push(*identify_mode); - #[cfg(not(feature = "alloc"))] - buf.push(*identify_mode).unwrap(); - } Self::GetPresetInfo => {} Self::GetPresetStatus { scene_id } => { #[cfg(feature = "alloc")] @@ -1891,9 +1909,7 @@ impl RequestParameter { (CommandClass::DiscoveryCommand, ParameterId::DiscMute) => Ok(Self::DiscMute), (CommandClass::DiscoveryCommand, ParameterId::DiscUnMute) => Ok(Self::DiscUnMute), (CommandClass::DiscoveryCommand, ParameterId::DiscUniqueBranch) => { - if bytes.len() < 12 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 12); let lower_bound_uid = DeviceUID::from(<[u8; 6]>::try_from(&bytes[0..=5])?); let upper_bound_uid = DeviceUID::from(<[u8; 6]>::try_from(&bytes[6..=11])?); @@ -1903,28 +1919,24 @@ impl RequestParameter { upper_bound_uid, }) } + (CommandClass::GetCommand, ParameterId::ProxiedDeviceCount) => Ok(Self::GetProxiedDeviceCount), + (CommandClass::GetCommand, ParameterId::ProxiedDevices) => Ok(Self::GetProxiedDevices), (CommandClass::GetCommand, ParameterId::CommsStatus) => Ok(Self::GetCommsStatus), (CommandClass::SetCommand, ParameterId::CommsStatus) => Ok(Self::SetCommsStatus), (CommandClass::GetCommand, ParameterId::QueuedMessage) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetQueuedMessage { status_type: bytes[0].try_into()?, }) } (CommandClass::GetCommand, ParameterId::StatusMessages) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetStatusMessages { status_type: bytes[0].try_into()?, }) } (CommandClass::GetCommand, ParameterId::StatusIdDescription) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 2); Ok(Self::GetStatusIdDescription { status_id: u16::from_be_bytes([bytes[0], bytes[1]]), }) @@ -1934,9 +1946,7 @@ impl RequestParameter { Ok(Self::GetSubDeviceIdStatusReportThreshold) } (CommandClass::SetCommand, ParameterId::SubDeviceIdStatusReportThreshold) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetSubDeviceIdStatusReportThreshold { status_type: bytes[0].try_into()?, }) @@ -1945,9 +1955,7 @@ impl RequestParameter { Ok(Self::GetSupportedParameters) } (CommandClass::GetCommand, ParameterId::ParameterDescription) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 2); Ok(Self::GetParameterDescription { parameter_id: u16::from_be_bytes([bytes[0], bytes[1]]), }) @@ -1963,12 +1971,14 @@ impl RequestParameter { Ok(Self::GetManufacturerLabel) } (CommandClass::GetCommand, ParameterId::DeviceLabel) => Ok(Self::GetDeviceLabel), - (CommandClass::SetCommand, ParameterId::DeviceLabel) => Ok(Self::SetDeviceLabel { - #[cfg(feature = "alloc")] - device_label: decode_string_bytes(bytes)?, - #[cfg(not(feature = "alloc"))] - device_label: decode_string_bytes(bytes)?, - }), + (CommandClass::SetCommand, ParameterId::DeviceLabel) => { + Ok(Self::SetDeviceLabel { + #[cfg(feature = "alloc")] + device_label: decode_string_bytes(&bytes[..bytes.len().min(32)])?, + #[cfg(not(feature = "alloc"))] + device_label: decode_string_bytes(&bytes[..bytes.len().min(32)])?, + }) + } (CommandClass::GetCommand, ParameterId::FactoryDefaults) => { Ok(Self::GetFactoryDefaults) } @@ -1979,12 +1989,14 @@ impl RequestParameter { Ok(Self::GetLanguageCapabilities) } (CommandClass::GetCommand, ParameterId::Language) => Ok(Self::GetLanguage), - (CommandClass::SetCommand, ParameterId::Language) => Ok(Self::SetLanguage { - #[cfg(feature = "alloc")] - language: decode_string_bytes(bytes)?, - #[cfg(not(feature = "alloc"))] - language: decode_string_bytes(bytes)?, - }), + (CommandClass::SetCommand, ParameterId::Language) => { + Ok(Self::SetLanguage { + #[cfg(feature = "alloc")] + language: decode_string_bytes(&bytes[..bytes.len().min(2)])?, + #[cfg(not(feature = "alloc"))] + language: decode_string_bytes(&bytes[..bytes.len().min(2)])?, + }) + } (CommandClass::GetCommand, ParameterId::SoftwareVersionLabel) => { Ok(Self::GetSoftwareVersionLabel) } @@ -1996,17 +2008,13 @@ impl RequestParameter { } (CommandClass::GetCommand, ParameterId::DmxPersonality) => Ok(Self::GetDmxPersonality), (CommandClass::SetCommand, ParameterId::DmxPersonality) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetDmxPersonality { personality_id: bytes[0], }) } (CommandClass::GetCommand, ParameterId::DmxPersonalityDescription) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetDmxPersonalityDescription { personality: bytes[0], }) @@ -2015,18 +2023,14 @@ impl RequestParameter { Ok(Self::GetDmxStartAddress) } (CommandClass::SetCommand, ParameterId::DmxStartAddress) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 2); Ok(Self::SetDmxStartAddress { dmx_start_address: u16::from_be_bytes([bytes[0], bytes[1]]), }) } (CommandClass::GetCommand, ParameterId::SlotInfo) => Ok(Self::GetSlotInfo), (CommandClass::GetCommand, ParameterId::SlotDescription) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 2); Ok(Self::GetSlotDescription { slot_id: u16::from_be_bytes([bytes[0], bytes[1]]), }) @@ -2035,78 +2039,60 @@ impl RequestParameter { Ok(Self::GetDefaultSlotValue) } (CommandClass::GetCommand, ParameterId::SensorDefinition) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetSensorDefinition { sensor_id: bytes[0], }) } (CommandClass::GetCommand, ParameterId::SensorValue) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetSensorValue { sensor_id: bytes[0], }) } (CommandClass::SetCommand, ParameterId::SensorValue) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetSensorValue { sensor_id: bytes[0], }) } (CommandClass::SetCommand, ParameterId::RecordSensors) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetRecordSensors { sensor_id: bytes[0], }) } (CommandClass::GetCommand, ParameterId::DeviceHours) => Ok(Self::GetDeviceHours), (CommandClass::SetCommand, ParameterId::DeviceHours) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::SetDeviceHours { device_hours: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::GetCommand, ParameterId::LampHours) => Ok(Self::GetLampHours), (CommandClass::SetCommand, ParameterId::LampHours) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::SetLampHours { lamp_hours: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::GetCommand, ParameterId::LampStrikes) => Ok(Self::GetLampStrikes), (CommandClass::SetCommand, ParameterId::LampStrikes) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::SetLampStrikes { lamp_strikes: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::GetCommand, ParameterId::LampState) => Ok(Self::GetLampState), (CommandClass::SetCommand, ParameterId::LampState) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetLampState { lamp_state: bytes[0].try_into()?, }) } (CommandClass::GetCommand, ParameterId::LampOnMode) => Ok(Self::GetLampOnMode), (CommandClass::SetCommand, ParameterId::LampOnMode) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetLampOnMode { lamp_on_mode: bytes[0].try_into()?, }) @@ -2115,9 +2101,7 @@ impl RequestParameter { Ok(Self::GetDevicePowerCycles) } (CommandClass::SetCommand, ParameterId::DevicePowerCycles) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::SetDevicePowerCycles { device_power_cycles: u32::from_be_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], @@ -2126,54 +2110,42 @@ impl RequestParameter { } (CommandClass::GetCommand, ParameterId::DisplayInvert) => Ok(Self::GetDisplayInvert), (CommandClass::SetCommand, ParameterId::DisplayInvert) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetDisplayInvert { display_invert: bytes[0].try_into()?, }) } (CommandClass::GetCommand, ParameterId::DisplayLevel) => Ok(Self::GetDisplayLevel), (CommandClass::SetCommand, ParameterId::DisplayLevel) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetDisplayLevel { display_level: bytes[0], }) } (CommandClass::GetCommand, ParameterId::PanInvert) => Ok(Self::GetPanInvert), (CommandClass::SetCommand, ParameterId::PanInvert) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetPanInvert { pan_invert: bytes[0] != 0, }) } (CommandClass::GetCommand, ParameterId::TiltInvert) => Ok(Self::GetTiltInvert), (CommandClass::SetCommand, ParameterId::TiltInvert) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetTiltInvert { tilt_invert: bytes[0] != 0, }) } (CommandClass::GetCommand, ParameterId::PanTiltSwap) => Ok(Self::GetPanTiltSwap), (CommandClass::SetCommand, ParameterId::PanTiltSwap) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetPanTiltSwap { pan_tilt_swap: bytes[0] != 0, }) } (CommandClass::GetCommand, ParameterId::RealTimeClock) => Ok(Self::GetRealTimeClock), (CommandClass::SetCommand, ParameterId::RealTimeClock) => { - if bytes.len() < 7 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 7); Ok(Self::SetRealTimeClock { year: u16::from_be_bytes([bytes[0], bytes[1]]), month: bytes[2], @@ -2185,43 +2157,44 @@ impl RequestParameter { } (CommandClass::GetCommand, ParameterId::IdentifyDevice) => Ok(Self::GetIdentifyDevice), (CommandClass::SetCommand, ParameterId::IdentifyDevice) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetIdentifyDevice { identify: bytes[0] != 0, }) } (CommandClass::SetCommand, ParameterId::ResetDevice) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetResetDevice { reset_device: bytes[0].try_into()?, }) } (CommandClass::GetCommand, ParameterId::PowerState) => Ok(Self::GetPowerState), - (CommandClass::SetCommand, ParameterId::PowerState) => Ok(Self::SetPowerState { - power_state: bytes[0].try_into()?, - }), + (CommandClass::SetCommand, ParameterId::PowerState) => { + check_msg_len!(bytes, 1); + Ok(Self::SetPowerState { + power_state: bytes[0].try_into()?, + }) + } (CommandClass::GetCommand, ParameterId::PerformSelfTest) => { Ok(Self::GetPerformSelfTest) } (CommandClass::SetCommand, ParameterId::PerformSelfTest) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetPerformSelfTest { self_test_id: bytes[0].into(), }) } + (CommandClass::GetCommand, ParameterId::SelfTestDescription) => { + check_msg_len!(bytes, 1); + Ok(Self::GetSelfTestDescription { + self_test_id: bytes[0].into(), + }) + } (CommandClass::SetCommand, ParameterId::CapturePreset) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 2); let scene_id = u16::from_be_bytes([bytes[0], bytes[1]]); - let fade_times = if bytes.len() > 2 { + let fade_times = if bytes.len() >= 8 { Some(FadeTimes { up_fade_time: u16::from_be_bytes([bytes[2], bytes[3]]), down_fade_time: u16::from_be_bytes([bytes[4], bytes[5]]), @@ -2236,41 +2209,34 @@ impl RequestParameter { fade_times, }) } - (CommandClass::GetCommand, ParameterId::SelfTestDescription) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } - Ok(Self::GetSelfTestDescription { - self_test_id: bytes[0].into(), - }) - } (CommandClass::GetCommand, ParameterId::PresetPlayback) => Ok(Self::GetPresetPlayback), (CommandClass::SetCommand, ParameterId::PresetPlayback) => { - if bytes.len() < 3 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 3); Ok(Self::SetPresetPlayback { mode: u16::from_be_bytes([bytes[0], bytes[1]]).into(), level: bytes[2], }) } // E1.37-1 + (CommandClass::GetCommand, ParameterId::IdentifyMode) => Ok(Self::GetIdentifyMode), + (CommandClass::SetCommand, ParameterId::IdentifyMode) => { + check_msg_len!(bytes, 1); + Ok(Self::SetIdentifyMode { + identify_mode: bytes[0].try_into()?, + }) + } (CommandClass::GetCommand, ParameterId::DmxBlockAddress) => { Ok(Self::GetDmxBlockAddress) } (CommandClass::SetCommand, ParameterId::DmxBlockAddress) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 2); Ok(Self::SetDmxBlockAddress { dmx_block_address: u16::from_be_bytes([bytes[0], bytes[1]]), }) } (CommandClass::GetCommand, ParameterId::DmxFailMode) => Ok(Self::GetDmxFailMode), (CommandClass::SetCommand, ParameterId::DmxFailMode) => { - if bytes.len() < 7 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 7); Ok(Self::SetDmxFailMode { scene_id: u16::from_be_bytes([bytes[0], bytes[1]]).into(), loss_of_signal_delay_time: u16::from_be_bytes([bytes[2], bytes[3]]).into(), @@ -2280,9 +2246,7 @@ impl RequestParameter { } (CommandClass::GetCommand, ParameterId::DmxStartupMode) => Ok(Self::GetDmxStartupMode), (CommandClass::SetCommand, ParameterId::DmxStartupMode) => { - if bytes.len() < 7 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 7); Ok(Self::SetDmxStartupMode { scene_id: u16::from_be_bytes([bytes[0], bytes[1]]).into(), startup_delay: u16::from_be_bytes([bytes[2], bytes[3]]).into(), @@ -2290,12 +2254,46 @@ impl RequestParameter { level: bytes[6], }) } + (CommandClass::GetCommand, ParameterId::PowerOnSelfTest) => { + Ok(Self::GetPowerOnSelfTest) + } + (CommandClass::SetCommand, ParameterId::PowerOnSelfTest) => { + check_msg_len!(bytes, 1); + Ok(Self::SetPowerOnSelfTest { + perform_test: bytes[0] != 0, + }) + } + (CommandClass::GetCommand, ParameterId::LockState) => Ok(Self::GetLockState), + (CommandClass::SetCommand, ParameterId::LockState) => { + check_msg_len!(bytes, 3); + Ok(Self::SetLockState { + pin_code: u16::from_be_bytes([bytes[0], bytes[1]]).try_into()?, + lock_state: bytes[2], + }) + } + (CommandClass::GetCommand, ParameterId::LockStateDescription) => { + check_msg_len!(bytes, 1); + Ok(Self::GetLockStateDescription { + lock_state: bytes[0], + }) + } + (CommandClass::GetCommand, ParameterId::LockPin) => Ok(Self::GetLockPin), + (CommandClass::SetCommand, ParameterId::LockPin) => { + check_msg_len!(bytes, 4); + Ok(Self::SetLockPin { + new_pin_code: u16::from_be_bytes([bytes[0], bytes[1]]).try_into()?, + current_pin_code: u16::from_be_bytes([bytes[2], bytes[3]]).try_into()?, + }) + } + (CommandClass::GetCommand, ParameterId::BurnIn) => Ok(Self::GetBurnIn), + (CommandClass::SetCommand, ParameterId::BurnIn) => { + check_msg_len!(bytes, 1); + Ok(Self::SetBurnIn { hours: bytes[0] }) + } (CommandClass::GetCommand, ParameterId::DimmerInfo) => Ok(Self::GetDimmerInfo), (CommandClass::GetCommand, ParameterId::MinimumLevel) => Ok(Self::GetMinimumLevel), (CommandClass::SetCommand, ParameterId::MinimumLevel) => { - if bytes.len() < 5 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 5); Ok(Self::SetMinimumLevel { minimum_level_increasing: u16::from_be_bytes([bytes[0], bytes[1]]), minimum_level_decreasing: u16::from_be_bytes([bytes[2], bytes[3]]), @@ -2304,41 +2302,31 @@ impl RequestParameter { } (CommandClass::GetCommand, ParameterId::MaximumLevel) => Ok(Self::GetMaximumLevel), (CommandClass::SetCommand, ParameterId::MaximumLevel) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 2); Ok(Self::SetMaximumLevel { maximum_level: u16::from_be_bytes([bytes[0], bytes[1]]), }) } (CommandClass::GetCommand, ParameterId::Curve) => Ok(Self::GetCurve), (CommandClass::SetCommand, ParameterId::Curve) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetCurve { curve_id: bytes[0] }) } (CommandClass::GetCommand, ParameterId::CurveDescription) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetCurveDescription { curve_id: bytes[0] }) } (CommandClass::GetCommand, ParameterId::OutputResponseTime) => { Ok(Self::GetOutputResponseTime) } (CommandClass::SetCommand, ParameterId::OutputResponseTime) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetOutputResponseTime { output_response_time_id: bytes[0], }) } (CommandClass::GetCommand, ParameterId::OutputResponseTimeDescription) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetOutputResponseTimeDescription { output_response_time_id: bytes[0], }) @@ -2347,84 +2335,26 @@ impl RequestParameter { Ok(Self::GetModulationFrequency) } (CommandClass::SetCommand, ParameterId::ModulationFrequency) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetModulationFrequency { modulation_frequency_id: bytes[0], }) } (CommandClass::GetCommand, ParameterId::ModulationFrequencyDescription) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetModulationFrequencyDescription { modulation_frequency_id: bytes[0], }) } - (CommandClass::GetCommand, ParameterId::PowerOnSelfTest) => { - Ok(Self::GetPowerOnSelfTest) - } - (CommandClass::SetCommand, ParameterId::PowerOnSelfTest) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } - Ok(Self::SetPowerOnSelfTest { - self_test_id: bytes[0].into(), - }) - } - (CommandClass::GetCommand, ParameterId::LockState) => Ok(Self::GetLockState), - (CommandClass::SetCommand, ParameterId::LockState) => { - if bytes.len() < 5 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } - Ok(Self::SetLockState { - pin_code: u16::from_be_bytes([bytes[0], bytes[1]]).try_into()?, - lock_state: bytes[4] != 0, - }) - } - (CommandClass::GetCommand, ParameterId::LockStateDescription) => { - Ok(Self::GetLockStateDescription) - } - (CommandClass::GetCommand, ParameterId::LockPin) => Ok(Self::GetLockPin), - (CommandClass::SetCommand, ParameterId::LockPin) => { - if bytes.len() < 8 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } - Ok(Self::SetLockPin { - new_pin_code: u16::from_be_bytes([bytes[0], bytes[1]]).try_into()?, - current_pin_code: u16::from_be_bytes([bytes[2], bytes[3]]).try_into()?, - }) - } - (CommandClass::GetCommand, ParameterId::BurnIn) => Ok(Self::GetBurnIn), - (CommandClass::SetCommand, ParameterId::BurnIn) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } - Ok(Self::SetBurnIn { hours: bytes[0] }) - } - (CommandClass::GetCommand, ParameterId::IdentifyMode) => Ok(Self::GetIdentifyMode), - (CommandClass::SetCommand, ParameterId::IdentifyMode) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } - Ok(Self::SetIdentifyMode { - identify_mode: bytes[0], - }) - } (CommandClass::GetCommand, ParameterId::PresetInfo) => Ok(Self::GetPresetInfo), (CommandClass::GetCommand, ParameterId::PresetStatus) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 2); Ok(Self::GetPresetStatus { scene_id: u16::from_be_bytes([bytes[0], bytes[1]]), }) } (CommandClass::SetCommand, ParameterId::PresetStatus) => { - if bytes.len() < 9 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 9); Ok(Self::SetPresetStatus { scene_id: u16::from_be_bytes([bytes[0], bytes[1]]), up_fade_time: u16::from_be_bytes([bytes[2], bytes[3]]), @@ -2437,9 +2367,7 @@ impl RequestParameter { Ok(Self::GetPresetMergeMode) } (CommandClass::SetCommand, ParameterId::PresetMergeMode) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetPresetMergeMode { merge_mode: bytes[0].try_into()?, }) @@ -2447,75 +2375,57 @@ impl RequestParameter { // E1.37-2 (CommandClass::GetCommand, ParameterId::ListInterfaces) => Ok(Self::GetListInterfaces), (CommandClass::GetCommand, ParameterId::InterfaceLabel) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::GetInterfaceLabel { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::GetCommand, ParameterId::InterfaceHardwareAddressType1) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::GetInterfaceHardwareAddressType1 { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::GetCommand, ParameterId::IpV4DhcpMode) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::GetIpV4DhcpMode { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::SetCommand, ParameterId::IpV4DhcpMode) => { - if bytes.len() < 5 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 5); Ok(Self::SetIpV4DhcpMode { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), dhcp_mode: bytes[4] != 0, }) } (CommandClass::GetCommand, ParameterId::IpV4ZeroConfMode) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::GetIpV4ZeroConfMode { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::SetCommand, ParameterId::IpV4ZeroConfMode) => { - if bytes.len() < 5 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 5); Ok(Self::SetIpV4ZeroConfMode { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), zero_conf_mode: bytes[4] != 0, }) } (CommandClass::GetCommand, ParameterId::IpV4CurrentAddress) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::GetIpV4CurrentAddress { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::GetCommand, ParameterId::IpV4StaticAddress) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::GetIpV4StaticAddress { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::SetCommand, ParameterId::IpV4StaticAddress) => { - if bytes.len() < 9 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 9); Ok(Self::SetIpV4StaticAddress { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), address: Ipv4Address::from([bytes[4], bytes[5], bytes[6], bytes[7]]), @@ -2523,25 +2433,19 @@ impl RequestParameter { }) } (CommandClass::SetCommand, ParameterId::InterfaceApplyConfiguration) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::SetInterfaceApplyConfiguration { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::SetCommand, ParameterId::InterfaceRenewDhcp) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::SetInterfaceRenewDhcp { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) } (CommandClass::SetCommand, ParameterId::InterfaceReleaseDhcp) => { - if bytes.len() < 4 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 4); Ok(Self::SetInterfaceReleaseDhcp { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), }) @@ -2550,26 +2454,20 @@ impl RequestParameter { Ok(Self::GetIpV4DefaultRoute) } (CommandClass::SetCommand, ParameterId::IpV4DefaultRoute) => { - if bytes.len() < 8 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 8); Ok(Self::SetIpV4DefaultRoute { interface_id: u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), ipv4_default_route: Ipv4Route::from([bytes[4], bytes[5], bytes[6], bytes[7]]), }) } (CommandClass::GetCommand, ParameterId::DnsIpV4NameServer) => { - if bytes.is_empty() { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::GetDnsIpV4NameServer { name_server_index: bytes[0], }) } (CommandClass::SetCommand, ParameterId::DnsIpV4NameServer) => { - if bytes.len() < 5 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 5); Ok(Self::SetDnsIpV4NameServer { name_server_index: bytes[0], name_server_address: Ipv4Address::from([ @@ -2578,9 +2476,11 @@ impl RequestParameter { }) } (CommandClass::GetCommand, ParameterId::DnsHostName) => Ok(Self::GetDnsHostName), - (CommandClass::SetCommand, ParameterId::DnsHostName) => Ok(Self::SetDnsHostName { - host_name: decode_string_bytes(bytes)?, - }), + (CommandClass::SetCommand, ParameterId::DnsHostName) => { + Ok(Self::SetDnsHostName { + host_name: decode_string_bytes(&bytes[..bytes.len().min(63)])?, + }) + } (CommandClass::GetCommand, ParameterId::DnsDomainName) => Ok(Self::GetDnsDomainName), (CommandClass::SetCommand, ParameterId::DnsDomainName) => Ok(Self::SetDnsDomainName { domain_name: decode_string_bytes(bytes)?, @@ -2591,101 +2491,129 @@ impl RequestParameter { Ok(Self::GetEndpointListChange) } (CommandClass::GetCommand, ParameterId::IdentifyEndpoint) => { + check_msg_len!(bytes, 2); Ok(Self::GetIdentifyEndpoint { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } (CommandClass::SetCommand, ParameterId::IdentifyEndpoint) => { + check_msg_len!(bytes, 3); Ok(Self::SetIdentifyEndpoint { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), identify: bytes[2] != 0, }) } (CommandClass::GetCommand, ParameterId::EndpointToUniverse) => { + check_msg_len!(bytes, 2); Ok(Self::GetEndpointToUniverse { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } (CommandClass::SetCommand, ParameterId::EndpointToUniverse) => { + check_msg_len!(bytes, 4); Ok(Self::SetEndpointToUniverse { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), universe: u16::from_be_bytes(bytes[2..=3].try_into()?), }) } - (CommandClass::GetCommand, ParameterId::EndpointMode) => Ok(Self::GetEndpointMode { - endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), - }), - (CommandClass::SetCommand, ParameterId::EndpointMode) => Ok(Self::SetEndpointMode { - endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), - mode: bytes[2].try_into()?, - }), - (CommandClass::GetCommand, ParameterId::EndpointLabel) => Ok(Self::GetEndpointLabel { - endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), - }), - (CommandClass::SetCommand, ParameterId::EndpointLabel) => Ok(Self::SetEndpointLabel { - endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), - label: decode_string_bytes(&bytes[2..])?, - }), + (CommandClass::GetCommand, ParameterId::EndpointMode) => { + check_msg_len!(bytes, 2); + Ok(Self::GetEndpointMode { + endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), + }) + } + (CommandClass::SetCommand, ParameterId::EndpointMode) => { + check_msg_len!(bytes, 3); + Ok(Self::SetEndpointMode { + endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), + mode: bytes[2].try_into()?, + }) + } + (CommandClass::GetCommand, ParameterId::EndpointLabel) => { + check_msg_len!(bytes, 2); + Ok(Self::GetEndpointLabel { + endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), + }) + } + (CommandClass::SetCommand, ParameterId::EndpointLabel) => { + check_msg_len!(bytes, 2); + Ok(Self::SetEndpointLabel { + endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), + label: decode_string_bytes(&bytes[2..bytes.len().min(2+32)])?, + }) + } (CommandClass::GetCommand, ParameterId::RdmTrafficEnable) => { + check_msg_len!(bytes, 2); Ok(Self::GetRdmTrafficEnable { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } (CommandClass::SetCommand, ParameterId::RdmTrafficEnable) => { + check_msg_len!(bytes, 3); Ok(Self::SetRdmTrafficEnable { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), enable: bytes[2] == 1, }) } (CommandClass::GetCommand, ParameterId::DiscoveryState) => { + check_msg_len!(bytes, 2); Ok(Self::GetDiscoveryState { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } (CommandClass::SetCommand, ParameterId::DiscoveryState) => { + check_msg_len!(bytes, 3); Ok(Self::SetDiscoveryState { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), state: bytes[2].try_into()?, }) } (CommandClass::GetCommand, ParameterId::BackgroundDiscovery) => { + check_msg_len!(bytes, 2); Ok(Self::GetBackgroundDiscovery { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } (CommandClass::SetCommand, ParameterId::BackgroundDiscovery) => { + check_msg_len!(bytes, 3); Ok(Self::SetBackgroundDiscovery { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), enable: bytes[2] == 1, }) } (CommandClass::GetCommand, ParameterId::EndpointTiming) => { + check_msg_len!(bytes, 2); Ok(Self::GetEndpointTiming { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } (CommandClass::SetCommand, ParameterId::EndpointTiming) => { + check_msg_len!(bytes, 3); Ok(Self::SetEndpointTiming { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), setting_id: bytes[2], }) } (CommandClass::GetCommand, ParameterId::EndpointTimingDescription) => { + check_msg_len!(bytes, 1); Ok(Self::GetEndpointTimingDescription { setting_id: bytes[0], }) } (CommandClass::GetCommand, ParameterId::EndpointResponders) => { + check_msg_len!(bytes, 2); Ok(Self::GetEndpointResponders { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } (CommandClass::GetCommand, ParameterId::EndpointResponderListChange) => { + check_msg_len!(bytes, 2); Ok(Self::GetEndpointResponderListChange { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } (CommandClass::GetCommand, ParameterId::BindingControlFields) => { + check_msg_len!(bytes, 8); Ok(Self::GetBindingControlFields { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), uid: DeviceUID::new( @@ -2698,22 +2626,26 @@ impl RequestParameter { Ok(Self::GetBackgroundQueuedStatusPolicy) } (CommandClass::SetCommand, ParameterId::BackgroundQueuedStatusPolicy) => { + check_msg_len!(bytes, 1); Ok(Self::SetBackgroundQueuedStatusPolicy { policy_id: bytes[0], }) } (CommandClass::GetCommand, ParameterId::BackgroundQueuedStatusPolicyDescription) => { + check_msg_len!(bytes, 1); Ok(Self::GetBackgroundQueuedStatusPolicyDescription { policy_id: bytes[0], }) } // E1.33 (CommandClass::GetCommand, ParameterId::ComponentScope) => { + check_msg_len!(bytes, 2); Ok(Self::GetComponentScope { scope_slot: u16::from_be_bytes([bytes[0], bytes[1]]), }) } (CommandClass::SetCommand, ParameterId::ComponentScope) => { + check_msg_len!(bytes, 89); Ok(Self::SetComponentScope { scope_slot: u16::from_be_bytes([bytes[0], bytes[1]]), scope_string: decode_string_bytes(&bytes[2..=65])?, @@ -2735,18 +2667,13 @@ impl RequestParameter { } (CommandClass::GetCommand, ParameterId::TcpCommsStatus) => Ok(Self::GetTcpCommsStatus), (CommandClass::SetCommand, ParameterId::TcpCommsStatus) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } Ok(Self::SetTcpCommsStatus { - scope_string: decode_string_bytes(bytes)?, + scope_string: decode_string_bytes(&bytes[..bytes.len().min(63)])?, }) } (CommandClass::GetCommand, ParameterId::BrokerStatus) => Ok(Self::GetBrokerStatus), (CommandClass::SetCommand, ParameterId::BrokerStatus) => { - if bytes.len() < 2 { - return Err(RdmError::InvalidMessageLength(bytes.len() as u8)); - } + check_msg_len!(bytes, 1); Ok(Self::SetBrokerStatus { broker_state: bytes[0].try_into()?, }) diff --git a/src/rdm/response.rs b/src/rdm/response.rs index 8df8aec..c3826fd 100644 --- a/src/rdm/response.rs +++ b/src/rdm/response.rs @@ -49,7 +49,7 @@ use super::{ bsd_16_crc, parameter::{ decode_string_bytes, BrokerState, DefaultSlotValue, DhcpMode, DiscoveryCountStatus, - DiscoveryState, DisplayInvertMode, EndpointId, EndpointMode, EndpointType, Ipv4Address, + DiscoveryState, DisplayInvertMode, EndpointId, EndpointMode, EndpointType, IdentifyMode, Ipv4Address, Ipv4Route, Ipv6Address, LampOnMode, LampState, MergeMode, NetworkInterface, ParameterDescription, ParameterId, PinCode, PowerState, PresetPlaybackMode, PresetProgrammed, ProductCategory, ProductDetail, ProtocolVersion, SelfTest, @@ -66,6 +66,7 @@ use macaddr::MacAddr6; #[cfg(not(feature = "alloc"))] use heapless::{String, Vec}; +// E1.20 2025 Table A-17 #[derive(Copy, Clone, Debug, PartialEq)] pub enum ResponseNackReasonCode { UnknownPid = 0x0000, @@ -134,6 +135,7 @@ impl Display for ResponseNackReasonCode { } } +// E1.20 Table A-2 #[derive(Copy, Clone, Debug, PartialEq)] pub enum ResponseType { Ack = 0x00, @@ -160,6 +162,7 @@ impl TryFrom for ResponseType { #[derive(Clone, Debug, PartialEq)] pub enum ResponseData { ParameterData(Option), + /// Estimated response time in 10ths of a second (100ms) EstimateResponseTime(u16), NackReason(ResponseNackReasonCode), } @@ -385,6 +388,7 @@ pub enum ResponseParameterData { level: u8, }, // E1.37-1 + GetIdentifyMode(IdentifyMode), GetDmxBlockAddress { total_sub_device_footprint: u16, base_dmx_address: u16, @@ -542,7 +546,7 @@ pub enum ResponseParameterData { ), GetDnsDomainName( #[cfg(feature = "alloc")] String, - #[cfg(not(feature = "alloc"))] String<32>, + #[cfg(not(feature = "alloc"))] String<231>, ), // E1.37-7 GetEndpointList { @@ -1282,6 +1286,15 @@ impl ResponseParameterData { #[cfg(not(feature = "alloc"))] buf.push(*level).unwrap(); } + Self::GetIdentifyMode(identify_mode) => { + #[cfg(feature = "alloc")] + buf.reserve(1); + + #[cfg(feature = "alloc")] + buf.push(*identify_mode as u8); + #[cfg(not(feature = "alloc"))] + buf.push(*identify_mode as u8).unwrap(); + }, Self::GetDmxBlockAddress { total_sub_device_footprint, base_dmx_address, @@ -2118,7 +2131,8 @@ impl ResponseParameterData { ) -> Result { match (command_class, parameter_id) { (CommandClass::DiscoveryCommandResponse, ParameterId::DiscMute) => { - let binding_uid = if bytes.len() > 2 { + check_msg_len!(bytes, 2); + let binding_uid = if bytes.len() >= 8 { Some(DeviceUID::from(<[u8; 6]>::try_from(&bytes[2..=7])?)) } else { None @@ -2130,7 +2144,8 @@ impl ResponseParameterData { }) } (CommandClass::DiscoveryCommandResponse, ParameterId::DiscUnMute) => { - let binding_uid = if bytes.len() > 2 { + check_msg_len!(bytes, 2); + let binding_uid = if bytes.len() >= 8 { Some(DeviceUID::from(<[u8; 6]>::try_from(&bytes[2..=7])?)) } else { None @@ -2142,6 +2157,7 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::ProxiedDeviceCount) => { + check_msg_len!(bytes, 3); Ok(Self::GetProxiedDeviceCount { device_count: u16::from_be_bytes(bytes[0..=1].try_into()?), list_change: bytes[2] == 1, @@ -2162,6 +2178,7 @@ impl ResponseParameterData { )) } (CommandClass::GetCommandResponse, ParameterId::CommsStatus) => { + check_msg_len!(bytes, 6); Ok(Self::GetCommsStatus { short_message: u16::from_be_bytes(bytes[0..=1].try_into()?), length_mismatch: u16::from_be_bytes(bytes[2..=3].try_into()?), @@ -2199,9 +2216,10 @@ impl ResponseParameterData { )) } (CommandClass::GetCommandResponse, ParameterId::StatusIdDescription) => { - Ok(Self::GetStatusIdDescription(decode_string_bytes(bytes)?)) + Ok(Self::GetStatusIdDescription(decode_string_bytes(&bytes[..bytes.len().min(32)])?)) } (CommandClass::GetCommandResponse, ParameterId::SubDeviceIdStatusReportThreshold) => { + check_msg_len!(bytes, 1); Ok(Self::GetSubDeviceIdStatusReportThreshold( bytes[0].try_into()?, )) @@ -2220,6 +2238,7 @@ impl ResponseParameterData { )) } (CommandClass::GetCommandResponse, ParameterId::ParameterDescription) => { + check_msg_len!(bytes, 20); Ok(Self::GetParameterDescription(ParameterDescription { parameter_id: u16::from_be_bytes(bytes[0..=1].try_into()?), parameter_data_length: bytes[2], @@ -2230,10 +2249,11 @@ impl ResponseParameterData { raw_minimum_valid_value: bytes[8..=11].try_into()?, raw_maximum_valid_value: bytes[12..=15].try_into()?, raw_default_value: bytes[16..=19].try_into()?, - description: decode_string_bytes(&bytes[20..])?, + description: decode_string_bytes(&bytes[20..bytes.len().min(20+32)])?, })) } (CommandClass::GetCommandResponse, ParameterId::DeviceInfo) => { + check_msg_len!(bytes, 19); Ok(Self::GetDeviceInfo { protocol_version: ProtocolVersion::new(bytes[0], bytes[1]), model_id: u16::from_be_bytes(bytes[2..=3].try_into()?), @@ -2262,15 +2282,16 @@ impl ResponseParameterData { )) } (CommandClass::GetCommandResponse, ParameterId::DeviceModelDescription) => { - Ok(Self::GetDeviceModelDescription(decode_string_bytes(bytes)?)) + Ok(Self::GetDeviceModelDescription(decode_string_bytes(&bytes[..bytes.len().min(32)])?)) } (CommandClass::GetCommandResponse, ParameterId::ManufacturerLabel) => { - Ok(Self::GetManufacturerLabel(decode_string_bytes(bytes)?)) + Ok(Self::GetManufacturerLabel(decode_string_bytes(&bytes[..bytes.len().min(32)])?)) } (CommandClass::GetCommandResponse, ParameterId::DeviceLabel) => { - Ok(Self::GetDeviceLabel(decode_string_bytes(bytes)?)) + Ok(Self::GetDeviceLabel(decode_string_bytes(&bytes[..bytes.len().min(32)])?)) } (CommandClass::GetCommandResponse, ParameterId::FactoryDefaults) => { + check_msg_len!(bytes, 1); Ok(Self::GetFactoryDefaults(bytes[0] == 1)) } (CommandClass::GetCommandResponse, ParameterId::LanguageCapabilities) => { @@ -2289,37 +2310,48 @@ impl ResponseParameterData { .collect::, 115>, RdmError>>()?, )) } - (CommandClass::GetCommandResponse, ParameterId::Language) => Ok(Self::GetLanguage( - #[cfg(feature = "alloc")] - core::str::from_utf8(&bytes[0..=1])?.to_string(), - #[cfg(not(feature = "alloc"))] - String::from_utf8(Vec::::from_slice(&bytes[0..=1]).unwrap())?, - )), + (CommandClass::GetCommandResponse, ParameterId::Language) => { + check_msg_len!(bytes, 2); + Ok(Self::GetLanguage( + #[cfg(feature = "alloc")] + core::str::from_utf8(&bytes[0..=1])?.to_string(), + #[cfg(not(feature = "alloc"))] + String::from_utf8(Vec::::from_slice(&bytes[0..=1]).unwrap())?, + )) + } (CommandClass::GetCommandResponse, ParameterId::SoftwareVersionLabel) => { - Ok(Self::GetSoftwareVersionLabel(decode_string_bytes(bytes)?)) - } - (CommandClass::GetCommandResponse, ParameterId::BootSoftwareVersionId) => Ok( - Self::GetBootSoftwareVersionId(u32::from_be_bytes(bytes.try_into()?)), - ), - (CommandClass::GetCommandResponse, ParameterId::BootSoftwareVersionLabel) => Ok( - Self::GetBootSoftwareVersionLabel(decode_string_bytes(bytes)?), - ), + Ok(Self::GetSoftwareVersionLabel(decode_string_bytes(&bytes[..bytes.len().min(32)])?)) + } + (CommandClass::GetCommandResponse, ParameterId::BootSoftwareVersionId) => { + check_msg_len!(bytes, 4); + Ok(Self::GetBootSoftwareVersionId( + u32::from_be_bytes(bytes[0..=3].try_into()?) + )) + } + (CommandClass::GetCommandResponse, ParameterId::BootSoftwareVersionLabel) => { + Ok(Self::GetBootSoftwareVersionLabel(decode_string_bytes(&bytes[..bytes.len().min(32)])?)) + } (CommandClass::GetCommandResponse, ParameterId::DmxPersonality) => { + check_msg_len!(bytes, 2); Ok(Self::GetDmxPersonality { current_personality: bytes[0], personality_count: bytes[1], }) } (CommandClass::GetCommandResponse, ParameterId::DmxPersonalityDescription) => { + check_msg_len!(bytes, 3); Ok(Self::GetDmxPersonalityDescription { id: bytes[0], dmx_slots_required: u16::from_be_bytes(bytes[1..=2].try_into()?), - description: decode_string_bytes(&bytes[3..])?, + description: decode_string_bytes(&bytes[3..bytes.len().min(3+32)])?, }) } - (CommandClass::GetCommandResponse, ParameterId::DmxStartAddress) => Ok( - Self::GetDmxStartAddress(u16::from_be_bytes(bytes[0..=1].try_into()?)), - ), + (CommandClass::GetCommandResponse, ParameterId::DmxStartAddress) => { + check_msg_len!(bytes, 2); + Ok(Self::GetDmxStartAddress( + u16::from_be_bytes(bytes[0..=1].try_into()?) + )) + } (CommandClass::GetCommandResponse, ParameterId::SlotInfo) => Ok(Self::GetSlotInfo( #[cfg(feature = "alloc")] bytes @@ -2345,9 +2377,10 @@ impl ResponseParameterData { .collect::, RdmError>>()?, )), (CommandClass::GetCommandResponse, ParameterId::SlotDescription) => { + check_msg_len!(bytes, 2); Ok(Self::GetSlotDescription { slot_id: u16::from_be_bytes(bytes[0..=1].try_into()?), - description: decode_string_bytes(&bytes[2..])?, + description: decode_string_bytes(&bytes[2..bytes.len().min(2+32)])?, }) } (CommandClass::GetCommandResponse, ParameterId::DefaultSlotValue) => { @@ -2375,6 +2408,7 @@ impl ResponseParameterData { )) } (CommandClass::GetCommandResponse, ParameterId::SensorDefinition) => { + check_msg_len!(bytes, 13); Ok(Self::GetSensorDefinition(SensorDefinition { id: bytes[0], kind: bytes[1].try_into()?, @@ -2386,10 +2420,11 @@ impl ResponseParameterData { normal_maximum_value: i16::from_be_bytes(bytes[10..=11].try_into()?), is_lowest_highest_detected_value_supported: bytes[12] >> 1 & 1 == 1, is_recorded_value_supported: bytes[12] & 1 == 1, - description: decode_string_bytes(&bytes[13..])?, + description: decode_string_bytes(&bytes[13..bytes.len().min(13+32)])?, })) } (CommandClass::GetCommandResponse, ParameterId::SensorValue) => { + check_msg_len!(bytes, 9); Ok(Self::GetSensorValue(SensorValue::new( bytes[0], i16::from_be_bytes(bytes[1..=2].try_into()?), @@ -2399,6 +2434,7 @@ impl ResponseParameterData { ))) } (CommandClass::SetCommandResponse, ParameterId::SensorValue) => { + check_msg_len!(bytes, 9); Ok(Self::SetSensorValue(SensorValue::new( bytes[0], i16::from_be_bytes(bytes[1..=2].try_into()?), @@ -2407,40 +2443,60 @@ impl ResponseParameterData { i16::from_be_bytes(bytes[7..=8].try_into()?), ))) } - (CommandClass::GetCommandResponse, ParameterId::DeviceHours) => Ok( - Self::GetDeviceHours(u32::from_be_bytes(bytes[0..=3].try_into()?)), - ), - (CommandClass::GetCommandResponse, ParameterId::LampHours) => Ok(Self::GetLampHours( - u32::from_be_bytes(bytes[0..=3].try_into()?), - )), - (CommandClass::GetCommandResponse, ParameterId::LampStrikes) => Ok( - Self::GetLampStrikes(u32::from_be_bytes(bytes[0..=3].try_into()?)), - ), + (CommandClass::GetCommandResponse, ParameterId::DeviceHours) => { + check_msg_len!(bytes, 4); + Ok(Self::GetDeviceHours( + u32::from_be_bytes(bytes[0..=3].try_into()?) + )) + } + (CommandClass::GetCommandResponse, ParameterId::LampHours) => { + check_msg_len!(bytes, 4); + Ok(Self::GetLampHours( + u32::from_be_bytes(bytes[0..=3].try_into()?), + )) + } + (CommandClass::GetCommandResponse, ParameterId::LampStrikes) => { + check_msg_len!(bytes, 4); + Ok(Self::GetLampStrikes( + u32::from_be_bytes(bytes[0..=3].try_into()?) + )) + } (CommandClass::GetCommandResponse, ParameterId::LampState) => { + check_msg_len!(bytes, 1); Ok(Self::GetLampState(bytes[0].try_into()?)) } (CommandClass::GetCommandResponse, ParameterId::LampOnMode) => { + check_msg_len!(bytes, 1); Ok(Self::GetLampOnMode(bytes[0].try_into()?)) } - (CommandClass::GetCommandResponse, ParameterId::DevicePowerCycles) => Ok( - Self::GetDevicePowerCycles(u32::from_be_bytes(bytes[0..=3].try_into()?)), - ), + (CommandClass::GetCommandResponse, ParameterId::DevicePowerCycles) => { + check_msg_len!(bytes, 4); + Ok(Self::GetDevicePowerCycles( + u32::from_be_bytes(bytes[0..=3].try_into()?) + )) + } (CommandClass::GetCommandResponse, ParameterId::DisplayInvert) => { + check_msg_len!(bytes, 1); Ok(Self::GetDisplayInvert(bytes[0].try_into()?)) } (CommandClass::GetCommandResponse, ParameterId::DisplayLevel) => { + check_msg_len!(bytes, 1); Ok(Self::GetDisplayLevel(bytes[0])) } (CommandClass::GetCommandResponse, ParameterId::PanInvert) => { + check_msg_len!(bytes, 1); Ok(Self::GetPanInvert(bytes[0] == 1)) } (CommandClass::GetCommandResponse, ParameterId::TiltInvert) => { + check_msg_len!(bytes, 1); Ok(Self::GetTiltInvert(bytes[0] == 1)) } (CommandClass::GetCommandResponse, ParameterId::PanTiltSwap) => { + check_msg_len!(bytes, 1); Ok(Self::GetPanTiltSwap(bytes[0] == 1)) } (CommandClass::GetCommandResponse, ParameterId::RealTimeClock) => { + check_msg_len!(bytes, 7); Ok(Self::GetRealTimeClock { year: u16::from_be_bytes(bytes[0..=1].try_into()?), month: bytes[2], @@ -2451,34 +2507,47 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::IdentifyDevice) => { + check_msg_len!(bytes, 1); Ok(Self::GetIdentifyDevice(bytes[0] == 1)) } (CommandClass::GetCommandResponse, ParameterId::PowerState) => { + check_msg_len!(bytes, 1); Ok(Self::GetPowerState(bytes[0].try_into()?)) } (CommandClass::GetCommandResponse, ParameterId::PerformSelfTest) => { + check_msg_len!(bytes, 1); Ok(Self::GetPerformSelfTest(bytes[0] == 1)) } (CommandClass::GetCommandResponse, ParameterId::SelfTestDescription) => { + check_msg_len!(bytes, 1); Ok(Self::GetSelfTestDescription { self_test_id: bytes[0].into(), - description: decode_string_bytes(&bytes[1..])?, + description: decode_string_bytes(&bytes[1..bytes.len().min(1+32)])?, }) } (CommandClass::GetCommandResponse, ParameterId::PresetPlayback) => { + check_msg_len!(bytes, 3); Ok(Self::GetPresetPlayback { mode: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), level: bytes[2], }) } // E1.37-1 + (CommandClass::GetCommandResponse, ParameterId::IdentifyMode) => { + check_msg_len!(bytes, 1); + Ok(Self::GetIdentifyMode( + bytes[0].try_into()? + )) + }, (CommandClass::GetCommandResponse, ParameterId::DmxBlockAddress) => { + check_msg_len!(bytes, 4); Ok(Self::GetDmxBlockAddress { total_sub_device_footprint: u16::from_be_bytes(bytes[0..=1].try_into()?), base_dmx_address: u16::from_be_bytes(bytes[2..=3].try_into()?), }) } (CommandClass::GetCommandResponse, ParameterId::DmxFailMode) => { + check_msg_len!(bytes, 7); Ok(Self::GetDmxFailMode { scene_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), loss_of_signal_delay: u16::from_be_bytes(bytes[2..=3].try_into()?).into(), @@ -2487,6 +2556,7 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::DmxStartupMode) => { + check_msg_len!(bytes, 7); Ok(Self::GetDmxStartupMode { scene_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), startup_delay: u16::from_be_bytes(bytes[2..=3].try_into()?).into(), @@ -2495,25 +2565,35 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::PowerOnSelfTest) => { + check_msg_len!(bytes, 1); Ok(Self::GetPowerOnSelfTest(bytes[0] == 1)) } - (CommandClass::GetCommandResponse, ParameterId::LockState) => Ok(Self::GetLockState { - lock_state_id: bytes[0], - lock_state_count: bytes[1], - }), + (CommandClass::GetCommandResponse, ParameterId::LockState) => { + check_msg_len!(bytes, 2); + Ok(Self::GetLockState { + lock_state_id: bytes[0], + lock_state_count: bytes[1], + }) + } (CommandClass::GetCommandResponse, ParameterId::LockStateDescription) => { + check_msg_len!(bytes, 1); Ok(Self::GetLockStateDescription { lock_state_id: bytes[0], - description: decode_string_bytes(&bytes[1..])?, + description: decode_string_bytes(&bytes[1..bytes.len().min(1+32)])?, }) } - (CommandClass::GetCommandResponse, ParameterId::LockPin) => Ok(Self::GetLockPin( - PinCode::try_from(u16::from_be_bytes(bytes[0..=1].try_into()?))?, - )), + (CommandClass::GetCommandResponse, ParameterId::LockPin) => { + check_msg_len!(bytes, 2); + Ok(Self::GetLockPin( + PinCode::try_from(u16::from_be_bytes(bytes[0..=1].try_into()?))?, + )) + } (CommandClass::GetCommandResponse, ParameterId::BurnIn) => { + check_msg_len!(bytes, 1); Ok(Self::GetBurnIn(bytes[0])) } (CommandClass::GetCommandResponse, ParameterId::DimmerInfo) => { + check_msg_len!(bytes, 11); Ok(Self::GetDimmerInfo { minimum_level_lower_limit: u16::from_be_bytes(bytes[0..=1].try_into()?), minimum_level_upper_limit: u16::from_be_bytes(bytes[2..=3].try_into()?), @@ -2525,51 +2605,64 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::MinimumLevel) => { + check_msg_len!(bytes, 5); Ok(Self::GetMinimumLevel { minimum_level_increasing: u16::from_be_bytes(bytes[0..=1].try_into()?), minimum_level_decreasing: u16::from_be_bytes(bytes[2..=3].try_into()?), on_below_minimum: bytes[4] == 1, }) } - (CommandClass::GetCommandResponse, ParameterId::MaximumLevel) => Ok( - Self::GetMaximumLevel(u16::from_be_bytes(bytes[0..=1].try_into()?)), - ), - (CommandClass::GetCommandResponse, ParameterId::Curve) => Ok(Self::GetCurve { - curve_id: bytes[0], - curve_count: bytes[1], - }), + (CommandClass::GetCommandResponse, ParameterId::MaximumLevel) => { + check_msg_len!(bytes, 2); + Ok(Self::GetMaximumLevel( + u16::from_be_bytes(bytes[0..=1].try_into()?) + )) + } + (CommandClass::GetCommandResponse, ParameterId::Curve) => { + check_msg_len!(bytes, 2); + Ok(Self::GetCurve { + curve_id: bytes[0], + curve_count: bytes[1], + }) + } (CommandClass::GetCommandResponse, ParameterId::CurveDescription) => { + check_msg_len!(bytes, 1); Ok(Self::GetCurveDescription { curve_id: bytes[0], - description: decode_string_bytes(&bytes[1..])?, + description: decode_string_bytes(&bytes[1..bytes.len().min(1+32)])?, }) } (CommandClass::GetCommandResponse, ParameterId::OutputResponseTime) => { + check_msg_len!(bytes, 2); Ok(Self::GetOutputResponseTime { response_time_id: bytes[0], response_time_count: bytes[1], }) } (CommandClass::GetCommandResponse, ParameterId::OutputResponseTimeDescription) => { + check_msg_len!(bytes, 1); Ok(Self::GetOutputResponseTimeDescription { response_time_id: bytes[0], - description: decode_string_bytes(&bytes[1..])?, + description: decode_string_bytes(&bytes[1..bytes.len().min(1+32)])?, }) } (CommandClass::GetCommandResponse, ParameterId::ModulationFrequency) => { + check_msg_len!(bytes, 2); Ok(Self::GetModulationFrequency { modulation_frequency_id: bytes[0], modulation_frequency_count: bytes[1], }) } (CommandClass::GetCommandResponse, ParameterId::ModulationFrequencyDescription) => { + check_msg_len!(bytes, 5); Ok(Self::GetModulationFrequencyDescription { modulation_frequency_id: bytes[0], frequency: u32::from_be_bytes(bytes[1..=4].try_into()?), - description: decode_string_bytes(&bytes[5..])?, + description: decode_string_bytes(&bytes[5..bytes.len().min(5+32)])?, }) } (CommandClass::GetCommandResponse, ParameterId::PresetInfo) => { + check_msg_len!(bytes, 32); Ok(Self::GetPresetInfo { level_field_supported: bytes[0] == 1, preset_sequence_supported: bytes[1] == 1, @@ -2617,6 +2710,7 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::PresetStatus) => { + check_msg_len!(bytes, 9); Ok(Self::GetPresetStatus { scene_id: u16::from_be_bytes(bytes[0..=1].try_into()?), up_fade_time: u16::from_be_bytes(bytes[2..=3].try_into()?), @@ -2626,6 +2720,7 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::PresetMergeMode) => { + check_msg_len!(bytes, 1); Ok(Self::GetPresetMergeMode(MergeMode::try_from(bytes[0])?)) } // E1.37-2 @@ -2654,30 +2749,35 @@ impl ResponseParameterData { )) } (CommandClass::GetCommandResponse, ParameterId::InterfaceLabel) => { + check_msg_len!(bytes, 4); Ok(Self::GetInterfaceLabel { interface_id: u32::from_be_bytes(bytes[0..=3].try_into()?), - interface_label: decode_string_bytes(&bytes[4..])?, + interface_label: decode_string_bytes(&bytes[4..bytes.len().min(4+32)])?, }) } (CommandClass::GetCommandResponse, ParameterId::InterfaceHardwareAddressType1) => { + check_msg_len!(bytes, 10); Ok(Self::GetInterfaceHardwareAddressType1 { interface_id: u32::from_be_bytes(bytes[0..=3].try_into()?), hardware_address: <[u8; 6]>::try_from(&bytes[4..=9])?.into(), }) } (CommandClass::GetCommandResponse, ParameterId::IpV4DhcpMode) => { + check_msg_len!(bytes, 5); Ok(Self::GetIpV4DhcpMode { interface_id: u32::from_be_bytes(bytes[0..=3].try_into()?), dhcp_mode: bytes[4] == 1, }) } (CommandClass::GetCommandResponse, ParameterId::IpV4ZeroConfMode) => { + check_msg_len!(bytes, 5); Ok(Self::GetIpV4ZeroConfMode { interface_id: u32::from_be_bytes(bytes[0..=3].try_into()?), zero_conf_mode: bytes[4] == 1, }) } (CommandClass::GetCommandResponse, ParameterId::IpV4CurrentAddress) => { + check_msg_len!(bytes, 10); Ok(Self::GetIpV4CurrentAddress { interface_id: u32::from_be_bytes(bytes[0..=3].try_into()?), address: <[u8; 4]>::try_from(&bytes[4..=7])?.into(), @@ -2686,6 +2786,7 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::IpV4StaticAddress) => { + check_msg_len!(bytes, 9); Ok(Self::GetIpV4StaticAddress { interface_id: u32::from_be_bytes(bytes[0..=3].try_into()?), address: <[u8; 4]>::try_from(&bytes[4..=7])?.into(), @@ -2693,135 +2794,173 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::IpV4DefaultRoute) => { + check_msg_len!(bytes, 8); Ok(Self::GetIpV4DefaultRoute { interface_id: u32::from_be_bytes(bytes[0..=3].try_into()?), address: <[u8; 4]>::try_from(&bytes[4..=7])?.into(), }) } (CommandClass::GetCommandResponse, ParameterId::DnsIpV4NameServer) => { + check_msg_len!(bytes, 5); Ok(Self::GetDnsIpV4NameServer { name_server_index: bytes[0], address: <[u8; 4]>::try_from(&bytes[1..=4])?.into(), }) } + (CommandClass::GetCommandResponse, ParameterId::DnsHostName) => { + Ok(Self::GetDnsHostName(decode_string_bytes(&bytes[..bytes.len().min(63)])?)) + }, + (CommandClass::GetCommandResponse, ParameterId::DnsDomainName) => { + Ok(Self::GetDnsHostName(decode_string_bytes(&bytes[..bytes.len().min(231)])?)) + }, // E1.37-7 - (CommandClass::GetCommand, ParameterId::EndpointList) => Ok(Self::GetEndpointList { - list_change_number: u32::from_be_bytes(bytes[0..=3].try_into()?), - #[cfg(feature = "alloc")] - endpoint_list: bytes[4..] - .chunks(3) - .map(|chunk| { - Ok(( - u16::from_be_bytes(chunk[0..=1].try_into()?).into(), - chunk[1].try_into()?, - )) - }) - .collect::, RdmError>>()?, - #[cfg(not(feature = "alloc"))] - endpoint_list: bytes[4..] - .chunks(6) - .map(|chunk| { - Ok(( - u16::from_be_bytes(chunk[0..=1].try_into()?).into(), - chunk[1].try_into()?, - )) - }) - .collect::, RdmError>>()?, - }), - (CommandClass::GetCommand, ParameterId::EndpointListChange) => { + (CommandClass::GetCommandResponse, ParameterId::EndpointList) => { + check_msg_len!(bytes, 4); + Ok(Self::GetEndpointList { + list_change_number: u32::from_be_bytes(bytes[0..=3].try_into()?), + #[cfg(feature = "alloc")] + endpoint_list: bytes[4..] + .chunks(3) + .map(|chunk| { + Ok(( + u16::from_be_bytes(chunk[0..=1].try_into()?).into(), + chunk[1].try_into()?, + )) + }) + .collect::, RdmError>>()?, + #[cfg(not(feature = "alloc"))] + endpoint_list: bytes[4..] + .chunks(6) + .map(|chunk| { + Ok(( + u16::from_be_bytes(chunk[0..=1].try_into()?).into(), + chunk[1].try_into()?, + )) + }) + .collect::, RdmError>>()?, + }) + } + (CommandClass::GetCommandResponse, ParameterId::EndpointListChange) => { + check_msg_len!(bytes, 4); Ok(Self::GetEndpointListChange { list_change_number: u32::from_be_bytes(bytes[0..=3].try_into()?), }) } - (CommandClass::GetCommand, ParameterId::IdentifyEndpoint) => { + (CommandClass::GetCommandResponse, ParameterId::IdentifyEndpoint) => { + check_msg_len!(bytes, 3); Ok(Self::GetIdentifyEndpoint { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), identify: bytes[2] == 1, }) } - (CommandClass::SetCommand, ParameterId::IdentifyEndpoint) => { + (CommandClass::SetCommandResponse, ParameterId::IdentifyEndpoint) => { + check_msg_len!(bytes, 2); Ok(Self::SetIdentifyEndpoint { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } - (CommandClass::GetCommand, ParameterId::EndpointToUniverse) => { + (CommandClass::GetCommandResponse, ParameterId::EndpointToUniverse) => { + check_msg_len!(bytes, 4); Ok(Self::GetEndpointToUniverse { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), universe: u16::from_be_bytes(bytes[2..=3].try_into()?), }) } - (CommandClass::SetCommand, ParameterId::EndpointToUniverse) => { + (CommandClass::SetCommandResponse, ParameterId::EndpointToUniverse) => { + check_msg_len!(bytes, 2); Ok(Self::SetEndpointToUniverse { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } - (CommandClass::GetCommand, ParameterId::EndpointMode) => Ok(Self::GetEndpointMode { - endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), - mode: bytes[2].try_into()?, - }), - (CommandClass::SetCommand, ParameterId::EndpointMode) => Ok(Self::SetEndpointMode { - endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), - }), - (CommandClass::GetCommand, ParameterId::EndpointLabel) => Ok(Self::GetEndpointLabel { - endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), - label: decode_string_bytes(&bytes[2..])?, - }), - (CommandClass::SetCommand, ParameterId::EndpointLabel) => Ok(Self::SetEndpointLabel { - endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), - }), - (CommandClass::GetCommand, ParameterId::RdmTrafficEnable) => { + (CommandClass::GetCommandResponse, ParameterId::EndpointMode) => { + check_msg_len!(bytes, 3); + Ok(Self::GetEndpointMode { + endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), + mode: bytes[2].try_into()?, + }) + } + (CommandClass::SetCommandResponse, ParameterId::EndpointMode) => { + check_msg_len!(bytes, 2); + Ok(Self::SetEndpointMode { + endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), + }) + } + (CommandClass::GetCommandResponse, ParameterId::EndpointLabel) => { + check_msg_len!(bytes, 2); + Ok(Self::GetEndpointLabel { + endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), + label: decode_string_bytes(&bytes[2..bytes.len().min(1+32)])?, + }) + } + (CommandClass::SetCommandResponse, ParameterId::EndpointLabel) => { + check_msg_len!(bytes, 2); + Ok(Self::SetEndpointLabel { + endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), + }) + } + (CommandClass::GetCommandResponse, ParameterId::RdmTrafficEnable) => { + check_msg_len!(bytes, 3); Ok(Self::GetRdmTrafficEnable { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), enable: bytes[2] == 1, }) } - (CommandClass::SetCommand, ParameterId::RdmTrafficEnable) => { + (CommandClass::SetCommandResponse, ParameterId::RdmTrafficEnable) => { + check_msg_len!(bytes, 2); Ok(Self::SetRdmTrafficEnable { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } - (CommandClass::GetCommand, ParameterId::DiscoveryState) => { + (CommandClass::GetCommandResponse, ParameterId::DiscoveryState) => { + check_msg_len!(bytes, 5); Ok(Self::GetDiscoveryState { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), device_count: u16::from_be_bytes(bytes[2..=3].try_into()?).into(), discovery_state: bytes[4].try_into()?, }) } - (CommandClass::SetCommand, ParameterId::DiscoveryState) => { + (CommandClass::SetCommandResponse, ParameterId::DiscoveryState) => { + check_msg_len!(bytes, 2); Ok(Self::SetDiscoveryState { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } - (CommandClass::GetCommand, ParameterId::BackgroundDiscovery) => { + (CommandClass::GetCommandResponse, ParameterId::BackgroundDiscovery) => { + check_msg_len!(bytes, 3); Ok(Self::GetBackgroundDiscovery { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), enabled: bytes[2] == 1, }) } - (CommandClass::SetCommand, ParameterId::BackgroundDiscovery) => { + (CommandClass::SetCommandResponse, ParameterId::BackgroundDiscovery) => { + check_msg_len!(bytes, 2); Ok(Self::SetBackgroundDiscovery { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } - (CommandClass::GetCommand, ParameterId::EndpointTiming) => { + (CommandClass::GetCommandResponse, ParameterId::EndpointTiming) => { + check_msg_len!(bytes, 4); Ok(Self::GetEndpointTiming { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), current_setting_id: bytes[2], setting_count: bytes[3], }) } - (CommandClass::SetCommand, ParameterId::EndpointTiming) => { + (CommandClass::SetCommandResponse, ParameterId::EndpointTiming) => { + check_msg_len!(bytes, 2); Ok(Self::SetEndpointTiming { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), }) } - (CommandClass::GetCommand, ParameterId::EndpointTimingDescription) => { + (CommandClass::GetCommandResponse, ParameterId::EndpointTimingDescription) => { + check_msg_len!(bytes, 1); Ok(Self::GetEndpointTimingDescription { setting_id: bytes[0], - description: decode_string_bytes(&bytes[1..])?, + description: decode_string_bytes(&bytes[1..bytes.len().min(1+32)])?, }) } - (CommandClass::GetCommand, ParameterId::EndpointResponders) => { + (CommandClass::GetCommandResponse, ParameterId::EndpointResponders) => { + check_msg_len!(bytes, 6); Ok(Self::GetEndpointResponders { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), list_change_number: u32::from_be_bytes(bytes[2..=5].try_into()?), @@ -2847,13 +2986,15 @@ impl ResponseParameterData { .collect::, RdmError>>()?, }) } - (CommandClass::GetCommand, ParameterId::EndpointResponderListChange) => { + (CommandClass::GetCommandResponse, ParameterId::EndpointResponderListChange) => { + check_msg_len!(bytes, 6); Ok(Self::GetEndpointResponderListChange { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), list_change_number: u32::from_be_bytes(bytes[2..=5].try_into()?), }) } - (CommandClass::GetCommand, ParameterId::BindingControlFields) => { + (CommandClass::GetCommandResponse, ParameterId::BindingControlFields) => { + check_msg_len!(bytes, 16); Ok(Self::GetBindingControlFields { endpoint_id: u16::from_be_bytes(bytes[0..=1].try_into()?).into(), uid: DeviceUID::new( @@ -2867,13 +3008,15 @@ impl ResponseParameterData { ), }) } - (CommandClass::GetCommand, ParameterId::BackgroundQueuedStatusPolicy) => { + (CommandClass::GetCommandResponse, ParameterId::BackgroundQueuedStatusPolicy) => { + check_msg_len!(bytes, 2); Ok(Self::GetBackgroundQueuedStatusPolicy { current_policy_id: bytes[0], policy_count: bytes[1], }) } - (CommandClass::GetCommand, ParameterId::BackgroundQueuedStatusPolicyDescription) => { + (CommandClass::GetCommandResponse, ParameterId::BackgroundQueuedStatusPolicyDescription) => { + check_msg_len!(bytes, 1); Ok(Self::GetBackgroundQueuedStatusPolicyDescription { policy_id: bytes[0], description: decode_string_bytes(&bytes[1..])?, @@ -2881,6 +3024,7 @@ impl ResponseParameterData { } // E1.33 (CommandClass::GetCommandResponse, ParameterId::ComponentScope) => { + check_msg_len!(bytes, 86); Ok(Self::GetComponentScope { scope_slot: u16::from_be_bytes(bytes[0..=1].try_into()?), scope_string: decode_string_bytes(&bytes[2..=64])?, @@ -2891,9 +3035,10 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::SearchDomain) => { - Ok(Self::GetSearchDomain(decode_string_bytes(bytes)?)) + Ok(Self::GetSearchDomain(decode_string_bytes(&bytes[..bytes.len().min(231)])?)) } (CommandClass::GetCommandResponse, ParameterId::TcpCommsStatus) => { + check_msg_len!(bytes, 87); Ok(Self::GetTcpCommsStatus { scope_string: decode_string_bytes(&bytes[0..=62])?, broker_ipv4_address: <[u8; 4]>::try_from(&bytes[63..=66])?.into(), @@ -2903,6 +3048,7 @@ impl ResponseParameterData { }) } (CommandClass::GetCommandResponse, ParameterId::BrokerStatus) => { + check_msg_len!(bytes, 2); Ok(Self::GetBrokerStatus { is_allowing_set_commands: bytes[0] == 1, broker_state: bytes[1].try_into()?, @@ -2910,15 +3056,15 @@ impl ResponseParameterData { } (_, ParameterId::ManufacturerSpecific(_)) => Ok(Self::ManufacturerSpecific( #[cfg(feature = "alloc")] - bytes.to_vec(), + bytes[..bytes.len().min(231)].to_vec(), #[cfg(not(feature = "alloc"))] - Vec::::from_slice(bytes).unwrap(), + Vec::::from_slice(&bytes[..bytes.len().min(231)]).unwrap(), )), (_, _) => Ok(Self::Unsupported( #[cfg(feature = "alloc")] - bytes.to_vec(), + bytes[..bytes.len().min(231)].to_vec(), #[cfg(not(feature = "alloc"))] - Vec::::from_slice(bytes).unwrap(), + Vec::::from_slice(&bytes[..bytes.len().min(231)]).unwrap(), )), } } diff --git a/src/rdm/utils.rs b/src/rdm/utils.rs new file mode 100644 index 0000000..8e547f7 --- /dev/null +++ b/src/rdm/utils.rs @@ -0,0 +1,9 @@ + +#[macro_export] +macro_rules! check_msg_len { + ($msg:ident, $min_len:literal) => { + if $msg.len() < $min_len { + return Err(RdmError::InvalidMessageLength($msg.len() as u8)); + } + }; +}