rust_ethernet_ip_protocol/
cip.rs1use bytes::{Buf, BufMut, BytesMut};
4
5use crate::{Decode, Encode, ProtocolError, Result};
6
7pub const READ_TAG: u8 = 0x4C;
9pub const WRITE_TAG: u8 = 0x4D;
11#[allow(dead_code)]
12pub const MULTIPLE_SERVICE_PACKET: u8 = 0x0A;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct CipRequest {
18 pub service: u8,
20 pub path: Vec<u8>,
22 pub data: Vec<u8>,
24}
25
26impl CipRequest {
27 pub fn new(service: u8, path: Vec<u8>, data: Vec<u8>) -> Self {
29 Self {
30 service,
31 path,
32 data,
33 }
34 }
35
36 pub fn validate(&self) -> Result<()> {
38 if self.path.is_empty() {
39 return Err(ProtocolError::new(format!(
40 "invalid CIP request path for service 0x{:02X}: path must not be empty",
41 self.service
42 )));
43 }
44
45 if !self.path.len().is_multiple_of(2) {
46 return Err(ProtocolError::new(format!(
47 "invalid CIP request path for service 0x{:02X}: path length {} is not word-aligned",
48 self.service,
49 self.path.len()
50 )));
51 }
52
53 let path_words = self.path.len() / 2;
54 if path_words > usize::from(u8::MAX) {
55 return Err(ProtocolError::new(format!(
56 "invalid CIP request path for service 0x{:02X}: path length {} bytes exceeds 510-byte CIP limit",
57 self.service,
58 self.path.len()
59 )));
60 }
61
62 Ok(())
63 }
64
65 pub fn encode(&self, buf: &mut BytesMut) -> Result<()> {
67 self.validate()?;
68 buf.put_u8(self.service);
69 let path_words =
70 u8::try_from(self.path.len() / 2).expect("validated path word count fits in u8");
71 buf.put_u8(path_words);
72 buf.put_slice(&self.path);
73 buf.put_slice(&self.data);
74 Ok(())
75 }
76}
77
78impl Decode for CipRequest {
79 fn decode(buf: &mut impl Buf) -> Result<Self> {
80 if buf.remaining() < 2 {
81 return Err(ProtocolError::new("CIP request too short".to_string()));
82 }
83
84 let service = buf.get_u8();
85 let path_size_words = buf.get_u8() as usize;
86 let path_len = path_size_words * 2;
87 if buf.remaining() < path_len {
88 return Err(ProtocolError::new("CIP request path truncated".to_string()));
89 }
90 let path = buf.copy_to_bytes(path_len).to_vec();
91 let data = buf.copy_to_bytes(buf.remaining()).to_vec();
92
93 Ok(Self {
94 service,
95 path,
96 data,
97 })
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct CipResponse {
104 pub service: u8,
106 pub status: u8,
108 pub additional_status: Vec<u16>,
110 pub data: Vec<u8>,
112}
113
114impl Encode for CipResponse {
115 fn encode(&self, buf: &mut BytesMut) {
116 buf.put_u8(self.service);
117 buf.put_u8(0);
118 buf.put_u8(self.status);
119 buf.put_u8(self.additional_status.len() as u8);
120 for status in &self.additional_status {
121 buf.put_u16_le(*status);
122 }
123 buf.put_slice(&self.data);
124 }
125}
126
127impl Decode for CipResponse {
128 fn decode(buf: &mut impl Buf) -> Result<Self> {
129 if buf.remaining() < 4 {
130 return Err(ProtocolError::new("CIP response too short".to_string()));
131 }
132
133 let service = buf.get_u8();
134 let _reserved = buf.get_u8();
135 let status = buf.get_u8();
136 let additional_status_size = buf.get_u8() as usize;
137 if buf.remaining() < additional_status_size * 2 {
138 return Err(ProtocolError::new(
139 "CIP response additional status truncated".to_string(),
140 ));
141 }
142
143 let mut additional_status = Vec::with_capacity(additional_status_size);
144 for _ in 0..additional_status_size {
145 additional_status.push(buf.get_u16_le());
146 }
147 let data = buf.copy_to_bytes(buf.remaining()).to_vec();
148
149 Ok(Self {
150 service,
151 status,
152 additional_status,
153 data,
154 })
155 }
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct CpfItem {
161 pub type_id: u16,
163 pub data: Vec<u8>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct SendDataRequest {
170 pub interface_handle: u32,
172 pub timeout: u16,
174 pub items: Vec<CpfItem>,
176}
177
178impl SendDataRequest {
179 pub fn unconnected(item_data: &[u8]) -> Self {
181 Self {
182 interface_handle: 0,
183 timeout: 5,
184 items: vec![
185 CpfItem {
186 type_id: 0x0000,
187 data: Vec::new(),
188 },
189 CpfItem {
190 type_id: 0x00B2,
191 data: item_data.to_vec(),
192 },
193 ],
194 }
195 }
196}
197
198impl Encode for SendDataRequest {
199 fn encode(&self, buf: &mut BytesMut) {
200 buf.put_u32_le(self.interface_handle);
201 buf.put_u16_le(self.timeout);
202 buf.put_u16_le(self.items.len() as u16);
203 for item in &self.items {
204 buf.put_u16_le(item.type_id);
205 buf.put_u16_le(item.data.len() as u16);
206 buf.put_slice(&item.data);
207 }
208 }
209}
210
211impl Decode for SendDataRequest {
212 fn decode(buf: &mut impl Buf) -> Result<Self> {
213 if buf.remaining() < 8 {
214 return Err(ProtocolError::new("CPF data too short"));
215 }
216
217 let interface_handle = buf.get_u32_le();
218 let timeout = buf.get_u16_le();
219 let item_count = buf.get_u16_le() as usize;
220 let mut items = Vec::with_capacity(item_count);
221 for _ in 0..item_count {
222 if buf.remaining() < 4 {
223 return Err(ProtocolError::new("Response truncated while parsing items"));
224 }
225 let type_id = buf.get_u16_le();
226 let item_length = buf.get_u16_le() as usize;
227 if buf.remaining() < item_length {
228 return Err(ProtocolError::new("Data item truncated"));
229 }
230 items.push(CpfItem {
231 type_id,
232 data: buf.copy_to_bytes(item_length).to_vec(),
233 });
234 }
235
236 Ok(Self {
237 interface_handle,
238 timeout,
239 items,
240 })
241 }
242}