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 pub v1: u8,
42 pub v2: u8,
44 pub v3: u8,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum Protocol {
54 S7,
56 S7Plus,
58}
59
60impl std::fmt::Display for Protocol {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 Protocol::S7 => write!(f, "S7"),
64 Protocol::S7Plus => write!(f, "S7+"),
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
71pub struct CpuInfo {
72 pub module_type: String,
74 pub serial_number: String,
76 pub as_name: String,
78 pub copyright: String,
80 pub module_name: String,
82 pub protocol: Protocol,
84}
85
86#[derive(Debug, Clone)]
88pub struct CpInfo {
89 pub max_pdu_len: u32,
91 pub max_connections: u32,
93 pub max_mpi_rate: u32,
95 pub max_bus_rate: u32,
97}
98
99#[derive(Debug, Clone)]
101pub struct Protection {
102 pub scheme_szl: u16,
104 pub scheme_module: u16,
106 pub scheme_bus: u16,
108 pub level: u16,
110 pub password_set: bool,
112}
113
114pub fn encrypt_password(password: &str) -> [u8; 8] {
120 let bytes = password.as_bytes();
121 let mut pw = [0x20u8; 8]; let len = bytes.len().min(8);
123 pw[..len].copy_from_slice(&bytes[..len]);
124 let mut result = [0u8; 8];
125 for i in 0..8 {
126 result[i] = (pw[i] << 4) | (pw[i] >> 4);
128 result[i] ^= 0x55;
129 }
130 result
131}
132
133#[derive(Debug, Clone)]
135pub struct ModuleEntry {
136 pub module_type: u16,
138}
139
140#[derive(Debug, Clone)]
142pub struct BlockListEntry {
143 pub block_type: u16,
145 pub count: u16,
147}
148
149#[derive(Debug, Clone)]
151pub struct BlockList {
152 pub total_count: u32,
154 pub entries: Vec<BlockListEntry>,
156}
157
158#[derive(Debug, Clone)]
166pub struct BlockData {
167 pub block_type: u16,
169 pub block_number: u16,
171 pub format: u16,
173 pub total_length: u32,
175 pub flags: u16,
177 pub crc1: u16,
179 pub crc2: u16,
181 pub payload: Vec<u8>,
183}
184
185#[derive(Debug, Clone, Default)]
187pub struct BlockAttributes {
188 pub author: Option<String>,
190 pub family: Option<String>,
192 pub name: Option<String>,
194 pub version: Option<u8>,
196 pub flags: Option<u16>,
198}
199
200impl BlockData {
201 pub fn from_bytes(data: &[u8]) -> Option<Self> {
203 if data.len() < 20 {
204 return None;
205 }
206 let block_type = u16::from_be_bytes([data[0], data[1]]);
207 let block_number = u16::from_be_bytes([data[2], data[3]]);
208 let format = u16::from_be_bytes([data[4], data[5]]);
209 let total_length = u32::from_be_bytes([data[6], data[7], data[8], data[9]]);
210 let flags = u16::from_be_bytes([data[10], data[11]]);
211 let crc1 = u16::from_be_bytes([data[12], data[13]]);
212 let crc2 = u16::from_be_bytes([data[14], data[15]]);
213 let payload = data[20..].to_vec();
215 Some(BlockData {
216 block_type,
217 block_number,
218 format,
219 total_length,
220 flags,
221 crc1,
222 crc2,
223 payload,
224 })
225 }
226
227 pub fn to_bytes(&self) -> Vec<u8> {
229 let mut buf = Vec::with_capacity(20 + self.payload.len());
230 buf.extend_from_slice(&self.block_type.to_be_bytes());
231 buf.extend_from_slice(&self.block_number.to_be_bytes());
232 buf.extend_from_slice(&self.format.to_be_bytes());
233 buf.extend_from_slice(&self.total_length.to_be_bytes());
234 buf.extend_from_slice(&self.flags.to_be_bytes());
235 buf.extend_from_slice(&self.crc1.to_be_bytes());
236 buf.extend_from_slice(&self.crc2.to_be_bytes());
237 buf.extend_from_slice(&[0u8; 4]); buf.extend_from_slice(&self.payload);
239 buf
240 }
241
242 pub fn new_db(db_number: u16, size_bytes: u16) -> Self {
247 let size = (size_bytes as usize + 1) & !1; let mut payload = Vec::with_capacity(2 + size);
250 payload.extend_from_slice(&(size as u16).to_be_bytes());
251 payload.extend(std::iter::repeat(0u8).take(size));
252 let total_length = (20 + payload.len()) as u32;
253 BlockData {
254 block_type: BlockType::DB as u16,
255 block_number: db_number,
256 format: 0x0001,
257 total_length,
258 flags: 0x0000,
259 crc1: 0x0000,
260 crc2: 0x0000,
261 payload,
262 }
263 }
264
265 pub fn crc32(&self) -> u32 {
270 let bytes = self.to_bytes();
271 crc32_ieee(&bytes)
272 }
273
274 pub fn set_attributes(&mut self, attrs: &BlockAttributes) {
280 if let Some(f) = attrs.flags {
281 self.flags = f;
282 }
283 let plen = self.payload.len();
285 if plen < 48 {
286 return;
287 }
288 let footer = &mut self.payload[plen - 48..];
289 if let Some(ref s) = attrs.author {
297 write_padded(&mut footer[8..16], s);
298 }
299 if let Some(ref s) = attrs.family {
300 write_padded(&mut footer[16..24], s);
301 }
302 if let Some(ref s) = attrs.name {
303 write_padded(&mut footer[24..32], s);
304 }
305 if let Some(v) = attrs.version {
306 footer[32] = v;
307 }
308 }
309
310 pub fn type_name(&self) -> &'static str {
312 block_type_name(self.block_type as u8)
313 }
314}
315
316pub fn block_type_name(bt: u8) -> &'static str {
317 match bt {
318 0x38 => "OB",
319 0x41 => "DB",
320 0x42 => "SDB",
321 0x43 => "FC",
322 0x44 => "SFC",
323 0x45 => "FB",
324 0x46 => "SFB",
325 0x47 => "UDT",
326 _ => "??",
327 }
328}
329
330fn write_padded(dst: &mut [u8], s: &str) {
331 let bytes = s.as_bytes();
332 let n = bytes.len().min(dst.len());
333 dst[..n].copy_from_slice(&bytes[..n]);
334 for b in dst[n..].iter_mut() {
335 *b = b' ';
336 }
337}
338
339fn crc32_ieee(data: &[u8]) -> u32 {
341 let mut crc: u32 = 0xFFFF_FFFF;
342 for &byte in data {
343 crc ^= byte as u32;
344 for _ in 0..8 {
345 if crc & 1 != 0 {
346 crc = (crc >> 1) ^ 0xEDB8_8320;
347 } else {
348 crc >>= 1;
349 }
350 }
351 }
352 !crc
353}
354
355#[derive(Debug, Clone, PartialEq, Eq)]
360pub enum BlockCmpResult {
361 Match,
363 Mismatch { local_crc: u32, plc_crc: u32 },
365 OnlyLocal,
367 OnlyPlc,
369}
370
371#[derive(Debug, Clone)]
375pub struct BlockInfo {
376 pub block_type: u16,
377 pub block_number: u16,
378 pub language: u16,
379 pub flags: u16,
380 pub size: u16,
381 pub size_ram: u16,
382 pub mc7_size: u16,
383 pub local_data: u16,
384 pub checksum: u16,
385 pub version: u16,
386 pub author: String,
387 pub family: String,
388 pub header: String,
389 pub date: String,
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
393#[repr(u8)]
394pub enum BlockType {
395 OB = 0x38,
396 DB = 0x41,
397 SDB = 0x42,
398 FC = 0x43,
399 SFC = 0x44,
400 FB = 0x45,
401 SFB = 0x46,
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn connect_params_default() {
410 let p = ConnectParams::default();
411 assert_eq!(p.rack, 0);
412 assert_eq!(p.slot, 1);
413 assert_eq!(p.pdu_size, 480);
414 }
415
416 #[test]
417 fn block_data_roundtrip() {
418 let bd = super::BlockData {
419 block_type: 0x41, block_number: 1,
421 format: 0,
422 total_length: 24,
423 flags: 0,
424 crc1: 0x1234,
425 crc2: 0x5678,
426 payload: vec![0xDE, 0xAD],
427 };
428 let bytes = bd.to_bytes();
429 assert_eq!(bytes.len(), 22); let parsed = super::BlockData::from_bytes(&bytes).unwrap();
431 assert_eq!(parsed.block_type, 0x41);
432 assert_eq!(parsed.block_number, 1);
433 assert_eq!(parsed.payload, vec![0xDE, 0xAD]);
434 }
435
436 #[test]
437 fn block_data_short_input_returns_none() {
438 let result = super::BlockData::from_bytes(&[0u8; 10]);
439 assert!(result.is_none());
440 }
441
442 #[test]
443 fn encrypt_8_char_password() {
444 let result = super::encrypt_password("PASSWORD");
446 assert_eq!(result.len(), 8);
447 let result2 = super::encrypt_password("PASSWORD");
453 assert_eq!(result, result2);
454 }
455
456 #[test]
457 fn encrypt_short_password_padded() {
458 let result = super::encrypt_password("abc");
459 assert_eq!((0x61u8 << 4) | (0x61u8 >> 4), 0x16);
462 assert_eq!(0x16 ^ 0x55, 0x43);
463 assert_eq!(result[0], 0x43);
464 assert_eq!((0x20u8 << 4) | (0x20u8 >> 4), 0x02);
466 assert_eq!(0x02 ^ 0x55, 0x57);
467 assert_eq!(result[3], 0x57);
468 }
469
470 #[test]
471 fn encrypt_long_password_truncated() {
472 let result = super::encrypt_password("1234567890");
473 assert_eq!(result.len(), 8);
474 let result8 = super::encrypt_password("12345678");
475 assert_eq!(result, result8);
476 }
477
478 #[test]
479 fn block_type_discriminants() {
480 assert_eq!(BlockType::DB as u8, 0x41);
481 assert_eq!(BlockType::OB as u8, 0x38);
482 }
483}