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