Skip to main content

rust_ethernet_ip_types/
lib.rs

1use bytes::{BufMut, BytesMut};
2use std::collections::HashMap;
3
4const STANDARD_STRING_DATA_LEN: usize = 82;
5const STANDARD_STRING_PAD_LEN: usize = 2;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct TypeError {
9    message: String,
10}
11
12impl TypeError {
13    #[must_use]
14    pub fn new(message: impl Into<String>) -> Self {
15        Self {
16            message: message.into(),
17        }
18    }
19}
20
21impl std::fmt::Display for TypeError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "{}", self.message)
24    }
25}
26
27impl std::error::Error for TypeError {}
28
29pub type Result<T> = std::result::Result<T, TypeError>;
30
31pub trait UdtCodec {
32    fn to_hash_map(&self, data: &[u8]) -> Result<HashMap<String, PlcValue>>;
33    fn encode_hash_map(&self, values: &HashMap<String, PlcValue>) -> Result<Vec<u8>>;
34}
35
36#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
37pub struct UdtData {
38    pub symbol_id: i32,
39    pub data: Vec<u8>,
40}
41
42#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
43pub enum PlcValue {
44    Bool(bool),
45    Sint(i8),
46    Int(i16),
47    Dint(i32),
48    Lint(i64),
49    Usint(u8),
50    Uint(u16),
51    Udint(u32),
52    Ulint(u64),
53    Real(f32),
54    Lreal(f64),
55    String(String),
56    Udt(UdtData),
57}
58
59impl UdtData {
60    pub fn parse(&self, definition: &impl UdtCodec) -> Result<HashMap<String, PlcValue>> {
61        definition.to_hash_map(&self.data)
62    }
63
64    pub fn from_hash_map(
65        members: &HashMap<String, PlcValue>,
66        definition: &impl UdtCodec,
67        symbol_id: i32,
68    ) -> Result<Self> {
69        let data = definition.encode_hash_map(members)?;
70        Ok(UdtData { symbol_id, data })
71    }
72}
73
74impl PlcValue {
75    #[must_use]
76    pub fn to_bytes(&self) -> Vec<u8> {
77        let mut bytes = BytesMut::new();
78        encode_payload(self, &mut bytes);
79        bytes.to_vec()
80    }
81
82    #[must_use]
83    pub fn get_data_type(&self) -> u16 {
84        self.known_data_type().unwrap_or(0x00A0)
85    }
86
87    /// Returns a precise CIP type code when this value carries enough metadata.
88    ///
89    /// UDT values decoded without a template symbol id return `None` instead of
90    /// pretending that the generic structure marker is a concrete write type.
91    #[must_use]
92    pub fn known_data_type(&self) -> Option<u16> {
93        match self {
94            PlcValue::Bool(_) => Some(0x00C1),
95            PlcValue::Sint(_) => Some(0x00C2),
96            PlcValue::Int(_) => Some(0x00C3),
97            PlcValue::Dint(_) => Some(0x00C4),
98            PlcValue::Lint(_) => Some(0x00C5),
99            PlcValue::Usint(_) => Some(0x00C6),
100            PlcValue::Uint(_) => Some(0x00C7),
101            PlcValue::Udint(_) => Some(0x00C8),
102            PlcValue::Ulint(_) => Some(0x00C9),
103            PlcValue::Real(_) => Some(0x00CA),
104            PlcValue::Lreal(_) => Some(0x00CB),
105            PlcValue::String(_) => Some(0x00CE),
106            PlcValue::Udt(udt_data) => {
107                (udt_data.symbol_id > 0).then(|| 0x02A0u16.wrapping_add(udt_data.symbol_id as u16))
108            }
109        }
110    }
111}
112
113fn encode_payload(value: &PlcValue, buf: &mut BytesMut) {
114    match value {
115        PlcValue::Bool(v) => buf.put_u8(if *v { 0xFF } else { 0x00 }),
116        PlcValue::Sint(v) => buf.put_i8(*v),
117        PlcValue::Int(v) => buf.put_i16_le(*v),
118        PlcValue::Dint(v) => buf.put_i32_le(*v),
119        PlcValue::Lint(v) => buf.put_i64_le(*v),
120        PlcValue::Usint(v) => buf.put_u8(*v),
121        PlcValue::Uint(v) => buf.put_u16_le(*v),
122        PlcValue::Udint(v) => buf.put_u32_le(*v),
123        PlcValue::Ulint(v) => buf.put_u64_le(*v),
124        PlcValue::Real(v) => buf.put_slice(&v.to_le_bytes()),
125        PlcValue::Lreal(v) => buf.put_slice(&v.to_le_bytes()),
126        PlcValue::String(v) => encode_standard_string_payload(v, buf),
127        PlcValue::Udt(udt_data) => buf.put_slice(&udt_data.data),
128    }
129}
130
131fn encode_standard_string_payload(value: &str, buf: &mut BytesMut) {
132    let string_bytes = value.as_bytes();
133    let data_len = string_bytes.len().min(STANDARD_STRING_DATA_LEN);
134    buf.put_u32_le(data_len as u32);
135    buf.put_slice(&string_bytes[..data_len]);
136    buf.resize(buf.len() + (STANDARD_STRING_DATA_LEN - data_len), 0);
137    buf.resize(buf.len() + STANDARD_STRING_PAD_LEN, 0);
138}