rust_ethernet_ip_types/
lib.rs1use 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 #[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}