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 connected: bool,
74 job_start: Option<std::time::Instant>,
75 last_exec_ms: u32,
76}
77
78pub struct S7Client<T: AsyncRead + AsyncWrite + Unpin + Send> {
79 inner: Mutex<Inner<T>>,
80 params: ConnectParams,
81 remote_addr: Option<SocketAddr>,
82}
83
84impl<T: AsyncRead + AsyncWrite + Unpin + Send> S7Client<T> {
85 pub async fn from_transport(transport: T, params: ConnectParams) -> Result<Self> {
86 let mut t = transport;
87 let connection = connect(&mut t, ¶ms).await?;
88 let timeout = params.request_timeout;
89 Ok(S7Client {
90 inner: Mutex::new(Inner {
91 transport: t,
92 connection,
93 pdu_ref: 1,
94 request_timeout: timeout,
95 connected: true,
96 job_start: None,
97 last_exec_ms: 0,
98 }),
99 params,
100 remote_addr: None,
101 })
102 }
103
104 pub fn request_timeout(&self) -> std::time::Duration {
106 self.params.request_timeout
107 }
108
109 pub async fn get_exec_time(&self) -> u32 {
113 self.inner.lock().await.last_exec_ms
114 }
115
116 pub async fn is_connected(&self) -> bool {
121 self.inner.lock().await.connected
122 }
123
124 pub async fn set_request_timeout(&self, timeout: std::time::Duration) {
128 let mut inner = self.inner.lock().await;
129 inner.request_timeout = timeout;
130 }
131
132 pub fn get_param(&self, name: &str) -> Result<std::time::Duration> {
136 match name {
137 "request_timeout" => Ok(self.params.request_timeout),
138 "connect_timeout" => Ok(self.params.connect_timeout),
139 "pdu_size" => Err(Error::PlcError {
140 code: 0,
141 message: "pdu_size is not a Duration; use .params.pdu_size directly".into(),
142 }),
143 _ => Err(Error::PlcError {
144 code: 0,
145 message: format!("unknown parameter: {name}"),
146 }),
147 }
148 }
149
150 pub fn set_param(&mut self, name: &str, value: std::time::Duration) -> Result<()> {
154 match name {
155 "request_timeout" => {
156 self.params.request_timeout = value;
157 Ok(())
158 }
159 _ => Err(Error::PlcError {
160 code: 0,
161 message: format!("unknown parameter: {name}"),
162 }),
163 }
164 }
165
166 fn next_pdu_ref(inner: &mut Inner<T>) -> u16 {
167 inner.pdu_ref = inner.pdu_ref.wrapping_add(1);
168 inner.pdu_ref
169 }
170
171 async fn send_s7(
172 inner: &mut Inner<T>,
173 param_buf: Bytes,
174 data_buf: Bytes,
175 pdu_ref: u16,
176 pdu_type: PduType,
177 ) -> Result<()> {
178 let header = S7Header {
179 pdu_type,
180 reserved: 0,
181 pdu_ref,
182 param_len: param_buf.len() as u16,
183 data_len: data_buf.len() as u16,
184 error_class: None,
185 error_code: None,
186 };
187 let mut s7b = BytesMut::new();
188 header.encode(&mut s7b);
189 s7b.extend_from_slice(¶m_buf);
190 s7b.extend_from_slice(&data_buf);
191
192 let dt = CotpPdu::Data {
193 tpdu_nr: 0,
194 last: true,
195 payload: s7b.freeze(),
196 };
197 let mut cotpb = BytesMut::new();
198 dt.encode(&mut cotpb);
199 let tpkt = TpktFrame {
200 payload: cotpb.freeze(),
201 };
202 let mut tb = BytesMut::new();
203 tpkt.encode(&mut tb)?;
204 inner.job_start = Some(std::time::Instant::now());
205 inner.transport.write_all(&tb).await?;
206 Ok(())
207 }
208
209 async fn recv_s7(inner: &mut Inner<T>) -> Result<(S7Header, Bytes)> {
210 let timeout = inner.request_timeout;
211 let mut tpkt_hdr = [0u8; 4];
212 if let Err(e) = tokio::time::timeout(timeout, inner.transport.read_exact(&mut tpkt_hdr))
213 .await
214 .map_err(|_| Error::Timeout(timeout))
215 .and_then(|r| r.map_err(Error::Io))
216 {
217 inner.connected = false;
218 return Err(e);
219 }
220 let total = u16::from_be_bytes([tpkt_hdr[2], tpkt_hdr[3]]) as usize;
221 if total < 4 {
222 return Err(Error::UnexpectedResponse);
223 }
224 let mut payload = vec![0u8; total - 4];
225 if let Err(e) = tokio::time::timeout(timeout, inner.transport.read_exact(&mut payload))
226 .await
227 .map_err(|_| Error::Timeout(timeout))
228 .and_then(|r| r.map_err(Error::Io))
229 {
230 inner.connected = false;
231 return Err(e);
232 }
233 let mut b = Bytes::from(payload);
234
235 if b.remaining() < 3 {
237 return Err(Error::UnexpectedResponse);
238 }
239 let _li = b.get_u8();
240 let cotp_code = b.get_u8();
241 if cotp_code != 0xF0 {
242 return Err(Error::UnexpectedResponse);
243 }
244 b.advance(1); let header = S7Header::decode(&mut b)?;
247 if let Some(t0) = inner.job_start.take() {
248 inner.last_exec_ms = t0.elapsed().as_millis() as u32;
249 }
250 Ok((header, b))
251 }
252
253 pub async fn db_read(&self, db: u16, start: u32, length: u16) -> Result<Bytes> {
254 let mut inner = self.inner.lock().await;
255 let pdu_ref = Self::next_pdu_ref(&mut inner);
256
257 let req = ReadVarRequest {
258 items: vec![AddressItem {
259 area: Area::DataBlock,
260 db_number: db,
261 start,
262 bit_offset: 0,
263 length,
264 transport: TransportSize::Byte,
265 }],
266 };
267 let mut param_buf = BytesMut::new();
268 req.encode(&mut param_buf);
269
270 Self::send_s7(
271 &mut inner,
272 param_buf.freeze(),
273 Bytes::new(),
274 pdu_ref,
275 PduType::Job,
276 )
277 .await?;
278
279 let (header, mut body) = Self::recv_s7(&mut inner).await?;
280 check_plc_error(&header, "db_read")?;
281 if body.remaining() >= 2 {
282 body.advance(2); }
284 let resp = ReadVarResponse::decode(&mut body, 1)?;
285 if resp.items.is_empty() {
286 return Err(Error::UnexpectedResponse);
287 }
288 if resp.items[0].return_code != 0xFF {
289 return Err(Error::PlcError {
290 code: resp.items[0].return_code as u32,
291 message: "item error".into(),
292 });
293 }
294 Ok(resp.items[0].data.clone())
295 }
296
297 pub async fn read_area(
303 &self,
304 area: Area,
305 db_number: u16,
306 start: u32,
307 element_count: u16,
308 transport: TransportSize,
309 ) -> Result<Bytes> {
310 let mut inner = self.inner.lock().await;
311 let pdu_ref = Self::next_pdu_ref(&mut inner);
312
313 let req = ReadVarRequest {
314 items: vec![AddressItem {
315 area,
316 db_number,
317 start,
318 bit_offset: 0,
319 length: element_count,
320 transport,
321 }],
322 };
323 let mut param_buf = BytesMut::new();
324 req.encode(&mut param_buf);
325
326 Self::send_s7(
327 &mut inner,
328 param_buf.freeze(),
329 Bytes::new(),
330 pdu_ref,
331 PduType::Job,
332 )
333 .await?;
334
335 let (header, mut body) = Self::recv_s7(&mut inner).await?;
336 check_plc_error(&header, "read_area")?;
337 if body.remaining() >= 2 {
338 body.advance(2);
339 }
340 let resp = ReadVarResponse::decode(&mut body, 1)?;
341 if resp.items.is_empty() {
342 return Err(Error::UnexpectedResponse);
343 }
344 if resp.items[0].return_code != 0xFF {
345 return Err(Error::PlcError {
346 code: resp.items[0].return_code as u32,
347 message: "item error".into(),
348 });
349 }
350 Ok(resp.items[0].data.clone())
351 }
352
353 pub async fn read_multi_vars(&self, items: &[MultiReadItem]) -> Result<Vec<Bytes>> {
361 if items.is_empty() {
362 return Ok(Vec::new());
363 }
364
365 const S7_HEADER: usize = 10;
368 const PARAM_OVERHEAD: usize = 2; const ADDR_ITEM_SIZE: usize = 12;
370 const DATA_ITEM_OVERHEAD: usize = 4;
372 const MAX_ITEMS_PER_PDU: usize = 20;
373
374 let mut inner = self.inner.lock().await;
375 let pdu_size = inner.connection.pdu_size as usize;
376 let max_req_payload = pdu_size.saturating_sub(S7_HEADER + PARAM_OVERHEAD);
377 let max_resp_payload = pdu_size.saturating_sub(S7_HEADER + PARAM_OVERHEAD);
378
379 let mut results = vec![Bytes::new(); items.len()];
380 let mut batch_start = 0;
381
382 while batch_start < items.len() {
383 let mut batch_end = batch_start;
385 let mut req_bytes_used = 0usize;
386 let mut resp_bytes_used = 0usize;
387
388 while batch_end < items.len() && (batch_end - batch_start) < MAX_ITEMS_PER_PDU {
389 let item = &items[batch_end];
390 let item_resp_size =
391 DATA_ITEM_OVERHEAD + item.length as usize + (item.length as usize % 2);
392
393 if batch_end > batch_start
394 && (req_bytes_used + ADDR_ITEM_SIZE > max_req_payload
395 || resp_bytes_used + item_resp_size > max_resp_payload)
396 {
397 break;
398 }
399 req_bytes_used += ADDR_ITEM_SIZE;
400 resp_bytes_used += item_resp_size;
401 batch_end += 1;
402 }
403
404 let batch = &items[batch_start..batch_end];
405 let pdu_ref = Self::next_pdu_ref(&mut inner);
406
407 let req = ReadVarRequest {
408 items: batch
409 .iter()
410 .map(|item| AddressItem {
411 area: item.area,
412 db_number: item.db_number,
413 start: item.start,
414 bit_offset: 0,
415 length: item.length,
418 transport: TransportSize::Byte,
419 })
420 .collect(),
421 };
422 let mut param_buf = BytesMut::new();
423 req.encode(&mut param_buf);
424
425 Self::send_s7(
426 &mut inner,
427 param_buf.freeze(),
428 Bytes::new(),
429 pdu_ref,
430 PduType::Job,
431 )
432 .await?;
433
434 let (header, mut body) = Self::recv_s7(&mut inner).await?;
435 check_plc_error(&header, "read_multi_vars")?;
436 if body.remaining() >= 2 {
437 body.advance(2); }
439 let resp = ReadVarResponse::decode(&mut body, batch.len())?;
440
441 for (i, item) in resp.items.into_iter().enumerate() {
442 if item.return_code != 0xFF {
443 return Err(Error::PlcError {
444 code: item.return_code as u32,
445 message: format!("item {} error", batch_start + i),
446 });
447 }
448 results[batch_start + i] = item.data;
449 }
450
451 batch_start = batch_end;
452 }
453
454 Ok(results)
455 }
456
457 pub async fn write_multi_vars(&self, items: &[MultiWriteItem]) -> Result<()> {
463 if items.is_empty() {
464 return Ok(());
465 }
466
467 const S7_HEADER: usize = 10;
468 const PARAM_OVERHEAD: usize = 2; const ADDR_ITEM_SIZE: usize = 12;
470 const DATA_ITEM_OVERHEAD: usize = 4; const MAX_ITEMS_PER_PDU: usize = 20;
472
473 let mut inner = self.inner.lock().await;
474 let pdu_size = inner.connection.pdu_size as usize;
475 let max_payload = pdu_size.saturating_sub(S7_HEADER + PARAM_OVERHEAD);
476
477 let mut batch_start = 0;
478
479 while batch_start < items.len() {
480 let mut batch_end = batch_start;
481 let mut bytes_used = 0usize;
482
483 while batch_end < items.len() && (batch_end - batch_start) < MAX_ITEMS_PER_PDU {
484 let item = &items[batch_end];
485 let data_len = item.data.len();
486 let item_size = ADDR_ITEM_SIZE + DATA_ITEM_OVERHEAD + data_len + (data_len % 2);
487
488 if batch_end > batch_start && bytes_used + item_size > max_payload {
489 break;
490 }
491 bytes_used += item_size;
492 batch_end += 1;
493 }
494
495 let batch = &items[batch_start..batch_end];
496 let pdu_ref = Self::next_pdu_ref(&mut inner);
497
498 let req = WriteVarRequest {
499 items: batch
500 .iter()
501 .map(|item| WriteItem {
502 address: AddressItem {
503 area: item.area,
504 db_number: item.db_number,
505 start: item.start,
506 bit_offset: 0,
507 length: item.data.len() as u16,
508 transport: TransportSize::Byte,
509 },
510 data: item.data.clone(),
511 })
512 .collect(),
513 };
514 let mut param_buf = BytesMut::new();
515 req.encode(&mut param_buf);
516
517 Self::send_s7(
518 &mut inner,
519 param_buf.freeze(),
520 Bytes::new(),
521 pdu_ref,
522 PduType::Job,
523 )
524 .await?;
525
526 let (header, mut body) = Self::recv_s7(&mut inner).await?;
527 check_plc_error(&header, "write_multi_vars")?;
528 if body.remaining() >= 2 {
529 body.advance(2); }
531 let resp = WriteVarResponse::decode(&mut body, batch.len())?;
532 for (i, &code) in resp.return_codes.iter().enumerate() {
533 if code != 0xFF {
534 return Err(Error::PlcError {
535 code: code as u32,
536 message: format!("item {} write error", batch_start + i),
537 });
538 }
539 }
540
541 batch_start = batch_end;
542 }
543
544 Ok(())
545 }
546
547 pub async fn db_write(&self, db: u16, start: u32, data: &[u8]) -> Result<()> {
548 let mut inner = self.inner.lock().await;
549 let pdu_ref = Self::next_pdu_ref(&mut inner);
550
551 let req = WriteVarRequest {
552 items: vec![WriteItem {
553 address: AddressItem {
554 area: Area::DataBlock,
555 db_number: db,
556 start,
557 bit_offset: 0,
558 length: data.len() as u16,
559 transport: TransportSize::Byte,
560 },
561 data: Bytes::copy_from_slice(data),
562 }],
563 };
564 let mut param_buf = BytesMut::new();
565 req.encode(&mut param_buf);
566
567 Self::send_s7(
568 &mut inner,
569 param_buf.freeze(),
570 Bytes::new(),
571 pdu_ref,
572 PduType::Job,
573 )
574 .await?;
575
576 let (header, mut body) = Self::recv_s7(&mut inner).await?;
577 check_plc_error(&header, "db_write")?;
578 if body.has_remaining() {
579 body.advance(2); }
581 let resp = WriteVarResponse::decode(&mut body, 1)?;
582 if resp.return_codes[0] != 0xFF {
583 return Err(Error::PlcError {
584 code: resp.return_codes[0] as u32,
585 message: "write error".into(),
586 });
587 }
588 Ok(())
589 }
590
591 pub async fn write_area(
596 &self,
597 area: Area,
598 db_number: u16,
599 start: u32,
600 transport: TransportSize,
601 data: &[u8],
602 ) -> Result<()> {
603 let mut inner = self.inner.lock().await;
604 let pdu_ref = Self::next_pdu_ref(&mut inner);
605
606 let req = WriteVarRequest {
607 items: vec![WriteItem {
608 address: AddressItem {
609 area,
610 db_number,
611 start,
612 bit_offset: 0,
613 length: data.len() as u16,
614 transport,
615 },
616 data: Bytes::copy_from_slice(data),
617 }],
618 };
619 let mut param_buf = BytesMut::new();
620 req.encode(&mut param_buf);
621
622 Self::send_s7(
623 &mut inner,
624 param_buf.freeze(),
625 Bytes::new(),
626 pdu_ref,
627 PduType::Job,
628 )
629 .await?;
630
631 let (header, mut body) = Self::recv_s7(&mut inner).await?;
632 check_plc_error(&header, "write_area")?;
633 if body.has_remaining() {
634 body.advance(2);
635 }
636 let resp = WriteVarResponse::decode(&mut body, 1)?;
637 if resp.return_codes[0] != 0xFF {
638 return Err(Error::PlcError {
639 code: resp.return_codes[0] as u32,
640 message: "write_area error".into(),
641 });
642 }
643 Ok(())
644 }
645
646 pub async fn ab_read(
651 &self,
652 area: Area,
653 db_number: u16,
654 start: u32,
655 length: u16,
656 ) -> Result<Bytes> {
657 let items = [MultiReadItem {
658 area,
659 db_number,
660 start,
661 length,
662 transport: TransportSize::Byte,
663 }];
664 let mut results = self.read_multi_vars(&items).await?;
665 Ok(results.swap_remove(0))
666 }
667
668 pub async fn ab_write(
673 &self,
674 area: Area,
675 db_number: u16,
676 start: u32,
677 data: &[u8],
678 ) -> Result<()> {
679 let items = [MultiWriteItem {
680 area,
681 db_number,
682 start,
683 data: Bytes::copy_from_slice(data),
684 }];
685 self.write_multi_vars(&items).await
686 }
687
688 pub async fn mb_read(&self, start: u32, length: u16) -> Result<Bytes> {
690 self.ab_read(Area::Marker, 0, start, length).await
691 }
692
693 pub async fn mb_write(&self, start: u32, data: &[u8]) -> Result<()> {
695 self.ab_write(Area::Marker, 0, start, data).await
696 }
697
698 pub async fn eb_read(&self, start: u32, length: u16) -> Result<Bytes> {
700 self.ab_read(Area::ProcessInput, 0, start, length).await
701 }
702
703 pub async fn eb_write(&self, start: u32, data: &[u8]) -> Result<()> {
705 self.ab_write(Area::ProcessInput, 0, start, data).await
706 }
707
708 pub async fn ib_read(&self, start: u32, length: u16) -> Result<Bytes> {
710 self.ab_read(Area::ProcessOutput, 0, start, length).await
711 }
712
713 pub async fn ib_write(&self, start: u32, data: &[u8]) -> Result<()> {
715 self.ab_write(Area::ProcessOutput, 0, start, data).await
716 }
717
718 pub async fn tm_read(&self, start: u32, amount: u16) -> Result<Bytes> {
720 let items = [MultiReadItem {
721 area: Area::Timer,
722 db_number: 0,
723 start,
724 length: amount,
725 transport: TransportSize::Timer,
726 }];
727 let mut results = self.read_multi_vars(&items).await?;
728 Ok(results.swap_remove(0))
729 }
730
731 pub async fn tm_write(&self, start: u32, data: &[u8]) -> Result<()> {
733 let amount = (data.len() / 2) as u16;
734 let items = [MultiWriteItem {
735 area: Area::Timer,
736 db_number: 0,
737 start,
738 data: Bytes::copy_from_slice(data),
739 }];
740 let _ = amount;
741 self.write_multi_vars(&items).await
742 }
743
744 pub async fn ct_read(&self, start: u32, amount: u16) -> Result<Bytes> {
746 let items = [MultiReadItem {
747 area: Area::Counter,
748 db_number: 0,
749 start,
750 length: amount,
751 transport: TransportSize::Counter,
752 }];
753 let mut results = self.read_multi_vars(&items).await?;
754 Ok(results.swap_remove(0))
755 }
756
757 pub async fn ct_write(&self, start: u32, data: &[u8]) -> Result<()> {
759 let items = [MultiWriteItem {
760 area: Area::Counter,
761 db_number: 0,
762 start,
763 data: Bytes::copy_from_slice(data),
764 }];
765 self.write_multi_vars(&items).await
766 }
767
768 pub async fn read_szl(&self, szl_id: u16, szl_index: u16) -> Result<SzlResponse> {
769 let payload = self.read_szl_payload(szl_id, szl_index).await?;
770 let mut b = payload;
771 Ok(SzlResponse::decode(&mut b)?)
772 }
773
774 async fn read_szl_payload(&self, szl_id: u16, szl_index: u16) -> Result<Bytes> {
777 let mut inner = self.inner.lock().await;
778 let pdu_ref = Self::next_pdu_ref(&mut inner);
779
780 let req = SzlRequest { szl_id, szl_index };
781 let mut param_buf = BytesMut::new();
782 req.encode_params(&mut param_buf);
783 let mut data_buf = BytesMut::new();
784 req.encode_data(&mut data_buf);
785
786 Self::send_s7(
787 &mut inner,
788 param_buf.freeze(),
789 data_buf.freeze(),
790 pdu_ref,
791 PduType::UserData,
792 )
793 .await?;
794
795 let (header, mut body) = Self::recv_s7(&mut inner).await?;
796
797 if body.remaining() < header.param_len as usize {
799 return Err(Error::UnexpectedResponse);
800 }
801 body.advance(header.param_len as usize);
802
803 if body.remaining() < 4 {
807 return Ok(Bytes::new());
808 }
809 let return_code = body.get_u8();
810 let _transport = body.get_u8();
811 let _data_len = body.get_u16();
812
813 if return_code != 0xFF {
816 return Ok(Bytes::new());
817 }
818
819 Ok(body.copy_to_bytes(body.remaining()))
821 }
822
823 pub async fn read_clock(&self) -> Result<PlcDateTime> {
824 let mut inner = self.inner.lock().await;
825 let pdu_ref = Self::next_pdu_ref(&mut inner);
826 let mut param_buf = BytesMut::new();
828 param_buf.extend_from_slice(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x47, 0x01, 0x00]);
829 Self::send_s7(
830 &mut inner,
831 param_buf.freeze(),
832 Bytes::new(),
833 pdu_ref,
834 PduType::UserData,
835 )
836 .await?;
837 let (header, mut body) = Self::recv_s7(&mut inner).await?;
838 let total = header.param_len as usize + header.data_len as usize;
844 if body.remaining() < total || total < 8 {
845 return Err(Error::UnexpectedResponse);
846 }
847 body.advance(total - 8);
848 Ok(PlcDateTime::decode(&mut body)?)
849 }
850
851 pub async fn set_clock(&self, dt: &PlcDateTime) -> Result<()> {
853 let mut inner = self.inner.lock().await;
854 let pdu_ref = Self::next_pdu_ref(&mut inner);
855 let mut param_buf = BytesMut::new();
857 param_buf.extend_from_slice(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x47, 0x02, 0x00]);
858 let mut data_buf = BytesMut::new();
860 data_buf.extend_from_slice(&[0xFF, 0x09, 0x00, 0x08]);
861 dt.encode(&mut data_buf);
862 Self::send_s7(
863 &mut inner,
864 param_buf.freeze(),
865 data_buf.freeze(),
866 pdu_ref,
867 PduType::UserData,
868 )
869 .await?;
870 let (header, _body) = Self::recv_s7(&mut inner).await?;
871 check_plc_error(&header, "set_clock")?;
872 Ok(())
873 }
874
875 pub async fn set_clock_to_now(&self) -> Result<()> {
880 use std::time::{SystemTime, UNIX_EPOCH};
881 let secs = SystemTime::now()
882 .duration_since(UNIX_EPOCH)
883 .unwrap_or_default()
884 .as_secs();
885 let s = secs % 60;
887 let m = (secs / 60) % 60;
888 let h = (secs / 3600) % 24;
889 let days = secs / 86400;
891 let mut year = 1970u16;
893 let mut d = days;
894 loop {
895 let leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
896 let days_in_year: u64 = if leap { 366 } else { 365 };
897 if d < days_in_year {
898 break;
899 }
900 d -= days_in_year;
901 year += 1;
902 }
903 let leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
904 let days_per_month: [u64; 12] = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
905 let mut month = 1u8;
906 for &dpm in &days_per_month {
907 if d < dpm {
908 break;
909 }
910 d -= dpm;
911 month += 1;
912 }
913 let dt = PlcDateTime {
914 year,
915 month,
916 day: (d + 1) as u8,
917 hour: h as u8,
918 minute: m as u8,
919 second: s as u8,
920 millisecond: 0,
921 weekday: 0,
922 };
923 self.set_clock(&dt).await
924 }
925
926 pub async fn force_bit(
940 &self,
941 area: Area,
942 byte_addr: u32,
943 bit: u8,
944 value: bool,
945 ) -> Result<()> {
946 let bit = bit & 0x07;
947 let current = self.read_area(area, 0, byte_addr * 8, 1, TransportSize::Byte).await?;
949 let mut byte_val = if current.is_empty() { 0u8 } else { current[0] };
950 if value {
951 byte_val |= 1 << bit;
952 } else {
953 byte_val &= !(1 << bit);
954 }
955 self.write_area(area, 0, byte_addr * 8, TransportSize::Byte, &[byte_val]).await
956 }
957
958 pub async fn force_byte(
960 &self,
961 area: Area,
962 byte_addr: u32,
963 value: u8,
964 ) -> Result<()> {
965 self.write_area(area, 0, byte_addr * 8, TransportSize::Byte, &[value]).await
966 }
967
968 pub async fn force_cancel_byte(&self, area: Area, byte_addr: u32) -> Result<()> {
970 self.write_area(area, 0, byte_addr * 8, TransportSize::Byte, &[0x00]).await
971 }
972
973 pub async fn read_force_list(&self) -> Result<bytes::Bytes> {
977 self.read_szl_payload(0x0025, 0x0000).await
978 }
979
980 pub async fn read_szl_list(&self) -> Result<Vec<u16>> {
984 let payload = self.read_szl_payload(0x0000, 0x0000).await?;
985 if payload.is_empty() {
986 return Ok(Vec::new());
987 }
988 let mut b = payload;
989 if b.remaining() < 8 {
991 return Err(Error::UnexpectedResponse);
992 }
993 let _szl_id = b.get_u16();
994 let _szl_index = b.get_u16();
995 let entry_len = b.get_u16() as usize;
996 let entry_count = b.get_u16() as usize;
997 if entry_len < 2 {
998 return Err(Error::UnexpectedResponse);
999 }
1000 let mut ids = Vec::with_capacity(entry_count);
1001 for _ in 0..entry_count {
1002 if b.remaining() < entry_len {
1003 break;
1004 }
1005 ids.push(b.get_u16());
1006 b.advance(entry_len - 2);
1007 }
1008 Ok(ids)
1009 }
1010
1011 pub async fn copy_ram_to_rom(&self) -> Result<()> {
1015 let mut inner = self.inner.lock().await;
1016 let pdu_ref = Self::next_pdu_ref(&mut inner);
1017 let param = Bytes::copy_from_slice(&[
1018 0x00, 0x01, 0x12, 0x04, 0x43, 0x44, 0x01, 0x00,
1019 ]);
1020 Self::send_s7(&mut inner, param, Bytes::new(), pdu_ref, PduType::UserData).await?;
1021 let (header, _body) = Self::recv_s7(&mut inner).await?;
1022 check_plc_error(&header, "copy_ram_to_rom")?;
1023 Ok(())
1024 }
1025
1026 pub async fn compress(&self) -> Result<()> {
1031 let mut inner = self.inner.lock().await;
1032 let pdu_ref = Self::next_pdu_ref(&mut inner);
1033 let param = Bytes::copy_from_slice(&[
1034 0x00, 0x01, 0x12, 0x04, 0x42, 0x44, 0x01, 0x00,
1035 ]);
1036 Self::send_s7(&mut inner, param, Bytes::new(), pdu_ref, PduType::UserData).await?;
1037 let (header, _body) = Self::recv_s7(&mut inner).await?;
1038 check_plc_error(&header, "compress")?;
1039 Ok(())
1040 }
1041
1042 async fn pi_service(inner: &mut Inner<T>, pdu_ref: u16, service: &str) -> Result<()> {
1049 let name = service.as_bytes();
1050 let mut param = BytesMut::with_capacity(5 + name.len());
1051 param.put_u8(0x28); param.put_u8(0x00);
1053 param.put_u8(0x00);
1054 param.put_u16(name.len() as u16);
1055 param.extend_from_slice(name);
1056 Self::send_s7(inner, param.freeze(), Bytes::new(), pdu_ref, PduType::Job).await?;
1057 let (header, _body) = Self::recv_s7(inner).await?;
1058 check_plc_error(&header, service)?;
1059 Ok(())
1060 }
1061
1062 pub async fn memory_reset(&self) -> Result<()> {
1068 let mut inner = self.inner.lock().await;
1069 let pdu_ref = Self::next_pdu_ref(&mut inner);
1070 Self::pi_service(&mut inner, pdu_ref, "_MRES").await
1071 }
1072
1073 pub async fn overall_reset(&self) -> Result<()> {
1078 let mut inner = self.inner.lock().await;
1079 let pdu_ref = Self::next_pdu_ref(&mut inner);
1080 Self::pi_service(&mut inner, pdu_ref, "_OVERALL_RESET").await
1081 }
1082
1083 pub async fn upload_all_blocks(
1091 &self,
1092 block_types: &[u8],
1093 ) -> Result<Vec<(u8, u16, Vec<u8>)>> {
1094 let mut results = Vec::new();
1095 for &bt in block_types {
1096 let numbers = self.list_blocks_of_type(bt).await?;
1097 for num in numbers {
1098 match self.full_upload(bt, num).await {
1099 Ok(data) => results.push((bt, num, data)),
1100 Err(e) => return Err(e),
1101 }
1102 }
1103 }
1104 Ok(results)
1105 }
1106
1107 async fn simple_control(inner: &mut Inner<T>, pdu_ref: u16, func: u8) -> Result<()> {
1111 let param = Bytes::copy_from_slice(&[func, 0x00]);
1112 Self::send_s7(inner, param, Bytes::new(), pdu_ref, PduType::Job).await?;
1113 let (header, _body) = Self::recv_s7(inner).await?;
1114 check_plc_error(&header, "plc_control")?;
1115 Ok(())
1116 }
1117
1118 pub async fn plc_stop(&self) -> Result<()> {
1124 let mut inner = self.inner.lock().await;
1125 let pdu_ref = Self::next_pdu_ref(&mut inner);
1126 Self::simple_control(&mut inner, pdu_ref, 0x29).await
1127 }
1128
1129 pub async fn plc_hot_start(&self) -> Result<()> {
1133 let mut inner = self.inner.lock().await;
1134 let pdu_ref = Self::next_pdu_ref(&mut inner);
1135 Self::simple_control(&mut inner, pdu_ref, 0x28).await
1136 }
1137
1138 pub async fn plc_cold_start(&self) -> Result<()> {
1142 let mut inner = self.inner.lock().await;
1143 let pdu_ref = Self::next_pdu_ref(&mut inner);
1144 Self::simple_control(&mut inner, pdu_ref, 0x2A).await
1145 }
1146
1147 pub async fn get_plc_status(&self) -> Result<crate::types::PlcStatus> {
1152 let payload = self.read_szl_payload(0x0424, 0x0000).await?;
1153 if payload.len() < 12 {
1162 return Ok(crate::types::PlcStatus::Unknown);
1163 }
1164 let status_byte = payload[11];
1165 match status_byte {
1166 0x00 => Ok(crate::types::PlcStatus::Unknown),
1167 0x04 => Ok(crate::types::PlcStatus::Stop),
1168 0x08 => Ok(crate::types::PlcStatus::Run),
1169 0x03 => Ok(crate::types::PlcStatus::Stop),
1171 _ => Ok(crate::types::PlcStatus::Stop),
1172 }
1173 }
1174
1175 pub async fn get_order_code(&self) -> Result<crate::types::OrderCode> {
1181 let payload = self.read_szl_payload(0x0011, 0x0000).await?;
1182 if payload.len() < 8 {
1183 return Err(Error::UnexpectedResponse);
1184 }
1185
1186 let n = payload.len();
1190 let (v1, v2, v3) = if n >= 3 {
1191 (payload[n - 3], payload[n - 2], payload[n - 1])
1192 } else {
1193 (0, 0, 0)
1194 };
1195
1196 let mut b = payload.clone();
1197 let szl_id = b.get_u16();
1198 let _szl_idx = b.get_u16();
1199 let entry_len = b.get_u16() as usize;
1200 let entry_count = b.get_u16() as usize;
1201
1202 if (szl_id == 0x0011 || szl_id == 0x001C) && entry_len >= 4 && entry_count > 0 {
1203 for _ in 0..entry_count {
1204 if b.remaining() < entry_len { break; }
1205 let entry_idx = b.get_u16();
1206 let string_len = entry_len - 2;
1207 let raw = b.copy_to_bytes(string_len);
1208 if entry_idx == 0x0001 {
1209 let null_end = raw.iter().position(|&x| x == 0).unwrap_or(string_len);
1210 let code = String::from_utf8_lossy(&raw[..null_end]).trim().to_string();
1211 if !code.is_empty() {
1212 return Ok(crate::types::OrderCode { code, v1, v2, v3 });
1213 }
1214 }
1215 }
1216 }
1217
1218 let code = scan_ascii_fields(&payload, 10, 4).into_iter().find(|s| {
1220 let su = s.to_uppercase();
1221 (su.starts_with("6ES") || su.starts_with("6AV") || su.starts_with("6GK"))
1222 && s.len() >= 10
1223 && s.bytes().all(|c| c.is_ascii_graphic() || c == b' ')
1224 }).unwrap_or_default();
1225 Ok(crate::types::OrderCode { code, v1, v2, v3 })
1226 }
1227
1228 pub async fn get_cpu_info(&self) -> Result<crate::types::CpuInfo> {
1234 let payload = self.read_szl_payload(0x001C, 0x0000).await?;
1235 if payload.len() < 8 {
1236 return Err(Error::UnexpectedResponse);
1237 }
1238
1239 let mut b = payload.clone();
1253 let szl_id = b.get_u16();
1254 let _szl_idx = b.get_u16();
1255 let entry_len = b.get_u16() as usize;
1256 let entry_count = b.get_u16() as usize;
1257
1258 if szl_id == 0x001C && entry_len >= 4 && entry_count > 0 {
1259 let mut module_type = String::new();
1260 let mut module_type_canonical = String::new(); let mut serial_number = String::new();
1262 let mut as_name = String::new();
1263 let mut copyright = String::new();
1264 let mut module_name = String::new();
1265
1266 for _ in 0..entry_count {
1267 if b.remaining() < entry_len { break; }
1268 let entry_idx = b.get_u16();
1269 let string_len = entry_len - 2;
1270 let raw = b.copy_to_bytes(string_len);
1271 let null_end = raw.iter().position(|&x| x == 0).unwrap_or(string_len);
1272 let val = String::from_utf8_lossy(&raw[..null_end]).trim().to_string();
1273 match entry_idx {
1274 0x0001 => { if as_name.is_empty() { as_name = val; } }
1275 0x0002 => { if module_type.is_empty() { module_type = val; } }
1278 0x0003 => { if module_name.is_empty() { module_name = val; } }
1279 0x0004 => { if copyright.is_empty() { copyright = val; } }
1280 0x0005 => { if serial_number.is_empty() { serial_number = val; } }
1281 0x0007 => { if module_type_canonical.is_empty() { module_type_canonical = val; } }
1283 _ => {}
1285 }
1286 }
1287
1288 if !module_type_canonical.is_empty() {
1290 module_type = module_type_canonical;
1291 }
1292
1293 if module_name.is_empty() && !as_name.is_empty() {
1294 module_name = as_name.clone();
1295 }
1296
1297 if !module_type.is_empty() || !serial_number.is_empty() || !as_name.is_empty() {
1298 let protocol = detect_protocol(&payload, &module_type);
1299 return Ok(crate::types::CpuInfo {
1300 module_type,
1301 serial_number,
1302 as_name,
1303 copyright,
1304 module_name,
1305 protocol,
1306 });
1307 }
1308 }
1309
1310 let data = payload.as_ref();
1313 let (module_type, serial_number, as_name, copyright, module_name) =
1314 parse_sub_record_fields(data);
1315
1316 if !module_type.is_empty() || !serial_number.is_empty() {
1317 let protocol = detect_protocol(&payload, &module_type);
1318 return Ok(crate::types::CpuInfo {
1319 module_type,
1320 serial_number,
1321 as_name,
1322 copyright,
1323 module_name,
1324 protocol,
1325 });
1326 }
1327
1328 let mut module_type = String::new();
1330 let mut serial_number = String::new();
1331 let mut as_name = String::new();
1332 let mut copyright = String::new();
1333 let mut module_name = String::new();
1334
1335 let mut scan = 0;
1336 while scan < data.len() {
1337 if data[scan].is_ascii_graphic() || data[scan] == b' ' {
1338 let start = scan;
1339 while scan < data.len() && (data[scan].is_ascii_graphic() || data[scan] == b' ') {
1340 scan += 1;
1341 }
1342 let val = String::from_utf8_lossy(&data[start..scan]).trim().to_string();
1343 if val.len() >= 3 {
1344 let tag = if start >= 2 && data[start - 2] == 0x00 {
1345 Some(data[start - 1])
1346 } else {
1347 None
1348 };
1349 let su = val.to_uppercase();
1350 if su.contains("BOOT") || su.starts_with("P B") || su.starts_with("HBOOT") {
1351 } else if tag == Some(0x07) && module_type.is_empty() {
1353 module_type = val;
1354 } else if tag == Some(0x08) && module_name.is_empty() {
1355 module_name = val;
1356 } else if tag == Some(0x05) && as_name.is_empty() {
1357 as_name = val;
1358 } else if tag == Some(0x06) && copyright.is_empty() {
1359 copyright = val;
1360 } else if tag == Some(0x04) && serial_number.is_empty() {
1361 serial_number = val;
1362 } else if val.contains('-')
1363 && val.chars().filter(|c| c.is_ascii_digit()).count() >= 4
1364 && !val.starts_with("6ES7")
1365 && serial_number.is_empty()
1366 {
1367 serial_number = val;
1368 } else if su.contains("CPU") && su.contains("PN") && module_type.is_empty() {
1369 module_type = val;
1370 } else if module_type.is_empty() && val.len() >= 8 && !su.contains("MC_") {
1371 module_type = val;
1372 }
1373 }
1374 } else {
1375 scan += 1;
1376 }
1377 }
1378
1379 let protocol = detect_protocol(&payload, &module_type);
1380 Ok(crate::types::CpuInfo {
1381 module_type,
1382 serial_number,
1383 as_name,
1384 copyright,
1385 module_name,
1386 protocol,
1387 })
1388 }
1389
1390 pub async fn get_cp_info(&self) -> Result<crate::types::CpInfo> {
1394 let payload = self.read_szl_payload(0x0131, 0x0001).await?;
1396
1397 let mut b = payload.clone();
1403 if b.remaining() < 8 {
1404 return Ok(crate::types::CpInfo {
1405 max_pdu_len: 0, max_connections: 0, max_mpi_rate: 0, max_bus_rate: 0,
1406 });
1407 }
1408
1409 let szl_id = b.get_u16();
1410 let _szl_idx = b.get_u16();
1411 let entry_len = b.get_u16() as usize;
1412 let entry_count = b.get_u16() as usize;
1413
1414 if szl_id == 0x0131 && entry_len >= 12 && entry_count >= 1 && b.remaining() >= entry_len {
1416 let _entry_idx = b.get_u16();
1417 let max_pdu_len = b.get_u16() as u32;
1418 let max_connections = b.get_u16() as u32;
1419 let max_mpi_rate = b.get_u32();
1420 let max_bus_rate = b.get_u32();
1421 return Ok(crate::types::CpInfo {
1422 max_pdu_len,
1423 max_connections,
1424 max_mpi_rate,
1425 max_bus_rate,
1426 });
1427 }
1428
1429 Ok(crate::types::CpInfo {
1431 max_pdu_len: 0,
1432 max_connections: 0,
1433 max_mpi_rate: 0,
1434 max_bus_rate: 0,
1435 })
1436 }
1437
1438 pub async fn read_module_list(&self) -> Result<Vec<crate::types::ModuleEntry>> {
1442 let payload = self.read_szl_payload(0x00A0, 0x0000).await?;
1443 if payload.len() < 6 {
1444 return Ok(Vec::new());
1445 }
1446 let mut b = payload;
1447 let _block_len = b.get_u16();
1448 let _szl_id = b.get_u16();
1449 let _szl_ix = b.get_u16();
1450 skip_szl_entry_header(&mut b);
1452 let mut modules = Vec::new();
1453 while b.remaining() >= 2 {
1454 modules.push(crate::types::ModuleEntry {
1455 module_type: b.get_u16(),
1456 });
1457 }
1458 Ok(modules)
1459 }
1460
1461 pub async fn list_blocks(&self) -> Result<crate::types::BlockList> {
1468 let mut inner = self.inner.lock().await;
1469 let pdu_ref = Self::next_pdu_ref(&mut inner);
1470
1471 let param = Bytes::from_static(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x43, 0x01, 0x00]);
1473 let data = Bytes::from_static(&[0x0A, 0x00, 0x00, 0x00]);
1475
1476 Self::send_s7(&mut inner, param, data, pdu_ref, PduType::UserData).await?;
1477 let (header, mut body) = Self::recv_s7(&mut inner).await?;
1478
1479 if body.remaining() < header.param_len as usize {
1481 return Err(Error::UnexpectedResponse);
1482 }
1483 body.advance(header.param_len as usize);
1484
1485 if body.remaining() < 4 {
1487 return Ok(crate::types::BlockList { total_count: 0, entries: Vec::new() });
1488 }
1489 let _ret_val = body.get_u8();
1490 let _tr_size = body.get_u8();
1491 let data_len = body.get_u16() as usize;
1492
1493 if data_len < 28 || body.remaining() < 28 {
1495 return Ok(crate::types::BlockList { total_count: 0, entries: Vec::new() });
1496 }
1497
1498 let mut entries = Vec::new();
1499 let mut total_count: u32 = 0;
1500 for _ in 0..7 {
1501 let _zero = body.get_u8();
1502 let block_type = body.get_u8() as u16;
1503 let count = body.get_u16();
1504 total_count += count as u32;
1505 entries.push(crate::types::BlockListEntry { block_type, count });
1506 }
1507
1508 Ok(crate::types::BlockList { total_count, entries })
1509 }
1510
1511 pub async fn list_blocks_of_type(&self, block_type: u8) -> Result<Vec<u16>> {
1517 let mut numbers: Vec<u16> = Vec::new();
1518 let mut first = true;
1519 let mut seq: u8 = 0x00;
1520
1521 loop {
1522 let mut inner = self.inner.lock().await;
1523 let pdu_ref = Self::next_pdu_ref(&mut inner);
1524
1525 let (param, data) = if first {
1526 let mut p = BytesMut::with_capacity(8);
1530 p.extend_from_slice(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x43, 0x02, 0x00]);
1531 let mut d = BytesMut::with_capacity(6);
1532 d.extend_from_slice(&[0xFF, 0x09, 0x00, 0x02, 0x30, block_type]);
1533 (p.freeze(), d.freeze())
1534 } else {
1535 let mut p = BytesMut::with_capacity(12);
1539 p.extend_from_slice(&[0x00, 0x01, 0x12, 0x08, 0x12, 0x43, 0x02, seq, 0x00, 0x00, 0x00, 0x00]);
1540 let d = Bytes::from_static(&[0x0A, 0x00, 0x00, 0x00]);
1541 (p.freeze(), d)
1542 };
1543
1544 Self::send_s7(&mut inner, param, data, pdu_ref, PduType::UserData).await?;
1545 let (header, mut body) = Self::recv_s7(&mut inner).await?;
1546
1547 if body.remaining() < header.param_len as usize {
1549 return Err(Error::UnexpectedResponse);
1550 }
1551 let param_bytes = body.slice(..header.param_len as usize);
1555 let done = param_bytes.len() >= 10 && param_bytes[8] == 0x00;
1556 seq = if param_bytes.len() >= 8 { param_bytes[7] } else { 0 };
1557 body.advance(header.param_len as usize);
1558 drop(inner);
1559
1560 if body.remaining() < 4 { break; }
1562 let ret_val = body.get_u8();
1563 let _tr_size = body.get_u8();
1564 let data_len = body.get_u16() as usize;
1565
1566 if ret_val != 0xFF || data_len < 4 || body.remaining() < data_len { break; }
1567
1568 let item_count = ((data_len - 4) / 4) + 1;
1571 for _ in 0..item_count {
1572 if body.remaining() < 4 { break; }
1573 let block_num = body.get_u16();
1574 let _unknown = body.get_u8();
1575 let _lang = body.get_u8();
1576 numbers.push(block_num);
1577 }
1578
1579 first = false;
1580 if done { break; }
1581 }
1582
1583 numbers.sort_unstable();
1584 Ok(numbers)
1585 }
1586
1587 async fn block_info_query(
1592 &self,
1593 _func: u8,
1594 block_type: u8,
1595 block_number: u16,
1596 ) -> Result<Bytes> {
1597 let mut inner = self.inner.lock().await;
1598 let pdu_ref = Self::next_pdu_ref(&mut inner);
1599
1600 let param = Bytes::from_static(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x43, 0x03, 0x00]);
1602
1603 let mut data_buf = BytesMut::with_capacity(12);
1605 data_buf.extend_from_slice(&[0xFF, 0x09, 0x00, 0x08, 0x30, block_type]);
1606 let n = block_number as u32;
1608 data_buf.put_u8((n / 10000) as u8 + 0x30);
1609 data_buf.put_u8(((n % 10000) / 1000) as u8 + 0x30);
1610 data_buf.put_u8(((n % 1000) / 100) as u8 + 0x30);
1611 data_buf.put_u8(((n % 100) / 10) as u8 + 0x30);
1612 data_buf.put_u8((n % 10) as u8 + 0x30);
1613 data_buf.put_u8(0x41); Self::send_s7(&mut inner, param, data_buf.freeze(), pdu_ref, PduType::UserData).await?;
1616
1617 let (header, mut body) = Self::recv_s7(&mut inner).await?;
1618
1619 let param_len = header.param_len as usize;
1622 if body.remaining() < param_len {
1623 return Err(Error::UnexpectedResponse);
1624 }
1625 let params = body.slice(..param_len);
1626 body.advance(param_len);
1627
1628 if params.len() >= 12 {
1630 let err_no = u16::from_be_bytes([params[10], params[11]]);
1631 if err_no != 0 {
1632 return Err(Error::PlcError {
1633 code: err_no as u32,
1634 message: format!("block info error: ErrNo=0x{err_no:04X}"),
1635 });
1636 }
1637 }
1638
1639 if body.remaining() < 4 {
1641 return Err(Error::UnexpectedResponse);
1642 }
1643 let ret_val = body.get_u8();
1644 let _tr_size = body.get_u8();
1645 let _data_len = body.get_u16();
1646
1647 if ret_val != 0xFF {
1648 return Err(Error::PlcError {
1649 code: ret_val as u32,
1650 message: format!("block info RetVal=0x{ret_val:02X}"),
1651 });
1652 }
1653
1654 Ok(body.copy_to_bytes(body.remaining()))
1655 }
1656
1657 pub async fn get_ag_block_info(
1662 &self,
1663 block_type: u8,
1664 block_number: u16,
1665 ) -> Result<crate::types::BlockInfo> {
1666 self.get_block_info(0x13, block_type, block_number).await
1667 }
1668
1669 pub async fn get_pg_block_info(
1674 &self,
1675 block_type: u8,
1676 block_number: u16,
1677 ) -> Result<crate::types::BlockInfo> {
1678 self.get_block_info(0x14, block_type, block_number).await
1679 }
1680
1681 async fn get_block_info(
1683 &self,
1684 func: u8,
1685 block_type: u8,
1686 block_number: u16,
1687 ) -> Result<crate::types::BlockInfo> {
1688 let payload = self
1689 .block_info_query(func, block_type, block_number)
1690 .await?;
1691
1692 if payload.len() < 40 {
1703 return Err(Error::UnexpectedResponse);
1704 }
1705 let mut b = payload;
1706
1707 let _cst_b = b.get_u8();
1708 let blk_type: u16 = b.get_u8().into();
1709 let _cst_w1 = b.get_u16();
1710 let _cst_w2 = b.get_u16();
1711 let _cst_pp = b.get_u16();
1712 let _unknown_1 = b.get_u8();
1713 let flags = b.get_u8() as u16;
1714 let language = b.get_u8() as u16;
1715 let _sub_blk = b.get_u8();
1716 let _blk_number = b.get_u16(); let len_load_mem = b.get_u32();
1718 let _blk_sec = b.get_u32();
1719 let _code_ms = b.get_u32();
1720 let _code_dy = b.get_u16();
1721 let _intf_ms = b.get_u32();
1722 let _intf_dy = b.get_u16();
1723 let sbb_len = b.get_u16();
1724 let _add_len = b.get_u16();
1725 let local_data = b.get_u16();
1726 let mc7_size = b.get_u16();
1727
1728 fn read_str(b: &mut Bytes, n: usize) -> String {
1729 let s = b.slice(..n.min(b.remaining()));
1730 b.advance(n.min(b.remaining()));
1731 let end = s.iter().position(|&x| x == 0).unwrap_or(s.len());
1732 String::from_utf8_lossy(&s[..end]).trim().to_string()
1733 }
1734
1735 let author = read_str(&mut b, 8);
1736 let family = read_str(&mut b, 8);
1737 let header = read_str(&mut b, 8);
1738 let version = if b.remaining() >= 1 { b.get_u8() as u16 } else { 0 };
1739 let _unk2 = if b.remaining() >= 1 { b.get_u8() } else { 0 };
1740 let checksum = if b.remaining() >= 2 { b.get_u16() } else { 0 };
1741
1742 Ok(crate::types::BlockInfo {
1743 block_type: blk_type,
1744 block_number,
1745 language,
1746 flags,
1747 size: (len_load_mem.min(0xFFFF)) as u16,
1748 size_ram: sbb_len,
1749 mc7_size,
1750 local_data,
1751 checksum,
1752 version,
1753 author,
1754 family,
1755 header,
1756 date: String::new(),
1757 })
1758 }
1759
1760 pub fn parse_block_info(data: &[u8]) -> Result<crate::types::BlockInfo> {
1765 const HDR: usize = 36;
1766 const FOOTER: usize = 48;
1767 if data.len() < HDR + FOOTER {
1768 return Err(Error::UnexpectedResponse);
1769 }
1770 let load_size = u32::from_be_bytes([data[8], data[9], data[10], data[11]]) as usize;
1771 if load_size != data.len() {
1772 return Err(Error::UnexpectedResponse);
1773 }
1774 let mc7_size = u16::from_be_bytes([data[34], data[35]]) as usize;
1775 if mc7_size + HDR >= load_size {
1776 return Err(Error::UnexpectedResponse);
1777 }
1778
1779 let flags = data[3] as u16;
1780 let language = data[4] as u16;
1781 let block_type = data[5] as u16;
1782 let block_number = u16::from_be_bytes([data[6], data[7]]);
1783 let sbb_len = u16::from_be_bytes([data[28], data[29]]);
1784 let local_data = u16::from_be_bytes([data[32], data[33]]);
1785
1786 fn read_str(s: &[u8]) -> String {
1787 let end = s.iter().position(|&x| x == 0).unwrap_or(s.len());
1788 String::from_utf8_lossy(&s[..end]).trim().to_string()
1789 }
1790
1791 let footer = &data[load_size - FOOTER..];
1792 let author = read_str(&footer[20..28]);
1793 let family = read_str(&footer[28..36]);
1794 let header = read_str(&footer[36..44]);
1795 let checksum = u16::from_be_bytes([footer[44], footer[45]]);
1796
1797 Ok(crate::types::BlockInfo {
1798 block_type,
1799 block_number,
1800 language,
1801 flags,
1802 size: load_size.min(0xFFFF) as u16,
1803 size_ram: sbb_len,
1804 mc7_size: mc7_size as u16,
1805 local_data,
1806 checksum,
1807 version: 0,
1808 author,
1809 family,
1810 header,
1811 date: String::new(),
1812 })
1813 }
1814
1815 pub async fn set_session_password(&self, password: &str) -> Result<()> {
1823 let encrypted = crate::types::encrypt_password(password);
1824 let mut inner = self.inner.lock().await;
1825 let pdu_ref = Self::next_pdu_ref(&mut inner);
1826 let param = Bytes::copy_from_slice(&[0x12, 0x00]);
1827 let data = Bytes::copy_from_slice(&encrypted);
1828 Self::send_s7(&mut inner, param, data, pdu_ref, PduType::Job).await?;
1829 let (header, _body) = Self::recv_s7(&mut inner).await?;
1830 check_plc_error(&header, "set_session_password")?;
1831 Ok(())
1832 }
1833
1834 pub async fn clear_session_password(&self) -> Result<()> {
1836 let mut inner = self.inner.lock().await;
1837 let pdu_ref = Self::next_pdu_ref(&mut inner);
1838 let param = Bytes::copy_from_slice(&[0x11, 0x00]);
1839 Self::send_s7(&mut inner, param, Bytes::new(), pdu_ref, PduType::Job).await?;
1840 let (header, _body) = Self::recv_s7(&mut inner).await?;
1841 check_plc_error(&header, "clear_session_password")?;
1842 Ok(())
1843 }
1844
1845 pub async fn get_protection(&self) -> Result<crate::types::Protection> {
1850 let payload = self.read_szl_payload(0x0032, 0x0004).await?;
1851 if payload.len() < 14 {
1852 return Err(Error::UnexpectedResponse);
1853 }
1854 let mut b = payload;
1855 let _block_len = b.get_u16();
1856 let _szl_id = b.get_u16();
1857 let _szl_ix = b.get_u16();
1858 skip_szl_entry_header(&mut b);
1860 let scheme_szl = b.get_u16();
1861 let scheme_module = b.get_u16();
1862 let scheme_bus = b.get_u16();
1863 let level = b.get_u16();
1864 let pass_wort = if b.remaining() >= 8 {
1866 String::from_utf8_lossy(&b[..8]).trim().to_string()
1867 } else {
1868 String::new()
1869 };
1870 let password_set = pass_wort.eq_ignore_ascii_case("PASSWORD");
1871 Ok(crate::types::Protection {
1872 scheme_szl,
1873 scheme_module,
1874 scheme_bus,
1875 level,
1876 password_set,
1877 })
1878 }
1879
1880 pub async fn delete_block(&self, block_type: u8, block_number: u16) -> Result<()> {
1888 let mut inner = self.inner.lock().await;
1889 let pdu_ref = Self::next_pdu_ref(&mut inner);
1890 let mut param = BytesMut::with_capacity(6);
1892 param.extend_from_slice(&[0x1F, 0x00, block_type, 0x00]);
1893 param.put_u16(block_number);
1894 Self::send_s7(
1895 &mut inner,
1896 param.freeze(),
1897 Bytes::new(),
1898 pdu_ref,
1899 PduType::Job,
1900 )
1901 .await?;
1902 let (header, _body) = Self::recv_s7(&mut inner).await?;
1903 check_plc_error(&header, "delete_block")?;
1904 Ok(())
1905 }
1906
1907 pub async fn upload(&self, block_type: u8, block_number: u16) -> Result<Vec<u8>> {
1912 let mut inner = self.inner.lock().await;
1913 let pdu_ref = Self::next_pdu_ref(&mut inner);
1914
1915 let mut param = BytesMut::with_capacity(6);
1918 param.extend_from_slice(&[0x1D, 0x00, block_type, 0x00]);
1919 param.put_u16(block_number);
1920 Self::send_s7(
1921 &mut inner,
1922 param.freeze(),
1923 Bytes::new(),
1924 pdu_ref,
1925 PduType::Job,
1926 )
1927 .await?;
1928 let (header, mut body) = Self::recv_s7(&mut inner).await?;
1929 check_plc_error(&header, "upload_start")?;
1930 if body.remaining() < 8 {
1932 return Err(Error::UnexpectedResponse);
1933 }
1934 if body.remaining() >= 2 {
1935 body.advance(2); }
1937 let upload_id = body.get_u32();
1938 let _total_len = body.get_u32();
1939
1940 let mut block_data = Vec::new();
1942 loop {
1943 let chunk_pdu_ref = Self::next_pdu_ref(&mut inner);
1944 let mut dparam = BytesMut::with_capacity(6);
1945 dparam.extend_from_slice(&[0x1D, 0x01]);
1946 dparam.put_u32(upload_id);
1947 Self::send_s7(
1948 &mut inner,
1949 dparam.freeze(),
1950 Bytes::new(),
1951 chunk_pdu_ref,
1952 PduType::Job,
1953 )
1954 .await?;
1955 let (dheader, mut dbody) = Self::recv_s7(&mut inner).await?;
1956 check_plc_error(&dheader, "upload_data")?;
1957 if dbody.remaining() >= 2 {
1959 dbody.advance(2);
1960 }
1961 if dbody.is_empty() {
1962 break; }
1964 if block_data.is_empty() && dbody.remaining() >= 4 {
1967 if dbody[0] == 0xFF || dbody[0] == 0x00 {
1969 dbody.advance(4);
1970 }
1971 }
1972 let chunk = dbody.copy_to_bytes(dbody.remaining());
1973 block_data.extend_from_slice(&chunk);
1974
1975 if chunk.len() < inner.connection.pdu_size as usize - 50 {
1977 break;
1978 }
1979 if block_data.len() > 1024 * 1024 * 4 {
1981 return Err(Error::UnexpectedResponse);
1983 }
1984 }
1985
1986 let end_pdu_ref = Self::next_pdu_ref(&mut inner);
1988 let mut eparam = BytesMut::with_capacity(6);
1989 eparam.extend_from_slice(&[0x1D, 0x02]);
1990 eparam.put_u32(upload_id);
1991 Self::send_s7(
1992 &mut inner,
1993 eparam.freeze(),
1994 Bytes::new(),
1995 end_pdu_ref,
1996 PduType::Job,
1997 )
1998 .await?;
1999 let (eheader, _ebody) = Self::recv_s7(&mut inner).await?;
2000 check_plc_error(&eheader, "upload_end")?;
2001
2002 Ok(block_data)
2003 }
2004
2005 pub async fn full_upload(&self, block_type: u8, block_number: u16) -> Result<Vec<u8>> {
2010 let mut inner = self.inner.lock().await;
2011 let pdu_ref = Self::next_pdu_ref(&mut inner);
2012
2013 let mut param = BytesMut::with_capacity(6);
2015 param.extend_from_slice(&[0x1F, 0x00, block_type, 0x00]);
2016 param.put_u16(block_number);
2017 Self::send_s7(&mut inner, param.freeze(), Bytes::new(), pdu_ref, PduType::Job).await?;
2018 let (header, mut body) = Self::recv_s7(&mut inner).await?;
2019 check_plc_error(&header, "full_upload_start")?;
2020 if body.remaining() < 8 {
2021 return Err(Error::UnexpectedResponse);
2022 }
2023 if body.remaining() >= 2 {
2024 body.advance(2);
2025 }
2026 let upload_id = body.get_u32();
2027 let _total_len = body.get_u32();
2028
2029 let mut block_data = Vec::new();
2031 loop {
2032 let chunk_ref = Self::next_pdu_ref(&mut inner);
2033 let mut dparam = BytesMut::with_capacity(6);
2034 dparam.extend_from_slice(&[0x1F, 0x01]);
2035 dparam.put_u32(upload_id);
2036 Self::send_s7(&mut inner, dparam.freeze(), Bytes::new(), chunk_ref, PduType::Job).await?;
2037 let (dheader, mut dbody) = Self::recv_s7(&mut inner).await?;
2038 check_plc_error(&dheader, "full_upload_data")?;
2039 if dbody.remaining() >= 2 {
2040 dbody.advance(2);
2041 }
2042 if dbody.is_empty() {
2043 break;
2044 }
2045 if block_data.is_empty() && dbody.remaining() >= 4 {
2046 if dbody[0] == 0xFF || dbody[0] == 0x00 {
2047 dbody.advance(4);
2048 }
2049 }
2050 let chunk = dbody.copy_to_bytes(dbody.remaining());
2051 block_data.extend_from_slice(&chunk);
2052 if chunk.len() < inner.connection.pdu_size as usize - 50 {
2053 break;
2054 }
2055 if block_data.len() > 1024 * 1024 * 4 {
2056 return Err(Error::UnexpectedResponse);
2057 }
2058 }
2059
2060 let end_ref = Self::next_pdu_ref(&mut inner);
2062 let mut eparam = BytesMut::with_capacity(6);
2063 eparam.extend_from_slice(&[0x1F, 0x02]);
2064 eparam.put_u32(upload_id);
2065 Self::send_s7(&mut inner, eparam.freeze(), Bytes::new(), end_ref, PduType::Job).await?;
2066 let (eheader, _) = Self::recv_s7(&mut inner).await?;
2067 check_plc_error(&eheader, "full_upload_end")?;
2068
2069 Ok(block_data)
2070 }
2071
2072 pub async fn get_pdu_length(&self) -> u16 {
2074 self.inner.lock().await.connection.pdu_size
2075 }
2076
2077 pub async fn db_get(&self, db_number: u16) -> Result<Vec<u8>> {
2079 self.upload(0x41, db_number).await }
2081
2082 pub async fn download(&self, block_type: u8, block_number: u16, data: &[u8]) -> Result<()> {
2087 let total_len = data.len() as u32;
2088 let mut inner = self.inner.lock().await;
2089 let pdu_avail = (inner.connection.pdu_size as usize).saturating_sub(50);
2090
2091 let start_ref = Self::next_pdu_ref(&mut inner);
2093 let mut sparam = BytesMut::with_capacity(10);
2095 sparam.extend_from_slice(&[0x1E, 0x00, block_type, 0x00]);
2096 sparam.put_u16(block_number);
2097 sparam.put_u32(total_len);
2098
2099 let chunk_len = pdu_avail.min(data.len());
2101 let first_chunk = Bytes::copy_from_slice(&data[..chunk_len]);
2102 Self::send_s7(
2103 &mut inner,
2104 sparam.freeze(),
2105 first_chunk,
2106 start_ref,
2107 PduType::Job,
2108 )
2109 .await?;
2110
2111 let (sheader, mut sbody) = Self::recv_s7(&mut inner).await?;
2112 check_plc_error(&sheader, "download_start")?;
2113 if sbody.remaining() >= 2 {
2115 sbody.advance(2); }
2117 if sbody.remaining() < 4 {
2118 return Err(Error::UnexpectedResponse);
2119 }
2120 let download_id = sbody.get_u32();
2121
2122 let mut offset = chunk_len;
2123
2124 while offset < data.len() {
2126 let chunk_ref = Self::next_pdu_ref(&mut inner);
2127 let end = (offset + pdu_avail).min(data.len());
2128 let chunk = Bytes::copy_from_slice(&data[offset..end]);
2129
2130 let mut dparam = BytesMut::with_capacity(6);
2131 dparam.extend_from_slice(&[0x1E, 0x01]);
2132 dparam.put_u32(download_id);
2133
2134 Self::send_s7(
2135 &mut inner,
2136 dparam.freeze(),
2137 chunk,
2138 chunk_ref,
2139 PduType::Job,
2140 )
2141 .await?;
2142
2143 let (dheader, _dbody) = Self::recv_s7(&mut inner).await?;
2144 check_plc_error(&dheader, "download_data")?;
2145 offset = end;
2146 }
2147
2148 let end_ref = Self::next_pdu_ref(&mut inner);
2150 let mut eparam = BytesMut::with_capacity(6);
2151 eparam.extend_from_slice(&[0x1E, 0x02]);
2152 eparam.put_u32(download_id);
2153 Self::send_s7(
2154 &mut inner,
2155 eparam.freeze(),
2156 Bytes::new(),
2157 end_ref,
2158 PduType::Job,
2159 )
2160 .await?;
2161 let (eheader, _ebody) = Self::recv_s7(&mut inner).await?;
2162 check_plc_error(&eheader, "download_end")?;
2163
2164 Ok(())
2165 }
2166
2167 pub async fn create_db(
2173 &self,
2174 db_number: u16,
2175 size_bytes: u16,
2176 attrs: Option<&crate::types::BlockAttributes>,
2177 ) -> Result<()> {
2178 let mut block = crate::types::BlockData::new_db(db_number, size_bytes);
2179 if let Some(a) = attrs {
2180 block.set_attributes(a);
2181 }
2182 let bytes = block.to_bytes();
2183 self.download(crate::types::BlockType::DB as u8, db_number, &bytes).await
2184 }
2185
2186 pub async fn compare_blocks(
2193 &self,
2194 local: &[(u8, u16, Vec<u8>)],
2195 report_plc_only: bool,
2196 ) -> Result<Vec<(u8, u16, crate::types::BlockCmpResult)>> {
2197 use std::collections::HashMap;
2198 use crate::types::{BlockCmpResult, BlockData};
2199
2200 let local_map: HashMap<(u8, u16), u32> = local
2202 .iter()
2203 .filter_map(|(bt, bn, bytes)| {
2204 BlockData::from_bytes(bytes).map(|bd| ((*bt, *bn), bd.crc32()))
2205 })
2206 .collect();
2207
2208 let mut out = Vec::new();
2209
2210 for (bt, bn, local_bytes) in local {
2212 let local_crc = match BlockData::from_bytes(local_bytes) {
2213 Some(bd) => bd.crc32(),
2214 None => continue,
2215 };
2216 match self.full_upload(*bt, *bn).await {
2217 Ok(plc_bytes) => {
2218 let plc_crc = BlockData::from_bytes(&plc_bytes)
2219 .map(|bd| bd.crc32())
2220 .unwrap_or(0);
2221 let result = if local_crc == plc_crc {
2222 BlockCmpResult::Match
2223 } else {
2224 BlockCmpResult::Mismatch { local_crc, plc_crc }
2225 };
2226 out.push((*bt, *bn, result));
2227 }
2228 Err(_) => {
2229 out.push((*bt, *bn, BlockCmpResult::OnlyLocal));
2230 }
2231 }
2232 }
2233
2234 if report_plc_only {
2236 for bt in &[0x38u8, 0x41, 0x43, 0x45] {
2237 let numbers = self.list_blocks_of_type(*bt).await?;
2238 for num in numbers {
2239 if !local_map.contains_key(&(*bt, num)) {
2240 out.push((*bt, num, BlockCmpResult::OnlyPlc));
2241 }
2242 }
2243 }
2244 }
2245
2246 Ok(out)
2247 }
2248
2249 pub async fn db_fill(&self, db_number: u16, value: u8) -> Result<()> {
2254 let info = self.get_ag_block_info(0x41, db_number).await?; let size = info.size as usize;
2256 if size == 0 {
2257 return Err(Error::PlcError {
2258 code: 0,
2259 message: format!("DB{db_number} has zero size"),
2260 });
2261 }
2262 let data = vec![value; size];
2263 let chunk_size = 240usize; for offset in (0..size).step_by(chunk_size) {
2266 let end = (offset + chunk_size).min(size);
2267 self.db_write(db_number, offset as u32, &data[offset..end])
2268 .await?;
2269 }
2270 Ok(())
2271 }
2272}
2273
2274fn skip_szl_entry_header(data: &mut Bytes) {
2278 if data.len() >= 2 && data[0] == 0x00 && data[1] > 0 && data[1] <= 200 {
2279 data.advance(2);
2280 }
2281}
2282
2283fn scan_ascii_fields(data: &[u8], max_count: usize, min_len: usize) -> Vec<String> {
2288 let mut fields = Vec::new();
2289 let mut i = 0;
2290 while i < data.len() && fields.len() < max_count {
2291 if !data[i].is_ascii_graphic() && data[i] != b' ' {
2293 i += 1;
2294 continue;
2295 }
2296 let start = i;
2298 while i < data.len() && (data[i].is_ascii_graphic() || data[i] == b' ') {
2299 i += 1;
2300 }
2301 let s = String::from_utf8_lossy(&data[start..i]).trim().to_string();
2302 if s.len() >= min_len {
2303 fields.push(s);
2304 }
2305 }
2306 fields
2307}
2308
2309fn parse_sub_record_fields(b: &[u8]) -> (String, String, String, String, String) {
2319 let mut module_type = String::new();
2320 let mut serial_number = String::new();
2321 let mut as_name = String::new();
2322 let mut copyright = String::new();
2323 let mut module_name = String::new();
2324
2325 let mut i = 0;
2326 while i + 2 < b.len() {
2327 if b[i] == 0x00 && (1..=8).contains(&b[i + 1]) {
2329 let tag = b[i + 1];
2330 let start = i + 2;
2331
2332 let mut end = start;
2334 while end < b.len() && b[end] != 0x00 {
2335 end += 1;
2336 }
2337
2338 let raw = &b[start..end];
2339 let val = String::from_utf8_lossy(raw).trim().to_string();
2340
2341 let su = val.to_uppercase();
2343 if !val.is_empty() && !su.contains("BOOT") && !su.starts_with("P B") {
2344 match tag {
2345 0x01 => {
2346 if !val.starts_with("6ES") && module_type.is_empty() {
2348 module_type = val;
2349 }
2350 }
2351 0x05 => { if as_name.is_empty() { as_name = val; } }
2352 0x06 => { if serial_number.is_empty() { serial_number = val; } }
2353 0x07 => { if module_type.is_empty() { module_type = val; } }
2354 0x08 => { if module_name.is_empty() { module_name = val; } }
2355 _ => {}
2356 }
2357 }
2358
2359 i = end;
2360 } else {
2361 i += 1;
2362 }
2363 }
2364
2365 if copyright.is_empty() {
2368 let mut scan = 0;
2369 while scan < b.len() {
2370 if b[scan].is_ascii_graphic() || b[scan] == b' ' {
2371 let s = scan;
2372 while scan < b.len() && (b[scan].is_ascii_graphic() || b[scan] == b' ') {
2373 scan += 1;
2374 }
2375 let val = String::from_utf8_lossy(&b[s..scan]).trim().to_string();
2376 let su = val.to_uppercase();
2377 if val.len() >= 3 {
2378 if su.contains("BOOT") || su.starts_with("P B") {
2379 copyright = val;
2380 break;
2381 }
2382 }
2383 } else {
2384 scan += 1;
2385 }
2386 }
2387 }
2388
2389 (module_type, serial_number, as_name, copyright, module_name)
2390}
2391
2392fn detect_protocol(_payload: &[u8], module_type: &str) -> crate::types::Protocol {
2398 let upper = module_type.to_uppercase();
2401 let is_s7plus = upper.contains("1500")
2402 || upper.contains("1200")
2403 || upper.contains("ET 200SP")
2404 || upper.contains("ET200SP")
2405 || (upper.contains("CPU") && {
2407 let after_cpu = upper.find("CPU").map(|i| &upper[i+3..]).unwrap_or("");
2408 let num: String = after_cpu.chars().skip_while(|c| !c.is_ascii_digit()).take_while(|c| c.is_ascii_digit()).collect();
2409 matches!(num.get(..2), Some("12") | Some("15"))
2410 });
2411
2412 if is_s7plus {
2413 crate::types::Protocol::S7Plus
2414 } else {
2415 crate::types::Protocol::S7
2416 }
2417}
2418
2419
2420fn s7_error_description(ec: u8, ecd: u8) -> &'static str {
2422 match (ec, ecd) {
2423 (0x81, 0x04) => "function not supported or access denied by PLC",
2424 (0x81, 0x01) => "reserved by HW or SW function not available",
2425 (0x82, 0x04) => "PLC is in STOP mode, function not possible",
2426 (0x05, 0x01) => "invalid block type number",
2427 (0xD2, 0x01) => "object already exists, download rejected",
2428 (0xD2, 0x02) => "object does not exist, upload failed",
2429 (0xD6, 0x01) => "password protection violation",
2430 (0xD6, 0x05) => "insufficient privilege for this operation",
2431 _ => "unknown error",
2432 }
2433}
2434
2435fn check_plc_error(header: &S7Header, context: &str) -> Result<()> {
2436 if let (Some(ec), Some(ecd)) = (header.error_class, header.error_code) {
2437 if ec != 0 || ecd != 0 {
2438 let detail = s7_error_description(ec, ecd);
2439 return Err(Error::PlcError {
2440 code: ((ec as u32) << 8) | ecd as u32,
2441 message: format!("{}: {} (error_class=0x{ec:02X}, error_code=0x{ecd:02X})", context, detail),
2442 });
2443 }
2444 }
2445 Ok(())
2446}
2447
2448impl S7Client<crate::transport::TcpTransport> {
2449 pub async fn connect(addr: SocketAddr, params: ConnectParams) -> Result<Self> {
2450 let transport =
2451 crate::transport::TcpTransport::connect(addr, params.connect_timeout).await?;
2452 let mut client = Self::from_transport(transport, params).await?;
2453 client.remote_addr = Some(addr);
2454 Ok(client)
2455 }
2456
2457 pub async fn reconnect(&self) -> Result<()> {
2462 let addr = self.remote_addr.ok_or(Error::ConnectionRefused)?;
2463 let transport =
2464 crate::transport::TcpTransport::connect(addr, self.params.connect_timeout).await?;
2465 let mut t = transport;
2466 let connection = connect(&mut t, &self.params).await?;
2467 let mut inner = self.inner.lock().await;
2468 inner.transport = t;
2469 inner.connection = connection;
2470 inner.pdu_ref = 1;
2471 inner.connected = true;
2472 inner.job_start = None;
2473 inner.last_exec_ms = 0;
2474 Ok(())
2475 }
2476}
2477
2478impl S7Client<crate::UdpTransport> {
2479 pub async fn connect_udp(addr: SocketAddr, params: ConnectParams) -> Result<Self> {
2481 let transport = crate::UdpTransport::connect(addr)
2482 .await
2483 .map_err(Error::Io)?;
2484 Self::from_transport(transport, params).await
2485 }
2486}
2487
2488#[cfg(test)]
2489mod tests {
2490 use super::*;
2491 use bytes::BufMut;
2492 use crate::proto::{
2493 cotp::CotpPdu,
2494 s7::{
2495 header::{PduType, S7Header},
2496 negotiate::NegotiateResponse,
2497 },
2498 tpkt::TpktFrame,
2499 };
2500 use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt};
2501
2502 async fn mock_plc_db_read(mut server_io: tokio::io::DuplexStream, response_data: Vec<u8>) {
2503 let mut buf = vec![0u8; 4096];
2504
2505 let _ = server_io.read(&mut buf).await;
2507 let cc = CotpPdu::ConnectConfirm {
2508 dst_ref: 1,
2509 src_ref: 1,
2510 };
2511 let mut cb = BytesMut::new();
2512 cc.encode(&mut cb);
2513 let mut tb = BytesMut::new();
2514 TpktFrame {
2515 payload: cb.freeze(),
2516 }
2517 .encode(&mut tb)
2518 .unwrap();
2519 server_io.write_all(&tb).await.unwrap();
2520
2521 let _ = server_io.read(&mut buf).await;
2523 let neg = NegotiateResponse {
2524 max_amq_calling: 1,
2525 max_amq_called: 1,
2526 pdu_length: 480,
2527 };
2528 let mut s7b = BytesMut::new();
2529 S7Header {
2530 pdu_type: PduType::AckData,
2531 reserved: 0,
2532 pdu_ref: 1,
2533 param_len: 8,
2534 data_len: 0,
2535 error_class: Some(0),
2536 error_code: Some(0),
2537 }
2538 .encode(&mut s7b);
2539 neg.encode(&mut s7b);
2540 let dt = CotpPdu::Data {
2541 tpdu_nr: 0,
2542 last: true,
2543 payload: s7b.freeze(),
2544 };
2545 let mut cb = BytesMut::new();
2546 dt.encode(&mut cb);
2547 let mut tb = BytesMut::new();
2548 TpktFrame {
2549 payload: cb.freeze(),
2550 }
2551 .encode(&mut tb)
2552 .unwrap();
2553 server_io.write_all(&tb).await.unwrap();
2554
2555 let _ = server_io.read(&mut buf).await;
2557 let mut s7b = BytesMut::new();
2558 S7Header {
2559 pdu_type: PduType::AckData,
2560 reserved: 0,
2561 pdu_ref: 2,
2562 param_len: 2,
2563 data_len: (4 + response_data.len()) as u16,
2564 error_class: Some(0),
2565 error_code: Some(0),
2566 }
2567 .encode(&mut s7b);
2568 s7b.extend_from_slice(&[0x04, 0x01]); s7b.put_u8(0xFF); s7b.put_u8(0x04); s7b.put_u16((response_data.len() * 8) as u16);
2572 s7b.extend_from_slice(&response_data);
2573 let dt = CotpPdu::Data {
2574 tpdu_nr: 0,
2575 last: true,
2576 payload: s7b.freeze(),
2577 };
2578 let mut cb = BytesMut::new();
2579 dt.encode(&mut cb);
2580 let mut tb = BytesMut::new();
2581 TpktFrame {
2582 payload: cb.freeze(),
2583 }
2584 .encode(&mut tb)
2585 .unwrap();
2586 server_io.write_all(&tb).await.unwrap();
2587 }
2588
2589 #[tokio::test]
2590 async fn db_read_returns_data() {
2591 let (client_io, server_io) = duplex(4096);
2592 let params = ConnectParams::default();
2593 let expected = vec![0xDE, 0xAD, 0xBE, 0xEF];
2594 tokio::spawn(mock_plc_db_read(server_io, expected.clone()));
2595 let client = S7Client::from_transport(client_io, params).await.unwrap();
2596 let data = client.db_read(1, 0, 4).await.unwrap();
2597 assert_eq!(&data[..], &expected[..]);
2598 }
2599
2600 async fn mock_plc_multi_read(
2602 mut server_io: tokio::io::DuplexStream,
2603 items: Vec<Vec<u8>>, ) {
2605 let mut buf = vec![0u8; 4096];
2606
2607 let _ = server_io.read(&mut buf).await;
2609 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
2610 let mut cb = BytesMut::new();
2611 cc.encode(&mut cb);
2612 let mut tb = BytesMut::new();
2613 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2614 server_io.write_all(&tb).await.unwrap();
2615
2616 let _ = server_io.read(&mut buf).await;
2618 let neg = NegotiateResponse { max_amq_calling: 1, max_amq_called: 1, pdu_length: 480 };
2619 let mut s7b = BytesMut::new();
2620 S7Header {
2621 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 1,
2622 param_len: 8, data_len: 0, error_class: Some(0), error_code: Some(0),
2623 }.encode(&mut s7b);
2624 neg.encode(&mut s7b);
2625 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2626 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2627 let mut tb = BytesMut::new();
2628 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2629 server_io.write_all(&tb).await.unwrap();
2630
2631 let _ = server_io.read(&mut buf).await;
2633
2634 let item_count = items.len() as u8;
2636 let mut data_bytes = BytesMut::new();
2637 for item_data in &items {
2638 data_bytes.put_u8(0xFF); data_bytes.put_u8(0x04); data_bytes.put_u16((item_data.len() * 8) as u16);
2641 data_bytes.extend_from_slice(item_data);
2642 if item_data.len() % 2 != 0 {
2643 data_bytes.put_u8(0x00); }
2645 }
2646 let data_len = data_bytes.len() as u16;
2647 let mut s7b = BytesMut::new();
2648 S7Header {
2649 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 2,
2650 param_len: 2, data_len, error_class: Some(0), error_code: Some(0),
2651 }.encode(&mut s7b);
2652 s7b.extend_from_slice(&[0x04, item_count]); s7b.extend_from_slice(&data_bytes);
2654
2655 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2656 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2657 let mut tb = BytesMut::new();
2658 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2659 server_io.write_all(&tb).await.unwrap();
2660 }
2661
2662 #[tokio::test]
2663 async fn read_multi_vars_returns_all_items() {
2664 let (client_io, server_io) = duplex(4096);
2665 let params = ConnectParams::default();
2666 let item1 = vec![0xDE, 0xAD, 0xBE, 0xEF];
2667 let item2 = vec![0x01, 0x02];
2668 tokio::spawn(mock_plc_multi_read(server_io, vec![item1.clone(), item2.clone()]));
2669 let client = S7Client::from_transport(client_io, params).await.unwrap();
2670 let items = [MultiReadItem::db(1, 0, 4), MultiReadItem::db(2, 10, 2)];
2671 let results = client.read_multi_vars(&items).await.unwrap();
2672 assert_eq!(results.len(), 2);
2673 assert_eq!(&results[0][..], &item1[..]);
2674 assert_eq!(&results[1][..], &item2[..]);
2675 }
2676
2677 #[tokio::test]
2678 async fn read_multi_vars_empty_returns_empty() {
2679 let (client_io, server_io) = duplex(4096);
2680 let params = ConnectParams::default();
2681 tokio::spawn(mock_plc_multi_read(server_io, vec![]));
2682 let client = S7Client::from_transport(client_io, params).await.unwrap();
2683 let results = client.read_multi_vars(&[]).await.unwrap();
2684 assert!(results.is_empty());
2685 }
2686
2687 async fn mock_plc_multi_write(
2690 mut server_io: tokio::io::DuplexStream,
2691 pdu_size: u16,
2692 batches: Vec<usize>,
2693 ) {
2694 let mut buf = vec![0u8; 65536];
2695
2696 let _ = server_io.read(&mut buf).await;
2698 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
2699 let mut cb = BytesMut::new(); cc.encode(&mut cb);
2700 let mut tb = BytesMut::new();
2701 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2702 server_io.write_all(&tb).await.unwrap();
2703
2704 let _ = server_io.read(&mut buf).await;
2706 let neg = NegotiateResponse { max_amq_calling: 1, max_amq_called: 1, pdu_length: pdu_size };
2707 let mut s7b = BytesMut::new();
2708 S7Header {
2709 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 1,
2710 param_len: 8, data_len: 0, error_class: Some(0), error_code: Some(0),
2711 }.encode(&mut s7b);
2712 neg.encode(&mut s7b);
2713 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2714 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2715 let mut tb = BytesMut::new();
2716 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2717 server_io.write_all(&tb).await.unwrap();
2718
2719 for (i, item_count) in batches.iter().enumerate() {
2721 let _ = server_io.read(&mut buf).await;
2722 let mut s7b = BytesMut::new();
2724 S7Header {
2725 pdu_type: PduType::AckData, reserved: 0, pdu_ref: (i + 2) as u16,
2726 param_len: 2, data_len: *item_count as u16,
2727 error_class: Some(0), error_code: Some(0),
2728 }.encode(&mut s7b);
2729 s7b.extend_from_slice(&[0x05, *item_count as u8]); for _ in 0..*item_count {
2731 s7b.put_u8(0xFF); }
2733 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2734 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2735 let mut tb = BytesMut::new();
2736 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2737 server_io.write_all(&tb).await.unwrap();
2738 }
2739 }
2740
2741 #[tokio::test]
2742 async fn write_multi_vars_returns_ok() {
2743 let (client_io, server_io) = duplex(65536);
2744 let params = ConnectParams::default();
2745 tokio::spawn(mock_plc_multi_write(server_io, 480, vec![2]));
2746 let client = S7Client::from_transport(client_io, params).await.unwrap();
2747 let items = [
2748 MultiWriteItem::db(1, 0, vec![0xAA, 0xBB, 0xCC, 0xDD]),
2749 MultiWriteItem::db(2, 10, vec![0x01, 0x02]),
2750 ];
2751 client.write_multi_vars(&items).await.unwrap();
2752 }
2753
2754 #[tokio::test]
2755 async fn write_multi_vars_empty_returns_ok() {
2756 let (client_io, server_io) = duplex(4096);
2757 let params = ConnectParams::default();
2758 tokio::spawn(mock_plc_multi_write(server_io, 480, vec![]));
2760 let client = S7Client::from_transport(client_io, params).await.unwrap();
2761 client.write_multi_vars(&[]).await.unwrap();
2762 }
2763
2764 #[tokio::test]
2770 async fn write_multi_vars_batches_when_pdu_limit_exceeded() {
2771 let (client_io, server_io) = duplex(65536);
2772 let params = ConnectParams::default();
2773 tokio::spawn(mock_plc_multi_write(server_io, 64, vec![1, 1]));
2774 let client = S7Client::from_transport(client_io, params).await.unwrap();
2775 let items = [
2776 MultiWriteItem::db(1, 0, vec![0x11u8; 20]),
2777 MultiWriteItem::db(2, 0, vec![0x22u8; 20]),
2778 ];
2779 client.write_multi_vars(&items).await.unwrap();
2780 }
2781
2782 #[tokio::test]
2788 async fn read_multi_vars_batches_when_pdu_limit_exceeded() {
2789 use crate::proto::s7::negotiate::NegotiateResponse;
2790
2791 async fn mock_split_pdu(mut server_io: tokio::io::DuplexStream) {
2792 let mut buf = vec![0u8; 4096];
2793
2794 let _ = server_io.read(&mut buf).await;
2796 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
2797 let mut cb = BytesMut::new(); cc.encode(&mut cb);
2798 let mut tb = BytesMut::new();
2799 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2800 server_io.write_all(&tb).await.unwrap();
2801
2802 let _ = server_io.read(&mut buf).await;
2804 let neg = NegotiateResponse {
2805 max_amq_calling: 1, max_amq_called: 1, pdu_length: 64,
2806 };
2807 let mut s7b = BytesMut::new();
2808 S7Header {
2809 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 1,
2810 param_len: 8, data_len: 0, error_class: Some(0), error_code: Some(0),
2811 }.encode(&mut s7b);
2812 neg.encode(&mut s7b);
2813 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2814 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2815 let mut tb = BytesMut::new();
2816 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2817 server_io.write_all(&tb).await.unwrap();
2818
2819 let payloads: &[&[u8]] = &[&[0x11u8; 30], &[0x22u8; 30]];
2821 for (i, payload) in payloads.iter().enumerate() {
2822 let _ = server_io.read(&mut buf).await;
2823 let bit_len = (payload.len() * 8) as u16;
2824 let mut data_bytes = BytesMut::new();
2825 data_bytes.put_u8(0xFF);
2826 data_bytes.put_u8(0x04);
2827 data_bytes.put_u16(bit_len);
2828 data_bytes.extend_from_slice(payload);
2829 if payload.len() % 2 != 0 { data_bytes.put_u8(0x00); }
2830 let data_len = data_bytes.len() as u16;
2831 let mut s7b = BytesMut::new();
2832 S7Header {
2833 pdu_type: PduType::AckData, reserved: 0, pdu_ref: (i + 2) as u16,
2834 param_len: 2, data_len, error_class: Some(0), error_code: Some(0),
2835 }.encode(&mut s7b);
2836 s7b.extend_from_slice(&[0x04, 0x01]);
2837 s7b.extend_from_slice(&data_bytes);
2838 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2839 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2840 let mut tb = BytesMut::new();
2841 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2842 server_io.write_all(&tb).await.unwrap();
2843 }
2844 }
2845
2846 let (client_io, server_io) = duplex(4096);
2847 let params = ConnectParams::default();
2848 tokio::spawn(mock_split_pdu(server_io));
2849 let client = S7Client::from_transport(client_io, params).await.unwrap();
2850
2851 let items = [MultiReadItem::db(1, 0, 30), MultiReadItem::db(2, 0, 30)];
2852 let results = client.read_multi_vars(&items).await.unwrap();
2853 assert_eq!(results.len(), 2);
2854 assert_eq!(&results[0][..], &[0x11u8; 30][..]);
2855 assert_eq!(&results[1][..], &[0x22u8; 30][..]);
2856 }
2857
2858 async fn mock_handshake(server_io: &mut (impl AsyncRead + AsyncWrite + Unpin)) {
2862 let mut buf = vec![0u8; 4096];
2863
2864 let _ = server_io.read(&mut buf).await;
2866 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
2867 let mut cb = BytesMut::new(); cc.encode(&mut cb);
2868 let mut tb = BytesMut::new();
2869 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2870 server_io.write_all(&tb).await.unwrap();
2871
2872 let _ = server_io.read(&mut buf).await;
2874 let neg = NegotiateResponse { max_amq_calling: 1, max_amq_called: 1, pdu_length: 480 };
2875 let mut s7b = BytesMut::new();
2876 S7Header {
2877 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 1,
2878 param_len: 8, data_len: 0, error_class: Some(0), error_code: Some(0),
2879 }.encode(&mut s7b);
2880 neg.encode(&mut s7b);
2881 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2882 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2883 let mut tb = BytesMut::new();
2884 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2885 server_io.write_all(&tb).await.unwrap();
2886 }
2887
2888 async fn mock_plc_control(
2891 mut server_io: tokio::io::DuplexStream,
2892 ok: bool,
2893 ) {
2894 let mut buf = vec![0u8; 4096];
2895 mock_handshake(&mut server_io).await;
2896
2897 let _ = server_io.read(&mut buf).await;
2899
2900 let (ec, ecd) = if ok { (0u8, 0u8) } else { (0x81u8, 0x04u8) };
2902 let mut s7b = BytesMut::new();
2903 S7Header {
2904 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 2,
2905 param_len: 0, data_len: 0,
2906 error_class: Some(ec), error_code: Some(ecd),
2907 }.encode(&mut s7b);
2908 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2909 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2910 let mut tb = BytesMut::new();
2911 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2912 server_io.write_all(&tb).await.unwrap();
2913 }
2914
2915 #[tokio::test]
2916 async fn plc_stop_succeeds() {
2917 let (client_io, server_io) = duplex(4096);
2918 let params = ConnectParams::default();
2919 tokio::spawn(mock_plc_control(server_io, true));
2920 let client = S7Client::from_transport(client_io, params).await.unwrap();
2921 client.plc_stop().await.unwrap();
2922 }
2923
2924 #[tokio::test]
2925 async fn plc_hot_start_succeeds() {
2926 let (client_io, server_io) = duplex(4096);
2927 let params = ConnectParams::default();
2928 tokio::spawn(mock_plc_control(server_io, true));
2929 let client = S7Client::from_transport(client_io, params).await.unwrap();
2930 client.plc_hot_start().await.unwrap();
2931 }
2932
2933 #[tokio::test]
2934 async fn plc_cold_start_succeeds() {
2935 let (client_io, server_io) = duplex(4096);
2936 let params = ConnectParams::default();
2937 tokio::spawn(mock_plc_control(server_io, true));
2938 let client = S7Client::from_transport(client_io, params).await.unwrap();
2939 client.plc_cold_start().await.unwrap();
2940 }
2941
2942 #[tokio::test]
2943 async fn plc_stop_rejected_returns_error() {
2944 let (client_io, server_io) = duplex(4096);
2945 let params = ConnectParams::default();
2946 tokio::spawn(mock_plc_control(server_io, false));
2947 let client = S7Client::from_transport(client_io, params).await.unwrap();
2948 let result = client.plc_stop().await;
2949 assert!(result.is_err());
2950 }
2951
2952 async fn mock_plc_status(
2954 mut server_io: tokio::io::DuplexStream,
2955 status_byte: u8,
2956 ) {
2957 let mut buf = vec![0u8; 4096];
2958 mock_handshake(&mut server_io).await;
2959
2960 let _ = server_io.read(&mut buf).await;
2962
2963 let mut szl_payload = [0u8; 12];
2971 szl_payload[0..2].copy_from_slice(&0x0424u16.to_be_bytes());
2972 szl_payload[6..8].copy_from_slice(&0x0001u16.to_be_bytes()); szl_payload[11] = status_byte;
2974
2975 let params: [u8; 8] = [0x00, 0x01, 0x12, 0x08, 0x12, 0x84, 0x01, 0x00];
2980 let data_envelope: [u8; 4] = [0xFF, 0x09, 0x00, 0x0C];
2981 let param_len = params.len() as u16;
2982 let data_len = (data_envelope.len() + szl_payload.len()) as u16;
2983
2984 let mut s7b = BytesMut::new();
2985 S7Header {
2986 pdu_type: PduType::UserData, reserved: 0, pdu_ref: 2,
2987 param_len, data_len,
2988 error_class: None, error_code: None,
2989 }.encode(&mut s7b);
2990 s7b.extend_from_slice(¶ms);
2991 s7b.extend_from_slice(&data_envelope);
2992 s7b.extend_from_slice(&szl_payload);
2993 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
2994 let mut cb = BytesMut::new(); dt.encode(&mut cb);
2995 let mut tb = BytesMut::new();
2996 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
2997 server_io.write_all(&tb).await.unwrap();
2998 }
2999
3000 #[tokio::test]
3001 async fn get_plc_status_returns_run() {
3002 let (client_io, server_io) = duplex(4096);
3003 let params = ConnectParams::default();
3004 tokio::spawn(mock_plc_status(server_io, 0x08));
3005 let client = S7Client::from_transport(client_io, params).await.unwrap();
3006 let status = client.get_plc_status().await.unwrap();
3007 assert_eq!(status, crate::types::PlcStatus::Run);
3008 }
3009
3010 #[tokio::test]
3011 async fn get_plc_status_returns_stop() {
3012 let (client_io, server_io) = duplex(4096);
3013 let params = ConnectParams::default();
3014 tokio::spawn(mock_plc_status(server_io, 0x04));
3015 let client = S7Client::from_transport(client_io, params).await.unwrap();
3016 let status = client.get_plc_status().await.unwrap();
3017 assert_eq!(status, crate::types::PlcStatus::Stop);
3018 }
3019
3020 #[tokio::test]
3021 async fn get_plc_status_returns_unknown() {
3022 let (client_io, server_io) = duplex(4096);
3023 let params = ConnectParams::default();
3024 tokio::spawn(mock_plc_status(server_io, 0x00));
3025 let client = S7Client::from_transport(client_io, params).await.unwrap();
3026 let status = client.get_plc_status().await.unwrap();
3027 assert_eq!(status, crate::types::PlcStatus::Unknown);
3028 }
3029
3030 #[tokio::test]
3031 async fn get_plc_status_unknown_byte_returns_stop() {
3032 let (client_io, server_io) = duplex(4096);
3034 let params = ConnectParams::default();
3035 tokio::spawn(mock_plc_status(server_io, 0xFF));
3036 let client = S7Client::from_transport(client_io, params).await.unwrap();
3037 let status = client.get_plc_status().await.unwrap();
3038 assert_eq!(status, crate::types::PlcStatus::Stop);
3039 }
3040
3041 #[tokio::test]
3042 async fn mb_read_returns_data() {
3043 let (client_io, server_io) = duplex(4096);
3044 let params = ConnectParams::default();
3045 let expected = vec![0xAA, 0xBB];
3046 tokio::spawn(mock_plc_multi_read(server_io, vec![expected.clone()]));
3047 let client = S7Client::from_transport(client_io, params).await.unwrap();
3048 let data = client.mb_read(10, 2).await.unwrap();
3049 assert_eq!(&data[..], &expected[..]);
3050 }
3051
3052 #[tokio::test]
3053 async fn eb_read_returns_data() {
3054 let (client_io, server_io) = duplex(4096);
3055 let params = ConnectParams::default();
3056 let expected = vec![0x01, 0x02, 0x03];
3057 tokio::spawn(mock_plc_multi_read(server_io, vec![expected.clone()]));
3058 let client = S7Client::from_transport(client_io, params).await.unwrap();
3059 let data = client.eb_read(0, 3).await.unwrap();
3060 assert_eq!(&data[..], &expected[..]);
3061 }
3062
3063 #[tokio::test]
3064 async fn ib_read_returns_data() {
3065 let (client_io, server_io) = duplex(4096);
3066 let params = ConnectParams::default();
3067 let expected = vec![0x11, 0x22];
3068 tokio::spawn(mock_plc_multi_read(server_io, vec![expected.clone()]));
3069 let client = S7Client::from_transport(client_io, params).await.unwrap();
3070 let data = client.ib_read(0, 2).await.unwrap();
3071 assert_eq!(&data[..], &expected[..]);
3072 }
3073
3074 #[tokio::test]
3075 async fn tm_read_returns_data() {
3076 let (client_io, server_io) = duplex(4096);
3077 let params = ConnectParams::default();
3078 let expected = vec![0x00, 0x14, 0x00, 0x28];
3080 tokio::spawn(mock_plc_multi_read(server_io, vec![expected.clone()]));
3081 let client = S7Client::from_transport(client_io, params).await.unwrap();
3082 let data = client.tm_read(0, 2).await.unwrap();
3083 assert_eq!(&data[..], &expected[..]);
3084 }
3085
3086 #[tokio::test]
3087 async fn ct_read_returns_data() {
3088 let (client_io, server_io) = duplex(4096);
3089 let params = ConnectParams::default();
3090 let expected = vec![0x00, 0x07];
3092 tokio::spawn(mock_plc_multi_read(server_io, vec![expected.clone()]));
3093 let client = S7Client::from_transport(client_io, params).await.unwrap();
3094 let data = client.ct_read(3, 1).await.unwrap();
3095 assert_eq!(&data[..], &expected[..]);
3096 }
3097
3098 async fn mock_set_clock(mut server_io: tokio::io::DuplexStream) {
3099 let mut buf = vec![0u8; 4096];
3100 mock_handshake(&mut server_io).await;
3101 let _ = server_io.read(&mut buf).await;
3102 let mut s7b = BytesMut::new();
3104 S7Header {
3105 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 2,
3106 param_len: 0, data_len: 0, error_class: Some(0), error_code: Some(0),
3107 }.encode(&mut s7b);
3108 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
3109 let mut cb = BytesMut::new(); dt.encode(&mut cb);
3110 let mut tb = BytesMut::new();
3111 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
3112 server_io.write_all(&tb).await.unwrap();
3113 }
3114
3115 #[tokio::test]
3116 async fn set_clock_succeeds() {
3117 let (client_io, server_io) = duplex(4096);
3118 let params = ConnectParams::default();
3119 tokio::spawn(mock_set_clock(server_io));
3120 let client = S7Client::from_transport(client_io, params).await.unwrap();
3121 let dt = crate::proto::s7::clock::PlcDateTime {
3122 year: 2025, month: 5, day: 9, hour: 12, minute: 0, second: 0,
3123 millisecond: 0, weekday: 5,
3124 };
3125 client.set_clock(&dt).await.unwrap();
3126 }
3127
3128 #[tokio::test]
3129 async fn set_clock_to_now_succeeds() {
3130 let (client_io, server_io) = duplex(4096);
3131 let params = ConnectParams::default();
3132 tokio::spawn(mock_set_clock(server_io));
3133 let client = S7Client::from_transport(client_io, params).await.unwrap();
3134 client.set_clock_to_now().await.unwrap();
3135 }
3136
3137 async fn mock_read_clock(mut server_io: tokio::io::DuplexStream, dt: crate::proto::s7::clock::PlcDateTime) {
3138 let mut buf = vec![0u8; 4096];
3139 mock_handshake(&mut server_io).await;
3140 let _ = server_io.read(&mut buf).await;
3141 let mut datetime_bytes = bytes::BytesMut::new();
3144 dt.encode(&mut datetime_bytes);
3145 let param_len: u16 = 12;
3146 let data_len: u16 = 4;
3147 let mut s7b = BytesMut::new();
3148 S7Header {
3149 pdu_type: PduType::UserData, reserved: 0, pdu_ref: 2,
3150 param_len, data_len, error_class: None, error_code: None,
3151 }.encode(&mut s7b);
3152 s7b.extend_from_slice(&[0x00, 0x01, 0x12, 0x08, 0x12, 0x87, 0x01, 0x00]);
3154 s7b.extend_from_slice(&datetime_bytes[..4]);
3155 s7b.extend_from_slice(&datetime_bytes[4..]);
3157 let dt_pdu = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
3158 let mut cb = BytesMut::new(); dt_pdu.encode(&mut cb);
3159 let mut tb = BytesMut::new();
3160 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
3161 server_io.write_all(&tb).await.unwrap();
3162 }
3163
3164 #[tokio::test]
3165 async fn read_clock_returns_correct_datetime() {
3166 let expected = crate::proto::s7::clock::PlcDateTime {
3167 year: 2025, month: 5, day: 9, hour: 14, minute: 30, second: 0,
3168 millisecond: 0, weekday: 5,
3169 };
3170 let (client_io, server_io) = duplex(4096);
3171 let params = ConnectParams::default();
3172 tokio::spawn(mock_read_clock(server_io, expected.clone()));
3173 let client = S7Client::from_transport(client_io, params).await.unwrap();
3174 let result = client.read_clock().await.unwrap();
3175 assert_eq!(result, expected);
3176 }
3177
3178 async fn mock_szl_list(mut server_io: tokio::io::DuplexStream, ids: Vec<u16>) {
3179 let mut buf = vec![0u8; 4096];
3180 mock_handshake(&mut server_io).await;
3181 let _ = server_io.read(&mut buf).await;
3182
3183 let entry_len: u16 = 4;
3185 let entry_count = ids.len() as u16;
3186 let mut szl = BytesMut::new();
3187 szl.put_u16(0x0000); szl.put_u16(0x0000); szl.put_u16(entry_len);
3190 szl.put_u16(entry_count);
3191 for id in &ids {
3192 szl.put_u16(*id);
3193 szl.put_u16(0x0000); }
3195 let szl_bytes = szl.freeze();
3196 let data_len = (4 + szl_bytes.len()) as u16; let mut s7b = BytesMut::new();
3199 let param_len: u16 = 8;
3201 S7Header {
3202 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 2,
3203 param_len, data_len, error_class: Some(0), error_code: Some(0),
3204 }.encode(&mut s7b);
3205 s7b.extend_from_slice(&[0x00, 0x01, 0x12, 0x04, 0x11, 0x44, 0x01, 0x00]);
3207 s7b.put_u8(0xFF); s7b.put_u8(0x09);
3209 s7b.put_u16(szl_bytes.len() as u16);
3210 s7b.extend_from_slice(&szl_bytes);
3211
3212 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
3213 let mut cb = BytesMut::new(); dt.encode(&mut cb);
3214 let mut tb = BytesMut::new();
3215 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
3216 server_io.write_all(&tb).await.unwrap();
3217 }
3218
3219 #[tokio::test]
3220 async fn read_szl_list_returns_ids() {
3221 let (client_io, server_io) = duplex(4096);
3222 let params = ConnectParams::default();
3223 let ids = vec![0x0011u16, 0x001C, 0x0131, 0x0424];
3224 tokio::spawn(mock_szl_list(server_io, ids.clone()));
3225 let client = S7Client::from_transport(client_io, params).await.unwrap();
3226 let result = client.read_szl_list().await.unwrap();
3227 assert_eq!(result, ids);
3228 }
3229
3230 #[tokio::test]
3231 async fn read_szl_list_empty_returns_empty() {
3232 let (client_io, server_io) = duplex(4096);
3233 let params = ConnectParams::default();
3234 tokio::spawn(mock_szl_list(server_io, vec![]));
3235 let client = S7Client::from_transport(client_io, params).await.unwrap();
3236 let result = client.read_szl_list().await.unwrap();
3237 assert!(result.is_empty());
3238 }
3239
3240 async fn mock_full_upload(mut server_io: tokio::io::DuplexStream, block_data: Vec<u8>) {
3242 let mut buf = vec![0u8; 4096];
3243 mock_handshake(&mut server_io).await;
3244
3245 let _ = server_io.read(&mut buf).await;
3247 let mut s7b = BytesMut::new();
3248 S7Header {
3249 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 2,
3250 param_len: 2, data_len: 8, error_class: Some(0), error_code: Some(0),
3251 }.encode(&mut s7b);
3252 s7b.extend_from_slice(&[0x1F, 0x00]); s7b.put_u32(0xDEAD_BEEF_u32); s7b.put_u32(block_data.len() as u32); let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
3256 let mut cb = BytesMut::new(); dt.encode(&mut cb);
3257 let mut tb = BytesMut::new();
3258 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
3259 server_io.write_all(&tb).await.unwrap();
3260
3261 let _ = server_io.read(&mut buf).await;
3263 let data_payload_len = (4 + block_data.len()) as u16; let mut s7b = BytesMut::new();
3265 S7Header {
3266 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 3,
3267 param_len: 2, data_len: data_payload_len, error_class: Some(0), error_code: Some(0),
3268 }.encode(&mut s7b);
3269 s7b.extend_from_slice(&[0x1F, 0x01]);
3270 s7b.put_u8(0xFF); s7b.put_u8(0x04);
3271 s7b.put_u16((block_data.len() * 8) as u16);
3272 s7b.extend_from_slice(&block_data);
3273 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
3274 let mut cb = BytesMut::new(); dt.encode(&mut cb);
3275 let mut tb = BytesMut::new();
3276 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
3277 server_io.write_all(&tb).await.unwrap();
3278
3279 let _ = server_io.read(&mut buf).await;
3281 let mut s7b = BytesMut::new();
3282 S7Header {
3283 pdu_type: PduType::AckData, reserved: 0, pdu_ref: 4,
3284 param_len: 0, data_len: 0, error_class: Some(0), error_code: Some(0),
3285 }.encode(&mut s7b);
3286 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
3287 let mut cb = BytesMut::new(); dt.encode(&mut cb);
3288 let mut tb = BytesMut::new();
3289 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
3290 server_io.write_all(&tb).await.unwrap();
3291 }
3292
3293 #[tokio::test]
3294 async fn full_upload_returns_block_data() {
3295 let (client_io, server_io) = duplex(4096);
3296 let params = ConnectParams::default();
3297 let expected = vec![0x01u8, 0x02, 0x03, 0x04];
3298 tokio::spawn(mock_full_upload(server_io, expected.clone()));
3299 let client = S7Client::from_transport(client_io, params).await.unwrap();
3300 let data = client.full_upload(0x41, 1).await.unwrap();
3301 assert_eq!(data, expected);
3302 }
3303
3304 #[tokio::test]
3305 async fn get_pdu_length_returns_negotiated_size() {
3306 let (client_io, server_io) = duplex(4096);
3307 let params = ConnectParams::default();
3308 tokio::spawn(mock_plc_db_read(server_io, vec![0x00]));
3310 let client = S7Client::from_transport(client_io, params).await.unwrap();
3311 let pdu_len = client.get_pdu_length().await;
3312 assert_eq!(pdu_len, 480);
3313 }
3314
3315 #[test]
3316 fn parse_block_info_valid() {
3317 type C = S7Client<tokio::io::DuplexStream>;
3318 const TOTAL: usize = 84;
3320 let mut buf = vec![0u8; TOTAL];
3321 buf[0] = 0x70; buf[1] = 0x70;
3322 buf[3] = 0x09; buf[4] = 0x01; buf[5] = 0x41; buf[6] = 0x00; buf[7] = 0x05; let total_be = (TOTAL as u32).to_be_bytes();
3327 buf[8..12].copy_from_slice(&total_be);
3328 buf[28] = 0x00; buf[29] = 0x10; buf[32] = 0x00; buf[33] = 0x08; buf[34] = 0x00; buf[35] = 0x0A; let footer_start = TOTAL - 48;
3332 buf[footer_start + 20..footer_start + 27].copy_from_slice(b"SIEMENS");
3333 buf[footer_start + 28..footer_start + 32].copy_from_slice(b"TEST");
3334 buf[footer_start + 36..footer_start + 40].copy_from_slice(b"V1.0");
3335 buf[footer_start + 44] = 0xAB; buf[footer_start + 45] = 0xCD;
3336
3337 let info = C::parse_block_info(&buf).unwrap();
3338 assert_eq!(info.block_number, 5);
3339 assert_eq!(info.block_type, 0x41);
3340 assert_eq!(info.language, 1);
3341 assert_eq!(info.flags, 9);
3342 assert_eq!(info.size, TOTAL as u16);
3343 assert_eq!(info.size_ram, 16);
3344 assert_eq!(info.mc7_size, 10);
3345 assert_eq!(info.local_data, 8);
3346 assert_eq!(info.checksum, 0xABCD);
3347 assert_eq!(info.author, "SIEMENS");
3348 assert_eq!(info.family, "TEST");
3349 assert_eq!(info.header, "V1.0");
3350 }
3351
3352 #[test]
3353 fn parse_block_info_too_short() {
3354 type C = S7Client<tokio::io::DuplexStream>;
3355 let buf = vec![0u8; 10];
3356 assert!(C::parse_block_info(&buf).is_err());
3357 }
3358
3359 #[test]
3360 fn parse_block_info_mismatched_load_size() {
3361 type C = S7Client<tokio::io::DuplexStream>;
3362 const TOTAL: usize = 84;
3363 let mut buf = vec![0u8; TOTAL];
3364 let wrong = 100u32.to_be_bytes();
3365 buf[8..12].copy_from_slice(&wrong);
3366 buf[34] = 0x00; buf[35] = 0x0A;
3367 assert!(C::parse_block_info(&buf).is_err());
3368 }
3369
3370 #[tokio::test]
3371 async fn reconnect_resets_state() {
3372 use std::net::SocketAddr;
3373
3374 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
3376 let addr: SocketAddr = listener.local_addr().unwrap();
3377
3378 tokio::spawn(async move {
3380 for _ in 0..2 {
3381 if let Ok((stream, _)) = listener.accept().await {
3382 tokio::spawn(mock_tcp_plc(stream));
3383 }
3384 }
3385 });
3386
3387 let params = ConnectParams::default();
3388 let client = S7Client::<crate::transport::TcpTransport>::connect(addr, params)
3389 .await
3390 .unwrap();
3391
3392 assert!(client.is_connected().await);
3393
3394 tokio::time::sleep(std::time::Duration::from_millis(20)).await;
3396 client.reconnect().await.unwrap();
3397 assert!(client.is_connected().await);
3398 }
3399
3400 async fn mock_tcp_plc(mut stream: tokio::net::TcpStream) {
3402 use tokio::io::{AsyncReadExt, AsyncWriteExt};
3403 let mut buf = vec![0u8; 512];
3404
3405 let _ = stream.read(&mut buf).await;
3407 let cc = CotpPdu::ConnectConfirm { dst_ref: 1, src_ref: 1 };
3408 let mut cb = BytesMut::new();
3409 cc.encode(&mut cb);
3410 let mut tb = BytesMut::new();
3411 TpktFrame { payload: cb.freeze() }.encode(&mut tb).unwrap();
3412 let _ = stream.write_all(&tb).await;
3413
3414 let _ = stream.read(&mut buf).await;
3416 let neg_resp = NegotiateResponse { max_amq_calling: 1, max_amq_called: 1, pdu_length: 480 };
3417 let ack = S7Header {
3418 pdu_type: PduType::AckData,
3419 reserved: 0,
3420 pdu_ref: 1,
3421 param_len: 8,
3422 data_len: 0,
3423 error_class: Some(0),
3424 error_code: Some(0),
3425 };
3426 let mut s7b = BytesMut::new();
3427 ack.encode(&mut s7b);
3428 neg_resp.encode(&mut s7b);
3429 let dt = CotpPdu::Data { tpdu_nr: 0, last: true, payload: s7b.freeze() };
3430 let mut cotpb = BytesMut::new();
3431 dt.encode(&mut cotpb);
3432 let mut tb2 = BytesMut::new();
3433 TpktFrame { payload: cotpb.freeze() }.encode(&mut tb2).unwrap();
3434 let _ = stream.write_all(&tb2).await;
3435 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
3437 }
3438
3439 #[tokio::test]
3440 async fn get_exec_time_after_request() {
3441 let (client_io, server_io) = duplex(4096);
3442 let params = ConnectParams::default();
3443 tokio::spawn(mock_plc_db_read(server_io, vec![0x00, 0x01, 0x02, 0x03]));
3444 let client = S7Client::from_transport(client_io, params).await.unwrap();
3445 client.db_read(1, 0, 4).await.unwrap();
3446 let exec_ms = client.get_exec_time().await;
3447 let _ = exec_ms;
3450 }
3451}