rust_ethernet_ip/
udt.rs

1use crate::error::Result;
2use crate::PlcValue;
3use std::collections::HashMap;
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(
43        &self,
44        _udt_value: &HashMap<String, PlcValue>,
45    ) -> Result<Vec<u8>> {
46        // For now, return empty bytes
47        // Full UDT serialization can be implemented later
48        Ok(Vec::new())
49    }
50}
51
52impl Default for UdtManager {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58/// Represents a User Defined Type (UDT)
59#[derive(Debug, Clone)]
60pub struct UserDefinedType {
61    /// Name of the UDT
62    pub name: String,
63    /// Total size of the UDT in bytes
64    pub size: u32,
65    /// Members of the UDT
66    pub members: Vec<UdtMember>,
67    /// Cache of member offsets for quick lookup
68    member_offsets: HashMap<String, u32>,
69}
70
71impl UserDefinedType {
72    /// Creates a new UDT
73    pub fn new(name: String) -> Self {
74        Self {
75            name,
76            size: 0,
77            members: Vec::new(),
78            member_offsets: HashMap::new(),
79        }
80    }
81
82    /// Adds a member to the UDT
83    pub fn add_member(&mut self, member: UdtMember) {
84        self.member_offsets
85            .insert(member.name.clone(), member.offset);
86        self.members.push(member);
87        // Calculate total size including padding
88        self.size = self
89            .members
90            .iter()
91            .map(|m| m.offset + m.size)
92            .max()
93            .unwrap_or(0);
94    }
95
96    /// Gets the offset of a member by name
97    pub fn get_member_offset(&self, name: &str) -> Option<u32> {
98        self.member_offsets.get(name).copied()
99    }
100
101    /// Parses a UDT from CIP data
102    pub fn from_cip_data(_data: &[u8]) -> crate::error::Result<Self> {
103        // TODO: Implement CIP data parsing
104        Ok(Self {
105            name: String::new(),
106            members: Vec::new(),
107            size: 0,
108            member_offsets: HashMap::new(),
109        })
110    }
111
112    /// Converts a UDT instance to a HashMap of member values
113    pub fn to_hash_map(&self, data: &[u8]) -> crate::error::Result<HashMap<String, PlcValue>> {
114        let mut result = HashMap::new();
115
116        for member in &self.members {
117            let offset = member.offset as usize;
118            if offset + member.size as usize <= data.len() {
119                let member_data = &data[offset..offset + member.size as usize];
120                let value = self.parse_member_value(member, member_data)?;
121                result.insert(member.name.clone(), value);
122            }
123        }
124
125        Ok(result)
126    }
127
128    /// Parses a member value from raw data
129    fn parse_member_value(
130        &self,
131        member: &UdtMember,
132        data: &[u8],
133    ) -> crate::error::Result<PlcValue> {
134        match member.data_type {
135            0x00C1 => Ok(PlcValue::Bool(data[0] != 0)),
136            0x00C4 => {
137                let mut bytes = [0u8; 4];
138                bytes.copy_from_slice(&data[..4]);
139                Ok(PlcValue::Dint(i32::from_le_bytes(bytes)))
140            }
141            0x00CA => {
142                let mut bytes = [0u8; 4];
143                bytes.copy_from_slice(&data[..4]);
144                Ok(PlcValue::Real(f32::from_le_bytes(bytes)))
145            }
146            _ => Err(crate::error::EtherNetIpError::Protocol(
147                "Unsupported data type".to_string(),
148            )),
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_udt_member_offsets() {
159        let mut udt = UserDefinedType::new("TestUDT".to_string());
160
161        udt.add_member(UdtMember {
162            name: "Bool1".to_string(),
163            data_type: 0x00C1,
164            offset: 0,
165            size: 1,
166        });
167
168        udt.add_member(UdtMember {
169            name: "Dint1".to_string(),
170            data_type: 0x00C4,
171            offset: 4,
172            size: 4,
173        });
174
175        assert_eq!(udt.get_member_offset("Bool1"), Some(0));
176        assert_eq!(udt.get_member_offset("Dint1"), Some(4));
177        assert_eq!(udt.size, 8);
178    }
179
180    #[test]
181    fn test_udt_parsing() {
182        let mut udt = UserDefinedType::new("TestUDT".to_string());
183
184        udt.add_member(UdtMember {
185            name: "Bool1".to_string(),
186            data_type: 0x00C1,
187            offset: 0,
188            size: 1,
189        });
190
191        udt.add_member(UdtMember {
192            name: "Dint1".to_string(),
193            data_type: 0x00C4,
194            offset: 4,
195            size: 4,
196        });
197
198        let data = vec![0xFF, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00];
199        let result = udt.to_hash_map(&data).unwrap();
200
201        assert_eq!(result.get("Bool1"), Some(&PlcValue::Bool(true)));
202        assert_eq!(result.get("Dint1"), Some(&PlcValue::Dint(42)));
203    }
204}