Skip to main content

rusty_modbus_codec/request/
reg_write.rs

1//! Register-access write requests: FC 06, 10, 16, 17.
2
3use rusty_modbus_types::{Address, FunctionCode, Quantity};
4
5use crate::error::{DecodeError, EncodeError};
6use crate::request::Encode;
7
8/// FC 0x06 — Write Single Register request.
9///
10/// Writes a single 16-bit value to the holding register at `address`.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct WriteSingleRegisterRequest {
13    /// Register address (0-indexed).
14    pub address: Address,
15    /// Value to write.
16    pub value: u16,
17}
18
19impl WriteSingleRegisterRequest {
20    /// Decode from PDU data after the function code byte.
21    ///
22    /// # Errors
23    ///
24    /// Returns [`DecodeError::Truncated`] if `data` is shorter than 4 bytes.
25    /// Returns [`DecodeError::LengthMismatch`] if `data` has extra bytes.
26    pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
27        DecodeError::check_exact_len(data, 4)?;
28        let address = Address(u16::from_be_bytes([data[0], data[1]]));
29        let value = u16::from_be_bytes([data[2], data[3]]);
30        Ok(Self { address, value })
31    }
32}
33
34impl Encode for WriteSingleRegisterRequest {
35    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
36        let len = self.encoded_len();
37        if buf.len() < len {
38            return Err(EncodeError::BufferTooSmall {
39                required: len,
40                available: buf.len(),
41            });
42        }
43        EncodeError::check_pdu_len(len)?;
44        buf[0] = FunctionCode::WriteSingleRegister.code();
45        buf[1..3].copy_from_slice(&self.address.0.to_be_bytes());
46        buf[3..5].copy_from_slice(&self.value.to_be_bytes());
47        Ok(len)
48    }
49
50    fn encoded_len(&self) -> usize {
51        5
52    }
53}
54
55/// FC 0x10 — Write Multiple Registers request.
56///
57/// Writes 1..=123 contiguous holding registers starting at `address`.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct WriteMultipleRegistersRequest<'buf> {
60    /// Starting address (0-indexed).
61    pub address: Address,
62    /// Number of registers to write (1..=123).
63    pub quantity: Quantity,
64    /// Number of data bytes that follow (should be `quantity * 2`).
65    pub byte_count: u8,
66    /// Register values in big-endian byte order.
67    pub register_values: &'buf [u8],
68}
69
70impl<'buf> WriteMultipleRegistersRequest<'buf> {
71    /// Maximum quantity for Write Multiple Registers.
72    const MAX_QUANTITY: u16 = 123;
73
74    /// Decode from PDU data after the function code byte.
75    ///
76    /// # Errors
77    ///
78    /// Returns [`DecodeError::Truncated`] if `data` is too short.
79    /// Returns [`DecodeError::QuantityOutOfRange`] if the quantity is not in 1..=123.
80    /// Returns [`DecodeError::ByteCountMismatch`] if the declared byte count does not
81    /// match the remaining data length.
82    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
83        if data.len() < 5 {
84            return Err(DecodeError::Truncated {
85                expected: 5,
86                actual: data.len(),
87            });
88        }
89        let address = Address(u16::from_be_bytes([data[0], data[1]]));
90        let quantity = u16::from_be_bytes([data[2], data[3]]);
91        if quantity == 0 || quantity > Self::MAX_QUANTITY {
92            return Err(DecodeError::QuantityOutOfRange { quantity });
93        }
94        let byte_count = data[4];
95        // Semantic check per spec Figure 22: byte_count must equal quantity × 2.
96        if u16::from(byte_count) != quantity * 2 {
97            return Err(DecodeError::ByteCountMismatch {
98                declared: usize::from(byte_count),
99                actual: (quantity * 2) as usize,
100            });
101        }
102        // Wire check: declared byte_count must match actual remaining data.
103        let remaining = data.len() - 5;
104        if byte_count as usize != remaining {
105            return Err(DecodeError::ByteCountMismatch {
106                declared: byte_count as usize,
107                actual: remaining,
108            });
109        }
110        let register_values = &data[5..];
111        Ok(Self {
112            address,
113            quantity: Quantity(quantity),
114            byte_count,
115            register_values,
116        })
117    }
118}
119
120impl Encode for WriteMultipleRegistersRequest<'_> {
121    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
122        let len = self.encoded_len();
123        if buf.len() < len {
124            return Err(EncodeError::BufferTooSmall {
125                required: len,
126                available: buf.len(),
127            });
128        }
129        EncodeError::check_quantity(self.quantity.0, Self::MAX_QUANTITY)?;
130        let expected_bytes = usize::from(self.quantity.0) * 2;
131        EncodeError::check_byte_count(usize::from(self.byte_count), expected_bytes)?;
132        EncodeError::check_byte_count(expected_bytes, self.register_values.len())?;
133        EncodeError::check_pdu_len(len)?;
134        buf[0] = FunctionCode::WriteMultipleRegisters.code();
135        buf[1..3].copy_from_slice(&self.address.0.to_be_bytes());
136        buf[3..5].copy_from_slice(&self.quantity.0.to_be_bytes());
137        buf[5] = self.byte_count;
138        buf[6..6 + self.register_values.len()].copy_from_slice(self.register_values);
139        Ok(len)
140    }
141
142    fn encoded_len(&self) -> usize {
143        // FC(1) + address(2) + quantity(2) + byte_count(1) + register_values
144        6 + self.register_values.len()
145    }
146}
147
148/// FC 0x16 — Mask Write Register request.
149///
150/// Modifies a single holding register using AND and OR masks:
151/// `result = (current AND and_mask) OR (or_mask AND (NOT and_mask))`.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct MaskWriteRegisterRequest {
154    /// Register address (0-indexed).
155    pub address: Address,
156    /// AND mask.
157    pub and_mask: u16,
158    /// OR mask.
159    pub or_mask: u16,
160}
161
162impl MaskWriteRegisterRequest {
163    /// Decode from PDU data after the function code byte.
164    ///
165    /// # Errors
166    ///
167    /// Returns [`DecodeError::Truncated`] if `data` is shorter than 6 bytes.
168    /// Returns [`DecodeError::LengthMismatch`] if `data` has extra bytes.
169    pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
170        DecodeError::check_exact_len(data, 6)?;
171        let address = Address(u16::from_be_bytes([data[0], data[1]]));
172        let and_mask = u16::from_be_bytes([data[2], data[3]]);
173        let or_mask = u16::from_be_bytes([data[4], data[5]]);
174        Ok(Self {
175            address,
176            and_mask,
177            or_mask,
178        })
179    }
180}
181
182impl Encode for MaskWriteRegisterRequest {
183    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
184        let len = self.encoded_len();
185        if buf.len() < len {
186            return Err(EncodeError::BufferTooSmall {
187                required: len,
188                available: buf.len(),
189            });
190        }
191        EncodeError::check_pdu_len(len)?;
192        buf[0] = FunctionCode::MaskWriteRegister.code();
193        buf[1..3].copy_from_slice(&self.address.0.to_be_bytes());
194        buf[3..5].copy_from_slice(&self.and_mask.to_be_bytes());
195        buf[5..7].copy_from_slice(&self.or_mask.to_be_bytes());
196        Ok(len)
197    }
198
199    fn encoded_len(&self) -> usize {
200        7
201    }
202}
203
204/// FC 0x17 — Read/Write Multiple Registers request.
205///
206/// Atomically reads `read_quantity` registers starting at `read_address` and writes
207/// `write_quantity` registers starting at `write_address`.
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub struct ReadWriteMultipleRegistersRequest<'buf> {
210    /// Starting address for reading (0-indexed).
211    pub read_address: Address,
212    /// Number of registers to read (1..=125).
213    pub read_quantity: Quantity,
214    /// Starting address for writing (0-indexed).
215    pub write_address: Address,
216    /// Number of registers to write (1..=121).
217    pub write_quantity: Quantity,
218    /// Number of bytes in the write data (should be `write_quantity * 2`).
219    pub write_byte_count: u8,
220    /// Register values to write in big-endian byte order.
221    pub write_register_values: &'buf [u8],
222}
223
224impl<'buf> ReadWriteMultipleRegistersRequest<'buf> {
225    /// Maximum read quantity.
226    const MAX_READ_QUANTITY: u16 = 125;
227    /// Maximum write quantity.
228    const MAX_WRITE_QUANTITY: u16 = 121;
229
230    /// Decode from PDU data after the function code byte.
231    ///
232    /// # Errors
233    ///
234    /// Returns [`DecodeError::Truncated`] if `data` is too short.
235    /// Returns [`DecodeError::QuantityOutOfRange`] if either quantity is out of range.
236    /// Returns [`DecodeError::ByteCountMismatch`] if the declared byte count does not
237    /// match the remaining data length.
238    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
239        if data.len() < 9 {
240            return Err(DecodeError::Truncated {
241                expected: 9,
242                actual: data.len(),
243            });
244        }
245        let read_address = Address(u16::from_be_bytes([data[0], data[1]]));
246        let read_quantity = u16::from_be_bytes([data[2], data[3]]);
247        if read_quantity == 0 || read_quantity > Self::MAX_READ_QUANTITY {
248            return Err(DecodeError::QuantityOutOfRange {
249                quantity: read_quantity,
250            });
251        }
252        let write_address = Address(u16::from_be_bytes([data[4], data[5]]));
253        let write_quantity = u16::from_be_bytes([data[6], data[7]]);
254        if write_quantity == 0 || write_quantity > Self::MAX_WRITE_QUANTITY {
255            return Err(DecodeError::QuantityOutOfRange {
256                quantity: write_quantity,
257            });
258        }
259        let write_byte_count = data[8];
260        // Semantic check per spec Figure 27: byte_count must equal write_quantity × 2.
261        if u16::from(write_byte_count) != write_quantity * 2 {
262            return Err(DecodeError::ByteCountMismatch {
263                declared: usize::from(write_byte_count),
264                actual: (write_quantity * 2) as usize,
265            });
266        }
267        // Wire check: declared byte_count must match actual remaining data.
268        let remaining = data.len() - 9;
269        if write_byte_count as usize != remaining {
270            return Err(DecodeError::ByteCountMismatch {
271                declared: write_byte_count as usize,
272                actual: remaining,
273            });
274        }
275        let write_register_values = &data[9..];
276        Ok(Self {
277            read_address,
278            read_quantity: Quantity(read_quantity),
279            write_address,
280            write_quantity: Quantity(write_quantity),
281            write_byte_count,
282            write_register_values,
283        })
284    }
285}
286
287impl Encode for ReadWriteMultipleRegistersRequest<'_> {
288    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
289        let len = self.encoded_len();
290        if buf.len() < len {
291            return Err(EncodeError::BufferTooSmall {
292                required: len,
293                available: buf.len(),
294            });
295        }
296        EncodeError::check_quantity(self.read_quantity.0, Self::MAX_READ_QUANTITY)?;
297        EncodeError::check_quantity(self.write_quantity.0, Self::MAX_WRITE_QUANTITY)?;
298        let expected_bytes = usize::from(self.write_quantity.0) * 2;
299        EncodeError::check_byte_count(usize::from(self.write_byte_count), expected_bytes)?;
300        EncodeError::check_byte_count(expected_bytes, self.write_register_values.len())?;
301        EncodeError::check_pdu_len(len)?;
302        buf[0] = FunctionCode::ReadWriteMultipleRegisters.code();
303        buf[1..3].copy_from_slice(&self.read_address.0.to_be_bytes());
304        buf[3..5].copy_from_slice(&self.read_quantity.0.to_be_bytes());
305        buf[5..7].copy_from_slice(&self.write_address.0.to_be_bytes());
306        buf[7..9].copy_from_slice(&self.write_quantity.0.to_be_bytes());
307        buf[9] = self.write_byte_count;
308        buf[10..10 + self.write_register_values.len()].copy_from_slice(self.write_register_values);
309        Ok(len)
310    }
311
312    fn encoded_len(&self) -> usize {
313        // FC(1) + read_addr(2) + read_qty(2) + write_addr(2) + write_qty(2) + byte_count(1) + data
314        10 + self.write_register_values.len()
315    }
316}