dmx_rdm/
rdm_data.rs

1use crate::command_class::{RequestCommandClass, ResponseCommandClass};
2use crate::consts::{
3    RDM_DISCOVERY_RESPONSE_SIZE, RDM_MAX_PACKAGE_SIZE, RDM_MAX_PARAMETER_DATA_LENGTH,
4    RDM_MIN_PACKAGE_SIZE, SC_RDM, SC_SUB_MESSAGE, SEPARATOR_BYTE,
5};
6use crate::layouts::rdm_request_layout;
7use crate::types::{DataPack, ResponseType};
8use crate::unique_identifier::{PackageAddress, UniqueIdentifier};
9use crate::utils::calculate_checksum;
10
11/// Binary representation of an RDM package.
12pub type BinaryRdmPackage = heapless::Vec<u8, RDM_MAX_PACKAGE_SIZE>;
13
14/// Error that gets raised when attempting to convert an [RdmRequestData] object
15/// to a [RdmResponseData] object that contains a broadcast destination address.
16#[derive(Debug)]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18pub struct IsBroadcastError;
19
20impl core::fmt::Display for IsBroadcastError {
21    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22        write!(f, "tried to convert broadcast request to response")
23    }
24}
25
26#[cfg(feature = "std")]
27impl std::error::Error for IsBroadcastError {}
28
29/// An RDM Request package that does not have its parameter data deserialized.
30#[derive(Debug)]
31pub struct RdmRequestData {
32    pub destination_uid: PackageAddress,
33    pub source_uid: UniqueIdentifier,
34    pub transaction_number: u8,
35    pub port_id: u8,
36    pub message_count: u8,
37    pub sub_device: u16,
38    pub command_class: RequestCommandClass,
39    pub parameter_id: u16,
40    pub parameter_data: DataPack,
41}
42
43impl RdmRequestData {
44    pub fn build_response(
45        &self,
46        response_type: ResponseType,
47        response: DataPack,
48        message_count: u8,
49    ) -> Result<RdmResponseData, IsBroadcastError> {
50        Ok(RdmResponseData {
51            destination_uid: PackageAddress::Device(self.source_uid),
52            source_uid: match self.destination_uid {
53                PackageAddress::Device(uid) => uid,
54                _ => return Err(IsBroadcastError),
55            },
56            transaction_number: self.transaction_number,
57            response_type,
58            message_count,
59            sub_device: self.sub_device,
60            command_class: self.command_class.get_response_class(),
61            parameter_id: self.parameter_id,
62            parameter_data: response,
63        })
64    }
65}
66
67/// An RDM Response package that does not have its parameter data deserialized.
68#[derive(Debug, Clone)]
69pub struct RdmResponseData {
70    pub destination_uid: PackageAddress,
71    pub source_uid: UniqueIdentifier,
72    pub transaction_number: u8,
73    pub response_type: ResponseType,
74    pub message_count: u8,
75    pub sub_device: u16,
76    pub command_class: ResponseCommandClass,
77    pub parameter_id: u16,
78    pub parameter_data: DataPack,
79}
80
81#[derive(Debug, Copy, Clone, Eq, PartialEq)]
82#[cfg_attr(feature = "defmt", derive(defmt::Format))]
83pub enum RdmDeserializationError {
84    /// Buffer must be at least 22 bytes
85    BufferTooSmall,
86    /// Buffer must be at most 257 bytes
87    BufferTooBig,
88    /// The command class was not found; contains contents of command class field
89    CommandClassNotFound(u8),
90    /// The response type was not found; contains contents of response type field
91    ResponseTypeNotFound(u8),
92    /// The message length field is incorrect; contains result of parsing
93    WrongMessageLength(usize),
94    /// Wrong checksum; contains result of parsing
95    WrongChecksum,
96    /// Received wrong start code (0xCC) or sub start code (0x01); contains result of parsing
97    WrongStartCode,
98    /// The source uid is a broadcast address.
99    SourceUidIsBroadcast,
100}
101
102impl core::fmt::Display for RdmDeserializationError {
103    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104        match self {
105            RdmDeserializationError::BufferTooSmall => write!(f, "buffer too small"),
106            RdmDeserializationError::BufferTooBig => write!(f, "buffer to big"),
107            RdmDeserializationError::CommandClassNotFound(command_class) => {
108                write!(f, "command class {} not found", command_class)
109            },
110            RdmDeserializationError::ResponseTypeNotFound(response_type) => {
111                write!(f, "response type {} is unknown", response_type)
112            },
113            RdmDeserializationError::WrongMessageLength(message_length) => {
114                write!(f, "message length {} is incorrect", message_length)
115            },
116            RdmDeserializationError::WrongChecksum => write!(f, "checksum is incorrect"),
117            RdmDeserializationError::WrongStartCode => write!(f, "start code is incorrect"),
118            RdmDeserializationError::SourceUidIsBroadcast => write!(f, "source uid is a broadcast"),
119        }
120    }
121}
122
123#[cfg(feature = "std")]
124impl std::error::Error for RdmDeserializationError {}
125
126/// The result of deserializing an RDM package
127#[derive(Debug)]
128pub enum RdmData {
129    /// The deserialized RDM package is a request
130    Request(RdmRequestData),
131    /// The deserialized RDM package is a response
132    Response(RdmResponseData),
133}
134
135impl RdmData {
136    /// Convenience function for deserializing an RDM package.
137    /// Refer to [deserialize_rdm_data].
138    pub fn deserialize(buf: &[u8]) -> Result<Self, RdmDeserializationError> {
139        deserialize_rdm_data(buf)
140    }
141
142    /// Convenience function for serializing an RDM package.
143    /// Refer to [serialize_rdm_data].
144    pub fn serialize(&self) -> BinaryRdmPackage {
145        serialize_rdm_data(self)
146    }
147}
148
149/// Deserialize rdm data.
150/// Buffer must be between 22 and 257 bytes.
151/// This won't work for discovery responses. For this refer to [deserialize_discovery_response].
152pub fn deserialize_rdm_data(buffer: &[u8]) -> Result<RdmData, RdmDeserializationError> {
153    let buffer_size = buffer.len();
154
155    if buffer_size < RDM_MIN_PACKAGE_SIZE {
156        return Err(RdmDeserializationError::BufferTooSmall);
157    }
158
159    if buffer_size > RDM_MAX_PACKAGE_SIZE {
160        return Err(RdmDeserializationError::BufferTooBig);
161    }
162
163    // Exclude checksum field
164    let expected_checksum = calculate_checksum(&buffer[..buffer_size - 2]);
165    let actual_checksum =
166        u16::from_be_bytes(buffer[buffer_size - 2..buffer_size].try_into().unwrap());
167
168    if expected_checksum != actual_checksum {
169        return Err(RdmDeserializationError::WrongChecksum);
170    }
171
172    let request_data_view = rdm_request_layout::View::new(buffer);
173
174    if request_data_view.start_code().read() != SC_RDM
175        || request_data_view.sub_start_code().read() != SC_SUB_MESSAGE
176    {
177        return Err(RdmDeserializationError::WrongStartCode);
178    }
179
180    // exclude checksum
181    let message_length = request_data_view.message_length().read() as usize;
182    if message_length != buffer_size - 2 {
183        return Err(RdmDeserializationError::WrongMessageLength(message_length));
184    }
185
186    let parameter_data_and_checksum = request_data_view.parameter_data_and_checksum();
187    // Redundant check 😉
188    let parameter_data =
189        DataPack::from_slice(&parameter_data_and_checksum[..parameter_data_and_checksum.len() - 2])
190            .map_err(|_| RdmDeserializationError::BufferTooBig)?;
191
192    let command_class_field = request_data_view.command_class().read();
193    let is_request = RequestCommandClass::try_from(command_class_field).is_ok();
194
195    let rdm_data = if is_request {
196        RdmData::Request(RdmRequestData {
197            destination_uid: PackageAddress::from_bytes(request_data_view.destination_uid()),
198            source_uid: match PackageAddress::from_bytes(request_data_view.source_uid()) {
199                PackageAddress::Device(device_uid) => device_uid,
200                _ => return Err(RdmDeserializationError::SourceUidIsBroadcast),
201            },
202            transaction_number: request_data_view.transaction_number().read(),
203            port_id: request_data_view.port_id_response_type().read(),
204            message_count: request_data_view.message_count().read(),
205            sub_device: request_data_view.sub_device().read(),
206            command_class: command_class_field
207                .try_into()
208                .map_err(|_| RdmDeserializationError::CommandClassNotFound(command_class_field))?,
209            parameter_id: request_data_view.parameter_id().read(),
210            parameter_data,
211        })
212    } else {
213        let response_type_field = request_data_view.port_id_response_type().read();
214        let response_type = response_type_field
215            .try_into()
216            .map_err(|_| RdmDeserializationError::ResponseTypeNotFound(response_type_field))?;
217
218        RdmData::Response(RdmResponseData {
219            destination_uid: PackageAddress::from_bytes(request_data_view.destination_uid()),
220            source_uid: match PackageAddress::from_bytes(request_data_view.source_uid()) {
221                PackageAddress::Device(uid) => uid,
222                _ => return Err(RdmDeserializationError::SourceUidIsBroadcast),
223            },
224            transaction_number: request_data_view.transaction_number().read(),
225            response_type,
226            message_count: request_data_view.message_count().read(),
227            sub_device: request_data_view.sub_device().read(),
228            command_class: command_class_field
229                .try_into()
230                .map_err(|_| RdmDeserializationError::CommandClassNotFound(command_class_field))?,
231            parameter_id: request_data_view.parameter_id().read(),
232            parameter_data,
233        })
234    };
235
236    Ok(rdm_data)
237}
238
239/// Serializes RDM data to a binary Vec.
240pub fn serialize_rdm_data(rdm_data: &RdmData) -> BinaryRdmPackage {
241    let mut dst = [0u8; RDM_MAX_PACKAGE_SIZE];
242
243    let parameter_data_length = match rdm_data {
244        RdmData::Request(ref request) => request.parameter_data.len(),
245        RdmData::Response(ref response) => response.parameter_data.len(),
246    };
247    assert!(parameter_data_length <= RDM_MAX_PARAMETER_DATA_LENGTH);
248
249    // parameter data length + all other fields including checksum
250    let total_package_length = parameter_data_length + 26;
251    let mut memory_view = rdm_request_layout::View::new(&mut dst[..total_package_length]);
252
253    memory_view.start_code_mut().write(SC_RDM);
254    memory_view.sub_start_code_mut().write(SC_SUB_MESSAGE);
255
256    // 24 is the size of all the fields besides parameter_data excluding the checksum
257    memory_view
258        .message_length_mut()
259        .write(parameter_data_length as u8 + 24);
260
261    match rdm_data {
262        RdmData::Request(request) => {
263            memory_view
264                .destination_uid_mut()
265                .copy_from_slice(&request.destination_uid.to_bytes());
266            memory_view
267                .source_uid_mut()
268                .copy_from_slice(&request.source_uid.to_bytes());
269
270            memory_view
271                .transaction_number_mut()
272                .write(request.transaction_number);
273            memory_view
274                .port_id_response_type_mut()
275                .write(request.port_id);
276            memory_view.sub_device_mut().write(request.sub_device);
277            memory_view
278                .command_class_mut()
279                .write(request.command_class as u8);
280            memory_view.parameter_id_mut().write(request.parameter_id);
281            memory_view
282                .parameter_data_length_mut()
283                .write(parameter_data_length as u8);
284
285            memory_view.parameter_data_and_checksum_mut()[..parameter_data_length]
286                .copy_from_slice(&request.parameter_data);
287            let checksum = calculate_checksum(&dst[..total_package_length - 2]);
288            dst[total_package_length - 2..total_package_length]
289                .copy_from_slice(&checksum.to_be_bytes());
290        },
291        RdmData::Response(response) => {
292            memory_view
293                .destination_uid_mut()
294                .copy_from_slice(&response.destination_uid.to_bytes());
295            memory_view
296                .source_uid_mut()
297                .copy_from_slice(&response.source_uid.to_bytes());
298
299            memory_view
300                .transaction_number_mut()
301                .write(response.transaction_number);
302            memory_view
303                .port_id_response_type_mut()
304                .write(response.response_type as u8);
305            memory_view.sub_device_mut().write(response.sub_device);
306            memory_view
307                .command_class_mut()
308                .write(response.command_class as u8);
309            memory_view.parameter_id_mut().write(response.parameter_id);
310            memory_view
311                .parameter_data_length_mut()
312                .write(parameter_data_length as u8);
313
314            memory_view.parameter_data_and_checksum_mut()[..parameter_data_length]
315                .copy_from_slice(&response.parameter_data);
316            let checksum = calculate_checksum(&dst[..total_package_length - 2]);
317            dst[total_package_length - 2..total_package_length]
318                .copy_from_slice(&checksum.to_be_bytes());
319        },
320    }
321
322    // In the industry we call this a pro gamer move.
323    heapless::Vec::from_slice(&dst[..total_package_length]).unwrap()
324}
325
326/// Returns received device id if there is no collision.
327pub fn deserialize_discovery_response(
328    buffer: &[u8],
329) -> Result<UniqueIdentifier, RdmDeserializationError> {
330    let index_of_separator_byte = match buffer.iter().position(|&x| x == SEPARATOR_BYTE) {
331        None => {
332            return Err(RdmDeserializationError::WrongStartCode); // idk
333        },
334        Some(index) => index,
335    };
336
337    let start_index = index_of_separator_byte + 1;
338    let message_length = buffer.len() - start_index;
339    if message_length < RDM_DISCOVERY_RESPONSE_SIZE {
340        return Err(RdmDeserializationError::WrongMessageLength(message_length));
341    }
342
343    let calculated_checksum = calculate_checksum(&buffer[start_index..start_index + 12]);
344
345    let mut device_id_buf = [0u8; 6];
346    decode_disc_unique(&buffer[start_index..start_index + 12], &mut device_id_buf);
347    let uid = match PackageAddress::from_bytes(&device_id_buf) {
348        PackageAddress::Device(uid) => uid,
349        _ => return Err(RdmDeserializationError::SourceUidIsBroadcast),
350    };
351
352    let mut checksum_buf = [0u8; 2];
353    decode_disc_unique(
354        &buffer[start_index + 12..start_index + 16],
355        &mut checksum_buf,
356    );
357    let received_checksum = u16::from_be_bytes(checksum_buf);
358
359    if calculated_checksum != received_checksum {
360        return Err(RdmDeserializationError::WrongChecksum);
361    }
362
363    Ok(uid)
364}
365
366/// Decode a discovery package. The destination has to be at least half the source size.
367fn decode_disc_unique(src: &[u8], dest: &mut [u8]) {
368    assert!(
369        dest.len() * 2 >= src.len(),
370        "Destination buffer has to be at least half the size of the source buffer."
371    );
372
373    for (index, byte) in src.chunks(2).map(|chunk| chunk[0] & chunk[1]).enumerate() {
374        dest[index] = byte;
375    }
376}