1use std::collections::HashMap;
2use crate::PlcValue;
3use crate::error::Result;
4
5#[derive(Debug, Clone)]
7pub struct UdtDefinition {
8 pub name: String,
9 pub members: Vec<UdtMember>,
10}
11
12#[derive(Debug, Clone)]
14pub struct UdtMember {
15 pub name: String,
16 pub data_type: u16,
17 pub offset: u32,
18 pub size: u32,
19}
20
21#[derive(Debug)]
23pub struct UdtManager {
24 _definitions: HashMap<String, UdtDefinition>,
25}
26
27impl UdtManager {
28 pub fn new() -> Self {
29 Self {
30 _definitions: HashMap::new(),
31 }
32 }
33
34 pub fn parse_udt_instance(&self, _udt_name: &str, _data: &[u8]) -> Result<PlcValue> {
36 Ok(PlcValue::Udt(HashMap::new()))
39 }
40
41 pub fn serialize_udt_instance(&self, _udt_value: &HashMap<String, PlcValue>) -> Result<Vec<u8>> {
43 Ok(Vec::new())
46 }
47}
48
49impl Default for UdtManager {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct UserDefinedType {
58 pub name: String,
60 pub size: u32,
62 pub members: Vec<UdtMember>,
64 member_offsets: HashMap<String, u32>,
66}
67
68impl UserDefinedType {
69 pub fn new(name: String) -> Self {
71 Self {
72 name,
73 size: 0,
74 members: Vec::new(),
75 member_offsets: HashMap::new(),
76 }
77 }
78
79 pub fn add_member(&mut self, member: UdtMember) {
81 self.member_offsets.insert(member.name.clone(), member.offset);
82 self.members.push(member);
83 self.size = self.members.iter()
85 .map(|m| m.offset + m.size)
86 .max()
87 .unwrap_or(0);
88 }
89
90 pub fn get_member_offset(&self, name: &str) -> Option<u32> {
92 self.member_offsets.get(name).copied()
93 }
94
95 pub fn from_cip_data(_data: &[u8]) -> crate::error::Result<Self> {
97 Ok(Self {
99 name: String::new(),
100 members: Vec::new(),
101 size: 0,
102 member_offsets: HashMap::new(),
103 })
104 }
105
106 pub fn to_hash_map(&self, data: &[u8]) -> crate::error::Result<HashMap<String, PlcValue>> {
108 let mut result = HashMap::new();
109
110 for member in &self.members {
111 let offset = member.offset as usize;
112 if offset + member.size as usize <= data.len() {
113 let member_data = &data[offset..offset + member.size as usize];
114 let value = self.parse_member_value(member, member_data)?;
115 result.insert(member.name.clone(), value);
116 }
117 }
118
119 Ok(result)
120 }
121
122 fn parse_member_value(&self, member: &UdtMember, data: &[u8]) -> crate::error::Result<PlcValue> {
124 match member.data_type {
125 0x00C1 => Ok(PlcValue::Bool(data[0] != 0)),
126 0x00C4 => {
127 let mut bytes = [0u8; 4];
128 bytes.copy_from_slice(&data[..4]);
129 Ok(PlcValue::Dint(i32::from_le_bytes(bytes)))
130 }
131 0x00CA => {
132 let mut bytes = [0u8; 4];
133 bytes.copy_from_slice(&data[..4]);
134 Ok(PlcValue::Real(f32::from_le_bytes(bytes)))
135 }
136 _ => Err(crate::error::EtherNetIpError::Protocol("Unsupported data type".to_string())),
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_udt_member_offsets() {
147 let mut udt = UserDefinedType::new("TestUDT".to_string());
148
149 udt.add_member(UdtMember {
150 name: "Bool1".to_string(),
151 data_type: 0x00C1,
152 offset: 0,
153 size: 1,
154 });
155
156 udt.add_member(UdtMember {
157 name: "Dint1".to_string(),
158 data_type: 0x00C4,
159 offset: 4,
160 size: 4,
161 });
162
163 assert_eq!(udt.get_member_offset("Bool1"), Some(0));
164 assert_eq!(udt.get_member_offset("Dint1"), Some(4));
165 assert_eq!(udt.size, 8);
166 }
167
168 #[test]
169 fn test_udt_parsing() {
170 let mut udt = UserDefinedType::new("TestUDT".to_string());
171
172 udt.add_member(UdtMember {
173 name: "Bool1".to_string(),
174 data_type: 0x00C1,
175 offset: 0,
176 size: 1,
177 });
178
179 udt.add_member(UdtMember {
180 name: "Dint1".to_string(),
181 data_type: 0x00C4,
182 offset: 4,
183 size: 4,
184 });
185
186 let data = vec![0xFF, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00];
187 let result = udt.to_hash_map(&data).unwrap();
188
189 assert_eq!(result.get("Bool1"), Some(&PlcValue::Bool(true)));
190 assert_eq!(result.get("Dint1"), Some(&PlcValue::Dint(42)));
191 }
192}