1use bytes::{Buf, BufMut, Bytes, BytesMut};
2use std::net::SocketAddr;
3use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
4use tokio::sync::Mutex;
5
6use crate::proto::{
7 cotp::CotpPdu,
8 s7::{
9 clock::PlcDateTime,
10 header::{Area, PduType, S7Header, TransportSize},
11 read_var::{AddressItem, ReadVarRequest, ReadVarResponse},
12 szl::{SzlRequest, SzlResponse},
13 write_var::{WriteItem, WriteVarRequest, WriteVarResponse},
14 },
15 tpkt::TpktFrame,
16};
17
18use crate::{
19 connection::{connect, Connection},
20 error::{Error, Result},
21 types::ConnectParams,
22};
23
24#[derive(Debug, Clone)]
26pub struct MultiReadItem {
27 pub area: Area,
28 pub db_number: u16,
29 pub start: u32,
30 pub length: u16,
31 pub transport: TransportSize,
32}
33
34impl MultiReadItem {
35 pub fn db(db: u16, start: u32, length: u16) -> Self {
37 Self {
38 area: Area::DataBlock,
39 db_number: db,
40 start,
41 length,
42 transport: TransportSize::Byte,
43 }
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct MultiWriteItem {
50 pub area: Area,
51 pub db_number: u16,
52 pub start: u32,
53 pub data: Bytes,
54}
55
56impl MultiWriteItem {
57 pub fn db(db: u16, start: u32, data: impl Into<Bytes>) -> Self {
59 Self {
60 area: Area::DataBlock,
61 db_number: db,
62 start,
63 data: data.into(),
64 }
65 }
66}
67
68struct Inner<T> {
69 transport: T,
70 connection: Connection,
71 pdu_ref: u16,
72 request_timeout: std::time::Duration,
73}
74
75pub struct S7Client<T: AsyncRead + AsyncWrite + Unpin + Send> {
76 inner: Mutex<Inner<T>>,
77 params: ConnectParams,
78}
79
80impl<T: AsyncRead + AsyncWrite + Unpin + Send> S7Client<T> {
81 pub async fn from_transport(transport: T, params: ConnectParams) -> Result<Self> {
82 let mut t = transport;
83 let connection = connect(&mut t, ¶ms).await?;
84 let timeout = params.request_timeout;
85 Ok(S7Client {
86 inner: Mutex::new(Inner {
87 transport: t,
88 connection,
89 pdu_ref: 1,
90 request_timeout: timeout,
91 }),
92 params,
93 })
94 }
95
96 pub fn request_timeout(&self) -> std::time::Duration {
98 self.params.request_timeout
99 }
100
101 pub async fn set_request_timeout(&self, timeout: std::time::Duration) {
105 let mut inner = self.inner.lock().await;
106 inner.request_timeout = timeout;
107 }
108
109 pub fn get_param(&self, name: &str) -> Result<std::time::Duration> {
113 match name {
114 "request_timeout" => Ok(self.params.request_timeout),
115 "connect_timeout" => Ok(self.params.connect_timeout),
116 "pdu_size" => Err(Error::PlcError {
117 code: 0,
118 message: "pdu_size is not a Duration; use .params.pdu_size directly".into(),
119 }),
120 _ => Err(Error::PlcError {
121 code: 0,
122 message: format!("unknown parameter: {name}"),
123 }),
124 }
125 }
126
127 pub fn set_param(&mut self, name: &str, value: std::time::Duration) -> Result<()> {
131 match name {
132 "request_timeout" => {
133 self.params.request_timeout = value;
134 Ok(())
135 }
136 _ => Err(Error::PlcError {
137 code: 0,
138 message: format!("unknown parameter: {name}"),
139 }),
140 }
141 }
142
143 fn next_pdu_ref(inner: &mut Inner<T>) -> u16 {
144 inner.pdu_ref = inner.pdu_ref.wrapping_add(1);
145 inner.pdu_ref
146 }
147
148 async fn send_s7(
149 inner: &mut Inner<T>,
150 param_buf: Bytes,
151 data_buf: Bytes,
152 pdu_ref: u16,
153 pdu_type: PduType,
154 ) -> Result<()> {
155 let header = S7Header {
156 pdu_type,
157 reserved: 0,
158 pdu_ref,
159 param_len: param_buf.len() as u16,
160 data_len: data_buf.len() as u16,
161 error_class: None,
162 error_code: None,
163 };
164 let mut s7b = BytesMut::new();
165 header.encode(&mut s7b);
166 s7b.extend_from_slice(¶m_buf);
167 s7b.extend_from_slice(&data_buf);
168
169 let dt = CotpPdu::Data {
170 tpdu_nr: 0,
171 last: true,
172 payload: s7b.freeze(),
173 };
174 let mut cotpb = BytesMut::new();
175 dt.encode(&mut cotpb);
176 let tpkt = TpktFrame {
177 payload: cotpb.freeze(),
178 };
179 let mut tb = BytesMut::new();
180 tpkt.encode(&mut tb)?;
181 inner.transport.write_all(&tb).await?;
182 Ok(())
183 }
184
185 async fn recv_s7(inner: &mut Inner<T>) -> Result<(S7Header, Bytes)> {
186 let timeout = inner.request_timeout;
187 let mut tpkt_hdr = [0u8; 4];
188 tokio::time::timeout(timeout, inner.transport.read_exact(&mut tpkt_hdr))
189 .await
190 .map_err(|_| Error::Timeout(timeout))??;
191 let total = u16::from_be_bytes([tpkt_hdr[2], tpkt_hdr[3]]) as usize;
192 if total < 4 {
193 return Err(Error::UnexpectedResponse);
194 }
195 let mut payload = vec![0u8; total - 4];
196 tokio::time::timeout(timeout, inner.transport.read_exact(&mut payload))
197 .await
198 .map_err(|_| Error::Timeout(timeout))??;
199 let mut b = Bytes::from(payload);
200
201 if b.remaining() < 3 {
203 return Err(Error::UnexpectedResponse);
204 }
205 let _li = b.get_u8();
206 let cotp_code = b.get_u8();
207 if cotp_code != 0xF0 {
208 return Err(Error::UnexpectedResponse);
209 }
210 b.advance(1); let header = S7Header::decode(&mut b)?;
213 Ok((header, b))
214 }
215
216 pub async fn db_read(&self, db: u16, start: u32, length: u16) -> Result<Bytes> {
217 let mut inner = self.inner.lock().await;
218 let pdu_ref = Self::next_pdu_ref(&mut inner);
219
220 let req = ReadVarRequest {
221 items: vec![AddressItem {
222 area: Area::DataBlock,
223 db_number: db,
224 start,
225 bit_offset: 0,
226 length,
227 transport: TransportSize::Byte,
228 }],
229 };
230 let mut param_buf = BytesMut::new();
231 req.encode(&mut param_buf);
232
233 Self::send_s7(
234 &mut inner,
235 param_buf.freeze(),
236 Bytes::new(),
237 pdu_ref,
238 PduType::Job,
239 )
240 .await?;
241
242 let (header, mut body) = Self::recv_s7(&mut inner).await?;
243 check_plc_error(&header, "db_read")?;
244 if body.remaining() >= 2 {
245 body.advance(2); }
247 let resp = ReadVarResponse::decode(&mut body, 1)?;
248 if resp.items.is_empty() {
249 return Err(Error::UnexpectedResponse);
250 }
251 if resp.items[0].return_code != 0xFF {
252 return Err(Error::PlcError {
253 code: resp.items[0].return_code as u32,
254 message: "item error".into(),
255 });
256 }
257 Ok(resp.items[0].data.clone())
258 }
259
260 pub async fn read_area(
266 &self,
267 area: Area,
268 db_number: u16,
269 start: u32,
270 element_count: u16,
271 transport: TransportSize,
272 ) -> Result<Bytes> {
273 let mut inner = self.inner.lock().await;
274 let pdu_ref = Self::next_pdu_ref(&mut inner);
275
276 let req = ReadVarRequest {
277 items: vec![AddressItem {
278 area,
279 db_number,
280 start,
281 bit_offset: 0,
282 length: element_count,
283 transport,
284 }],
285 };
286 let mut param_buf = BytesMut::new();
287 req.encode(&mut param_buf);
288
289 Self::send_s7(
290 &mut inner,
291 param_buf.freeze(),
292 Bytes::new(),
293 pdu_ref,
294 PduType::Job,
295 )
296 .await?;
297
298 let (header, mut body) = Self::recv_s7(&mut inner).await?;
299 check_plc_error(&header, "read_area")?;
300 if body.remaining() >= 2 {
301 body.advance(2);
302 }
303 let resp = ReadVarResponse::decode(&mut body, 1)?;
304 if resp.items.is_empty() {
305 return Err(Error::UnexpectedResponse);
306 }
307 if resp.items[0].return_code != 0xFF {
308 return Err(Error::PlcError {
309 code: resp.items[0].return_code as u32,
310 message: "item error".into(),
311 });
312 }
313 Ok(resp.items[0].data.clone())
314 }
315
316 pub async fn read_multi_vars(&self, items: &[MultiReadItem]) -> Result<Vec<Bytes>> {
324 if items.is_empty() {
325 return Ok(Vec::new());
326 }
327
328 const S7_HEADER: usize = 10;
331 const PARAM_OVERHEAD: usize = 2; const ADDR_ITEM_SIZE: usize = 12;
333 const DATA_ITEM_OVERHEAD: usize = 4;
335 const MAX_ITEMS_PER_PDU: usize = 20;
336
337 let mut inner = self.inner.lock().await;
338 let pdu_size = inner.connection.pdu_size as usize;
339 let max_req_payload = pdu_size.saturating_sub(S7_HEADER + PARAM_OVERHEAD);
340 let max_resp_payload = pdu_size.saturating_sub(S7_HEADER + PARAM_OVERHEAD);
341
342 let mut results = vec![Bytes::new(); items.len()];
343 let mut batch_start = 0;
344
345 while batch_start < items.len() {
346 let mut batch_end = batch_start;
348 let mut req_bytes_used = 0usize;
349 let mut resp_bytes_used = 0usize;
350
351 while batch_end < items.len() && (batch_end - batch_start) < MAX_ITEMS_PER_PDU {
352 let item = &items[batch_end];
353 let item_resp_size =
354 DATA_ITEM_OVERHEAD + item.length as usize + (item.length as usize % 2);
355
356 if batch_end > batch_start
357 && (req_bytes_used + ADDR_ITEM_SIZE > max_req_payload
358 || resp_bytes_used + item_resp_size > max_resp_payload)
359 {
360 break;
361 }
362 req_bytes_used += ADDR_ITEM_SIZE;
363 resp_bytes_used += item_resp_size;
364 batch_end += 1;
365 }
366
367 let batch = &items[batch_start..batch_end];
368 let pdu_ref = Self::next_pdu_ref(&mut inner);
369
370 let req = ReadVarRequest {
371 items: batch
372 .iter()
373 .map(|item| AddressItem {
374 area: item.area,
375 db_number: item.db_number,
376 start: item.start,
377 bit_offset: 0,
378 length: item.length,
381 transport: TransportSize::Byte,
382 })
383 .collect(),
384 };
385 let mut param_buf = BytesMut::new();
386 req.encode(&mut param_buf);
387
388 Self::send_s7(
389 &mut inner,
390 param_buf.freeze(),
391 Bytes::new(),
392 pdu_ref,
393 PduType::Job,
394 )
395 .await?;
396
397 let (header, mut body) = Self::recv_s7(&mut inner).await?;
398 check_plc_error(&header, "read_multi_vars")?;
399 if body.remaining() >= 2 {
400 body.advance(2); }
402 let resp = ReadVarResponse::decode(&mut body, batch.len())?;
403
404 for (i, item) in resp.items.into_iter().enumerate() {
405 if item.return_code != 0xFF {
406 return Err(Error::PlcError {
407 code: item.return_code as u32,
408 message: format!("item {} error", batch_start + i),
409 });
410 }
411 results[batch_start + i] = item.data;
412 }
413
414 batch_start = batch_end;
415 }
416
417 Ok(results)
418 }
419
420 pub async fn write_multi_vars(&self, items: &[MultiWriteItem]) -> Result<()> {
426 if items.is_empty() {
427 return Ok(());
428 }
429
430 const S7_HEADER: usize = 10;
431 const PARAM_OVERHEAD: usize = 2; const ADDR_ITEM_SIZE: usize = 12;
433 const DATA_ITEM_OVERHEAD: usize = 4; const MAX_ITEMS_PER_PDU: usize = 20;
435
436 let mut inner = self.inner.lock().await;
437 let pdu_size = inner.connection.pdu_size as usize;
438 let max_payload = pdu_size.saturating_sub(S7_HEADER + PARAM_OVERHEAD);
439
440 let mut batch_start = 0;
441
442 while batch_start < items.len() {
443 let mut batch_end = batch_start;
444 let mut bytes_used = 0usize;
445
446 while batch_end < items.len() && (batch_end - batch_start) < MAX_ITEMS_PER_PDU {
447 let item = &items[batch_end];
448 let data_len = item.data.len();
449 let item_size = ADDR_ITEM_SIZE + DATA_ITEM_OVERHEAD + data_len + (data_len % 2);
450
451 if batch_end > batch_start && bytes_used + item_size > max_payload {
452 break;
453 }
454 bytes_used += item_size;
455 batch_end += 1;
456 }
457
458 let batch = &items[batch_start..batch_end];
459 let pdu_ref = Self::next_pdu_ref(&mut inner);
460
461 let req = WriteVarRequest {
462 items: batch
463 .iter()
464 .map(|item| WriteItem {
465 address: AddressItem {
466 area: item.area,
467 db_number: item.db_number,
468 start: item.start,
469 bit_offset: 0,
470 length: item.data.len() as u16,
471 transport: TransportSize::Byte,
472 },
473 data: item.data.clone(),
474 })
475 .collect(),
476 };
477 let mut param_buf = BytesMut::new();
478 req.encode(&mut param_buf);
479
480 Self::send_s7(
481 &mut inner,
482 param_buf.freeze(),
483 Bytes::new(),
484 pdu_ref,
485 PduType::Job,
486 )
487 .await?;
488
489 let (header, mut body) = Self::recv_s7(&mut inner).await?;
490 check_plc_error(&header, "write_multi_vars")?;
491 if body.remaining() >= 2 {
492 body.advance(2); }
494 let resp = WriteVarResponse::decode(&mut body, batch.len())?;
495 for (i, &code) in resp.return_codes.iter().enumerate() {
496 if code != 0xFF {
497 return Err(Error::PlcError {
498 code: code as u32,
499 message: format!("item {} write error", batch_start + i),
500 });
501 }
502 }
503
504 batch_start = batch_end;
505 }
506
507 Ok(())
508 }
509
510 pub async fn db_write(&self, db: u16, start: u32, data: &[u8]) -> Result<()> {
511 let mut inner = self.inner.lock().await;
512 let pdu_ref = Self::next_pdu_ref(&mut inner);
513
514 let req = WriteVarRequest {
515 items: vec![WriteItem {
516 address: AddressItem {
517 area: Area::DataBlock,
518 db_number: db,
519 start,
520 bit_offset: 0,
521 length: data.len() as u16,
522 transport: TransportSize::Byte,
523 },
524 data: Bytes::copy_from_slice(data),
525 }],
526 };
527 let mut param_buf = BytesMut::new();
528 req.encode(&mut param_buf);
529
530 Self::send_s7(
531 &mut inner,
532 param_buf.freeze(),
533 Bytes::new(),
534 pdu_ref,
535 PduType::Job,
536 )
537 .await?;
538
539 let (header, mut body) = Self::recv_s7(&mut inner).await?;
540 check_plc_error(&header, "db_write")?;
541 if body.has_remaining() {
542 body.advance(2); }
544 let resp = WriteVarResponse::decode(&mut body, 1)?;
545 if resp.return_codes[0] != 0xFF {
546 return Err(Error::PlcError {
547 code: resp.return_codes[0] as u32,
548 message: "write error".into(),
549 });
550 }
551 Ok(())
552 }
553
554 pub async fn write_area(
559 &self,
560 area: Area,
561 db_number: u16,
562 start: u32,
563 transport: TransportSize,
564 data: &[u8],
565 ) -> Result<()> {
566 let mut inner = self.inner.lock().await;
567 let pdu_ref = Self::next_pdu_ref(&mut inner);
568
569 let req = WriteVarRequest {
570 items: vec![WriteItem {
571 address: AddressItem {
572 area,
573 db_number,
574 start,
575 bit_offset: 0,
576 length: data.len() as u16,
577 transport,
578 },
579 data: Bytes::copy_from_slice(data),
580 }],
581 };
582 let mut param_buf = BytesMut::new();
583 req.encode(&mut param_buf);
584
585 Self::send_s7(
586 &mut inner,
587 param_buf.freeze(),
588 Bytes::new(),
589 pdu_ref,
590 PduType::Job,
591 )
592 .await?;
593
594 let (header, mut body) = Self::recv_s7(&mut inner).await?;
595 check_plc_error(&header, "write_area")?;
596 if body.has_remaining() {
597 body.advance(2);
598 }
599 let resp = WriteVarResponse::decode(&mut body, 1)?;
600 if resp.return_codes[0] != 0xFF {
601 return Err(Error::PlcError {
602 code: resp.return_codes[0] as u32,
603 message: "write_area error".into(),
604 });
605 }
606 Ok(())
607 }
608
609 pub async fn ab_read(
614 &self,
615 area: Area,
616 db_number: u16,
617 start: u32,
618 length: u16,
619 ) -> Result<Bytes> {
620 let items = [MultiReadItem {
621 area,
622 db_number,
623 start,
624 length,
625 transport: TransportSize::Byte,
626 }];
627 let mut results = self.read_multi_vars(&items).await?;
628 Ok(results.swap_remove(0))
629 }
630
631 pub async fn ab_write(
636 &self,
637 area: Area,
638 db_number: u16,
639 start: u32,
640 data: &[u8],
641 ) -> Result<()> {
642 let items = [MultiWriteItem {
643 area,
644 db_number,
645 start,
646 data: Bytes::copy_from_slice(data),
647 }];
648 self.write_multi_vars(&items).await
649 }
650
651 pub async fn read_szl(&self, szl_id: u16, szl_index: u16) -> Result<SzlResponse> {
652 let payload = self.read_szl_payload(szl_id, szl_index).await?;
653 let mut b = payload;
654 Ok(SzlResponse::decode(&mut b)?)
655 }
656
657 async fn read_szl_payload(&self, szl_id: u16, szl_index: u16) -> Result<Bytes> {
660 let mut inner = self.inner.lock().await;
661 let pdu_ref = Self::next_pdu_ref(&mut inner);
662
663 let req = SzlRequest { szl_id, szl_index };
664 let mut param_buf = BytesMut::new();
665 req.encode_params(&mut param_buf);
666 let mut data_buf = BytesMut::new();
667 req.encode_data(&mut data_buf);
668
669 Self::send_s7(
670 &mut inner,
671 param_buf.freeze(),
672 data_buf.freeze(),
673 pdu_ref,
674 PduType::UserData,
675 )
676 .await?;
677
678 let (header, mut body) = Self::recv_s7(&mut inner).await?;
679
680 if body.remaining() < header.param_len as usize {
682 return Err(Error::UnexpectedResponse);
683 }
684 body.advance(header.param_len as usize);
685
686 if body.remaining() < 4 {
690 return Ok(Bytes::new());
691 }
692 let return_code = body.get_u8();
693 let _transport = body.get_u8();
694 let _data_len = body.get_u16();
695
696 if return_code != 0xFF {
699 return Ok(Bytes::new());
700 }
701
702 Ok(body.copy_to_bytes(body.remaining()))
704 }
705
706 pub async fn read_clock(&self) -> Result<PlcDateTime> {
707 let mut inner = self.inner.lock().await;
708 let pdu_ref = Self::next_pdu_ref(&mut inner);
709 let mut param_buf = BytesMut::new();
710 param_buf.extend_from_slice(&[0x00, 0x01, 0x12, 0x04, 0xF5, 0x00]);
711 Self::send_s7(
712 &mut inner,
713 param_buf.freeze(),
714 Bytes::new(),
715 pdu_ref,
716 PduType::UserData,
717 )
718 .await?;
719 let (_header, mut body) = Self::recv_s7(&mut inner).await?;
720 if body.remaining() > 8 {
721 body.advance(body.remaining() - 8);
722 }
723 Ok(PlcDateTime::decode(&mut body)?)
724 }
725
726 pub async fn copy_ram_to_rom(&self) -> Result<()> {
730 let mut inner = self.inner.lock().await;
731 let pdu_ref = Self::next_pdu_ref(&mut inner);
732 let param = Bytes::copy_from_slice(&[
733 0x00, 0x01, 0x12, 0x04, 0x43, 0x44, 0x01, 0x00,
734 ]);
735 Self::send_s7(&mut inner, param, Bytes::new(), pdu_ref, PduType::UserData).await?;
736 let (header, _body) = Self::recv_s7(&mut inner).await?;
737 check_plc_error(&header, "copy_ram_to_rom")?;
738 Ok(())
739 }
740
741 pub async fn compress(&self) -> Result<()> {
746 let mut inner = self.inner.lock().await;
747 let pdu_ref = Self::next_pdu_ref(&mut inner);
748 let param = Bytes::copy_from_slice(&[
749 0x00, 0x01, 0x12, 0x04, 0x42, 0x44, 0x01, 0x00,
750 ]);
751 Self::send_s7(&mut inner, param, Bytes::new(), pdu_ref, PduType::UserData).await?;
752 let (header, _body) = Self::recv_s7(&mut inner).await?;
753 check_plc_error(&header, "compress")?;
754 Ok(())
755 }
756
757 async fn simple_control(inner: &mut Inner<T>, pdu_ref: u16, func: u8) -> Result<()> {
761 let param = Bytes::copy_from_slice(&[func, 0x00]);
762 Self::send_s7(inner, param, Bytes::new(), pdu_ref, PduType::Job).await?;
763 let (header, _body) = Self::recv_s7(inner).await?;
764 check_plc_error(&header, "plc_control")?;
765 Ok(())
766 }
767
768 pub async fn plc_stop(&self) -> Result<()> {
774 let mut inner = self.inner.lock().await;
775 let pdu_ref = Self::next_pdu_ref(&mut inner);
776 Self::simple_control(&mut inner, pdu_ref, 0x29).await
777 }
778
779 pub async fn plc_hot_start(&self) -> Result<()> {
783 let mut inner = self.inner.lock().await;
784 let pdu_ref = Self::next_pdu_ref(&mut inner);
785 Self::simple_control(&mut inner, pdu_ref, 0x28).await
786 }
787
788 pub async fn plc_cold_start(&self) -> Result<()> {
792 let mut inner = self.inner.lock().await;
793 let pdu_ref = Self::next_pdu_ref(&mut inner);
794 Self::simple_control(&mut inner, pdu_ref, 0x2A).await
795 }
796
797 pub async fn get_plc_status(&self) -> Result<crate::types::PlcStatus> {
802 let payload = self.read_szl_payload(0x0424, 0x0000).await?;
803 if payload.len() < 12 {
812 return Ok(crate::types::PlcStatus::Unknown);
813 }
814 let status_byte = payload[11];
815 match status_byte {
816 0x00 => Ok(crate::types::PlcStatus::Unknown),
817 0x04 => Ok(crate::types::PlcStatus::Stop),
818 0x08 => Ok(crate::types::PlcStatus::Run),
819 0x03 => Ok(crate::types::PlcStatus::Stop),
821 _ => Ok(crate::types::PlcStatus::Stop),
822 }
823 }
824
825 pub async fn get_order_code(&self) -> Result<crate::types::OrderCode> {
831 let payload = self.read_szl_payload(0x0011, 0x0000).await?;
832 if payload.len() < 8 {
833 return Err(Error::UnexpectedResponse);
834 }
835
836 let n = payload.len();
840 let (v1, v2, v3) = if n >= 3 {
841 (payload[n - 3], payload[n - 2], payload[n - 1])
842 } else {
843 (0, 0, 0)
844 };
845
846 let mut b = payload.clone();
847 let szl_id = b.get_u16();
848 let _szl_idx = b.get_u16();
849 let entry_len = b.get_u16() as usize;
850 let entry_count = b.get_u16() as usize;
851
852 if (szl_id == 0x0011 || szl_id == 0x001C) && entry_len >= 4 && entry_count > 0 {
853 for _ in 0..entry_count {
854 if b.remaining() < entry_len { break; }
855 let entry_idx = b.get_u16();
856 let string_len = entry_len - 2;
857 let raw = b.copy_to_bytes(string_len);
858 if entry_idx == 0x0001 {
859 let null_end = raw.iter().position(|&x| x == 0).unwrap_or(string_len);
860 let code = String::from_utf8_lossy(&raw[..null_end]).trim().to_string();
861 if !code.is_empty() {
862 return Ok(crate::types::OrderCode { code, v1, v2, v3 });
863 }
864 }
865 }
866 }
867
868 let code = scan_ascii_fields(&payload, 10, 4).into_iter().find(|s| {
870 let su = s.to_uppercase();
871 (su.starts_with("6ES") || su.starts_with("6AV") || su.starts_with("6GK"))
872 && s.len() >= 10
873 && s.bytes().all(|c| c.is_ascii_graphic() || c == b' ')
874 }).unwrap_or_default();
875 Ok(crate::types::OrderCode { code, v1, v2, v3 })
876 }
877
878 pub async fn get_cpu_info(&self) -> Result<crate::types::CpuInfo> {
884 let payload = self.read_szl_payload(0x001C, 0x0000).await?;
885 if payload.len() < 8 {
886 return Err(Error::UnexpectedResponse);
887 }
888
889 let mut b = payload.clone();
903 let szl_id = b.get_u16();
904 let _szl_idx = b.get_u16();
905 let entry_len = b.get_u16() as usize;
906 let entry_count = b.get_u16() as usize;
907
908 if szl_id == 0x001C && entry_len >= 4 && entry_count > 0 {
909 let mut module_type = String::new();
910 let mut module_type_canonical = String::new(); let mut serial_number = String::new();
912 let mut as_name = String::new();
913 let mut copyright = String::new();
914 let mut module_name = String::new();
915
916 for _ in 0..entry_count {
917 if b.remaining() < entry_len { break; }
918 let entry_idx = b.get_u16();
919 let string_len = entry_len - 2;
920 let raw = b.copy_to_bytes(string_len);
921 let null_end = raw.iter().position(|&x| x == 0).unwrap_or(string_len);
922 let val = String::from_utf8_lossy(&raw[..null_end]).trim().to_string();
923 match entry_idx {
924 0x0001 => { if as_name.is_empty() { as_name = val; } }
925 0x0002 => { if module_type.is_empty() { module_type = val; } }
928 0x0003 => { if module_name.is_empty() { module_name = val; } }
929 0x0004 => { if copyright.is_empty() { copyright = val; } }
930 0x0005 => { if serial_number.is_empty() { serial_number = val; } }
931 0x0007 => { if module_type_canonical.is_empty() { module_type_canonical = val; } }
933 _ => {}
935 }
936 }
937
938 if !module_type_canonical.is_empty() {
940 module_type = module_type_canonical;
941 }
942
943 if module_name.is_empty() && !as_name.is_empty() {
944 module_name = as_name.clone();
945 }
946
947 if !module_type.is_empty() || !serial_number.is_empty() || !as_name.is_empty() {
948 let protocol = detect_protocol(&payload, &module_type);
949 return Ok(crate::types::CpuInfo {
950 module_type,
951 serial_number,
952 as_name,
953 copyright,
954 module_name,
955 protocol,
956 });
957 }
958 }
959
960 let data = payload.as_ref();
963 let (module_type, serial_number, as_name, copyright, module_name) =
964 parse_sub_record_fields(data);
965
966 if !module_type.is_empty() || !serial_number.is_empty() {
967 let protocol = detect_protocol(&payload, &module_type);
968 return Ok(crate::types::CpuInfo {
969 module_type,
970 serial_number,
971 as_name,
972 copyright,
973 module_name,
974 protocol,
975 });
976 }
977
978 let mut module_type = String::new();
980 let mut serial_number = String::new();
981 let mut as_name = String::new();
982 let mut copyright = String::new();
983 let mut module_name = String::new();
984
985 let mut scan = 0;
986 while scan < data.len() {
987 if data[scan].is_ascii_graphic() || data[scan] == b' ' {
988 let start = scan;
989 while scan < data.len() && (data[scan].is_ascii_graphic() || data[scan] == b' ') {
990 scan += 1;
991 }
992 let val = String::from_utf8_lossy(&data[start..scan]).trim().to_string();
993 if val.len() >= 3 {
994 let tag = if start >= 2 && data[start - 2] == 0x00 {
995 Some(data[start - 1])
996 } else {
997 None
998 };
999 let su = val.to_uppercase();
1000 if su.contains("BOOT") || su.starts_with("P B") || su.starts_with("HBOOT") {
1001 } else if tag == Some(0x07) && module_type.is_empty() {
1003 module_type = val;
1004 } else if tag == Some(0x08) && module_name.is_empty() {
1005 module_name = val;
1006 } else if tag == Some(0x05) && as_name.is_empty() {
1007 as_name = val;
1008 } else if tag == Some(0x06) && copyright.is_empty() {
1009 copyright = val;
1010 } else if tag == Some(0x04) && serial_number.is_empty() {
1011 serial_number = val;
1012 } else if val.contains('-')
1013 && val.chars().filter(|c| c.is_ascii_digit()).count() >= 4
1014 && !val.starts_with("6ES7")
1015 && serial_number.is_empty()
1016 {
1017 serial_number = val;
1018 } else if su.contains("CPU") && su.contains("PN") && module_type.is_empty() {
1019 module_type = val;
1020 } else if module_type.is_empty() && val.len() >= 8 && !su.contains("MC_") {
1021 module_type = val;
1022 }
1023 }
1024 } else {
1025 scan += 1;
1026 }
1027 }
1028
1029 let protocol = detect_protocol(&payload, &module_type);
1030 Ok(crate::types::CpuInfo {
1031 module_type,
1032 serial_number,
1033 as_name,
1034 copyright,
1035 module_name,
1036 protocol,
1037 })
1038 }
1039
1040 pub async fn get_cp_info(&self) -> Result<crate::types::CpInfo> {
1044 let payload = self.read_szl_payload(0x0131, 0x0001).await?;
1046
1047 let mut b = payload.clone();
1053 if b.remaining() < 8 {
1054 return Ok(crate::types::CpInfo {
1055 max_pdu_len: 0, max_connections: 0, max_mpi_rate: 0, max_bus_rate: 0,
1056 });
1057 }
1058
1059 let szl_id = b.get_u16();
1060 let _szl_idx = b.get_u16();
1061 let entry_len = b.get_u16() as usize;
1062 let entry_count = b.get_u16() as usize;
1063
1064 if szl_id == 0x0131 && entry_len >= 12 && entry_count >= 1 && b.remaining() >= entry_len {
1066 let _entry_idx = b.get_u16();
1067 let max_pdu_len = b.get_u16() as u32;
1068 let max_connections = b.get_u16() as u32;
1069 let max_mpi_rate = b.get_u32();
1070 let max_bus_rate = b.get_u32();
1071 return Ok(crate::types::CpInfo {
1072 max_pdu_len,
1073 max_connections,
1074 max_mpi_rate,
1075 max_bus_rate,
1076 });
1077 }
1078
1079 Ok(crate::types::CpInfo {
1081 max_pdu_len: 0,
1082 max_connections: 0,
1083 max_mpi_rate: 0,
1084 max_bus_rate: 0,
1085 })
1086 }
1087
1088 pub async fn read_module_list(&self) -> Result<Vec<crate::types::ModuleEntry>> {
1092 let payload = self.read_szl_payload(0x00A0, 0x0000).await?;
1093 if payload.len() < 6 {
1094 return Ok(Vec::new());
1095 }
1096 let mut b = payload;
1097 let _block_len = b.get_u16();
1098 let _szl_id = b.get_u16();
1099 let _szl_ix = b.get_u16();
1100 skip_szl_entry_header(&mut b);
1102 let mut modules = Vec::new();
1103 while b.remaining() >= 2 {
1104 modules.push(crate::types::ModuleEntry {
1105 module_type: b.get_u16(),
1106 });
1107 }
1108 Ok(modules)
1109 }
1110
1111 pub async fn list_blocks(&self) -> Result<crate::types::BlockList> {
1118 let mut inner = self.inner.lock().await;
1119 let pdu_ref = Self::next_pdu_ref(&mut inner);
1120
1121 let param = Bytes::from_static(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x43, 0x01, 0x00]);
1123 let data = Bytes::from_static(&[0x0A, 0x00, 0x00, 0x00]);
1125
1126 Self::send_s7(&mut inner, param, data, pdu_ref, PduType::UserData).await?;
1127 let (header, mut body) = Self::recv_s7(&mut inner).await?;
1128
1129 if body.remaining() < header.param_len as usize {
1131 return Err(Error::UnexpectedResponse);
1132 }
1133 body.advance(header.param_len as usize);
1134
1135 if body.remaining() < 4 {
1137 return Ok(crate::types::BlockList { total_count: 0, entries: Vec::new() });
1138 }
1139 let _ret_val = body.get_u8();
1140 let _tr_size = body.get_u8();
1141 let data_len = body.get_u16() as usize;
1142
1143 if data_len < 28 || body.remaining() < 28 {
1145 return Ok(crate::types::BlockList { total_count: 0, entries: Vec::new() });
1146 }
1147
1148 let mut entries = Vec::new();
1149 let mut total_count: u32 = 0;
1150 for _ in 0..7 {
1151 let _zero = body.get_u8();
1152 let block_type = body.get_u8() as u16;
1153 let count = body.get_u16();
1154 total_count += count as u32;
1155 entries.push(crate::types::BlockListEntry { block_type, count });
1156 }
1157
1158 Ok(crate::types::BlockList { total_count, entries })
1159 }
1160
1161 pub async fn list_blocks_of_type(&self, block_type: u8) -> Result<Vec<u16>> {
1167 let mut numbers: Vec<u16> = Vec::new();
1168 let mut first = true;
1169 let mut seq: u8 = 0x00;
1170
1171 loop {
1172 let mut inner = self.inner.lock().await;
1173 let pdu_ref = Self::next_pdu_ref(&mut inner);
1174
1175 let (param, data) = if first {
1176 let mut p = BytesMut::with_capacity(8);
1180 p.extend_from_slice(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x43, 0x02, 0x00]);
1181 let mut d = BytesMut::with_capacity(6);
1182 d.extend_from_slice(&[0xFF, 0x09, 0x00, 0x02, 0x30, block_type]);
1183 (p.freeze(), d.freeze())
1184 } else {
1185 let mut p = BytesMut::with_capacity(12);
1189 p.extend_from_slice(&[0x00, 0x01, 0x12, 0x08, 0x12, 0x43, 0x02, seq, 0x00, 0x00, 0x00, 0x00]);
1190 let d = Bytes::from_static(&[0x0A, 0x00, 0x00, 0x00]);
1191 (p.freeze(), d)
1192 };
1193
1194 Self::send_s7(&mut inner, param, data, pdu_ref, PduType::UserData).await?;
1195 let (header, mut body) = Self::recv_s7(&mut inner).await?;
1196
1197 if body.remaining() < header.param_len as usize {
1199 return Err(Error::UnexpectedResponse);
1200 }
1201 let param_bytes = body.slice(..header.param_len as usize);
1205 let done = param_bytes.len() >= 10 && param_bytes[8] == 0x00;
1206 seq = if param_bytes.len() >= 8 { param_bytes[7] } else { 0 };
1207 body.advance(header.param_len as usize);
1208 drop(inner);
1209
1210 if body.remaining() < 4 { break; }
1212 let ret_val = body.get_u8();
1213 let _tr_size = body.get_u8();
1214 let data_len = body.get_u16() as usize;
1215
1216 if ret_val != 0xFF || data_len < 4 || body.remaining() < data_len { break; }
1217
1218 let item_count = ((data_len - 4) / 4) + 1;
1221 for _ in 0..item_count {
1222 if body.remaining() < 4 { break; }
1223 let block_num = body.get_u16();
1224 let _unknown = body.get_u8();
1225 let _lang = body.get_u8();
1226 numbers.push(block_num);
1227 }
1228
1229 first = false;
1230 if done { break; }
1231 }
1232
1233 numbers.sort_unstable();
1234 Ok(numbers)
1235 }
1236
1237 async fn block_info_query(
1242 &self,
1243 _func: u8,
1244 block_type: u8,
1245 block_number: u16,
1246 ) -> Result<Bytes> {
1247 let mut inner = self.inner.lock().await;
1248 let pdu_ref = Self::next_pdu_ref(&mut inner);
1249
1250 let param = Bytes::from_static(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x43, 0x03, 0x00]);
1252
1253 let mut data_buf = BytesMut::with_capacity(12);
1255 data_buf.extend_from_slice(&[0xFF, 0x09, 0x00, 0x08, 0x30, block_type]);
1256 let n = block_number as u32;
1258 data_buf.put_u8((n / 10000) as u8 + 0x30);
1259 data_buf.put_u8(((n % 10000) / 1000) as u8 + 0x30);
1260 data_buf.put_u8(((n % 1000) / 100) as u8 + 0x30);
1261 data_buf.put_u8(((n % 100) / 10) as u8 + 0x30);
1262 data_buf.put_u8((n % 10) as u8 + 0x30);
1263 data_buf.put_u8(0x41); Self::send_s7(&mut inner, param, data_buf.freeze(), pdu_ref, PduType::UserData).await?;
1266
1267 let (header, mut body) = Self::recv_s7(&mut inner).await?;
1268
1269 let param_len = header.param_len as usize;
1272 if body.remaining() < param_len {
1273 return Err(Error::UnexpectedResponse);
1274 }
1275 let params = body.slice(..param_len);
1276 body.advance(param_len);
1277
1278 if params.len() >= 12 {
1280 let err_no = u16::from_be_bytes([params[10], params[11]]);
1281 if err_no != 0 {
1282 return Err(Error::PlcError {
1283 code: err_no as u32,
1284 message: format!("block info error: ErrNo=0x{err_no:04X}"),
1285 });
1286 }
1287 }
1288
1289 if body.remaining() < 4 {
1291 return Err(Error::UnexpectedResponse);
1292 }
1293 let ret_val = body.get_u8();
1294 let _tr_size = body.get_u8();
1295 let _data_len = body.get_u16();
1296
1297 if ret_val != 0xFF {
1298 return Err(Error::PlcError {
1299 code: ret_val as u32,
1300 message: format!("block info RetVal=0x{ret_val:02X}"),
1301 });
1302 }
1303
1304 Ok(body.copy_to_bytes(body.remaining()))
1305 }
1306
1307 pub async fn get_ag_block_info(
1312 &self,
1313 block_type: u8,
1314 block_number: u16,
1315 ) -> Result<crate::types::BlockInfo> {
1316 self.get_block_info(0x13, block_type, block_number).await
1317 }
1318
1319 pub async fn get_pg_block_info(
1324 &self,
1325 block_type: u8,
1326 block_number: u16,
1327 ) -> Result<crate::types::BlockInfo> {
1328 self.get_block_info(0x14, block_type, block_number).await
1329 }
1330
1331 async fn get_block_info(
1333 &self,
1334 func: u8,
1335 block_type: u8,
1336 block_number: u16,
1337 ) -> Result<crate::types::BlockInfo> {
1338 let payload = self
1339 .block_info_query(func, block_type, block_number)
1340 .await?;
1341
1342 if payload.len() < 40 {
1353 return Err(Error::UnexpectedResponse);
1354 }
1355 let mut b = payload;
1356
1357 let _cst_b = b.get_u8();
1358 let blk_type: u16 = b.get_u8().into();
1359 let _cst_w1 = b.get_u16();
1360 let _cst_w2 = b.get_u16();
1361 let _cst_pp = b.get_u16();
1362 let _unknown_1 = b.get_u8();
1363 let flags = b.get_u8() as u16;
1364 let language = b.get_u8() as u16;
1365 let _sub_blk = b.get_u8();
1366 let _blk_number = b.get_u16(); let len_load_mem = b.get_u32();
1368 let _blk_sec = b.get_u32();
1369 let _code_ms = b.get_u32();
1370 let _code_dy = b.get_u16();
1371 let _intf_ms = b.get_u32();
1372 let _intf_dy = b.get_u16();
1373 let sbb_len = b.get_u16();
1374 let _add_len = b.get_u16();
1375 let local_data = b.get_u16();
1376 let mc7_size = b.get_u16();
1377
1378 fn read_str(b: &mut Bytes, n: usize) -> String {
1379 let s = b.slice(..n.min(b.remaining()));
1380 b.advance(n.min(b.remaining()));
1381 let end = s.iter().position(|&x| x == 0).unwrap_or(s.len());
1382 String::from_utf8_lossy(&s[..end]).trim().to_string()
1383 }
1384
1385 let author = read_str(&mut b, 8);
1386 let family = read_str(&mut b, 8);
1387 let header = read_str(&mut b, 8);
1388 let version = if b.remaining() >= 1 { b.get_u8() as u16 } else { 0 };
1389 let _unk2 = if b.remaining() >= 1 { b.get_u8() } else { 0 };
1390 let checksum = if b.remaining() >= 2 { b.get_u16() } else { 0 };
1391
1392 Ok(crate::types::BlockInfo {
1393 block_type: blk_type,
1394 block_number,
1395 language,
1396 flags,
1397 size: (len_load_mem.min(0xFFFF)) as u16,
1398 size_ram: sbb_len,
1399 mc7_size,
1400 local_data,
1401 checksum,
1402 version,
1403 author,
1404 family,
1405 header,
1406 date: String::new(),
1407 })
1408 }
1409
1410 pub async fn set_session_password(&self, password: &str) -> Result<()> {
1418 let encrypted = crate::types::encrypt_password(password);
1419 let mut inner = self.inner.lock().await;
1420 let pdu_ref = Self::next_pdu_ref(&mut inner);
1421 let param = Bytes::copy_from_slice(&[0x12, 0x00]);
1422 let data = Bytes::copy_from_slice(&encrypted);
1423 Self::send_s7(&mut inner, param, data, pdu_ref, PduType::Job).await?;
1424 let (header, _body) = Self::recv_s7(&mut inner).await?;
1425 check_plc_error(&header, "set_session_password")?;
1426 Ok(())
1427 }
1428
1429 pub async fn clear_session_password(&self) -> Result<()> {
1431 let mut inner = self.inner.lock().await;
1432 let pdu_ref = Self::next_pdu_ref(&mut inner);
1433 let param = Bytes::copy_from_slice(&[0x11, 0x00]);
1434 Self::send_s7(&mut inner, param, Bytes::new(), pdu_ref, PduType::Job).await?;
1435 let (header, _body) = Self::recv_s7(&mut inner).await?;
1436 check_plc_error(&header, "clear_session_password")?;
1437 Ok(())
1438 }
1439
1440 pub async fn get_protection(&self) -> Result<crate::types::Protection> {
1445 let payload = self.read_szl_payload(0x0032, 0x0004).await?;
1446 if payload.len() < 14 {
1447 return Err(Error::UnexpectedResponse);
1448 }
1449 let mut b = payload;
1450 let _block_len = b.get_u16();
1451 let _szl_id = b.get_u16();
1452 let _szl_ix = b.get_u16();
1453 skip_szl_entry_header(&mut b);
1455 let scheme_szl = b.get_u16();
1456 let scheme_module = b.get_u16();
1457 let scheme_bus = b.get_u16();
1458 let level = b.get_u16();
1459 let pass_wort = if b.remaining() >= 8 {
1461 String::from_utf8_lossy(&b[..8]).trim().to_string()
1462 } else {
1463 String::new()
1464 };
1465 let password_set = pass_wort.eq_ignore_ascii_case("PASSWORD");
1466 Ok(crate::types::Protection {
1467 scheme_szl,
1468 scheme_module,
1469 scheme_bus,
1470 level,
1471 password_set,
1472 })
1473 }
1474
1475 pub async fn delete_block(&self, block_type: u8, block_number: u16) -> Result<()> {
1483 let mut inner = self.inner.lock().await;
1484 let pdu_ref = Self::next_pdu_ref(&mut inner);
1485 let mut param = BytesMut::with_capacity(6);
1487 param.extend_from_slice(&[0x1F, 0x00, block_type, 0x00]);
1488 param.put_u16(block_number);
1489 Self::send_s7(
1490 &mut inner,
1491 param.freeze(),
1492 Bytes::new(),
1493 pdu_ref,
1494 PduType::Job,
1495 )
1496 .await?;
1497 let (header, _body) = Self::recv_s7(&mut inner).await?;
1498 check_plc_error(&header, "delete_block")?;
1499 Ok(())
1500 }
1501
1502 pub async fn upload(&self, block_type: u8, block_number: u16) -> Result<Vec<u8>> {
1507 let mut inner = self.inner.lock().await;
1508 let pdu_ref = Self::next_pdu_ref(&mut inner);
1509
1510 let mut param = BytesMut::with_capacity(6);
1513 param.extend_from_slice(&[0x1D, 0x00, block_type, 0x00]);
1514 param.put_u16(block_number);
1515 Self::send_s7(
1516 &mut inner,
1517 param.freeze(),
1518 Bytes::new(),
1519 pdu_ref,
1520 PduType::Job,
1521 )
1522 .await?;
1523 let (header, mut body) = Self::recv_s7(&mut inner).await?;
1524 check_plc_error(&header, "upload_start")?;
1525 if body.remaining() < 8 {
1527 return Err(Error::UnexpectedResponse);
1528 }
1529 if body.remaining() >= 2 {
1530 body.advance(2); }
1532 let upload_id = body.get_u32();
1533 let _total_len = body.get_u32();
1534
1535 let mut block_data = Vec::new();
1537 loop {
1538 let chunk_pdu_ref = Self::next_pdu_ref(&mut inner);
1539 let mut dparam = BytesMut::with_capacity(6);
1540 dparam.extend_from_slice(&[0x1D, 0x01]);
1541 dparam.put_u32(upload_id);
1542 Self::send_s7(
1543 &mut inner,
1544 dparam.freeze(),
1545 Bytes::new(),
1546 chunk_pdu_ref,
1547 PduType::Job,
1548 )
1549 .await?;
1550 let (dheader, mut dbody) = Self::recv_s7(&mut inner).await?;
1551 check_plc_error(&dheader, "upload_data")?;
1552 if dbody.remaining() >= 2 {
1554 dbody.advance(2);
1555 }
1556 if dbody.is_empty() {
1557 break; }
1559 if block_data.is_empty() && dbody.remaining() >= 4 {
1562 if dbody[0] == 0xFF || dbody[0] == 0x00 {
1564 dbody.advance(4);
1565 }
1566 }
1567 let chunk = dbody.copy_to_bytes(dbody.remaining());
1568 block_data.extend_from_slice(&chunk);
1569
1570 if chunk.len() < inner.connection.pdu_size as usize - 50 {
1572 break;
1573 }
1574 if block_data.len() > 1024 * 1024 * 4 {
1576 return Err(Error::UnexpectedResponse);
1578 }
1579 }
1580
1581 let end_pdu_ref = Self::next_pdu_ref(&mut inner);
1583 let mut eparam = BytesMut::with_capacity(6);
1584 eparam.extend_from_slice(&[0x1D, 0x02]);
1585 eparam.put_u32(upload_id);
1586 Self::send_s7(
1587 &mut inner,
1588 eparam.freeze(),
1589 Bytes::new(),
1590 end_pdu_ref,
1591 PduType::Job,
1592 )
1593 .await?;
1594 let (eheader, _ebody) = Self::recv_s7(&mut inner).await?;
1595 check_plc_error(&eheader, "upload_end")?;
1596
1597 Ok(block_data)
1598 }
1599
1600 pub async fn db_get(&self, db_number: u16) -> Result<Vec<u8>> {
1602 self.upload(0x41, db_number).await }
1604
1605 pub async fn download(&self, block_type: u8, block_number: u16, data: &[u8]) -> Result<()> {
1610 let total_len = data.len() as u32;
1611 let mut inner = self.inner.lock().await;
1612 let pdu_avail = (inner.connection.pdu_size as usize).saturating_sub(50);
1613
1614 let start_ref = Self::next_pdu_ref(&mut inner);
1616 let mut sparam = BytesMut::with_capacity(10);
1618 sparam.extend_from_slice(&[0x1E, 0x00, block_type, 0x00]);
1619 sparam.put_u16(block_number);
1620 sparam.put_u32(total_len);
1621
1622 let chunk_len = pdu_avail.min(data.len());
1624 let first_chunk = Bytes::copy_from_slice(&data[..chunk_len]);
1625 Self::send_s7(
1626 &mut inner,
1627 sparam.freeze(),
1628 first_chunk,
1629 start_ref,
1630 PduType::Job,
1631 )
1632 .await?;
1633
1634 let (sheader, mut sbody) = Self::recv_s7(&mut inner).await?;
1635 check_plc_error(&sheader, "download_start")?;
1636 if sbody.remaining() >= 2 {
1638 sbody.advance(2); }
1640 if sbody.remaining() < 4 {
1641 return Err(Error::UnexpectedResponse);
1642 }
1643 let download_id = sbody.get_u32();
1644
1645 let mut offset = chunk_len;
1646
1647 while offset < data.len() {
1649 let chunk_ref = Self::next_pdu_ref(&mut inner);
1650 let end = (offset + pdu_avail).min(data.len());
1651 let chunk = Bytes::copy_from_slice(&data[offset..end]);
1652
1653 let mut dparam = BytesMut::with_capacity(6);
1654 dparam.extend_from_slice(&[0x1E, 0x01]);
1655 dparam.put_u32(download_id);
1656
1657 Self::send_s7(
1658 &mut inner,
1659 dparam.freeze(),
1660 chunk,
1661 chunk_ref,
1662 PduType::Job,
1663 )
1664 .await?;
1665
1666 let (dheader, _dbody) = Self::recv_s7(&mut inner).await?;
1667 check_plc_error(&dheader, "download_data")?;
1668 offset = end;
1669 }
1670
1671 let end_ref = Self::next_pdu_ref(&mut inner);
1673 let mut eparam = BytesMut::with_capacity(6);
1674 eparam.extend_from_slice(&[0x1E, 0x02]);
1675 eparam.put_u32(download_id);
1676 Self::send_s7(
1677 &mut inner,
1678 eparam.freeze(),
1679 Bytes::new(),
1680 end_ref,
1681 PduType::Job,
1682 )
1683 .await?;
1684 let (eheader, _ebody) = Self::recv_s7(&mut inner).await?;
1685 check_plc_error(&eheader, "download_end")?;
1686
1687 Ok(())
1688 }
1689
1690 pub async fn db_fill(&self, db_number: u16, value: u8) -> Result<()> {
1695 let info = self.get_ag_block_info(0x41, db_number).await?; let size = info.size as usize;
1697 if size == 0 {
1698 return Err(Error::PlcError {
1699 code: 0,
1700 message: format!("DB{db_number} has zero size"),
1701 });
1702 }
1703 let data = vec![value; size];
1704 let chunk_size = 240usize; for offset in (0..size).step_by(chunk_size) {
1707 let end = (offset + chunk_size).min(size);
1708 self.db_write(db_number, offset as u32, &data[offset..end])
1709 .await?;
1710 }
1711 Ok(())
1712 }
1713}
1714
1715fn skip_szl_entry_header(data: &mut Bytes) {
1719 if data.len() >= 2 && data[0] == 0x00 && data[1] > 0 && data[1] <= 200 {
1720 data.advance(2);
1721 }
1722}
1723
1724fn scan_ascii_fields(data: &[u8], max_count: usize, min_len: usize) -> Vec<String> {
1729 let mut fields = Vec::new();
1730 let mut i = 0;
1731 while i < data.len() && fields.len() < max_count {
1732 if !data[i].is_ascii_graphic() && data[i] != b' ' {
1734 i += 1;
1735 continue;
1736 }
1737 let start = i;
1739 while i < data.len() && (data[i].is_ascii_graphic() || data[i] == b' ') {
1740 i += 1;
1741 }
1742 let s = String::from_utf8_lossy(&data[start..i]).trim().to_string();
1743 if s.len() >= min_len {
1744 fields.push(s);
1745 }
1746 }
1747 fields
1748}
1749
1750fn parse_sub_record_fields(b: &[u8]) -> (String, String, String, String, String) {
1760 let mut module_type = String::new();
1761 let mut serial_number = String::new();
1762 let mut as_name = String::new();
1763 let mut copyright = String::new();
1764 let mut module_name = String::new();
1765
1766 let mut i = 0;
1767 while i + 2 < b.len() {
1768 if b[i] == 0x00 && (1..=8).contains(&b[i + 1]) {
1770 let tag = b[i + 1];
1771 let start = i + 2;
1772
1773 let mut end = start;
1775 while end < b.len() && b[end] != 0x00 {
1776 end += 1;
1777 }
1778
1779 let raw = &b[start..end];
1780 let val = String::from_utf8_lossy(raw).trim().to_string();
1781
1782 let su = val.to_uppercase();
1784 if !val.is_empty() && !su.contains("BOOT") && !su.starts_with("P B") {
1785 match tag {
1786 0x01 => {
1787 if !val.starts_with("6ES") && module_type.is_empty() {
1789 module_type = val;
1790 }
1791 }
1792 0x05 => { if as_name.is_empty() { as_name = val; } }
1793 0x06 => { if serial_number.is_empty() { serial_number = val; } }
1794 0x07 => { if module_type.is_empty() { module_type = val; } }
1795 0x08 => { if module_name.is_empty() { module_name = val; } }
1796 _ => {}
1797 }
1798 }
1799
1800 i = end;
1801 } else {
1802 i += 1;
1803 }
1804 }
1805
1806 if copyright.is_empty() {
1809 let mut scan = 0;
1810 while scan < b.len() {
1811 if b[scan].is_ascii_graphic() || b[scan] == b' ' {
1812 let s = scan;
1813 while scan < b.len() && (b[scan].is_ascii_graphic() || b[scan] == b' ') {
1814 scan += 1;
1815 }
1816 let val = String::from_utf8_lossy(&b[s..scan]).trim().to_string();
1817 let su = val.to_uppercase();
1818 if val.len() >= 3 {
1819 if su.contains("BOOT") || su.starts_with("P B") {
1820 copyright = val;
1821 break;
1822 }
1823 }
1824 } else {
1825 scan += 1;
1826 }
1827 }
1828 }
1829
1830 (module_type, serial_number, as_name, copyright, module_name)
1831}
1832
1833fn detect_protocol(_payload: &[u8], module_type: &str) -> crate::types::Protocol {
1839 let upper = module_type.to_uppercase();
1842 let is_s7plus = upper.contains("1500")
1843 || upper.contains("1200")
1844 || upper.contains("ET 200SP")
1845 || upper.contains("ET200SP")
1846 || (upper.contains("CPU") && {
1848 let after_cpu = upper.find("CPU").map(|i| &upper[i+3..]).unwrap_or("");
1849 let num: String = after_cpu.chars().skip_while(|c| !c.is_ascii_digit()).take_while(|c| c.is_ascii_digit()).collect();
1850 matches!(num.get(..2), Some("12") | Some("15"))
1851 });
1852
1853 if is_s7plus {
1854 crate::types::Protocol::S7Plus
1855 } else {
1856 crate::types::Protocol::S7
1857 }
1858}
1859
1860
1861fn s7_error_description(ec: u8, ecd: u8) -> &'static str {
1863 match (ec, ecd) {
1864 (0x81, 0x04) => "function not supported or access denied by PLC",
1865 (0x81, 0x01) => "reserved by HW or SW function not available",
1866 (0x82, 0x04) => "PLC is in STOP mode, function not possible",
1867 (0x05, 0x01) => "invalid block type number",
1868 (0xD2, 0x01) => "object already exists, download rejected",
1869 (0xD2, 0x02) => "object does not exist, upload failed",
1870 (0xD6, 0x01) => "password protection violation",
1871 (0xD6, 0x05) => "insufficient privilege for this operation",
1872 _ => "unknown error",
1873 }
1874}
1875
1876fn check_plc_error(header: &S7Header, context: &str) -> Result<()> {
1877 if let (Some(ec), Some(ecd)) = (header.error_class, header.error_code) {
1878 if ec != 0 || ecd != 0 {
1879 let detail = s7_error_description(ec, ecd);
1880 return Err(Error::PlcError {
1881 code: ((ec as u32) << 8) | ecd as u32,
1882 message: format!("{}: {} (error_class=0x{ec:02X}, error_code=0x{ecd:02X})", context, detail),
1883 });
1884 }
1885 }
1886 Ok(())
1887}
1888
1889impl S7Client<crate::transport::TcpTransport> {
1890 pub async fn connect(addr: SocketAddr, params: ConnectParams) -> Result<Self> {
1891 let transport =
1892 crate::transport::TcpTransport::connect(addr, params.connect_timeout).await?;
1893 Self::from_transport(transport, params).await
1894 }
1895}
1896
1897impl S7Client<crate::UdpTransport> {
1898 pub async fn connect_udp(addr: SocketAddr, params: ConnectParams) -> Result<Self> {
1900 let transport = crate::UdpTransport::connect(addr)
1901 .await
1902 .map_err(Error::Io)?;
1903 Self::from_transport(transport, params).await
1904 }
1905}
1906
1907#[cfg(test)]
1908mod tests {
1909 use super::*;
1910 use bytes::BufMut;
1911 use crate::proto::{
1912 cotp::CotpPdu,
1913 s7::{
1914 header::{PduType, S7Header},
1915 negotiate::NegotiateResponse,
1916 },
1917 tpkt::TpktFrame,
1918 };
1919 use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt};
1920
1921 async fn mock_plc_db_read(mut server_io: tokio::io::DuplexStream, response_data: Vec<u8>) {
1922 let mut buf = vec![0u8; 4096];
1923
1924 let _ = server_io.read(&mut buf).await;
1926 let cc = CotpPdu::ConnectConfirm {
1927 dst_ref: 1,
1928 src_ref: 1,
1929 };
1930 let mut cb = BytesMut::new();
1931 cc.encode(&mut cb);
1932 let mut tb = BytesMut::new();
1933 TpktFrame {
1934 payload: cb.freeze(),
1935 }
1936 .encode(&mut tb)
1937 .unwrap();
1938 server_io.write_all(&tb).await.unwrap();
1939
1940 let _ = server_io.read(&mut buf).await;
1942 let neg = NegotiateResponse {
1943 max_amq_calling: 1,
1944 max_amq_called: 1,
1945 pdu_length: 480,
1946 };
1947 let mut s7b = BytesMut::new();
1948 S7Header {
1949 pdu_type: PduType::AckData,
1950 reserved: 0,
1951 pdu_ref: 1,
1952 param_len: 8,
1953 data_len: 0,
1954 error_class: Some(0),
1955 error_code: Some(0),
1956 }
1957 .encode(&mut s7b);
1958 neg.encode(&mut s7b);
1959 let dt = CotpPdu::Data {
1960 tpdu_nr: 0,
1961 last: true,
1962 payload: s7b.freeze(),
1963 };
1964 let mut cb = BytesMut::new();
1965 dt.encode(&mut cb);
1966 let mut tb = BytesMut::new();
1967 TpktFrame {
1968 payload: cb.freeze(),
1969 }
1970 .encode(&mut tb)
1971 .unwrap();
1972 server_io.write_all(&tb).await.unwrap();
1973
1974 let _ = server_io.read(&mut buf).await;
1976 let mut s7b = BytesMut::new();
1977 S7Header {
1978 pdu_type: PduType::AckData,
1979 reserved: 0,
1980 pdu_ref: 2,
1981 param_len: 2,
1982 data_len: (4 + response_data.len()) as u16,
1983 error_class: Some(0),
1984 error_code: Some(0),
1985 }
1986 .encode(&mut s7b);
1987 s7b.extend_from_slice(&[0x04, 0x01]); s7b.put_u8(0xFF); s7b.put_u8(0x04); s7b.put_u16((response_data.len() * 8) as u16);
1991 s7b.extend_from_slice(&response_data);
1992 let dt = CotpPdu::Data {
1993 tpdu_nr: 0,
1994 last: true,
1995 payload: s7b.freeze(),
1996 };
1997 let mut cb = BytesMut::new();
1998 dt.encode(&mut cb);
1999 let mut tb = BytesMut::new();
2000 TpktFrame {
2001 payload: cb.freeze(),
2002 }
2003 .encode(&mut tb)
2004 .unwrap();
2005 server_io.write_all(&tb).await.unwrap();
2006 }
2007
2008 #[tokio::test]
2009 async fn db_read_returns_data() {
2010 let (client_io, server_io) = duplex(4096);
2011 let params = ConnectParams::default();
2012 let expected = vec![0xDE, 0xAD, 0xBE, 0xEF];
2013 tokio::spawn(mock_plc_db_read(server_io, expected.clone()));
2014 let client = S7Client::from_transport(client_io, params).await.unwrap();
2015 let data = client.db_read(1, 0, 4).await.unwrap();
2016 assert_eq!(&data[..], &expected[..]);
2017 }
2018
2019 async fn mock_plc_multi_read(
2021 mut server_io: tokio::io::DuplexStream,
2022 items: Vec<Vec<u8>>, ) {
2024 let mut buf = vec![0u8; 4096];
2025
2026 let _ = server_io.read(&mut buf).await;
2028 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
2029 let mut cb = BytesMut::new();
2030 cc.encode(&mut cb);
2031 let mut tb = BytesMut::new();
2032 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2033 server_io.write_all(&tb).await.unwrap();
2034
2035 let _ = server_io.read(&mut buf).await;
2037 let neg = NegotiateResponse { max_amq_calling: 1, max_amq_called: 1, pdu_length: 480 };
2038 let mut s7b = BytesMut::new();
2039 S7Header {
2040 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 1,
2041 param_len: 8, data_len: 0, error_class: Some(0), error_code: Some(0),
2042 }.encode(&mut s7b);
2043 neg.encode(&mut s7b);
2044 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2045 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2046 let mut tb = BytesMut::new();
2047 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2048 server_io.write_all(&tb).await.unwrap();
2049
2050 let _ = server_io.read(&mut buf).await;
2052
2053 let item_count = items.len() as u8;
2055 let mut data_bytes = BytesMut::new();
2056 for item_data in &items {
2057 data_bytes.put_u8(0xFF); data_bytes.put_u8(0x04); data_bytes.put_u16((item_data.len() * 8) as u16);
2060 data_bytes.extend_from_slice(item_data);
2061 if item_data.len() % 2 != 0 {
2062 data_bytes.put_u8(0x00); }
2064 }
2065 let data_len = data_bytes.len() as u16;
2066 let mut s7b = BytesMut::new();
2067 S7Header {
2068 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 2,
2069 param_len: 2, data_len, error_class: Some(0), error_code: Some(0),
2070 }.encode(&mut s7b);
2071 s7b.extend_from_slice(&[0x04, item_count]); s7b.extend_from_slice(&data_bytes);
2073
2074 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2075 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2076 let mut tb = BytesMut::new();
2077 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2078 server_io.write_all(&tb).await.unwrap();
2079 }
2080
2081 #[tokio::test]
2082 async fn read_multi_vars_returns_all_items() {
2083 let (client_io, server_io) = duplex(4096);
2084 let params = ConnectParams::default();
2085 let item1 = vec![0xDE, 0xAD, 0xBE, 0xEF];
2086 let item2 = vec![0x01, 0x02];
2087 tokio::spawn(mock_plc_multi_read(server_io, vec![item1.clone(), item2.clone()]));
2088 let client = S7Client::from_transport(client_io, params).await.unwrap();
2089 let items = [MultiReadItem::db(1, 0, 4), MultiReadItem::db(2, 10, 2)];
2090 let results = client.read_multi_vars(&items).await.unwrap();
2091 assert_eq!(results.len(), 2);
2092 assert_eq!(&results[0][..], &item1[..]);
2093 assert_eq!(&results[1][..], &item2[..]);
2094 }
2095
2096 #[tokio::test]
2097 async fn read_multi_vars_empty_returns_empty() {
2098 let (client_io, server_io) = duplex(4096);
2099 let params = ConnectParams::default();
2100 tokio::spawn(mock_plc_multi_read(server_io, vec![]));
2101 let client = S7Client::from_transport(client_io, params).await.unwrap();
2102 let results = client.read_multi_vars(&[]).await.unwrap();
2103 assert!(results.is_empty());
2104 }
2105
2106 async fn mock_plc_multi_write(
2109 mut server_io: tokio::io::DuplexStream,
2110 pdu_size: u16,
2111 batches: Vec<usize>,
2112 ) {
2113 let mut buf = vec![0u8; 65536];
2114
2115 let _ = server_io.read(&mut buf).await;
2117 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
2118 let mut cb = BytesMut::new(); cc.encode(&mut cb);
2119 let mut tb = BytesMut::new();
2120 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2121 server_io.write_all(&tb).await.unwrap();
2122
2123 let _ = server_io.read(&mut buf).await;
2125 let neg = NegotiateResponse { max_amq_calling: 1, max_amq_called: 1, pdu_length: pdu_size };
2126 let mut s7b = BytesMut::new();
2127 S7Header {
2128 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 1,
2129 param_len: 8, data_len: 0, error_class: Some(0), error_code: Some(0),
2130 }.encode(&mut s7b);
2131 neg.encode(&mut s7b);
2132 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2133 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2134 let mut tb = BytesMut::new();
2135 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2136 server_io.write_all(&tb).await.unwrap();
2137
2138 for (i, item_count) in batches.iter().enumerate() {
2140 let _ = server_io.read(&mut buf).await;
2141 let mut s7b = BytesMut::new();
2143 S7Header {
2144 pdu_type: PduType::AckData, reserved: 0, pdu_ref: (i + 2) as u16,
2145 param_len: 2, data_len: *item_count as u16,
2146 error_class: Some(0), error_code: Some(0),
2147 }.encode(&mut s7b);
2148 s7b.extend_from_slice(&[0x05, *item_count as u8]); for _ in 0..*item_count {
2150 s7b.put_u8(0xFF); }
2152 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2153 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2154 let mut tb = BytesMut::new();
2155 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2156 server_io.write_all(&tb).await.unwrap();
2157 }
2158 }
2159
2160 #[tokio::test]
2161 async fn write_multi_vars_returns_ok() {
2162 let (client_io, server_io) = duplex(65536);
2163 let params = ConnectParams::default();
2164 tokio::spawn(mock_plc_multi_write(server_io, 480, vec![2]));
2165 let client = S7Client::from_transport(client_io, params).await.unwrap();
2166 let items = [
2167 MultiWriteItem::db(1, 0, vec![0xAA, 0xBB, 0xCC, 0xDD]),
2168 MultiWriteItem::db(2, 10, vec![0x01, 0x02]),
2169 ];
2170 client.write_multi_vars(&items).await.unwrap();
2171 }
2172
2173 #[tokio::test]
2174 async fn write_multi_vars_empty_returns_ok() {
2175 let (client_io, server_io) = duplex(4096);
2176 let params = ConnectParams::default();
2177 tokio::spawn(mock_plc_multi_write(server_io, 480, vec![]));
2179 let client = S7Client::from_transport(client_io, params).await.unwrap();
2180 client.write_multi_vars(&[]).await.unwrap();
2181 }
2182
2183 #[tokio::test]
2189 async fn write_multi_vars_batches_when_pdu_limit_exceeded() {
2190 let (client_io, server_io) = duplex(65536);
2191 let params = ConnectParams::default();
2192 tokio::spawn(mock_plc_multi_write(server_io, 64, vec![1, 1]));
2193 let client = S7Client::from_transport(client_io, params).await.unwrap();
2194 let items = [
2195 MultiWriteItem::db(1, 0, vec![0x11u8; 20]),
2196 MultiWriteItem::db(2, 0, vec![0x22u8; 20]),
2197 ];
2198 client.write_multi_vars(&items).await.unwrap();
2199 }
2200
2201 #[tokio::test]
2207 async fn read_multi_vars_batches_when_pdu_limit_exceeded() {
2208 use crate::proto::s7::negotiate::NegotiateResponse;
2209
2210 async fn mock_split_pdu(mut server_io: tokio::io::DuplexStream) {
2211 let mut buf = vec![0u8; 4096];
2212
2213 let _ = server_io.read(&mut buf).await;
2215 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
2216 let mut cb = BytesMut::new(); cc.encode(&mut cb);
2217 let mut tb = BytesMut::new();
2218 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2219 server_io.write_all(&tb).await.unwrap();
2220
2221 let _ = server_io.read(&mut buf).await;
2223 let neg = NegotiateResponse {
2224 max_amq_calling: 1, max_amq_called: 1, pdu_length: 64,
2225 };
2226 let mut s7b = BytesMut::new();
2227 S7Header {
2228 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 1,
2229 param_len: 8, data_len: 0, error_class: Some(0), error_code: Some(0),
2230 }.encode(&mut s7b);
2231 neg.encode(&mut s7b);
2232 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2233 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2234 let mut tb = BytesMut::new();
2235 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2236 server_io.write_all(&tb).await.unwrap();
2237
2238 let payloads: &[&[u8]] = &[&[0x11u8; 30], &[0x22u8; 30]];
2240 for (i, payload) in payloads.iter().enumerate() {
2241 let _ = server_io.read(&mut buf).await;
2242 let bit_len = (payload.len() * 8) as u16;
2243 let mut data_bytes = BytesMut::new();
2244 data_bytes.put_u8(0xFF);
2245 data_bytes.put_u8(0x04);
2246 data_bytes.put_u16(bit_len);
2247 data_bytes.extend_from_slice(payload);
2248 if payload.len() % 2 != 0 { data_bytes.put_u8(0x00); }
2249 let data_len = data_bytes.len() as u16;
2250 let mut s7b = BytesMut::new();
2251 S7Header {
2252 pdu_type: PduType::AckData, reserved: 0, pdu_ref: (i + 2) as u16,
2253 param_len: 2, data_len, error_class: Some(0), error_code: Some(0),
2254 }.encode(&mut s7b);
2255 s7b.extend_from_slice(&[0x04, 0x01]);
2256 s7b.extend_from_slice(&data_bytes);
2257 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2258 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2259 let mut tb = BytesMut::new();
2260 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2261 server_io.write_all(&tb).await.unwrap();
2262 }
2263 }
2264
2265 let (client_io, server_io) = duplex(4096);
2266 let params = ConnectParams::default();
2267 tokio::spawn(mock_split_pdu(server_io));
2268 let client = S7Client::from_transport(client_io, params).await.unwrap();
2269
2270 let items = [MultiReadItem::db(1, 0, 30), MultiReadItem::db(2, 0, 30)];
2271 let results = client.read_multi_vars(&items).await.unwrap();
2272 assert_eq!(results.len(), 2);
2273 assert_eq!(&results[0][..], &[0x11u8; 30][..]);
2274 assert_eq!(&results[1][..], &[0x22u8; 30][..]);
2275 }
2276
2277 async fn mock_handshake(server_io: &mut (impl AsyncRead + AsyncWrite + Unpin)) {
2281 let mut buf = vec![0u8; 4096];
2282
2283 let _ = server_io.read(&mut buf).await;
2285 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
2286 let mut cb = BytesMut::new(); cc.encode(&mut cb);
2287 let mut tb = BytesMut::new();
2288 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2289 server_io.write_all(&tb).await.unwrap();
2290
2291 let _ = server_io.read(&mut buf).await;
2293 let neg = NegotiateResponse { max_amq_calling: 1, max_amq_called: 1, pdu_length: 480 };
2294 let mut s7b = BytesMut::new();
2295 S7Header {
2296 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 1,
2297 param_len: 8, data_len: 0, error_class: Some(0), error_code: Some(0),
2298 }.encode(&mut s7b);
2299 neg.encode(&mut s7b);
2300 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2301 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2302 let mut tb = BytesMut::new();
2303 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2304 server_io.write_all(&tb).await.unwrap();
2305 }
2306
2307 async fn mock_plc_control(
2310 mut server_io: tokio::io::DuplexStream,
2311 ok: bool,
2312 ) {
2313 let mut buf = vec![0u8; 4096];
2314 mock_handshake(&mut server_io).await;
2315
2316 let _ = server_io.read(&mut buf).await;
2318
2319 let (ec, ecd) = if ok { (0u8, 0u8) } else { (0x81u8, 0x04u8) };
2321 let mut s7b = BytesMut::new();
2322 S7Header {
2323 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 2,
2324 param_len: 0, data_len: 0,
2325 error_class: Some(ec), error_code: Some(ecd),
2326 }.encode(&mut s7b);
2327 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2328 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2329 let mut tb = BytesMut::new();
2330 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2331 server_io.write_all(&tb).await.unwrap();
2332 }
2333
2334 #[tokio::test]
2335 async fn plc_stop_succeeds() {
2336 let (client_io, server_io) = duplex(4096);
2337 let params = ConnectParams::default();
2338 tokio::spawn(mock_plc_control(server_io, true));
2339 let client = S7Client::from_transport(client_io, params).await.unwrap();
2340 client.plc_stop().await.unwrap();
2341 }
2342
2343 #[tokio::test]
2344 async fn plc_hot_start_succeeds() {
2345 let (client_io, server_io) = duplex(4096);
2346 let params = ConnectParams::default();
2347 tokio::spawn(mock_plc_control(server_io, true));
2348 let client = S7Client::from_transport(client_io, params).await.unwrap();
2349 client.plc_hot_start().await.unwrap();
2350 }
2351
2352 #[tokio::test]
2353 async fn plc_cold_start_succeeds() {
2354 let (client_io, server_io) = duplex(4096);
2355 let params = ConnectParams::default();
2356 tokio::spawn(mock_plc_control(server_io, true));
2357 let client = S7Client::from_transport(client_io, params).await.unwrap();
2358 client.plc_cold_start().await.unwrap();
2359 }
2360
2361 #[tokio::test]
2362 async fn plc_stop_rejected_returns_error() {
2363 let (client_io, server_io) = duplex(4096);
2364 let params = ConnectParams::default();
2365 tokio::spawn(mock_plc_control(server_io, false));
2366 let client = S7Client::from_transport(client_io, params).await.unwrap();
2367 let result = client.plc_stop().await;
2368 assert!(result.is_err());
2369 }
2370
2371 async fn mock_plc_status(
2373 mut server_io: tokio::io::DuplexStream,
2374 status_byte: u8,
2375 ) {
2376 let mut buf = vec![0u8; 4096];
2377 mock_handshake(&mut server_io).await;
2378
2379 let _ = server_io.read(&mut buf).await;
2381
2382 let mut szl_payload = [0u8; 12];
2390 szl_payload[0..2].copy_from_slice(&0x0424u16.to_be_bytes());
2391 szl_payload[6..8].copy_from_slice(&0x0001u16.to_be_bytes()); szl_payload[11] = status_byte;
2393
2394 let params: [u8; 8] = [0x00, 0x01, 0x12, 0x08, 0x12, 0x84, 0x01, 0x00];
2399 let data_envelope: [u8; 4] = [0xFF, 0x09, 0x00, 0x0C];
2400 let param_len = params.len() as u16;
2401 let data_len = (data_envelope.len() + szl_payload.len()) as u16;
2402
2403 let mut s7b = BytesMut::new();
2404 S7Header {
2405 pdu_type: PduType::UserData, reserved: 0, pdu_ref: 2,
2406 param_len, data_len,
2407 error_class: None, error_code: None,
2408 }.encode(&mut s7b);
2409 s7b.extend_from_slice(¶ms);
2410 s7b.extend_from_slice(&data_envelope);
2411 s7b.extend_from_slice(&szl_payload);
2412 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2413 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2414 let mut tb = BytesMut::new();
2415 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2416 server_io.write_all(&tb).await.unwrap();
2417 }
2418
2419 #[tokio::test]
2420 async fn get_plc_status_returns_run() {
2421 let (client_io, server_io) = duplex(4096);
2422 let params = ConnectParams::default();
2423 tokio::spawn(mock_plc_status(server_io, 0x08));
2424 let client = S7Client::from_transport(client_io, params).await.unwrap();
2425 let status = client.get_plc_status().await.unwrap();
2426 assert_eq!(status, crate::types::PlcStatus::Run);
2427 }
2428
2429 #[tokio::test]
2430 async fn get_plc_status_returns_stop() {
2431 let (client_io, server_io) = duplex(4096);
2432 let params = ConnectParams::default();
2433 tokio::spawn(mock_plc_status(server_io, 0x04));
2434 let client = S7Client::from_transport(client_io, params).await.unwrap();
2435 let status = client.get_plc_status().await.unwrap();
2436 assert_eq!(status, crate::types::PlcStatus::Stop);
2437 }
2438
2439 #[tokio::test]
2440 async fn get_plc_status_returns_unknown() {
2441 let (client_io, server_io) = duplex(4096);
2442 let params = ConnectParams::default();
2443 tokio::spawn(mock_plc_status(server_io, 0x00));
2444 let client = S7Client::from_transport(client_io, params).await.unwrap();
2445 let status = client.get_plc_status().await.unwrap();
2446 assert_eq!(status, crate::types::PlcStatus::Unknown);
2447 }
2448
2449 #[tokio::test]
2450 async fn get_plc_status_unknown_byte_returns_stop() {
2451 let (client_io, server_io) = duplex(4096);
2453 let params = ConnectParams::default();
2454 tokio::spawn(mock_plc_status(server_io, 0xFF));
2455 let client = S7Client::from_transport(client_io, params).await.unwrap();
2456 let status = client.get_plc_status().await.unwrap();
2457 assert_eq!(status, crate::types::PlcStatus::Stop);
2458 }
2459}