1use crate::error::{EtherNetIpError, Result};
2use crate::PlcValue;
3use std::collections::HashMap;
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, Clone)]
23pub struct UdtTemplate {
24 pub template_id: u32,
25 pub name: String,
26 pub size: u32,
27 pub member_count: u16,
28 pub members: Vec<UdtMember>,
29}
30
31#[derive(Debug, Clone)]
33pub struct TagAttributes {
34 pub name: String,
35 pub data_type: u16,
36 pub data_type_name: String,
37 pub dimensions: Vec<u32>,
38 pub permissions: TagPermissions,
39 pub scope: TagScope,
40 pub template_instance_id: Option<u32>,
41 pub size: u32,
42}
43
44#[derive(Debug, Clone, PartialEq)]
46pub enum TagPermissions {
47 ReadOnly,
48 ReadWrite,
49 WriteOnly,
50 Unknown,
51}
52
53#[derive(Debug, Clone, PartialEq)]
55pub enum TagScope {
56 Controller,
57 Program(String),
58 Unknown,
59}
60
61#[derive(Debug)]
63pub struct UdtManager {
64 definitions: HashMap<String, UdtDefinition>,
65 templates: HashMap<u32, UdtTemplate>,
66 tag_attributes: HashMap<String, TagAttributes>,
67}
68
69impl UdtManager {
70 pub fn new() -> Self {
71 Self {
72 definitions: HashMap::new(),
73 templates: HashMap::new(),
74 tag_attributes: HashMap::new(),
75 }
76 }
77
78 pub fn add_definition(&mut self, definition: UdtDefinition) {
80 self.definitions.insert(definition.name.clone(), definition);
81 }
82
83 pub fn get_definition(&self, name: &str) -> Option<&UdtDefinition> {
85 self.definitions.get(name)
86 }
87
88 pub fn add_template(&mut self, template: UdtTemplate) {
90 self.templates.insert(template.template_id, template);
91 }
92
93 pub fn get_template(&self, template_id: u32) -> Option<&UdtTemplate> {
95 self.templates.get(&template_id)
96 }
97
98 pub fn add_tag_attributes(&mut self, attributes: TagAttributes) {
100 self.tag_attributes
101 .insert(attributes.name.clone(), attributes);
102 }
103
104 pub fn get_tag_attributes(&self, name: &str) -> Option<&TagAttributes> {
106 self.tag_attributes.get(name)
107 }
108
109 pub fn list_definitions(&self) -> Vec<String> {
111 self.definitions.keys().cloned().collect()
112 }
113
114 pub fn list_templates(&self) -> Vec<u32> {
116 self.templates.keys().cloned().collect()
117 }
118
119 pub fn list_tag_attributes(&self) -> Vec<String> {
121 self.tag_attributes.keys().cloned().collect()
122 }
123
124 pub fn clear_cache(&mut self) {
126 self.definitions.clear();
127 self.templates.clear();
128 self.tag_attributes.clear();
129 }
130
131 pub fn parse_udt_template(&self, template_id: u32, data: &[u8]) -> Result<UdtTemplate> {
133 if data.len() < 8 {
134 return Err(EtherNetIpError::Protocol(
135 "UDT template data too short".to_string(),
136 ));
137 }
138
139 let mut offset = 0;
140
141 let structure_size = u32::from_le_bytes([
143 data[offset],
144 data[offset + 1],
145 data[offset + 2],
146 data[offset + 3],
147 ]);
148 offset += 4;
149
150 let member_count = u16::from_le_bytes([data[offset], data[offset + 1]]);
151 offset += 2;
152
153 offset += 2;
155
156 let mut members = Vec::new();
157 let mut current_offset = 0u32;
158
159 for i in 0..member_count {
161 if offset + 8 > data.len() {
162 return Err(EtherNetIpError::Protocol(format!(
163 "UDT template member {} data incomplete",
164 i
165 )));
166 }
167
168 let member_info = u32::from_le_bytes([
170 data[offset],
171 data[offset + 1],
172 data[offset + 2],
173 data[offset + 3],
174 ]);
175 offset += 4;
176
177 let member_name_length = u16::from_le_bytes([data[offset], data[offset + 1]]);
178 offset += 2;
179
180 offset += 2;
182
183 let data_type = (member_info & 0xFFFF) as u16;
185 let _dimensions = ((member_info >> 16) & 0xFF) as u8;
186
187 if offset + member_name_length as usize > data.len() {
189 return Err(EtherNetIpError::Protocol(format!(
190 "UDT template member {} name data incomplete",
191 i
192 )));
193 }
194
195 let name_bytes = &data[offset..offset + member_name_length as usize];
196 let member_name = String::from_utf8_lossy(name_bytes).to_string();
197 offset += member_name_length as usize;
198
199 offset = (offset + 3) & !3;
201
202 let member_size = self.get_data_type_size(data_type);
204
205 let member = UdtMember {
207 name: member_name,
208 data_type,
209 offset: current_offset,
210 size: member_size,
211 };
212
213 members.push(member);
214 current_offset += member_size;
215 }
216
217 Ok(UdtTemplate {
218 template_id,
219 name: format!("Template_{}", template_id),
220 size: structure_size,
221 member_count,
222 members,
223 })
224 }
225
226 fn get_data_type_size(&self, data_type: u16) -> u32 {
228 match data_type {
229 0x00C1 => 1, 0x00C2 => 1, 0x00C3 => 2, 0x00C4 => 4, 0x00C5 => 8, 0x00C6 => 1, 0x00C7 => 2, 0x00C8 => 4, 0x00C9 => 8, 0x00CA => 4, 0x00CB => 8, 0x00CE => 88, _ => 4, }
243 }
244
245 pub fn parse_tag_attributes(&self, tag_name: &str, data: &[u8]) -> Result<TagAttributes> {
247 if data.len() < 8 {
248 return Err(EtherNetIpError::Protocol(
249 "Tag attributes data too short".to_string(),
250 ));
251 }
252
253 let mut offset = 0;
254
255 let data_type = u16::from_le_bytes([data[offset], data[offset + 1]]);
257 offset += 2;
258
259 let size = u32::from_le_bytes([
261 data[offset],
262 data[offset + 1],
263 data[offset + 2],
264 data[offset + 3],
265 ]);
266 offset += 4;
267
268 let mut dimensions = Vec::new();
270 if data.len() > offset {
271 let dimension_count = data[offset] as usize;
272 offset += 1;
273
274 for _ in 0..dimension_count {
275 if offset + 4 <= data.len() {
276 let dim = u32::from_le_bytes([
277 data[offset],
278 data[offset + 1],
279 data[offset + 2],
280 data[offset + 3],
281 ]);
282 dimensions.push(dim);
283 offset += 4;
284 }
285 }
286 }
287
288 let permissions = TagPermissions::ReadWrite; let scope = if tag_name.contains(':') {
293 let parts: Vec<&str> = tag_name.split(':').collect();
294 if parts.len() >= 2 {
295 TagScope::Program(parts[0].to_string())
296 } else {
297 TagScope::Controller
298 }
299 } else {
300 TagScope::Controller
301 };
302
303 let data_type_name = self.get_data_type_name(data_type);
305
306 let template_instance_id = if data_type == 0x00A0 {
308 Some(0) } else {
311 None
312 };
313
314 Ok(TagAttributes {
315 name: tag_name.to_string(),
316 data_type,
317 data_type_name,
318 dimensions,
319 permissions,
320 scope,
321 template_instance_id,
322 size,
323 })
324 }
325
326 fn get_data_type_name(&self, data_type: u16) -> String {
328 match data_type {
329 0x00C1 => "BOOL".to_string(),
330 0x00C2 => "SINT".to_string(),
331 0x00C3 => "INT".to_string(),
332 0x00C4 => "DINT".to_string(),
333 0x00C5 => "LINT".to_string(),
334 0x00C6 => "USINT".to_string(),
335 0x00C7 => "UINT".to_string(),
336 0x00C8 => "UDINT".to_string(),
337 0x00C9 => "ULINT".to_string(),
338 0x00CA => "REAL".to_string(),
339 0x00CB => "LREAL".to_string(),
340 0x00CE => "STRING".to_string(),
341 0x00A0 => "UDT".to_string(),
342 _ => format!("UNKNOWN(0x{:04X})", data_type),
343 }
344 }
345
346 pub fn parse_udt_instance(&self, _udt_name: &str, data: &[u8]) -> Result<PlcValue> {
352 Ok(PlcValue::Udt(crate::UdtData {
355 symbol_id: 0, data: data.to_vec(),
357 }))
358 }
359
360 pub fn serialize_udt_instance(
362 &self,
363 _udt_value: &HashMap<String, PlcValue>,
364 ) -> Result<Vec<u8>> {
365 Ok(Vec::new())
368 }
369}
370
371impl Default for UdtManager {
372 fn default() -> Self {
373 Self::new()
374 }
375}
376
377#[derive(Debug, Clone)]
381pub struct UserDefinedType {
382 pub name: String,
384 pub size: u32,
386 pub members: Vec<UdtMember>,
388 member_offsets: HashMap<String, u32>,
390}
391
392impl UserDefinedType {
393 pub fn new(name: String) -> Self {
395 Self {
396 name,
397 size: 0,
398 members: Vec::new(),
399 member_offsets: HashMap::new(),
400 }
401 }
402
403 pub fn add_member(&mut self, member: UdtMember) {
405 self.member_offsets
406 .insert(member.name.clone(), member.offset);
407 self.members.push(member);
408 self.size = self
410 .members
411 .iter()
412 .map(|m| m.offset + m.size)
413 .max()
414 .unwrap_or(0);
415 }
416
417 pub fn get_member_offset(&self, name: &str) -> Option<u32> {
419 self.member_offsets.get(name).copied()
420 }
421
422 pub fn from_cip_data(_data: &[u8]) -> crate::error::Result<Self> {
424 Ok(Self {
426 name: String::new(),
427 members: Vec::new(),
428 size: 0,
429 member_offsets: HashMap::new(),
430 })
431 }
432
433 pub fn to_hash_map(&self, data: &[u8]) -> crate::error::Result<HashMap<String, PlcValue>> {
435 if data.is_empty() {
436 return Err(crate::error::EtherNetIpError::Protocol(
437 "UDT data is empty".to_string(),
438 ));
439 }
440
441 let mut result = HashMap::new();
442
443 for member in &self.members {
444 let offset = member.offset as usize;
445 if offset + member.size as usize <= data.len() {
446 let member_data = &data[offset..offset + member.size as usize];
447 let value = self.parse_member_value(member, member_data)?;
448 result.insert(member.name.clone(), value);
449 }
450 }
451
452 Ok(result)
453 }
454
455 pub fn from_hash_map(
457 &self,
458 values: &HashMap<String, PlcValue>,
459 ) -> crate::error::Result<Vec<u8>> {
460 let mut data = vec![0u8; self.size as usize];
461
462 for member in &self.members {
463 if let Some(value) = values.get(&member.name) {
464 let member_data = self.serialize_member_value(member, value)?;
465 let offset = member.offset as usize;
466 let end_offset = offset + member_data.len();
467
468 if end_offset <= data.len() {
469 data[offset..end_offset].copy_from_slice(&member_data);
470 } else {
471 return Err(crate::error::EtherNetIpError::Protocol(format!(
472 "Member {} data exceeds UDT size",
473 member.name
474 )));
475 }
476 }
477 }
478
479 Ok(data)
480 }
481
482 pub fn read_member(&self, data: &[u8], member_name: &str) -> crate::error::Result<PlcValue> {
484 if let Some(member) = self.members.iter().find(|m| m.name == member_name) {
485 let offset = member.offset as usize;
486 if offset + member.size as usize <= data.len() {
487 let member_data = &data[offset..offset + member.size as usize];
488 self.parse_member_value(member, member_data)
489 } else {
490 Err(crate::error::EtherNetIpError::Protocol(format!(
491 "Member {} data incomplete",
492 member_name
493 )))
494 }
495 } else {
496 Err(crate::error::EtherNetIpError::TagNotFound(format!(
497 "UDT member '{}' not found",
498 member_name
499 )))
500 }
501 }
502
503 pub fn write_member(
505 &self,
506 data: &mut [u8],
507 member_name: &str,
508 value: &PlcValue,
509 ) -> crate::error::Result<()> {
510 if let Some(member) = self.members.iter().find(|m| m.name == member_name) {
511 let member_data = self.serialize_member_value(member, value)?;
512 let offset = member.offset as usize;
513 let end_offset = offset + member_data.len();
514
515 if end_offset <= data.len() {
516 data[offset..end_offset].copy_from_slice(&member_data);
517 Ok(())
518 } else {
519 Err(crate::error::EtherNetIpError::Protocol(format!(
520 "Member {} data exceeds UDT size",
521 member_name
522 )))
523 }
524 } else {
525 Err(crate::error::EtherNetIpError::TagNotFound(format!(
526 "UDT member '{}' not found",
527 member_name
528 )))
529 }
530 }
531
532 pub fn get_member_size(&self, member_name: &str) -> Option<u32> {
534 self.members
535 .iter()
536 .find(|m| m.name == member_name)
537 .map(|m| m.size)
538 }
539
540 pub fn get_member_data_type(&self, member_name: &str) -> Option<u16> {
542 self.members
543 .iter()
544 .find(|m| m.name == member_name)
545 .map(|m| m.data_type)
546 }
547
548 pub fn parse_member_value(
550 &self,
551 member: &UdtMember,
552 data: &[u8],
553 ) -> crate::error::Result<PlcValue> {
554 match member.data_type {
555 0x00C1 => {
556 if data.is_empty() {
557 return Err(crate::error::EtherNetIpError::Protocol(
558 "BOOL data too short".to_string(),
559 ));
560 }
561 Ok(PlcValue::Bool(data[0] != 0))
562 }
563 0x00C2 => {
564 if data.is_empty() {
566 return Err(crate::error::EtherNetIpError::Protocol(
567 "SINT data too short".to_string(),
568 ));
569 }
570 Ok(PlcValue::Sint(data[0] as i8))
571 }
572 0x00C3 => {
573 if data.len() < 2 {
575 return Err(crate::error::EtherNetIpError::Protocol(
576 "INT data too short".to_string(),
577 ));
578 }
579 let mut bytes = [0u8; 2];
580 bytes.copy_from_slice(&data[..2]);
581 Ok(PlcValue::Int(i16::from_le_bytes(bytes)))
582 }
583 0x00C4 => {
584 if data.len() < 4 {
586 return Err(crate::error::EtherNetIpError::Protocol(
587 "DINT data too short".to_string(),
588 ));
589 }
590 let mut bytes = [0u8; 4];
591 bytes.copy_from_slice(&data[..4]);
592 Ok(PlcValue::Dint(i32::from_le_bytes(bytes)))
593 }
594 0x00C5 => {
595 if data.len() < 8 {
597 return Err(crate::error::EtherNetIpError::Protocol(
598 "LINT data too short".to_string(),
599 ));
600 }
601 let mut bytes = [0u8; 8];
602 bytes.copy_from_slice(&data[..8]);
603 Ok(PlcValue::Lint(i64::from_le_bytes(bytes)))
604 }
605 0x00C6 => {
606 if data.is_empty() {
608 return Err(crate::error::EtherNetIpError::Protocol(
609 "USINT data too short".to_string(),
610 ));
611 }
612 Ok(PlcValue::Usint(data[0]))
613 }
614 0x00C7 => {
615 if data.len() < 2 {
617 return Err(crate::error::EtherNetIpError::Protocol(
618 "UINT data too short".to_string(),
619 ));
620 }
621 let mut bytes = [0u8; 2];
622 bytes.copy_from_slice(&data[..2]);
623 Ok(PlcValue::Uint(u16::from_le_bytes(bytes)))
624 }
625 0x00C8 => {
626 if data.len() < 4 {
628 return Err(crate::error::EtherNetIpError::Protocol(
629 "UDINT data too short".to_string(),
630 ));
631 }
632 let mut bytes = [0u8; 4];
633 bytes.copy_from_slice(&data[..4]);
634 Ok(PlcValue::Udint(u32::from_le_bytes(bytes)))
635 }
636 0x00C9 => {
637 if data.len() < 8 {
639 return Err(crate::error::EtherNetIpError::Protocol(
640 "ULINT data too short".to_string(),
641 ));
642 }
643 let mut bytes = [0u8; 8];
644 bytes.copy_from_slice(&data[..8]);
645 Ok(PlcValue::Ulint(u64::from_le_bytes(bytes)))
646 }
647 0x00CA => {
648 if data.len() < 4 {
650 return Err(crate::error::EtherNetIpError::Protocol(
651 "REAL data too short".to_string(),
652 ));
653 }
654 let mut bytes = [0u8; 4];
655 bytes.copy_from_slice(&data[..4]);
656 Ok(PlcValue::Real(f32::from_le_bytes(bytes)))
657 }
658 0x00CB => {
659 if data.len() < 8 {
661 return Err(crate::error::EtherNetIpError::Protocol(
662 "LREAL data too short".to_string(),
663 ));
664 }
665 let mut bytes = [0u8; 8];
666 bytes.copy_from_slice(&data[..8]);
667 Ok(PlcValue::Lreal(f64::from_le_bytes(bytes)))
668 }
669 0x00CE => {
670 if data.len() < 4 {
672 return Err(crate::error::EtherNetIpError::Protocol(
673 "STRING data too short".to_string(),
674 ));
675 }
676 let length = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
677 if data.len() - 4 < length {
678 return Err(crate::error::EtherNetIpError::Protocol(
679 "STRING data incomplete".to_string(),
680 ));
681 }
682 let string_data = &data[4..4 + length];
683 let string_value = String::from_utf8_lossy(string_data).to_string();
684 Ok(PlcValue::String(string_value))
685 }
686 _ => Err(crate::error::EtherNetIpError::Protocol(format!(
687 "Unsupported UDT data type: 0x{:04X}",
688 member.data_type
689 ))),
690 }
691 }
692
693 pub fn serialize_member_value(
695 &self,
696 member: &UdtMember,
697 value: &PlcValue,
698 ) -> crate::error::Result<Vec<u8>> {
699 match member.data_type {
700 0x00C1 => match value {
701 PlcValue::Bool(b) => Ok(vec![if *b { 0xFF } else { 0x00 }]),
702 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
703 expected: "BOOL".to_string(),
704 actual: format!("{:?}", value),
705 }),
706 },
707 0x00C2 => match value {
708 PlcValue::Sint(s) => Ok(vec![*s as u8]),
709 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
710 expected: "SINT".to_string(),
711 actual: format!("{:?}", value),
712 }),
713 },
714 0x00C3 => match value {
715 PlcValue::Int(i) => Ok(i.to_le_bytes().to_vec()),
716 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
717 expected: "INT".to_string(),
718 actual: format!("{:?}", value),
719 }),
720 },
721 0x00C4 => match value {
722 PlcValue::Dint(d) => Ok(d.to_le_bytes().to_vec()),
723 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
724 expected: "DINT".to_string(),
725 actual: format!("{:?}", value),
726 }),
727 },
728 0x00C5 => match value {
729 PlcValue::Lint(l) => Ok(l.to_le_bytes().to_vec()),
730 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
731 expected: "LINT".to_string(),
732 actual: format!("{:?}", value),
733 }),
734 },
735 0x00C6 => match value {
736 PlcValue::Usint(u) => Ok(vec![*u]),
737 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
738 expected: "USINT".to_string(),
739 actual: format!("{:?}", value),
740 }),
741 },
742 0x00C7 => match value {
743 PlcValue::Uint(u) => Ok(u.to_le_bytes().to_vec()),
744 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
745 expected: "UINT".to_string(),
746 actual: format!("{:?}", value),
747 }),
748 },
749 0x00C8 => match value {
750 PlcValue::Udint(u) => Ok(u.to_le_bytes().to_vec()),
751 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
752 expected: "UDINT".to_string(),
753 actual: format!("{:?}", value),
754 }),
755 },
756 0x00C9 => match value {
757 PlcValue::Ulint(u) => Ok(u.to_le_bytes().to_vec()),
758 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
759 expected: "ULINT".to_string(),
760 actual: format!("{:?}", value),
761 }),
762 },
763 0x00CA => match value {
764 PlcValue::Real(r) => Ok(r.to_le_bytes().to_vec()),
765 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
766 expected: "REAL".to_string(),
767 actual: format!("{:?}", value),
768 }),
769 },
770 0x00CB => match value {
771 PlcValue::Lreal(l) => Ok(l.to_le_bytes().to_vec()),
772 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
773 expected: "LREAL".to_string(),
774 actual: format!("{:?}", value),
775 }),
776 },
777 0x00CE => {
778 match value {
779 PlcValue::String(s) => {
780 let mut result = Vec::new();
781 let max_data_len = member.size.saturating_sub(4); let max_chars = (max_data_len as usize).min(82); let length = (s.len() as u32).min(max_chars as u32);
784 result.extend_from_slice(&length.to_le_bytes());
786 result.extend_from_slice(&s.as_bytes()[..length as usize]);
787 while result.len() < member.size as usize && result.len() % 2 != 0 {
789 result.push(0);
790 }
791 if result.len() > member.size as usize {
793 result.truncate(member.size as usize);
794 }
795 Ok(result)
796 }
797 _ => Err(crate::error::EtherNetIpError::DataTypeMismatch {
798 expected: "STRING".to_string(),
799 actual: format!("{:?}", value),
800 }),
801 }
802 }
803 _ => Err(crate::error::EtherNetIpError::Protocol(format!(
804 "Unsupported UDT data type for serialization: 0x{:04X}",
805 member.data_type
806 ))),
807 }
808 }
809}
810
811#[cfg(test)]
812mod tests {
813 use super::*;
814
815 #[test]
816 fn test_udt_member_offsets() {
817 let mut udt = UserDefinedType::new("TestUDT".to_string());
818
819 udt.add_member(UdtMember {
820 name: "Bool1".to_string(),
821 data_type: 0x00C1,
822 offset: 0,
823 size: 1,
824 });
825
826 udt.add_member(UdtMember {
827 name: "Dint1".to_string(),
828 data_type: 0x00C4,
829 offset: 4,
830 size: 4,
831 });
832
833 assert_eq!(udt.get_member_offset("Bool1"), Some(0));
834 assert_eq!(udt.get_member_offset("Dint1"), Some(4));
835 assert_eq!(udt.size, 8);
836 }
837
838 #[test]
839 fn test_udt_parsing() {
840 let mut udt = UserDefinedType::new("TestUDT".to_string());
841
842 udt.add_member(UdtMember {
843 name: "Bool1".to_string(),
844 data_type: 0x00C1,
845 offset: 0,
846 size: 1,
847 });
848
849 udt.add_member(UdtMember {
850 name: "Dint1".to_string(),
851 data_type: 0x00C4,
852 offset: 4,
853 size: 4,
854 });
855
856 let data = vec![0xFF, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00];
857 let result = udt.to_hash_map(&data).unwrap();
858
859 assert_eq!(result.get("Bool1"), Some(&PlcValue::Bool(true)));
860 assert_eq!(result.get("Dint1"), Some(&PlcValue::Dint(42)));
861 }
862}