Skip to content

Commit ddf86cb

Browse files
committed
add structured IdentityInfo support and full Identity Object reader
1 parent e2cf5d5 commit ddf86cb

3 files changed

Lines changed: 247 additions & 2 deletions

File tree

src/client/traits.rs

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::io;
22

33
use crate::cip::CipError;
44
use crate::client::ForwardOpenError;
5-
use crate::types::{CipValue, SymbolInfo};
5+
use crate::types::{CipValue, IdentityInfo, SymbolInfo};
66
use crate::MultiResult;
77

88
pub trait ConnectionManagement {
@@ -70,6 +70,11 @@ pub trait TagReadWrite {
7070
self.read_object_attributes(0x01, 0x01).await
7171
}
7272

73+
async fn read_identity(&mut self) -> Result<IdentityInfo, CipError> {
74+
let raw = self.read_identity_attributes().await?;
75+
IdentityInfo::decode(&raw).map_err(|_| CipError::VendorSpecific(0xFC))
76+
}
77+
7378
async fn read_bool(&mut self, tag: &str) -> Result<bool, CipError>;
7479
async fn read_sint(&mut self, tag: &str) -> Result<i8, CipError>;
7580
async fn read_int(&mut self, tag: &str) -> Result<i16, CipError>;
@@ -108,3 +113,128 @@ pub trait MultipleServicePacket {
108113
pub trait SymbolBrowsing {
109114
async fn browse_symbols(&mut self) -> Result<Vec<SymbolInfo>, CipError>;
110115
}
116+
117+
#[cfg(test)]
118+
mod tests {
119+
use super::*;
120+
121+
struct StubIdentityClient {
122+
raw_attributes: Vec<u8>,
123+
}
124+
125+
#[async_trait::async_trait]
126+
impl TagReadWrite for StubIdentityClient {
127+
async fn read_tag(&mut self, _: &str) -> Result<CipValue, CipError> {
128+
Err(CipError::VendorSpecific(0xFF))
129+
}
130+
131+
async fn write_tag(&mut self, _: &str, _: CipValue) -> Result<(), CipError> {
132+
Err(CipError::VendorSpecific(0xFF))
133+
}
134+
135+
async fn read_tag_multi(&mut self, _: &str, _: usize) -> Result<Vec<CipValue>, CipError> {
136+
Err(CipError::VendorSpecific(0xFF))
137+
}
138+
139+
async fn write_tag_multi(&mut self, _: &str, _: &[CipValue]) -> Result<(), CipError> {
140+
Err(CipError::VendorSpecific(0xFF))
141+
}
142+
143+
async fn read_object_attribute(
144+
&mut self,
145+
_: u8,
146+
_: u8,
147+
_: u8,
148+
) -> Result<CipValue, CipError> {
149+
Err(CipError::VendorSpecific(0xFF))
150+
}
151+
152+
async fn read_object_attributes(&mut self, _: u8, _: u8) -> Result<Vec<u8>, CipError> {
153+
Ok(self.raw_attributes.clone())
154+
}
155+
156+
async fn read_bool(&mut self, _: &str) -> Result<bool, CipError> {
157+
Err(CipError::VendorSpecific(0xFF))
158+
}
159+
160+
async fn read_sint(&mut self, _: &str) -> Result<i8, CipError> {
161+
Err(CipError::VendorSpecific(0xFF))
162+
}
163+
164+
async fn read_int(&mut self, _: &str) -> Result<i16, CipError> {
165+
Err(CipError::VendorSpecific(0xFF))
166+
}
167+
168+
async fn read_dint(&mut self, _: &str) -> Result<i32, CipError> {
169+
Err(CipError::VendorSpecific(0xFF))
170+
}
171+
172+
async fn read_real(&mut self, _: &str) -> Result<f32, CipError> {
173+
Err(CipError::VendorSpecific(0xFF))
174+
}
175+
176+
async fn read_string(&mut self, _: &str) -> Result<String, CipError> {
177+
Err(CipError::VendorSpecific(0xFF))
178+
}
179+
180+
async fn write_bool(&mut self, _: &str, _: bool) -> Result<(), CipError> {
181+
Err(CipError::VendorSpecific(0xFF))
182+
}
183+
184+
async fn write_sint(&mut self, _: &str, _: i8) -> Result<(), CipError> {
185+
Err(CipError::VendorSpecific(0xFF))
186+
}
187+
188+
async fn write_int(&mut self, _: &str, _: i16) -> Result<(), CipError> {
189+
Err(CipError::VendorSpecific(0xFF))
190+
}
191+
192+
async fn write_dint(&mut self, _: &str, _: i32) -> Result<(), CipError> {
193+
Err(CipError::VendorSpecific(0xFF))
194+
}
195+
196+
async fn write_real(&mut self, _: &str, _: f32) -> Result<(), CipError> {
197+
Err(CipError::VendorSpecific(0xFF))
198+
}
199+
200+
async fn write_string(&mut self, _: &str, _: &str) -> Result<(), CipError> {
201+
Err(CipError::VendorSpecific(0xFF))
202+
}
203+
}
204+
205+
#[tokio::test]
206+
async fn test_read_identity_default_impl() {
207+
let mut raw = Vec::new();
208+
raw.extend_from_slice(&0x1234u16.to_le_bytes());
209+
raw.extend_from_slice(&0x5678u16.to_le_bytes());
210+
raw.extend_from_slice(&0x9ABCu16.to_le_bytes());
211+
raw.extend_from_slice(&[0x01, 0x02]);
212+
raw.extend_from_slice(&0x3344u16.to_le_bytes());
213+
raw.extend_from_slice(&0x11223344u32.to_le_bytes());
214+
215+
let product_name = b"TestProduct";
216+
raw.extend_from_slice(&(product_name.len() as u16).to_le_bytes());
217+
raw.extend_from_slice(product_name);
218+
raw.extend(std::iter::repeat_n(0, 82 - product_name.len()));
219+
220+
raw.push(0x05);
221+
222+
let mut client = StubIdentityClient {
223+
raw_attributes: raw,
224+
};
225+
let identity = client
226+
.read_identity()
227+
.await
228+
.expect("read_identity should succeed");
229+
230+
assert_eq!(identity.vendor_id, 0x1234);
231+
assert_eq!(identity.device_type, 0x5678);
232+
assert_eq!(identity.product_code, 0x9ABC);
233+
assert_eq!(identity.revision_major, 0x01);
234+
assert_eq!(identity.revision_minor, 0x02);
235+
assert_eq!(identity.status, 0x3344);
236+
assert_eq!(identity.serial_number, 0x11223344);
237+
assert_eq!(identity.product_name, "TestProduct");
238+
assert_eq!(identity.state, 0x05);
239+
}
240+
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ pub use client::{
1818
SymbolBrowsing, TagReadWrite,
1919
};
2020

21-
pub use types::{CipType, CipValue, MultiResult};
21+
pub use types::{CipType, CipValue, IdentityInfo, MultiResult};

src/types.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,87 @@ pub enum CipValue {
5353
Unit,
5454
}
5555

56+
#[derive(Debug, Clone, PartialEq)]
57+
pub struct IdentityInfo {
58+
pub vendor_id: u16,
59+
pub device_type: u16,
60+
pub product_code: u16,
61+
pub revision_major: u8,
62+
pub revision_minor: u8,
63+
pub status: u16,
64+
pub serial_number: u32,
65+
pub product_name: String,
66+
pub state: u8,
67+
}
68+
69+
impl IdentityInfo {
70+
pub fn decode(data: &[u8]) -> Result<Self, &'static str> {
71+
if data.len() < 2 + 2 + 2 + 2 + 2 + 4 + 2 + 1 {
72+
return Err("not enough identity attribute data");
73+
}
74+
75+
let mut pos = 0;
76+
77+
let vendor_id = u16::from_le_bytes([data[pos], data[pos + 1]]);
78+
pos += 2;
79+
80+
let device_type = u16::from_le_bytes([data[pos], data[pos + 1]]);
81+
pos += 2;
82+
83+
let product_code = u16::from_le_bytes([data[pos], data[pos + 1]]);
84+
pos += 2;
85+
86+
let revision_major = data[pos];
87+
let revision_minor = data[pos + 1];
88+
pos += 2;
89+
90+
let status = u16::from_le_bytes([data[pos], data[pos + 1]]);
91+
pos += 2;
92+
93+
let serial_number =
94+
u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
95+
pos += 4;
96+
97+
if data.len() < pos + 2 {
98+
return Err("identity product name too short");
99+
}
100+
101+
let name_len = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
102+
pos += 2;
103+
104+
if data.len() < pos + name_len {
105+
return Err("identity product name length exceeds available data");
106+
}
107+
108+
let product_name = String::from_utf8_lossy(&data[pos..pos + name_len]).into_owned();
109+
110+
let string_block_len = 82;
111+
if data.len() >= pos + string_block_len {
112+
pos += string_block_len;
113+
} else {
114+
pos += name_len;
115+
}
116+
117+
if data.len() <= pos {
118+
return Err("identity state missing");
119+
}
120+
121+
let state = data[pos];
122+
123+
Ok(Self {
124+
vendor_id,
125+
device_type,
126+
product_code,
127+
revision_major,
128+
revision_minor,
129+
status,
130+
serial_number,
131+
product_name,
132+
state,
133+
})
134+
}
135+
}
136+
56137
impl CipValue {
57138
pub fn type_name(&self) -> &'static str {
58139
match self {
@@ -85,3 +166,37 @@ pub struct SymbolInfo {
85166
pub typ: CipType,
86167
pub array_dims: Option<(u16, u16, u16)>, // up to 3D, unused dims = 0
87168
}
169+
170+
#[cfg(test)]
171+
mod tests {
172+
use super::*;
173+
174+
#[test]
175+
fn test_identity_info_decode() {
176+
let mut raw = Vec::new();
177+
raw.extend_from_slice(&0x1234u16.to_le_bytes());
178+
raw.extend_from_slice(&0x5678u16.to_le_bytes());
179+
raw.extend_from_slice(&0x9ABCu16.to_le_bytes());
180+
raw.extend_from_slice(&[0x01, 0x02]);
181+
raw.extend_from_slice(&0x3344u16.to_le_bytes());
182+
raw.extend_from_slice(&0x11223344u32.to_le_bytes());
183+
184+
let product_name = b"TestProduct";
185+
raw.extend_from_slice(&(product_name.len() as u16).to_le_bytes());
186+
raw.extend_from_slice(product_name);
187+
raw.extend(std::iter::repeat_n(0, 82 - product_name.len()));
188+
189+
raw.push(0x05);
190+
191+
let identity = IdentityInfo::decode(&raw).expect("decode should succeed");
192+
assert_eq!(identity.vendor_id, 0x1234);
193+
assert_eq!(identity.device_type, 0x5678);
194+
assert_eq!(identity.product_code, 0x9ABC);
195+
assert_eq!(identity.revision_major, 0x01);
196+
assert_eq!(identity.revision_minor, 0x02);
197+
assert_eq!(identity.status, 0x3344);
198+
assert_eq!(identity.serial_number, 0x11223344);
199+
assert_eq!(identity.product_name, "TestProduct");
200+
assert_eq!(identity.state, 0x05);
201+
}
202+
}

0 commit comments

Comments
 (0)