rust_ethernet_ip/
udt.rs

1use std::collections::HashMap;
2use crate::PlcValue;
3use crate::error::Result;
4
5/// Definition of a User Defined Type
6#[derive(Debug, Clone)]
7pub struct UdtDefinition {
8    pub name: String,
9    pub members: Vec<UdtMember>,
10}
11
12/// Member of a UDT
13#[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/// Manager for UDT operations
22#[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    /// Parse a UDT instance from raw bytes
35    pub fn parse_udt_instance(&self, _udt_name: &str, _data: &[u8]) -> Result<PlcValue> {
36        // For now, return an empty UDT
37        // Full UDT parsing can be implemented later
38        Ok(PlcValue::Udt(HashMap::new()))
39    }
40
41    /// Serialize a UDT instance to bytes
42    pub fn serialize_udt_instance(&self, _udt_value: &HashMap<String, PlcValue>) -> Result<Vec<u8>> {
43        // For now, return empty bytes
44        // Full UDT serialization can be implemented later
45        Ok(Vec::new())
46    }
47}
48
49impl Default for UdtManager {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55/// Represents a User Defined Type (UDT)
56#[derive(Debug, Clone)]
57pub struct UserDefinedType {
58    /// Name of the UDT
59    pub name: String,
60    /// Total size of the UDT in bytes
61    pub size: u32,
62    /// Members of the UDT
63    pub members: Vec<UdtMember>,
64    /// Cache of member offsets for quick lookup
65    member_offsets: HashMap<String, u32>,
66}
67
68impl UserDefinedType {
69    /// Creates a new UDT
70    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    /// Adds a member to the UDT
80    pub fn add_member(&mut self, member: UdtMember) {
81        self.member_offsets.insert(member.name.clone(), member.offset);
82        self.members.push(member);
83        // Calculate total size including padding
84        self.size = self.members.iter()
85            .map(|m| m.offset + m.size)
86            .max()
87            .unwrap_or(0);
88    }
89
90    /// Gets the offset of a member by name
91    pub fn get_member_offset(&self, name: &str) -> Option<u32> {
92        self.member_offsets.get(name).copied()
93    }
94
95    /// Parses a UDT from CIP data
96    pub fn from_cip_data(_data: &[u8]) -> crate::error::Result<Self> {
97        // TODO: Implement CIP data parsing
98        Ok(Self {
99            name: String::new(),
100            members: Vec::new(),
101            size: 0,
102            member_offsets: HashMap::new(),
103        })
104    }
105
106    /// Converts a UDT instance to a HashMap of member values
107    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    /// Parses a member value from raw data
123    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}