modbius_core/
functions.rs1use core::{convert::From, mem};
8
9pub fn get_function(data: &[u8]) -> (Option<ModbusFunction>, Option<&[u8]>) {
13 (
14 data.get(0).map(|byte| ModbusFunction::new(*byte)),
15 data.get(1..),
16 )
17}
18
19pub unsafe fn get_function_unchecked(data: &[u8]) -> (ModbusFunction, &[u8]) {
24 (
25 ModbusFunction::new(*data.get_unchecked(0)),
26 data.get_unchecked(1..),
27 )
28}
29
30#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
34pub struct ModbusFunction(pub u8);
35
36impl ModbusFunction {
37 pub const fn new(function_code: u8) -> Self {
38 Self(function_code)
39 }
40
41 pub const fn new_public(public_function: PublicModbusFunction) -> Self {
43 Self(public_function as u8)
44 }
45
46 pub const fn is(self, public_function: PublicModbusFunction) -> bool {
48 self.0 == Self::new_public(public_function).0
49 }
50
51 pub const fn is_valid(self) -> bool {
53 self.0 != 0
54 }
55
56 pub const fn is_public(self) -> bool {
61 PublicModbusFunction::is_public_function(self.0)
62 }
63
64 pub const fn is_public_reserved(self) -> bool {
66 self.is_valid() && !self.is_custom()
67 }
68
69 pub const fn is_custom(self) -> bool {
71 self.0 >= 65 && self.0 <= 72 || self.0 >= 100 && self.0 <= 110
72 }
73
74 pub const fn is_exception(self) -> bool {
76 self.0 >= 128
77 }
78
79 pub fn from_data(data: &[u8]) -> (Option<Self>, Option<&[u8]>) {
83 get_function(data)
84 }
85
86 pub unsafe fn from_data_unchecked(data: &[u8]) -> (Self, &[u8]) {
91 get_function_unchecked(data)
92 }
93}
94
95impl PartialEq<PublicModbusFunction> for ModbusFunction {
96 fn eq(&self, other: &PublicModbusFunction) -> bool {
97 self.is(*other)
98 }
99}
100
101impl From<u8> for ModbusFunction {
102 fn from(b: u8) -> Self {
103 Self::new(b)
104 }
105}
106
107impl Into<u8> for ModbusFunction {
108 fn into(self) -> u8 {
109 self.0
110 }
111}
112
113impl From<PublicModbusFunction> for ModbusFunction {
114 fn from(public_function: PublicModbusFunction) -> Self {
115 Self::new_public(public_function)
116 }
117}
118
119macro_rules! public_modbus_function {
120 ($($name:ident = $fcode:expr,)*) => {
121 #[repr(u8)]
126 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
127 pub enum PublicModbusFunction {
128 $($name = $fcode,)*
129 }
130
131 impl PublicModbusFunction {
132 pub const fn is_public_function(code: u8) -> bool {
134 if code == 0 {
135 return false;
136 }
137 match code {
138 $($fcode => true,)*
139 _ => false,
140 }
141 }
142 }
143 };
144}
145
146public_modbus_function! {
147 Invalid = 0,
148 ReadCoils = 1,
149 ReadDiscreteInputs = 2,
150 ReadHoldingRegisters = 3,
151 ReadInputRegisters = 4,
152 WriteSingleCoil = 5,
153 WriteSingleRegister = 6,
154 ReadExceptionStatus = 7,
155 Diagnostics = 8,
156 GetCommEventCounter = 11,
157 GetCommEventLog = 12,
158 WriteMultipleCoils = 15,
159 WriteMultipleregisters = 16,
160 ReportServerID = 17,
161 ReadFileRecord = 20,
162 WriteFileRecord = 21,
163 MaskWriteRegister = 22,
164 ReadWriteMultipleRegisters = 23,
165 ReadFIFOQueue = 24,
166 EncapsulatedInterfaceTransport = 43,
167}
168
169impl PublicModbusFunction {
170 pub fn new(code: u8) -> Self {
172 if Self::is_public_function(code) {
173 unsafe { Self::new_unchecked(code) }
174 } else {
175 Self::Invalid
176 }
177 }
178
179 pub unsafe fn new_unchecked(code: u8) -> Self {
184 mem::transmute(code)
185 }
186}
187
188impl PartialEq<ModbusFunction> for PublicModbusFunction {
189 fn eq(&self, other: &ModbusFunction) -> bool {
190 other == self
191 }
192}
193
194impl From<ModbusFunction> for PublicModbusFunction {
195 fn from(mf: ModbusFunction) -> Self {
196 Self::new(mf.0)
197 }
198}
199
200impl From<u8> for PublicModbusFunction {
201 fn from(code: u8) -> Self {
202 Self::new(code)
203 }
204}
205
206impl Into<u8> for PublicModbusFunction {
207 fn into(self) -> u8 {
208 self as u8
209 }
210}
211
212#[cfg(test)]
213mod test {
214 use super::{get_function, ModbusFunction, PublicModbusFunction};
215
216 #[test]
217 fn invalid() {
218 assert!(!ModbusFunction::from(0).is_valid())
219 }
220
221 #[test]
222 fn public() {
223 assert!(ModbusFunction::from(1).is_public());
224 }
225
226 #[test]
227 fn custom() {
228 let mbf = ModbusFunction::from(69);
229 assert!(mbf.is_custom());
230 }
231
232 #[test]
233 fn custom_all() {
234 for i in 65..=72 {
235 let mbf = ModbusFunction::from(i);
236 assert!(mbf.is_custom() && mbf.is_valid());
237 }
238
239 for i in 100..=110 {
240 let mbf = ModbusFunction::from(i);
241 assert!(mbf.is_custom() && mbf.is_valid());
242 }
243 }
244
245 #[test]
246 fn invalid_from_data_no_tail() {
247 let data = [0];
248 let (function, tail) = get_function(&data);
249 assert_eq!(
250 function,
251 Some(ModbusFunction::new_public(PublicModbusFunction::Invalid))
252 );
253 assert_eq!(tail, Some(&[] as &[u8]));
254 }
255
256 #[test]
257 fn no_data() {
258 let data = [];
259 let (function, tail) = get_function(&data);
260 assert_eq!(function, None);
261 assert_eq!(tail, None);
262 }
263
264 #[test]
265 fn invalid_from_data() {
266 let data = [0, 1];
267 let (function, tail) = get_function(&data);
268 assert_eq!(
269 function,
270 Some(ModbusFunction::new_public(PublicModbusFunction::Invalid))
271 );
272 assert_eq!(tail, Some(&[1u8] as &[u8]));
273 }
274
275 #[test]
276 fn read_coils_from_data() {
277 let data = [1, 1];
278 let (function, tail) = get_function(&data);
279 assert_eq!(
280 function,
281 Some(ModbusFunction::new_public(PublicModbusFunction::ReadCoils))
282 );
283 assert_eq!(tail, Some(&[1u8] as &[u8]));
284 }
285}