Files
modbus/tcp_transport_test.go
2025-11-07 13:53:18 +08:00

374 lines
9.6 KiB
Go

package modbus
import (
"io"
"net"
"testing"
"time"
)
func TestAssembleMBAPFrame(t *testing.T) {
var tt *tcpTransport
var frame []byte
tt = &tcpTransport{}
frame = tt.assembleMBAPFrame(0x9219, &pdu{
unitId: 0x33,
functionCode: 0x11,
payload: []byte{0x22, 0x33, 0x44, 0x55},
})
// expect 7 bytes of MBAP header + 1 bytes of function code + 4 bytes of payload
if len(frame) != 12 {
t.Errorf("expected 12 bytes, got %v", len(frame))
}
for i, b := range []byte{
0x92, 0x19, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x06, // length (big endian)
0x33, 0x11, // unit id and function code
0x22, 0x33, // payload
0x44, 0x55, // payload
} {
if frame[i] != b {
t.Errorf("expected 0x%02x at position %v, got 0x%02x", b, i, frame[i])
}
}
frame = tt.assembleMBAPFrame(0x921a, &pdu{
unitId: 0x31,
functionCode: 0x06,
payload: []byte{0x12, 0x34},
})
// expect 7 bytes of MBAP header + 1 bytes of function code + 2 bytes of payload
if len(frame) != 10 {
t.Errorf("expected 10 bytes, got %v", len(frame))
}
for i, b := range []byte{
0x92, 0x1a, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x04, // length (big endian)
0x31, 0x06, // unit id and function code
0x12, 0x34, // payload
} {
if frame[i] != b {
t.Errorf("expected 0x%02x at position %v, got 0x%02x", b, i, frame[i])
}
}
return
}
func TestTCPTransportReadResponse(t *testing.T) {
var tt *tcpTransport
var p1, p2 net.Conn
var txchan chan []byte
var err error
var res *pdu
txchan = make(chan []byte, 2)
p1, p2 = net.Pipe()
go feedTestPipe(t, txchan, p1)
tt = newTCPTransport(p2, 10 * time.Millisecond, nil)
tt.lastTxnId = 0x9218
// read a valid response
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x04, // length (big endian)
0x31, 0x06, // unit id and function code
0x12, 0x34, // payload
}
res, err = tt.readResponse()
if err != nil {
t.Errorf("readResponse() should have succeeded, got %v", err)
}
if res.unitId != 0x31 {
t.Errorf("expected 0x31 as unit id, got 0x%02x", res.unitId)
}
if res.functionCode != 0x06 {
t.Errorf("expected 0x06 as function code, got 0x%02x", res.functionCode)
}
if len(res.payload) != 2 {
t.Errorf("expected a length of 2, got %v", len(res.payload))
}
if res.payload[0] != 0x12 || res.payload[1] != 0x34 {
t.Errorf("expected {0x12, 0x34} as payload, got {0x%02x, 0x%02x}",
res.payload[0], res.payload[1])
}
// read a frame with an unexpected transaction id followed by a frame with a
// matching transaction id: the first frame should be silently skipped
txchan <- []byte{
0x92, 0x19, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x04, // length (big endian)
0x31, 0x06, // unit id and function code
0x12, 0x34, // payload
}
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x04, // length (big endian)
0x39, 0x02, // unit id and function code
0x10, 0x01, // payload
}
res, err = tt.readResponse()
if err != nil {
t.Errorf("readResponse() should have succeeded, got %v", err)
}
if res.unitId != 0x39 {
t.Errorf("expected 0x39 as unit id, got 0x%02x", res.unitId)
}
if res.functionCode != 0x02 {
t.Errorf("expected 0x02 as function code, got 0x%02x", res.functionCode)
}
if len(res.payload) != 2 {
t.Errorf("expected a length of 2, got %v", len(res.payload))
}
if res.payload[0] != 0x10 || res.payload[1] != 0x01 {
t.Errorf("expected {0x10, 0x01 as payload, got {0x%02x, 0x%02x}",
res.payload[0], res.payload[1])
}
// read a frame with an illegal length, preceded by a frame with an unexpected
// protocol ID. While the first frame should be skipped without error,
// the second should yield an ErrProtocolError.
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x01, // protocol identifier
0x00, 0x04, // length (big endian)
0x31, 0x06, // unit id and function code
0x12, 0x34, // payload
}
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x01, // length (big endian)
0x31, // unit id
}
res, err = tt.readResponse()
if err != ErrProtocolError {
t.Errorf("readResponse() should have returned ErrProtocolError, got %v", err)
}
// read a valid frame again
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x0a, // length (big endian)
0x31, 0x32, // unit id and function code
0x44, 0x55, // payload
0x66, 0x77, // payload
0x88, 0x99, // payload
0xaa, 0xbb, // payload
}
res, err = tt.readResponse()
if err != nil {
t.Errorf("readResponse() should have succeeded, got %v", err)
}
if res.unitId != 0x31 {
t.Errorf("expected 0x31 as unit id, got 0x%02x", res.unitId)
}
if res.functionCode != 0x32 {
t.Errorf("expected 0x32 as response code, got 0x%02x", res.functionCode)
}
if len(res.payload) != 8 {
t.Errorf("expected a length of 8, got %v", len(res.payload))
}
for i, b := range []byte{
0x44, 0x55,
0x66, 0x77,
0x88, 0x99,
0xaa, 0xbb,
} {
if res.payload[i] != b {
t.Errorf("expected 0x%02x at position %v, got 0x%02x",
b, i, res.payload[i])
}
}
// read a huge frame
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x10, 0x0a, // length (big endian)
0x31, // unit id
}
res, err = tt.readResponse()
if err != ErrProtocolError {
t.Errorf("readResponse() should have returned ErrProtocolError, got %v", err)
}
p1.Close()
p2.Close()
return
}
func TestTCPTransportReadRequest(t *testing.T) {
var tt *tcpTransport
var p1, p2 net.Conn
var txchan chan []byte
var err error
var req *pdu
txchan = make(chan []byte, 2)
p1, p2 = net.Pipe()
go feedTestPipe(t, txchan, p1)
tt = newTCPTransport(p2, 10 * time.Millisecond, nil)
tt.lastTxnId = 0x0a00
// push three frames in a row:
// - the first with an unknown protocol ID
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x01, // protocol identifier
0x00, 0x04, // length (big endian)
0x31, 0x06, // unit id and function code
0x12, 0x34, // payload
}
// - the second with an illegal length
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x01, // length (big endian)
0x31, // unit id
}
// - the thid with a valid request
txchan <- []byte{
0x92, 0x18, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x0a, // length (big endian)
0xfa, 0x04, // unit id and function code
0x44, 0x55, // payload
0x66, 0x77, // payload
0x88, 0x99, // payload
0xaa, 0xbb, // payload
}
// read the first frame
req, err = tt.ReadRequest()
if req != nil || err != ErrUnknownProtocolId {
t.Errorf("ReadRequest() should have returned {nil, ErrUnknownProtocolId}, got {%v, %v}", req, err)
}
if tt.lastTxnId != 0x0a00 {
t.Errorf("tt.lastTxnId should have been 0x0a00, saw 0x%02x", tt.lastTxnId)
}
// read the second frame
req, err = tt.ReadRequest()
if req != nil || err != ErrProtocolError {
t.Errorf("ReadRequest() should have returned {nil, ErrProtocolError}, got {%v, %v}", req, err)
}
if tt.lastTxnId != 0x0a00 {
t.Errorf("tt.lastTxnId should have been 0x0a00, saw 0x%02x", tt.lastTxnId)
}
// read the third frame
req, err = tt.ReadRequest()
if err != nil {
t.Errorf("ReadRequest() should have succeeded, got %v", err)
}
if req == nil {
t.Errorf("ReadREsponse() should have returned a non-nil request")
}
if req.unitId != 0xfa {
t.Errorf("expected 0xfa as unit id, got 0x%02x", req.unitId)
}
if req.functionCode != 0x04 {
t.Errorf("expected 0x04 as response code, got 0x%02x", req.functionCode)
}
if len(req.payload) != 8 {
t.Errorf("expected a length of 8, got %v", len(req.payload))
}
for i, b := range []byte{
0x44, 0x55,
0x66, 0x77,
0x88, 0x99,
0xaa, 0xbb,
} {
if req.payload[i] != b {
t.Errorf("expected 0x%02x at position %v, got 0x%02x",
b, i, req.payload[i])
}
}
if tt.lastTxnId != 0x9218 {
t.Errorf("tt.lastTxnId should have been 0x0a00, saw 0x%02x", tt.lastTxnId)
}
return
}
func TestTCPTransportWriteResponse(t *testing.T) {
var tt *tcpTransport
var p1, p2 net.Conn
var done chan bool
var err error
done = make(chan bool, 0)
p1, p2 = net.Pipe()
go func(t *testing.T, pipe net.Conn, done chan bool) {
var err error
var rxbuf []byte
var expected []byte
expected = []byte{
0xc0, 0x1f, // transaction identifier (big endian)
0x00, 0x00, // protocol identifier
0x00, 0x0b, // length (big endian)
0x17, 0x06, // unit id and function code
0x44, 0x55, // payload
0x66, 0x77, // payload
0x88, 0x99, // payload
0xaa, 0xbb, // payload
0xf4, // payload
}
rxbuf = make([]byte, len(expected))
_, err = io.ReadFull(pipe, rxbuf)
if err != nil {
t.Errorf("failed to read frame: %v", err)
}
for i, b := range expected {
if rxbuf[i] != b {
t.Errorf("expected 0x%02x at position %v, got 0x%02x",
b, i, rxbuf[i])
}
}
done<- true
return
}(t, p2, done)
tt = newTCPTransport(p1, 10 * time.Millisecond, nil)
tt.lastTxnId = 0xc01f
err = tt.WriteResponse(&pdu{
unitId: 0x17,
functionCode: 0x06,
payload: []byte{
0x44, 0x55, // payload
0x66, 0x77, // payload
0x88, 0x99, // payload
0xaa, 0xbb, // payload
0xf4, // payload
},
})
if err != nil {
t.Errorf("WriteResponse() should have succeeded, got %v", err)
}
// wait for the checker goroutine to return
<-done
return
}