rust_ethernet_ip_protocol/
values.rs1use bytes::{Buf, BufMut, BytesMut};
2
3use crate::{Decode, Encode, ProtocolError, Result};
4use rust_ethernet_ip_types::{PlcValue, UdtData};
5
6pub const BOOL: u16 = 0x00C1;
7pub const SINT: u16 = 0x00C2;
8pub const INT: u16 = 0x00C3;
9pub const DINT: u16 = 0x00C4;
10pub const LINT: u16 = 0x00C5;
11pub const USINT: u16 = 0x00C6;
12pub const UINT: u16 = 0x00C7;
13pub const UDINT: u16 = 0x00C8;
14pub const ULINT: u16 = 0x00C9;
15pub const REAL: u16 = 0x00CA;
16pub const LREAL: u16 = 0x00CB;
17pub const STRING: u16 = 0x00CE;
18pub const ALT_STRING: u16 = 0x00DA;
19pub const BOOL_ARRAY_DWORD: u16 = 0x00D3;
20pub const UDT: u16 = 0x00A0;
21pub const AB_UDT: u16 = 0x02A0;
22
23pub fn write_data_type(value: &PlcValue) -> u16 {
24 if let PlcValue::Udt(udt_data) = value {
25 AB_UDT.wrapping_add(udt_data.symbol_id as u16)
26 } else {
27 value.get_data_type()
28 }
29}
30
31pub fn encode_payload(value: &PlcValue, buf: &mut BytesMut) {
32 match value {
33 PlcValue::Bool(v) => buf.put_u8(if *v { 0xFF } else { 0x00 }),
34 PlcValue::Sint(v) => buf.put_i8(*v),
35 PlcValue::Int(v) => buf.put_i16_le(*v),
36 PlcValue::Dint(v) => buf.put_i32_le(*v),
37 PlcValue::Lint(v) => buf.put_i64_le(*v),
38 PlcValue::Usint(v) => buf.put_u8(*v),
39 PlcValue::Uint(v) => buf.put_u16_le(*v),
40 PlcValue::Udint(v) => buf.put_u32_le(*v),
41 PlcValue::Ulint(v) => buf.put_u64_le(*v),
42 PlcValue::Real(v) => buf.put_slice(&v.to_le_bytes()),
43 PlcValue::Lreal(v) => buf.put_slice(&v.to_le_bytes()),
44 PlcValue::String(v) => {
45 let length = v.len().min(82) as u32;
46 buf.put_u32_le(length);
47 let string_bytes = v.as_bytes();
48 let data_len = string_bytes.len().min(82);
49 buf.put_slice(&string_bytes[..data_len]);
50 }
51 PlcValue::Udt(udt_data) => buf.put_slice(&udt_data.data),
52 }
53}
54
55pub fn encode_type_prefixed(value: &PlcValue, buf: &mut BytesMut) {
56 buf.put_u16_le(value.get_data_type());
57 match value {
58 PlcValue::String(v) => {
59 let length = v.len().min(82) as u32;
60 buf.put_u32_le(length);
61 let string_bytes = v.as_bytes();
62 let data_len = string_bytes.len().min(82);
63 buf.put_slice(&string_bytes[..data_len]);
64 buf.resize(buf.len() + (82 - data_len), 0);
65 }
66 PlcValue::Udt(udt_data) => buf.put_slice(&udt_data.data),
67 _ => encode_payload(value, buf),
68 }
69}
70
71pub fn decode_payload(data_type: u16, value_data: &[u8]) -> Result<PlcValue> {
72 match data_type {
73 BOOL => {
74 require_len(value_data, 1, "BOOL")?;
75 Ok(PlcValue::Bool(value_data[0] != 0))
76 }
77 SINT => {
78 require_len(value_data, 1, "SINT")?;
79 Ok(PlcValue::Sint(value_data[0] as i8))
80 }
81 INT => {
82 require_len(value_data, 2, "INT")?;
83 Ok(PlcValue::Int(i16::from_le_bytes([
84 value_data[0],
85 value_data[1],
86 ])))
87 }
88 DINT => {
89 require_len(value_data, 4, "DINT")?;
90 Ok(PlcValue::Dint(i32::from_le_bytes([
91 value_data[0],
92 value_data[1],
93 value_data[2],
94 value_data[3],
95 ])))
96 }
97 LINT => {
98 require_len(value_data, 8, "LINT")?;
99 Ok(PlcValue::Lint(i64::from_le_bytes(
100 value_data[..8]
101 .try_into()
102 .expect("length checked before fixed-width LINT decode"),
103 )))
104 }
105 USINT => {
106 require_len(value_data, 1, "USINT")?;
107 Ok(PlcValue::Usint(value_data[0]))
108 }
109 UINT => {
110 require_len(value_data, 2, "UINT")?;
111 Ok(PlcValue::Uint(u16::from_le_bytes([
112 value_data[0],
113 value_data[1],
114 ])))
115 }
116 UDINT => {
117 require_len(value_data, 4, "UDINT")?;
118 Ok(PlcValue::Udint(u32::from_le_bytes([
119 value_data[0],
120 value_data[1],
121 value_data[2],
122 value_data[3],
123 ])))
124 }
125 ULINT => {
126 require_len(value_data, 8, "ULINT")?;
127 Ok(PlcValue::Ulint(u64::from_le_bytes(
128 value_data[..8]
129 .try_into()
130 .expect("length checked before fixed-width ULINT decode"),
131 )))
132 }
133 REAL => {
134 require_len(value_data, 4, "REAL")?;
135 Ok(PlcValue::Real(f32::from_le_bytes([
136 value_data[0],
137 value_data[1],
138 value_data[2],
139 value_data[3],
140 ])))
141 }
142 LREAL => {
143 require_len(value_data, 8, "LREAL")?;
144 Ok(PlcValue::Lreal(f64::from_le_bytes(
145 value_data[..8]
146 .try_into()
147 .expect("length checked before fixed-width LREAL decode"),
148 )))
149 }
150 STRING => decode_dint_string(value_data),
151 ALT_STRING => decode_short_string(value_data),
152 AB_UDT | UDT => Ok(PlcValue::Udt(UdtData {
153 symbol_id: 0,
154 data: value_data.to_vec(),
155 })),
156 BOOL_ARRAY_DWORD => {
157 if value_data.len() >= 4 {
158 Ok(PlcValue::Udint(u32::from_le_bytes([
159 value_data[0],
160 value_data[1],
161 value_data[2],
162 value_data[3],
163 ])))
164 } else if value_data.len() >= 8 {
165 Ok(PlcValue::Ulint(u64::from_le_bytes(
166 value_data[..8]
167 .try_into()
168 .expect("length checked before fixed-width BOOL array decode"),
169 )))
170 } else {
171 Err(ProtocolError::new(
172 "Insufficient data for ULINT/DWORD value".to_string(),
173 ))
174 }
175 }
176 _ => Err(ProtocolError::new(format!(
177 "Unsupported data type: 0x{data_type:04X}"
178 ))),
179 }
180}
181
182pub fn decode_array_element(data_type: u16, chunk: &[u8]) -> Result<PlcValue> {
183 decode_payload(data_type, chunk)
184}
185
186fn decode_dint_string(value_data: &[u8]) -> Result<PlcValue> {
187 if value_data.len() < 4 {
188 return Err(ProtocolError::new(
189 "Insufficient data for STRING length field".to_string(),
190 ));
191 }
192
193 let length =
194 u32::from_le_bytes([value_data[0], value_data[1], value_data[2], value_data[3]]) as usize;
195 if value_data.len() - 4 < length {
196 return Err(ProtocolError::new(format!(
197 "Insufficient data for STRING value: need {} bytes, have {} bytes",
198 4 + length,
199 value_data.len()
200 )));
201 }
202 Ok(PlcValue::String(
203 String::from_utf8_lossy(&value_data[4..4 + length]).to_string(),
204 ))
205}
206
207fn decode_short_string(value_data: &[u8]) -> Result<PlcValue> {
208 if value_data.is_empty() {
209 return Ok(PlcValue::String(String::new()));
210 }
211 let length = value_data[0] as usize;
212 if value_data.len() < 1 + length {
213 return Err(ProtocolError::new(
214 "Insufficient data for STRING value".to_string(),
215 ));
216 }
217 Ok(PlcValue::String(
218 String::from_utf8_lossy(&value_data[1..1 + length]).to_string(),
219 ))
220}
221
222fn require_len(value_data: &[u8], min_len: usize, name: &str) -> Result<()> {
223 if value_data.len() < min_len {
224 let msg = if min_len == 1 {
225 format!("No data for {name} value")
226 } else {
227 format!("Insufficient data for {name} value")
228 };
229 Err(ProtocolError::new(msg))
230 } else {
231 Ok(())
232 }
233}
234
235impl Encode for PlcValue {
236 fn encode(&self, buf: &mut BytesMut) {
237 encode_type_prefixed(self, buf);
238 }
239}
240
241impl Decode for PlcValue {
242 fn decode(buf: &mut impl Buf) -> Result<Self> {
243 if buf.remaining() < 2 {
244 return Err(ProtocolError::new("Data too short for type".to_string()));
245 }
246 let data_type = buf.get_u16_le();
247 let remaining = buf.copy_to_bytes(buf.remaining());
248 decode_payload(data_type, &remaining)
249 }
250}
251
252impl Encode for UdtData {
253 fn encode(&self, buf: &mut BytesMut) {
254 buf.put_slice(&self.data);
255 }
256}
257
258impl Decode for UdtData {
259 fn decode(buf: &mut impl Buf) -> Result<Self> {
260 let data = buf.copy_to_bytes(buf.remaining()).to_vec();
261 Ok(Self { symbol_id: 0, data })
262 }
263}