1use std::time::Duration;
2
3#[derive(Debug, Clone)]
4pub struct ConnectParams {
5 pub rack: u8,
6 pub slot: u8,
7 pub pdu_size: u16,
8 pub connect_timeout: Duration,
9 pub request_timeout: Duration,
10}
11
12impl Default for ConnectParams {
13 fn default() -> Self {
14 Self {
15 rack: 0,
16 slot: 1,
17 pdu_size: 480,
18 connect_timeout: Duration::from_secs(5),
19 request_timeout: Duration::from_secs(10),
20 }
21 }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum PlcStatus {
27 Unknown = 0x00,
29 Stop = 0x04,
31 Run = 0x08,
33}
34
35#[derive(Debug, Clone)]
37pub struct OrderCode {
38 pub code: String,
40}
41
42#[derive(Debug, Clone)]
44pub struct CpuInfo {
45 pub module_type: String,
47 pub serial_number: String,
49 pub as_name: String,
51 pub copyright: String,
53 pub module_name: String,
55}
56
57#[derive(Debug, Clone)]
59pub struct CpInfo {
60 pub max_pdu_len: u32,
62 pub max_connections: u32,
64 pub max_mpi_rate: u32,
66 pub max_bus_rate: u32,
68}
69
70#[derive(Debug, Clone)]
72pub struct Protection {
73 pub scheme_szl: u16,
75 pub scheme_module: u16,
77 pub scheme_bus: u16,
79 pub level: u16,
81 pub password_set: bool,
83}
84
85pub fn encrypt_password(password: &str) -> [u8; 8] {
91 let bytes = password.as_bytes();
92 let mut pw = [0x20u8; 8]; let len = bytes.len().min(8);
94 pw[..len].copy_from_slice(&bytes[..len]);
95 let mut result = [0u8; 8];
96 for i in 0..8 {
97 result[i] = (pw[i] << 4) | (pw[i] >> 4);
99 result[i] ^= 0x55;
100 }
101 result
102}
103
104#[derive(Debug, Clone)]
106pub struct ModuleEntry {
107 pub module_type: u16,
109}
110
111#[derive(Debug, Clone)]
113pub struct BlockListEntry {
114 pub block_type: u16,
116 pub count: u16,
118}
119
120#[derive(Debug, Clone)]
122pub struct BlockList {
123 pub total_count: u32,
125 pub entries: Vec<BlockListEntry>,
127}
128
129#[derive(Debug, Clone)]
137pub struct BlockData {
138 pub block_type: u16,
140 pub block_number: u16,
142 pub format: u16,
144 pub total_length: u32,
146 pub flags: u16,
148 pub crc1: u16,
150 pub crc2: u16,
152 pub payload: Vec<u8>,
154}
155
156impl BlockData {
157 pub fn from_bytes(data: &[u8]) -> Option<Self> {
159 if data.len() < 20 {
160 return None;
161 }
162 let block_type = u16::from_be_bytes([data[0], data[1]]);
163 let block_number = u16::from_be_bytes([data[2], data[3]]);
164 let format = u16::from_be_bytes([data[4], data[5]]);
165 let total_length = u32::from_be_bytes([data[6], data[7], data[8], data[9]]);
166 let flags = u16::from_be_bytes([data[10], data[11]]);
167 let crc1 = u16::from_be_bytes([data[12], data[13]]);
168 let crc2 = u16::from_be_bytes([data[14], data[15]]);
169 let payload = data[20..].to_vec();
171 Some(BlockData {
172 block_type,
173 block_number,
174 format,
175 total_length,
176 flags,
177 crc1,
178 crc2,
179 payload,
180 })
181 }
182
183 pub fn to_bytes(&self) -> Vec<u8> {
185 let mut buf = Vec::with_capacity(20 + self.payload.len());
186 buf.extend_from_slice(&self.block_type.to_be_bytes());
187 buf.extend_from_slice(&self.block_number.to_be_bytes());
188 buf.extend_from_slice(&self.format.to_be_bytes());
189 buf.extend_from_slice(&self.total_length.to_be_bytes());
190 buf.extend_from_slice(&self.flags.to_be_bytes());
191 buf.extend_from_slice(&self.crc1.to_be_bytes());
192 buf.extend_from_slice(&self.crc2.to_be_bytes());
193 buf.extend_from_slice(&[0u8; 4]); buf.extend_from_slice(&self.payload);
195 buf
196 }
197}
198
199#[derive(Debug, Clone)]
203pub struct BlockInfo {
204 pub block_type: u16,
205 pub block_number: u16,
206 pub language: u16,
207 pub flags: u16,
208 pub size: u16,
209 pub size_ram: u16,
210 pub mc7_size: u16,
211 pub local_data: u16,
212 pub checksum: u16,
213 pub version: u16,
214 pub author: String,
215 pub family: String,
216 pub header: String,
217 pub date: String,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221#[repr(u8)]
222pub enum BlockType {
223 OB = 0x38,
224 DB = 0x41,
225 SDB = 0x42,
226 FC = 0x43,
227 SFC = 0x44,
228 FB = 0x45,
229 SFB = 0x46,
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn connect_params_default() {
238 let p = ConnectParams::default();
239 assert_eq!(p.rack, 0);
240 assert_eq!(p.slot, 1);
241 assert_eq!(p.pdu_size, 480);
242 }
243
244 #[test]
245 fn block_data_roundtrip() {
246 let bd = super::BlockData {
247 block_type: 0x41, block_number: 1,
249 format: 0,
250 total_length: 24,
251 flags: 0,
252 crc1: 0x1234,
253 crc2: 0x5678,
254 payload: vec![0xDE, 0xAD],
255 };
256 let bytes = bd.to_bytes();
257 assert_eq!(bytes.len(), 22); let parsed = super::BlockData::from_bytes(&bytes).unwrap();
259 assert_eq!(parsed.block_type, 0x41);
260 assert_eq!(parsed.block_number, 1);
261 assert_eq!(parsed.payload, vec![0xDE, 0xAD]);
262 }
263
264 #[test]
265 fn block_data_short_input_returns_none() {
266 let result = super::BlockData::from_bytes(&[0u8; 10]);
267 assert!(result.is_none());
268 }
269
270 #[test]
271 fn encrypt_8_char_password() {
272 let result = super::encrypt_password("PASSWORD");
274 assert_eq!(result.len(), 8);
275 let result2 = super::encrypt_password("PASSWORD");
281 assert_eq!(result, result2);
282 }
283
284 #[test]
285 fn encrypt_short_password_padded() {
286 let result = super::encrypt_password("abc");
287 assert_eq!((0x61u8 << 4) | (0x61u8 >> 4), 0x16);
290 assert_eq!(0x16 ^ 0x55, 0x43);
291 assert_eq!(result[0], 0x43);
292 assert_eq!((0x20u8 << 4) | (0x20u8 >> 4), 0x02);
294 assert_eq!(0x02 ^ 0x55, 0x57);
295 assert_eq!(result[3], 0x57);
296 }
297
298 #[test]
299 fn encrypt_long_password_truncated() {
300 let result = super::encrypt_password("1234567890");
301 assert_eq!(result.len(), 8);
302 let result8 = super::encrypt_password("12345678");
303 assert_eq!(result, result8);
304 }
305
306 #[test]
307 fn block_type_discriminants() {
308 assert_eq!(BlockType::DB as u8, 0x41);
309 assert_eq!(BlockType::OB as u8, 0x38);
310 }
311}