Skip to main content

rust_ethernet_ip_protocol/
values.rs

1use 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}