Skip to main content

snap7_client/proto/s7/
write_var.rs

1use bytes::{Buf, BufMut, Bytes, BytesMut};
2
3use crate::proto::error::ProtoError;
4use crate::proto::s7::header::TransportSize;
5use crate::proto::s7::read_var::{AddressItem, ADDR_ANY, ITEM_LEN, ITEM_SPEC};
6
7pub const FUNC_WRITE_VAR: u8 = 0x05;
8
9#[derive(Debug, Clone)]
10pub struct WriteItem {
11    pub address: AddressItem,
12    pub data: Bytes,
13}
14
15#[derive(Debug, Clone)]
16pub struct WriteVarRequest {
17    pub items: Vec<WriteItem>,
18}
19
20#[derive(Debug, Clone)]
21pub struct WriteVarResponse {
22    pub return_codes: Vec<u8>,
23}
24
25impl WriteVarRequest {
26    pub fn decode(buf: &mut Bytes) -> Result<Self, ProtoError> {
27        use crate::proto::s7::header::{Area, TransportSize};
28
29        if buf.len() < 2 {
30            return Err(ProtoError::BufferTooShort {
31                need: 2,
32                have: buf.len(),
33            });
34        }
35        let func = buf.get_u8();
36        if func != FUNC_WRITE_VAR {
37            return Err(ProtoError::UnsupportedFunction(func));
38        }
39        let count = buf.get_u8() as usize;
40
41        // Decode address items
42        let mut addresses = Vec::with_capacity(count);
43        for _ in 0..count {
44            if buf.len() < 12 {
45                return Err(ProtoError::BufferTooShort {
46                    need: 12,
47                    have: buf.len(),
48                });
49            }
50            let spec = buf.get_u8();
51            if spec != ITEM_SPEC {
52                return Err(ProtoError::InvalidMagic {
53                    expected: ITEM_SPEC,
54                    got: spec,
55                });
56            }
57            buf.get_u8(); // ITEM_LEN (reserved)
58            let addr_type = buf.get_u8();
59            if addr_type != ADDR_ANY {
60                return Err(ProtoError::InvalidMagic {
61                    expected: ADDR_ANY,
62                    got: addr_type,
63                });
64            }
65            let transport = TransportSize::try_from(buf.get_u8())?;
66            let length = buf.get_u16();
67            let db_number = buf.get_u16();
68            let area = Area::try_from(buf.get_u8())?;
69            let b0 = buf.get_u8() as u32;
70            let b1 = buf.get_u8() as u32;
71            let b2 = buf.get_u8() as u32;
72            let addr_bits = (b0 << 16) | (b1 << 8) | b2;
73            let start = addr_bits >> 3;
74            let bit_offset = (addr_bits & 0x07) as u8;
75            addresses.push(AddressItem {
76                area,
77                db_number,
78                start,
79                bit_offset,
80                length,
81                transport,
82            });
83        }
84
85        // Decode data items
86        let mut items = Vec::with_capacity(count);
87        for address in addresses {
88            if buf.len() < 4 {
89                return Err(ProtoError::BufferTooShort {
90                    need: 4,
91                    have: buf.len(),
92                });
93            }
94            buf.get_u8(); // reserved
95            buf.get_u8(); // transport size
96            let bit_len = buf.get_u16() as usize;
97            let byte_len = bit_len.div_ceil(8);
98            if buf.len() < byte_len {
99                return Err(ProtoError::BufferTooShort {
100                    need: byte_len,
101                    have: buf.len(),
102                });
103            }
104            let data = buf.copy_to_bytes(byte_len);
105            // consume pad byte if odd length
106            if !byte_len.is_multiple_of(2) && buf.has_remaining() {
107                buf.advance(1);
108            }
109            items.push(WriteItem { address, data });
110        }
111
112        Ok(WriteVarRequest { items })
113    }
114
115    pub fn encode(&self, buf: &mut BytesMut) {
116        buf.put_u8(FUNC_WRITE_VAR);
117        buf.put_u8(self.items.len() as u8);
118        // address items first
119        for item in &self.items {
120            let addr = &item.address;
121            buf.put_u8(ITEM_SPEC);
122            buf.put_u8(ITEM_LEN);
123            buf.put_u8(ADDR_ANY);
124            buf.put_u8(addr.transport as u8);
125            buf.put_u16(addr.length);
126            buf.put_u16(addr.db_number);
127            buf.put_u8(addr.area as u8);
128            let addr_bits = match addr.transport {
129                TransportSize::Timer | TransportSize::Counter => addr.start,
130                _ => (addr.start * 8) | (addr.bit_offset as u32),
131            };
132            buf.put_u8(((addr_bits >> 16) & 0xFF) as u8);
133            buf.put_u8(((addr_bits >> 8) & 0xFF) as u8);
134            buf.put_u8((addr_bits & 0xFF) as u8);
135        }
136        // then data items
137        for item in &self.items {
138            buf.put_u8(0x00); // reserved
139            buf.put_u8(item.address.transport as u8);
140            let bit_len = (item.data.len() * 8) as u16;
141            buf.put_u16(bit_len);
142            buf.put_slice(&item.data);
143            if !item.data.len().is_multiple_of(2) {
144                buf.put_u8(0x00); // pad to even boundary
145            }
146        }
147    }
148}
149
150impl WriteVarResponse {
151    pub fn decode(buf: &mut Bytes, item_count: usize) -> Result<Self, ProtoError> {
152        let mut return_codes = Vec::with_capacity(item_count);
153        for _ in 0..item_count {
154            if !buf.has_remaining() {
155                return Err(ProtoError::BufferTooShort { need: 1, have: 0 });
156            }
157            return_codes.push(buf.get_u8());
158        }
159        Ok(WriteVarResponse { return_codes })
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use bytes::Bytes;
167
168    use crate::proto::s7::header::{Area, TransportSize};
169    use crate::proto::s7::read_var::AddressItem;
170
171    fn make_write_item(db: u16, start: u32, data: &[u8]) -> WriteItem {
172        WriteItem {
173            address: AddressItem {
174                area: Area::DataBlock,
175                db_number: db,
176                start,
177                bit_offset: 0,
178                length: data.len() as u16,
179                transport: TransportSize::Byte,
180            },
181            data: Bytes::copy_from_slice(data),
182        }
183    }
184
185    #[test]
186    fn write_var_response_decode_ok() {
187        let raw: &[u8] = &[0xFF];
188        let mut b = Bytes::copy_from_slice(raw);
189        let resp = WriteVarResponse::decode(&mut b, 1).unwrap();
190        assert_eq!(resp.return_codes[0], 0xFF);
191    }
192
193    #[test]
194    fn write_var_response_decode_multi() {
195        let raw: &[u8] = &[0xFF, 0xFF, 0x05]; // 0x05 = access error
196        let mut b = Bytes::copy_from_slice(raw);
197        let resp = WriteVarResponse::decode(&mut b, 3).unwrap();
198        assert_eq!(resp.return_codes, vec![0xFF, 0xFF, 0x05]);
199    }
200
201    #[test]
202    fn write_var_request_encode_structure() {
203        let req = WriteVarRequest {
204            items: vec![make_write_item(1, 0, &[0xDE, 0xAD])],
205        };
206        let mut buf = BytesMut::new();
207        req.encode(&mut buf);
208        // func=0x05, count=1
209        assert_eq!(buf[0], FUNC_WRITE_VAR);
210        assert_eq!(buf[1], 1);
211        // after 12-byte address item: data header at offset 14
212        // reserved=0x00, transport=Byte(0x02), bit_len=16 (0x0010), data=0xDE 0xAD
213        assert_eq!(buf[14], 0x00); // reserved
214        assert_eq!(buf[15], TransportSize::Byte as u8);
215        assert_eq!(buf[16], 0x00);
216        assert_eq!(buf[17], 0x10); // 16 bits
217        assert_eq!(buf[18], 0xDE);
218        assert_eq!(buf[19], 0xAD);
219    }
220
221    #[test]
222    fn write_var_request_odd_data_padded() {
223        let req = WriteVarRequest {
224            items: vec![make_write_item(1, 0, &[0xAB])], // 1 byte, odd
225        };
226        let mut buf = BytesMut::new();
227        req.encode(&mut buf);
228        // total = 2 (func+count) + 12 (addr) + 4 (data header) + 1 (data) + 1 (pad) = 20
229        assert_eq!(buf.len(), 20);
230        assert_eq!(buf[19], 0x00); // pad byte
231    }
232
233    #[test]
234    fn write_var_response_truncated_returns_err() {
235        let mut b = Bytes::new();
236        assert!(WriteVarResponse::decode(&mut b, 1).is_err());
237    }
238
239    #[test]
240    fn write_var_request_encode_decode_roundtrip() {
241        let item = make_write_item(3, 4, &[0x11, 0x22, 0x33, 0x44]);
242        let req = WriteVarRequest { items: vec![item] };
243        let mut buf = BytesMut::new();
244        req.encode(&mut buf);
245        let mut b = buf.freeze();
246        let decoded = WriteVarRequest::decode(&mut b).unwrap();
247        assert_eq!(decoded.items.len(), 1);
248        assert_eq!(decoded.items[0].address.db_number, 3);
249        assert_eq!(decoded.items[0].address.start, 4);
250        assert_eq!(decoded.items[0].data.as_ref(), &[0x11, 0x22, 0x33, 0x44]);
251    }
252
253    #[test]
254    fn write_var_request_decode_wrong_func_returns_err() {
255        // func=0x04 (ReadVar), not WriteVar
256        let raw: &[u8] = &[0x04, 0x01];
257        let mut b = Bytes::copy_from_slice(raw);
258        assert!(WriteVarRequest::decode(&mut b).is_err());
259    }
260}