1use rust_ethernet_ip_types::{PlcValue, TypeError, UdtCodec, UdtData};
2
3pub type Result<T> = std::result::Result<T, UdtError>;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum UdtError {
7 Protocol(String),
8 TagNotFound(String),
9 DataTypeMismatch { expected: String, actual: String },
10}
11
12impl UdtError {
13 pub fn protocol(message: impl Into<String>) -> Self {
14 Self::Protocol(message.into())
15 }
16}
17
18impl std::fmt::Display for UdtError {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 UdtError::Protocol(message) => write!(f, "Protocol error: {message}"),
22 UdtError::TagNotFound(tag) => write!(f, "Tag not found: {tag}"),
23 UdtError::DataTypeMismatch { expected, actual } => {
24 write!(f, "Data type mismatch: expected {expected}, got {actual}")
25 }
26 }
27 }
28}
29
30impl std::error::Error for UdtError {}
31use std::collections::HashMap;
32
33#[derive(Debug, Clone)]
35pub struct UdtDefinition {
36 pub name: String,
37 pub members: Vec<UdtMember>,
38}
39
40#[derive(Debug, Clone)]
42pub struct UdtMember {
43 pub name: String,
44 pub data_type: u16,
45 pub offset: u32,
46 pub size: u32,
47}
48
49#[derive(Debug, Clone)]
51pub struct UdtTemplate {
52 pub template_id: u32,
53 pub name: String,
54 pub size: u32,
55 pub member_count: u16,
56 pub members: Vec<UdtMember>,
57}
58
59#[derive(Debug, Clone)]
61pub struct TagAttributes {
62 pub name: String,
63 pub data_type: u16,
64 pub data_type_name: String,
65 pub dimensions: Vec<u32>,
66 pub permissions: TagPermissions,
67 pub scope: TagScope,
68 pub template_instance_id: Option<u32>,
69 pub size: u32,
70}
71
72#[derive(Clone, Copy)]
73struct RawMember {
74 info: u16,
75 raw_type: u16,
76 offset: u32,
77}
78
79#[derive(Debug, Clone, PartialEq)]
81pub enum TagPermissions {
82 ReadOnly,
83 ReadWrite,
84 WriteOnly,
85 Unknown,
86}
87
88#[derive(Debug, Clone, PartialEq)]
90pub enum TagScope {
91 Controller,
92 Program(String),
93 Unknown,
94}
95
96#[derive(Debug)]
98pub struct UdtManager {
99 definitions: HashMap<String, UdtDefinition>,
100 templates: HashMap<u32, UdtTemplate>,
101 tag_attributes: HashMap<String, TagAttributes>,
102}
103
104impl UdtManager {
105 pub fn new() -> Self {
106 Self {
107 definitions: HashMap::new(),
108 templates: HashMap::new(),
109 tag_attributes: HashMap::new(),
110 }
111 }
112
113 pub fn add_definition(&mut self, definition: UdtDefinition) {
115 self.definitions.insert(definition.name.clone(), definition);
116 }
117
118 pub fn get_definition(&self, name: &str) -> Option<&UdtDefinition> {
120 self.definitions.get(name)
121 }
122
123 pub fn add_template(&mut self, template: UdtTemplate) {
125 self.templates.insert(template.template_id, template);
126 }
127
128 pub fn get_template(&self, template_id: u32) -> Option<&UdtTemplate> {
130 self.templates.get(&template_id)
131 }
132
133 pub fn add_tag_attributes(&mut self, attributes: TagAttributes) {
135 self.tag_attributes
136 .insert(attributes.name.clone(), attributes);
137 }
138
139 pub fn get_tag_attributes(&self, name: &str) -> Option<&TagAttributes> {
141 self.tag_attributes.get(name)
142 }
143
144 pub fn list_definitions(&self) -> Vec<String> {
146 self.definitions.keys().cloned().collect()
147 }
148
149 pub fn list_templates(&self) -> Vec<u32> {
151 self.templates.keys().cloned().collect()
152 }
153
154 pub fn list_tag_attributes(&self) -> Vec<String> {
156 self.tag_attributes.keys().cloned().collect()
157 }
158
159 pub fn clear_cache(&mut self) {
161 self.definitions.clear();
162 self.templates.clear();
163 self.tag_attributes.clear();
164 }
165
166 pub fn parse_udt_template(
168 &self,
169 template_id: u32,
170 member_count: u16,
171 structure_size: u32,
172 data: &[u8],
173 ) -> Result<UdtTemplate> {
174 let member_section_len = member_count as usize * 8;
175 if data.len() < member_section_len {
176 return Err(UdtError::protocol(
177 "UDT template data too short".to_string(),
178 ));
179 }
180
181 let mut raw_members = Vec::with_capacity(member_count as usize);
182 for i in 0..member_count as usize {
183 let base = i * 8;
184 raw_members.push(RawMember {
185 info: u16::from_le_bytes([data[base], data[base + 1]]),
186 raw_type: u16::from_le_bytes([data[base + 2], data[base + 3]]),
187 offset: u32::from_le_bytes([
188 data[base + 4],
189 data[base + 5],
190 data[base + 6],
191 data[base + 7],
192 ]),
193 });
194 }
195
196 let strings = self.parse_null_terminated_strings(&data[member_section_len..]);
197 let template_name = strings
198 .first()
199 .and_then(|value| value.split(';').next())
200 .filter(|value| !value.is_empty())
201 .map(|value| value.to_string())
202 .unwrap_or_else(|| format!("Template_{}", template_id));
203
204 let member_names = strings.into_iter().skip(1);
205 let mut members = Vec::new();
206
207 for (raw_member, member_name) in raw_members.into_iter().zip(member_names) {
208 if member_name.is_empty() || member_name.starts_with("ZZZZZZZZZZ") {
209 continue;
210 }
211
212 let normalized_type = self.normalize_member_data_type(raw_member.raw_type);
213 let member_size = self.estimate_member_size(
214 raw_member,
215 normalized_type,
216 &data[..member_section_len],
217 structure_size,
218 );
219
220 members.push(UdtMember {
221 name: member_name,
222 data_type: normalized_type,
223 offset: raw_member.offset,
224 size: member_size,
225 });
226 }
227
228 Ok(UdtTemplate {
229 template_id,
230 name: template_name,
231 size: structure_size,
232 member_count,
233 members,
234 })
235 }
236
237 fn parse_null_terminated_strings(&self, data: &[u8]) -> Vec<String> {
238 data.split(|byte| *byte == 0)
239 .filter(|chunk| !chunk.is_empty())
240 .map(|chunk| String::from_utf8_lossy(chunk).to_string())
241 .collect()
242 }
243
244 fn normalize_member_data_type(&self, raw_type: u16) -> u16 {
245 let base_type = raw_type & 0x0FFF;
246 let is_structure = (raw_type & 0x8000) != 0;
247
248 if is_structure {
249 0x00A0
250 } else if base_type != 0 {
251 base_type
252 } else {
253 raw_type
254 }
255 }
256
257 fn estimate_member_size(
258 &self,
259 raw_member: RawMember,
260 normalized_type: u16,
261 member_section: &[u8],
262 structure_size: u32,
263 ) -> u32 {
264 let is_array = (raw_member.raw_type & 0x2000) != 0;
265 let is_structure = (raw_member.raw_type & 0x8000) != 0;
266
267 if normalized_type == 0x00C1 {
268 return 1;
269 }
270
271 if is_array {
272 let element_size = self.get_data_type_size(normalized_type);
273 return if normalized_type == 0x00D3 {
274 element_size
275 } else {
276 element_size.saturating_mul(raw_member.info as u32)
277 };
278 }
279
280 if is_structure {
281 let next_offset = member_section
282 .chunks_exact(8)
283 .filter_map(|chunk| {
284 let offset = u32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]);
285 (offset > raw_member.offset).then_some(offset)
286 })
287 .min()
288 .unwrap_or(structure_size);
289
290 return next_offset.saturating_sub(raw_member.offset).max(1);
291 }
292
293 self.get_data_type_size(normalized_type)
294 }
295
296 fn get_data_type_size(&self, data_type: u16) -> u32 {
298 match data_type {
299 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, }
313 }
314
315 pub fn parse_tag_attributes(&self, tag_name: &str, data: &[u8]) -> Result<TagAttributes> {
317 if data.len() < 8 {
318 return Err(UdtError::protocol(
319 "Tag attributes data too short".to_string(),
320 ));
321 }
322
323 let mut offset = 0;
324
325 let data_type = u16::from_le_bytes([data[offset], data[offset + 1]]);
327 offset += 2;
328
329 let size = u32::from_le_bytes([
331 data[offset],
332 data[offset + 1],
333 data[offset + 2],
334 data[offset + 3],
335 ]);
336 offset += 4;
337
338 let mut dimensions = Vec::new();
340 if data.len() > offset {
341 let dimension_count = data[offset] as usize;
342 offset += 1;
343
344 for _ in 0..dimension_count {
345 if offset + 4 <= data.len() {
346 let dim = u32::from_le_bytes([
347 data[offset],
348 data[offset + 1],
349 data[offset + 2],
350 data[offset + 3],
351 ]);
352 dimensions.push(dim);
353 offset += 4;
354 }
355 }
356 }
357
358 let permissions = TagPermissions::ReadWrite; let scope = if tag_name.contains(':') {
363 let parts: Vec<&str> = tag_name.split(':').collect();
364 if parts.len() >= 2 {
365 TagScope::Program(parts[0].to_string())
366 } else {
367 TagScope::Controller
368 }
369 } else {
370 TagScope::Controller
371 };
372
373 let data_type_name = self.get_data_type_name(data_type);
375
376 let template_instance_id = if data_type == 0x00A0 {
378 Some(0) } else {
381 None
382 };
383
384 Ok(TagAttributes {
385 name: tag_name.to_string(),
386 data_type,
387 data_type_name,
388 dimensions,
389 permissions,
390 scope,
391 template_instance_id,
392 size,
393 })
394 }
395
396 fn get_data_type_name(&self, data_type: u16) -> String {
398 match data_type {
399 0x00C1 => "BOOL".to_string(),
400 0x00C2 => "SINT".to_string(),
401 0x00C3 => "INT".to_string(),
402 0x00C4 => "DINT".to_string(),
403 0x00C5 => "LINT".to_string(),
404 0x00C6 => "USINT".to_string(),
405 0x00C7 => "UINT".to_string(),
406 0x00C8 => "UDINT".to_string(),
407 0x00C9 => "ULINT".to_string(),
408 0x00CA => "REAL".to_string(),
409 0x00CB => "LREAL".to_string(),
410 0x00CE => "STRING".to_string(),
411 0x00A0 => "UDT".to_string(),
412 _ => format!("UNKNOWN(0x{:04X})", data_type),
413 }
414 }
415
416 pub fn parse_udt_instance(&self, _udt_name: &str, data: &[u8]) -> Result<PlcValue> {
422 Ok(PlcValue::Udt(UdtData {
425 symbol_id: 0, data: data.to_vec(),
427 }))
428 }
429
430 pub fn serialize_udt_instance(
432 &self,
433 _udt_value: &HashMap<String, PlcValue>,
434 ) -> Result<Vec<u8>> {
435 Err(UdtError::protocol(
436 "UDT instance serialization is not implemented yet".to_string(),
437 ))
438 }
439}
440
441impl Default for UdtManager {
442 fn default() -> Self {
443 Self::new()
444 }
445}
446
447#[derive(Debug, Clone)]
451pub struct UserDefinedType {
452 pub name: String,
454 pub size: u32,
456 pub members: Vec<UdtMember>,
458 member_offsets: HashMap<String, u32>,
460}
461
462impl UserDefinedType {
463 pub fn new(name: String) -> Self {
465 Self {
466 name,
467 size: 0,
468 members: Vec::new(),
469 member_offsets: HashMap::new(),
470 }
471 }
472
473 pub fn add_member(&mut self, member: UdtMember) {
475 self.member_offsets
476 .insert(member.name.clone(), member.offset);
477 self.members.push(member);
478 self.size = self
480 .members
481 .iter()
482 .map(|m| m.offset + m.size)
483 .max()
484 .unwrap_or(0);
485 }
486
487 pub fn get_member_offset(&self, name: &str) -> Option<u32> {
489 self.member_offsets.get(name).copied()
490 }
491
492 pub fn from_cip_data(_data: &[u8]) -> Result<Self> {
494 Err(UdtError::protocol(
495 "UDT CIP definition parsing is not implemented yet".to_string(),
496 ))
497 }
498
499 pub fn to_hash_map(&self, data: &[u8]) -> Result<HashMap<String, PlcValue>> {
501 if data.is_empty() {
502 return Err(UdtError::protocol("UDT data is empty".to_string()));
503 }
504
505 let mut result = HashMap::new();
506
507 for member in &self.members {
508 let offset = member.offset as usize;
509 if offset + member.size as usize <= data.len() {
510 let member_data = &data[offset..offset + member.size as usize];
511 let value = self.parse_member_value(member, member_data)?;
512 result.insert(member.name.clone(), value);
513 }
514 }
515
516 Ok(result)
517 }
518
519 pub fn from_hash_map(&self, values: &HashMap<String, PlcValue>) -> Result<Vec<u8>> {
521 let mut data = vec![0u8; self.size as usize];
522
523 for member in &self.members {
524 if let Some(value) = values.get(&member.name) {
525 let member_data = self.serialize_member_value(member, value)?;
526 let offset = member.offset as usize;
527 let end_offset = offset + member_data.len();
528
529 if end_offset <= data.len() {
530 data[offset..end_offset].copy_from_slice(&member_data);
531 } else {
532 return Err(UdtError::protocol(format!(
533 "Member {} data exceeds UDT size",
534 member.name
535 )));
536 }
537 }
538 }
539
540 Ok(data)
541 }
542
543 pub fn read_member(&self, data: &[u8], member_name: &str) -> Result<PlcValue> {
545 if let Some(member) = self.members.iter().find(|m| m.name == member_name) {
546 let offset = member.offset as usize;
547 if offset + member.size as usize <= data.len() {
548 let member_data = &data[offset..offset + member.size as usize];
549 self.parse_member_value(member, member_data)
550 } else {
551 Err(UdtError::protocol(format!(
552 "Member {} data incomplete",
553 member_name
554 )))
555 }
556 } else {
557 Err(UdtError::TagNotFound(format!(
558 "UDT member '{}' not found",
559 member_name
560 )))
561 }
562 }
563
564 pub fn write_member(&self, data: &mut [u8], member_name: &str, value: &PlcValue) -> Result<()> {
566 if let Some(member) = self.members.iter().find(|m| m.name == member_name) {
567 let member_data = self.serialize_member_value(member, value)?;
568 let offset = member.offset as usize;
569 let end_offset = offset + member_data.len();
570
571 if end_offset <= data.len() {
572 data[offset..end_offset].copy_from_slice(&member_data);
573 Ok(())
574 } else {
575 Err(UdtError::protocol(format!(
576 "Member {} data exceeds UDT size",
577 member_name
578 )))
579 }
580 } else {
581 Err(UdtError::TagNotFound(format!(
582 "UDT member '{}' not found",
583 member_name
584 )))
585 }
586 }
587
588 pub fn get_member_size(&self, member_name: &str) -> Option<u32> {
590 self.members
591 .iter()
592 .find(|m| m.name == member_name)
593 .map(|m| m.size)
594 }
595
596 pub fn get_member_data_type(&self, member_name: &str) -> Option<u16> {
598 self.members
599 .iter()
600 .find(|m| m.name == member_name)
601 .map(|m| m.data_type)
602 }
603
604 pub fn parse_member_value(&self, member: &UdtMember, data: &[u8]) -> Result<PlcValue> {
606 match member.data_type {
607 0x00C1 => {
608 if data.is_empty() {
609 return Err(UdtError::protocol("BOOL data too short".to_string()));
610 }
611 Ok(PlcValue::Bool(data[0] != 0))
612 }
613 0x00C2 => {
614 if data.is_empty() {
616 return Err(UdtError::protocol("SINT data too short".to_string()));
617 }
618 Ok(PlcValue::Sint(data[0] as i8))
619 }
620 0x00C3 => {
621 if data.len() < 2 {
623 return Err(UdtError::protocol("INT data too short".to_string()));
624 }
625 let mut bytes = [0u8; 2];
626 bytes.copy_from_slice(&data[..2]);
627 Ok(PlcValue::Int(i16::from_le_bytes(bytes)))
628 }
629 0x00C4 => {
630 if data.len() < 4 {
632 return Err(UdtError::protocol("DINT data too short".to_string()));
633 }
634 let mut bytes = [0u8; 4];
635 bytes.copy_from_slice(&data[..4]);
636 Ok(PlcValue::Dint(i32::from_le_bytes(bytes)))
637 }
638 0x00C5 => {
639 if data.len() < 8 {
641 return Err(UdtError::protocol("LINT data too short".to_string()));
642 }
643 let mut bytes = [0u8; 8];
644 bytes.copy_from_slice(&data[..8]);
645 Ok(PlcValue::Lint(i64::from_le_bytes(bytes)))
646 }
647 0x00C6 => {
648 if data.is_empty() {
650 return Err(UdtError::protocol("USINT data too short".to_string()));
651 }
652 Ok(PlcValue::Usint(data[0]))
653 }
654 0x00C7 => {
655 if data.len() < 2 {
657 return Err(UdtError::protocol("UINT data too short".to_string()));
658 }
659 let mut bytes = [0u8; 2];
660 bytes.copy_from_slice(&data[..2]);
661 Ok(PlcValue::Uint(u16::from_le_bytes(bytes)))
662 }
663 0x00C8 => {
664 if data.len() < 4 {
666 return Err(UdtError::protocol("UDINT data too short".to_string()));
667 }
668 let mut bytes = [0u8; 4];
669 bytes.copy_from_slice(&data[..4]);
670 Ok(PlcValue::Udint(u32::from_le_bytes(bytes)))
671 }
672 0x00C9 => {
673 if data.len() < 8 {
675 return Err(UdtError::protocol("ULINT data too short".to_string()));
676 }
677 let mut bytes = [0u8; 8];
678 bytes.copy_from_slice(&data[..8]);
679 Ok(PlcValue::Ulint(u64::from_le_bytes(bytes)))
680 }
681 0x00CA => {
682 if data.len() < 4 {
684 return Err(UdtError::protocol("REAL data too short".to_string()));
685 }
686 let mut bytes = [0u8; 4];
687 bytes.copy_from_slice(&data[..4]);
688 Ok(PlcValue::Real(f32::from_le_bytes(bytes)))
689 }
690 0x00CB => {
691 if data.len() < 8 {
693 return Err(UdtError::protocol("LREAL data too short".to_string()));
694 }
695 let mut bytes = [0u8; 8];
696 bytes.copy_from_slice(&data[..8]);
697 Ok(PlcValue::Lreal(f64::from_le_bytes(bytes)))
698 }
699 0x00CE => {
700 if data.len() < 4 {
702 return Err(UdtError::protocol("STRING data too short".to_string()));
703 }
704 let length = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
705 if data.len() - 4 < length {
706 return Err(UdtError::protocol("STRING data incomplete".to_string()));
707 }
708 let string_data = &data[4..4 + length];
709 let string_value = String::from_utf8_lossy(string_data).to_string();
710 Ok(PlcValue::String(string_value))
711 }
712 _ => Err(UdtError::protocol(format!(
713 "Unsupported UDT data type: 0x{:04X}",
714 member.data_type
715 ))),
716 }
717 }
718
719 pub fn serialize_member_value(&self, member: &UdtMember, value: &PlcValue) -> Result<Vec<u8>> {
721 match member.data_type {
722 0x00C1 => match value {
723 PlcValue::Bool(b) => Ok(vec![if *b { 0xFF } else { 0x00 }]),
724 _ => Err(UdtError::DataTypeMismatch {
725 expected: "BOOL".to_string(),
726 actual: format!("{:?}", value),
727 }),
728 },
729 0x00C2 => match value {
730 PlcValue::Sint(s) => Ok(vec![*s as u8]),
731 _ => Err(UdtError::DataTypeMismatch {
732 expected: "SINT".to_string(),
733 actual: format!("{:?}", value),
734 }),
735 },
736 0x00C3 => match value {
737 PlcValue::Int(i) => Ok(i.to_le_bytes().to_vec()),
738 _ => Err(UdtError::DataTypeMismatch {
739 expected: "INT".to_string(),
740 actual: format!("{:?}", value),
741 }),
742 },
743 0x00C4 => match value {
744 PlcValue::Dint(d) => Ok(d.to_le_bytes().to_vec()),
745 _ => Err(UdtError::DataTypeMismatch {
746 expected: "DINT".to_string(),
747 actual: format!("{:?}", value),
748 }),
749 },
750 0x00C5 => match value {
751 PlcValue::Lint(l) => Ok(l.to_le_bytes().to_vec()),
752 _ => Err(UdtError::DataTypeMismatch {
753 expected: "LINT".to_string(),
754 actual: format!("{:?}", value),
755 }),
756 },
757 0x00C6 => match value {
758 PlcValue::Usint(u) => Ok(vec![*u]),
759 _ => Err(UdtError::DataTypeMismatch {
760 expected: "USINT".to_string(),
761 actual: format!("{:?}", value),
762 }),
763 },
764 0x00C7 => match value {
765 PlcValue::Uint(u) => Ok(u.to_le_bytes().to_vec()),
766 _ => Err(UdtError::DataTypeMismatch {
767 expected: "UINT".to_string(),
768 actual: format!("{:?}", value),
769 }),
770 },
771 0x00C8 => match value {
772 PlcValue::Udint(u) => Ok(u.to_le_bytes().to_vec()),
773 _ => Err(UdtError::DataTypeMismatch {
774 expected: "UDINT".to_string(),
775 actual: format!("{:?}", value),
776 }),
777 },
778 0x00C9 => match value {
779 PlcValue::Ulint(u) => Ok(u.to_le_bytes().to_vec()),
780 _ => Err(UdtError::DataTypeMismatch {
781 expected: "ULINT".to_string(),
782 actual: format!("{:?}", value),
783 }),
784 },
785 0x00CA => match value {
786 PlcValue::Real(r) => Ok(r.to_le_bytes().to_vec()),
787 _ => Err(UdtError::DataTypeMismatch {
788 expected: "REAL".to_string(),
789 actual: format!("{:?}", value),
790 }),
791 },
792 0x00CB => match value {
793 PlcValue::Lreal(l) => Ok(l.to_le_bytes().to_vec()),
794 _ => Err(UdtError::DataTypeMismatch {
795 expected: "LREAL".to_string(),
796 actual: format!("{:?}", value),
797 }),
798 },
799 0x00CE => {
800 match value {
801 PlcValue::String(s) => {
802 let mut result = Vec::new();
803 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);
806 result.extend_from_slice(&length.to_le_bytes());
808 result.extend_from_slice(&s.as_bytes()[..length as usize]);
809 while result.len() < member.size as usize && result.len() % 2 != 0 {
811 result.push(0);
812 }
813 if result.len() > member.size as usize {
815 result.truncate(member.size as usize);
816 }
817 Ok(result)
818 }
819 _ => Err(UdtError::DataTypeMismatch {
820 expected: "STRING".to_string(),
821 actual: format!("{:?}", value),
822 }),
823 }
824 }
825 _ => Err(UdtError::protocol(format!(
826 "Unsupported UDT data type for serialization: 0x{:04X}",
827 member.data_type
828 ))),
829 }
830 }
831}
832
833impl UdtCodec for UserDefinedType {
834 fn to_hash_map(
835 &self,
836 data: &[u8],
837 ) -> rust_ethernet_ip_types::Result<HashMap<String, PlcValue>> {
838 UserDefinedType::to_hash_map(self, data).map_err(|error| TypeError::new(error.to_string()))
839 }
840
841 fn encode_hash_map(
842 &self,
843 values: &HashMap<String, PlcValue>,
844 ) -> rust_ethernet_ip_types::Result<Vec<u8>> {
845 UserDefinedType::from_hash_map(self, values)
846 .map_err(|error| TypeError::new(error.to_string()))
847 }
848}
849
850#[cfg(test)]
851mod tests {
852 use super::*;
853
854 #[test]
855 fn test_udt_member_offsets() {
856 let mut udt = UserDefinedType::new("TestUDT".to_string());
857
858 udt.add_member(UdtMember {
859 name: "Bool1".to_string(),
860 data_type: 0x00C1,
861 offset: 0,
862 size: 1,
863 });
864
865 udt.add_member(UdtMember {
866 name: "Dint1".to_string(),
867 data_type: 0x00C4,
868 offset: 4,
869 size: 4,
870 });
871
872 assert_eq!(udt.get_member_offset("Bool1"), Some(0));
873 assert_eq!(udt.get_member_offset("Dint1"), Some(4));
874 assert_eq!(udt.size, 8);
875 }
876
877 #[test]
878 fn test_udt_parsing() {
879 let mut udt = UserDefinedType::new("TestUDT".to_string());
880
881 udt.add_member(UdtMember {
882 name: "Bool1".to_string(),
883 data_type: 0x00C1,
884 offset: 0,
885 size: 1,
886 });
887
888 udt.add_member(UdtMember {
889 name: "Dint1".to_string(),
890 data_type: 0x00C4,
891 offset: 4,
892 size: 4,
893 });
894
895 let data = vec![0xFF, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00];
896 let result = udt.to_hash_map(&data).unwrap();
897
898 assert_eq!(result.get("Bool1"), Some(&PlcValue::Bool(true)));
899 assert_eq!(result.get("Dint1"), Some(&PlcValue::Dint(42)));
900 }
901
902 #[test]
903 fn test_from_cip_data_returns_explicit_error_until_implemented() {
904 let result = UserDefinedType::from_cip_data(&[0x01, 0x02, 0x03]);
905 assert!(result.is_err());
906 let error_text = result.err().unwrap().to_string();
907 assert!(error_text.contains("not implemented"));
908 }
909
910 #[test]
911 fn test_serialize_udt_instance_returns_explicit_error_until_implemented() {
912 let manager = UdtManager::new();
913 let values = HashMap::new();
914 let result = manager.serialize_udt_instance(&values);
915 assert!(result.is_err());
916 let error_text = result.err().unwrap().to_string();
917 assert!(error_text.contains("not implemented"));
918 }
919
920 #[test]
921 fn test_parse_udt_template_reads_live_style_member_records() {
922 let manager = UdtManager::new();
923 let data = vec![
924 0x00, 0x00, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xCA, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0xC2, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC1, 0x00, 0x08, 0x00, 0x00, 0x00, b'T', b'E', b'S', b'T', b'_', b'U', b'D', b'T', b';', b'n', 0x00, b'M', b'e', b'm',
929 b'b', b'e', b'r', b'1', 0x00, b'M', b'e', b'm', b'b', b'e', b'r', b'2', 0x00, b'Z',
930 b'Z', b'Z', b'Z', b'Z', b'Z', b'Z', b'Z', b'Z', b'Z', 0x00, b'F', b'l', b'a', b'g',
931 0x00,
932 ];
933
934 let template = manager
935 .parse_udt_template(123, 4, 12, &data)
936 .expect("template should parse");
937
938 assert_eq!(template.name, "TEST_UDT");
939 assert_eq!(template.members.len(), 3);
940 assert_eq!(template.members[0].name, "Member1");
941 assert_eq!(template.members[0].data_type, 0x00C4);
942 assert_eq!(template.members[0].offset, 0);
943 assert_eq!(template.members[1].name, "Member2");
944 assert_eq!(template.members[1].data_type, 0x00CA);
945 assert_eq!(template.members[1].offset, 4);
946 assert_eq!(template.members[2].name, "Flag");
947 assert_eq!(template.members[2].data_type, 0x00C1);
948 assert_eq!(template.members[2].offset, 8);
949 assert_eq!(template.members[2].size, 1);
950 }
951}