Skip to content

Commit 8b04b2f

Browse files
authored
Fix panic on disconnect() (#261)
* Add debug assertions * Fix panic on disconnect() * Update changelog
1 parent 13fcb83 commit 8b04b2f

File tree

8 files changed

+90
-43
lines changed

8 files changed

+90
-43
lines changed

CHANGELOG.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@
33

44
# Changelog
55

6-
## v0.13.0 (2024-06-23)
6+
## v0.13.1 (Unreleased)
7+
8+
- Fix: Do not panic on disconnect.
9+
10+
### Breaking Changes
11+
12+
- Add `FunctionCode::Disconnect`.
13+
14+
## v0.13.0 (2024-06-23, yanked)
715

816
- Fix: Do not panic on mismatching request/response function codes.
917

@@ -12,7 +20,7 @@
1220
- Client: Replace `std::io::Error` with a custom error type that handles both
1321
protocol and transport errors.
1422

15-
## v0.12.0 (2024-03-21)
23+
## v0.12.0 (2024-03-21, yanked)
1624

1725
- Client: Support handling of _Modbus_ exceptions by using nested `Result`s.
1826

examples/rtu-client.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
1717

1818
let mut ctx = rtu::attach_slave(port, slave);
1919
println!("Reading a sensor value");
20-
let rsp = ctx.read_holding_registers(0x082B, 2).await?;
20+
let rsp = ctx.read_holding_registers(0x082B, 2).await??;
2121
println!("Sensor value is: {rsp:?}");
2222

23+
println!("Disconnecting");
24+
ctx.disconnect().await??;
25+
2326
Ok(())
2427
}

examples/tcp-client.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2222
let id = String::from_utf8(bytes).unwrap();
2323
println!("The coupler ID is '{id}'");
2424

25+
println!("Disconnecting");
26+
ctx.disconnect().await??;
27+
2528
Ok(())
2629
}

src/codec/rtu.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,12 @@ impl<'a> Encoder<RequestAdu<'a>> for ClientCodec {
329329
type Error = Error;
330330

331331
fn encode(&mut self, adu: RequestAdu<'a>, buf: &mut BytesMut) -> Result<()> {
332-
if adu.disconnect {
332+
let RequestAdu {
333+
hdr,
334+
pdu,
335+
disconnect,
336+
} = adu;
337+
if disconnect {
333338
// The disconnect happens implicitly after letting this request
334339
// fail by returning an error. This will drop the attached
335340
// transport, e.g. for closing a stale, exclusive connection
@@ -339,7 +344,6 @@ impl<'a> Encoder<RequestAdu<'a>> for ClientCodec {
339344
"Disconnecting - not an error",
340345
));
341346
}
342-
let RequestAdu { hdr, pdu, .. } = adu;
343347
let pdu_data: Bytes = pdu.try_into()?;
344348
buf.reserve(pdu_data.len() + 3);
345349
buf.put_u8(hdr.slave_id);

src/codec/tcp.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,12 @@ impl<'a> Encoder<RequestAdu<'a>> for ClientCodec {
137137
type Error = Error;
138138

139139
fn encode(&mut self, adu: RequestAdu<'a>, buf: &mut BytesMut) -> Result<()> {
140-
if adu.disconnect {
140+
let RequestAdu {
141+
hdr,
142+
pdu,
143+
disconnect,
144+
} = adu;
145+
if disconnect {
141146
// The disconnect happens implicitly after letting this request
142147
// fail by returning an error. This will drop the attached
143148
// transport, e.g. for terminating an open connection.
@@ -146,7 +151,6 @@ impl<'a> Encoder<RequestAdu<'a>> for ClientCodec {
146151
"Disconnecting - not an error",
147152
));
148153
}
149-
let RequestAdu { hdr, pdu, .. } = adu;
150154
let pdu_data: Bytes = pdu.try_into()?;
151155
buf.reserve(pdu_data.len() + 7);
152156
buf.put_u16(hdr.transaction_id);

src/frame/mod.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,42 +46,49 @@ pub enum FunctionCode {
4646

4747
/// Custom Modbus Function Code.
4848
Custom(u8),
49+
50+
Disconnect,
4951
}
5052

5153
impl FunctionCode {
5254
/// Create a new [`FunctionCode`] with `value`.
5355
#[must_use]
5456
pub const fn new(value: u8) -> Self {
5557
match value {
56-
0x01 => FunctionCode::ReadCoils,
57-
0x02 => FunctionCode::ReadDiscreteInputs,
58-
0x05 => FunctionCode::WriteSingleCoil,
59-
0x06 => FunctionCode::WriteSingleRegister,
60-
0x03 => FunctionCode::ReadHoldingRegisters,
61-
0x04 => FunctionCode::ReadInputRegisters,
62-
0x0F => FunctionCode::WriteMultipleCoils,
63-
0x10 => FunctionCode::WriteMultipleRegisters,
64-
0x16 => FunctionCode::MaskWriteRegister,
65-
0x17 => FunctionCode::ReadWriteMultipleRegisters,
66-
code => FunctionCode::Custom(code),
58+
0x01 => Self::ReadCoils,
59+
0x02 => Self::ReadDiscreteInputs,
60+
0x05 => Self::WriteSingleCoil,
61+
0x06 => Self::WriteSingleRegister,
62+
0x03 => Self::ReadHoldingRegisters,
63+
0x04 => Self::ReadInputRegisters,
64+
0x0F => Self::WriteMultipleCoils,
65+
0x10 => Self::WriteMultipleRegisters,
66+
0x16 => Self::MaskWriteRegister,
67+
0x17 => Self::ReadWriteMultipleRegisters,
68+
code => Self::Custom(code),
6769
}
6870
}
6971

7072
/// Get the [`u8`] value of the current [`FunctionCode`].
73+
///
74+
/// # Panics
75+
///
76+
/// Panics on [`Disconnect`](Self::Disconnect) which has no corresponding Modbus function code.
7177
#[must_use]
7278
pub const fn value(self) -> u8 {
7379
match self {
74-
FunctionCode::ReadCoils => 0x01,
75-
FunctionCode::ReadDiscreteInputs => 0x02,
76-
FunctionCode::WriteSingleCoil => 0x05,
77-
FunctionCode::WriteSingleRegister => 0x06,
78-
FunctionCode::ReadHoldingRegisters => 0x03,
79-
FunctionCode::ReadInputRegisters => 0x04,
80-
FunctionCode::WriteMultipleCoils => 0x0F,
81-
FunctionCode::WriteMultipleRegisters => 0x10,
82-
FunctionCode::MaskWriteRegister => 0x16,
83-
FunctionCode::ReadWriteMultipleRegisters => 0x17,
84-
FunctionCode::Custom(code) => code,
80+
Self::ReadCoils => 0x01,
81+
Self::ReadDiscreteInputs => 0x02,
82+
Self::WriteSingleCoil => 0x05,
83+
Self::WriteSingleRegister => 0x06,
84+
Self::ReadHoldingRegisters => 0x03,
85+
Self::ReadInputRegisters => 0x04,
86+
Self::WriteMultipleCoils => 0x0F,
87+
Self::WriteMultipleRegisters => 0x10,
88+
Self::MaskWriteRegister => 0x16,
89+
Self::ReadWriteMultipleRegisters => 0x17,
90+
Self::Custom(code) => code,
91+
Self::Disconnect => unreachable!(),
8592
}
8693
}
8794
}

src/service/rtu.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,36 @@ where
4848
}
4949

5050
async fn call(&mut self, req: Request<'_>) -> Result<Response> {
51-
let disconnect = req == Request::Disconnect;
52-
let req_function_code = req.function_code();
53-
let req_adu = self.next_request_adu(req, disconnect);
51+
let req_function_code = if matches!(req, Request::Disconnect) {
52+
None
53+
} else {
54+
Some(req.function_code())
55+
};
56+
let req_adu = self.next_request_adu(req, req_function_code.is_none());
5457
let req_hdr = req_adu.hdr;
5558

5659
self.framed.read_buffer_mut().clear();
57-
5860
self.framed.send(req_adu).await?;
61+
5962
let res_adu = self
6063
.framed
6164
.next()
6265
.await
6366
.unwrap_or_else(|| Err(io::Error::from(io::ErrorKind::BrokenPipe)))?;
64-
65-
let ResponsePdu(result) = res_adu.pdu;
67+
let ResponseAdu {
68+
hdr: res_hdr,
69+
pdu: res_pdu,
70+
} = res_adu;
71+
let ResponsePdu(result) = res_pdu;
6672

6773
// Match headers of request and response.
68-
if let Err(message) = verify_response_header(&req_hdr, &res_adu.hdr) {
74+
if let Err(message) = verify_response_header(&req_hdr, &res_hdr) {
6975
return Err(ProtocolError::HeaderMismatch { message, result }.into());
7076
}
7177

78+
debug_assert!(req_function_code.is_some());
79+
let req_function_code = req_function_code.unwrap();
80+
7281
// Match function codes of request and response.
7382
let rsp_function_code = match &result {
7483
Ok(response) => response.function_code(),

src/service/tcp.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,27 +71,36 @@ where
7171

7272
pub(crate) async fn call(&mut self, req: Request<'_>) -> Result<Response> {
7373
log::debug!("Call {:?}", req);
74-
let disconnect = req == Request::Disconnect;
75-
let req_function_code = req.function_code();
76-
let req_adu = self.next_request_adu(req, disconnect);
74+
let req_function_code = if matches!(req, Request::Disconnect) {
75+
None
76+
} else {
77+
Some(req.function_code())
78+
};
79+
let req_adu = self.next_request_adu(req, req_function_code.is_none());
7780
let req_hdr = req_adu.hdr;
7881

7982
self.framed.read_buffer_mut().clear();
80-
8183
self.framed.send(req_adu).await?;
84+
8285
let res_adu = self
8386
.framed
8487
.next()
8588
.await
8689
.ok_or_else(io::Error::last_os_error)??;
87-
88-
let ResponsePdu(result) = res_adu.pdu;
90+
let ResponseAdu {
91+
hdr: res_hdr,
92+
pdu: res_pdu,
93+
} = res_adu;
94+
let ResponsePdu(result) = res_pdu;
8995

9096
// Match headers of request and response.
91-
if let Err(message) = verify_response_header(&req_hdr, &res_adu.hdr) {
97+
if let Err(message) = verify_response_header(&req_hdr, &res_hdr) {
9298
return Err(ProtocolError::HeaderMismatch { message, result }.into());
9399
}
94100

101+
debug_assert!(req_function_code.is_some());
102+
let req_function_code = req_function_code.unwrap();
103+
95104
// Match function codes of request and response.
96105
let rsp_function_code = match &result {
97106
Ok(response) => response.function_code(),

0 commit comments

Comments
 (0)