package modbus import ( "testing" "time" ) func TestTCPServerWithConcurrentConnections(t *testing.T) { var server *ModbusServer var err error var coils []bool var c1 *ModbusClient var c2 *ModbusClient var c3 *ModbusClient var th *tcpTestHandler th = &tcpTestHandler{} server, err = NewServer(&ServerConfiguration{ URL: "tcp://localhost:5502", MaxClients: 2, }, th) if err != nil { t.Errorf("failed to create server: %v", err) } err = server.Start() if err != nil { t.Errorf("failed to start server: %v", err) } // create 3 modbus clients c1, err = NewClient(&ClientConfiguration{ URL: "tcp://localhost:5502", }) if err != nil { t.Errorf("failed to create client: %v", err) } c2, err = NewClient(&ClientConfiguration{ URL: "tcp://localhost:5502", }) if err != nil { t.Errorf("failed to create client: %v", err) } c3, err = NewClient(&ClientConfiguration{ URL: "tcp://localhost:5502", }) if err != nil { t.Errorf("failed to create client: %v", err) } // the server should have zero client connections so far server.lock.Lock() if len(server.tcpClients) != 0 { t.Errorf("expected server.tcpClients to hold 0 entries, got: %v", len(server.tcpClients)) } server.lock.Unlock() // connect client #1 err = c1.Open() if err != nil { t.Errorf("c1.Connect() should have succeeded, got: %v", err) } c1.SetUnitId(9) // the server should have 1 client connection at this point time.Sleep(time.Millisecond) server.lock.Lock() if len(server.tcpClients) != 1 { t.Errorf("expected server.tcpClients to hold 1 entry, got: %v", len(server.tcpClients)) } server.lock.Unlock() // connect client #2 err = c2.Open() if err != nil { t.Errorf("c2.Connect() should have succeeded, got: %v", err) } c2.SetUnitId(9) time.Sleep(time.Millisecond) // the server should now have 2 client connections, its maximum allowed server.lock.Lock() if len(server.tcpClients) != 2 { t.Errorf("expected server.tcpClients to hold 2 entries, got: %v", len(server.tcpClients)) } server.lock.Unlock() // connect client #3 err = c3.Open() if err != nil { t.Errorf("c3.Connect() should have succeeded, got: %v", err) } c3.SetUnitId(9) // since the previous client was rejected, the active connection count // should stay at 2 server.lock.Lock() if len(server.tcpClients) != 2 { t.Errorf("expected server.tcpClients to hold 2 entries, got: %v", len(server.tcpClients)) } server.lock.Unlock() // c1 and c2 should both be able to make requests while c3 should error out // as it has been disconnected (conn closed) coils, err = c1.ReadCoils(0x0000, 2) if err != nil { t.Errorf("c1.ReadCoils() should have succeeded, got: %v", err) } if coils[0] == true || coils[1] == true { t.Errorf("expected {false, false}, got: %v", coils) } coils, err = c2.ReadCoils(0x0003, 5) if err != nil { t.Errorf("c2.ReadCoils() should have succeeded, got: %v", err) } if coils[0] != false || coils[1] != false { t.Errorf("expected {false, false}, got: %v", coils) } _, err = c3.ReadCoil(0x0001) if err == nil { t.Errorf("c3.ReadCoil() should have failed") } // close c2 and make sure the connection is freed c2.Close() time.Sleep(time.Millisecond) server.lock.Lock() if len(server.tcpClients) != 1 { t.Errorf("expected server.tcpClients to hold 1 entry, got: %v", len(server.tcpClients)) } server.lock.Unlock() // reconnect c2 err = c2.Open() if err != nil { t.Errorf("c2.Open should have succeeded, got: %v", err) } // write to the coil at address #1 err = c2.WriteCoil(0x0001, true) if err != nil { t.Errorf("c2.WriteCoil() should have succeeded, got: %v", err) } server.lock.Lock() if len(server.tcpClients) != 2 { t.Errorf("expected server.tcpClients to hold 2 entries, got: %v", len(server.tcpClients)) } server.lock.Unlock() // check the coil value with c1 coils, err = c1.ReadCoils(0x0000, 2) if err != nil { t.Errorf("c1.ReadCoils() should have succeeded, got: %v", err) } if coils[0] != false || coils[1] != true { t.Errorf("expected {false, true}, got: %v", coils) } // close c1 and make sure the connection is freed c1.Close() time.Sleep(time.Millisecond) server.lock.Lock() if len(server.tcpClients) != 1 { t.Errorf("expected server.tcpClients to hold 1 entry, got: %v", len(server.tcpClients)) } server.lock.Unlock() // stopping the server should disconnect all clients server.Stop() time.Sleep(time.Millisecond) server.lock.Lock() if len(server.tcpClients) != 0 { t.Errorf("expected server.tcpClients to hold 0 entries, got: %v", len(server.tcpClients)) } server.lock.Unlock() // c2 should have been disconnected coils, err = c2.ReadCoils(0x0003, 5) if err == nil { t.Errorf("c2.ReadCoils() should have failed") } return } func TestTCPServerCoilsAndDiscreteInputs(t *testing.T) { var server *ModbusServer var err error var coils []bool var dis []bool var client *ModbusClient var th *tcpTestHandler th = &tcpTestHandler{} server, err = NewServer(&ServerConfiguration{ URL: "tcp://localhost:5504", MaxClients: 2, }, th) if err != nil { t.Errorf("failed to create server: %v", err) } err = server.Start() if err != nil { t.Errorf("failed to start server: %v", err) } client, err = NewClient(&ClientConfiguration{ URL: "tcp://localhost:5504", }) if err != nil { t.Errorf("failed to create client: %v", err) } err = client.Open() if err != nil { t.Errorf("client.Open() should have succeeded, got: %v", err) } client.SetUnitId(9) // make sure both coils and discrete inputs are all false/0 coils, err = client.ReadCoils(0x0000, 10) if err != nil { t.Errorf("client.ReadCoils() should have succeeded, got: %v", err) } for i := 0; i < 10; i++ { if coils[i] != false { t.Errorf("expected coil at addr 0x%04x to be false", i) } } dis, err = client.ReadDiscreteInputs(0x0000, 10) if err != nil { t.Errorf("client.ReadDiscreteInputs() should have succeeded, got: %v", err) } for i := 0; i < 10; i++ { if dis[i] != false { t.Errorf("expected discrete input at addr 0x%04x to be false", i) } } // set discrete inputs to random values th.di = [10]bool{ false, false, false, true, false, true, true, true, true, true, } // read the discrete inputs again dis, err = client.ReadDiscreteInputs(0x0000, 10) if err != nil { t.Errorf("client.ReadDiscreteInput() should have succeeded, got: %v", err) } for i, b := range [10]bool{ false, false, false, true, false, true, true, true, true, true, } { if dis[i] != b { t.Errorf("expected discrete input at addr 0x%04x to be %v", i, b) } } // reading past the array size should return ErrIllegalDataAddress _, err = client.ReadDiscreteInputs(0x000a, 1) if err != ErrIllegalDataAddress { t.Errorf("expected ErrIllegalDataAddress, got: %v", err) } _, err = client.ReadCoils(0x000a, 1) if err != ErrIllegalDataAddress { t.Errorf("expected ErrIllegalDataAddress, got: %v", err) } _, err = client.ReadDiscreteInputs(0x8, 3) if err != ErrIllegalDataAddress { t.Errorf("expected ErrIllegalDataAddress, got: %v", err) } _, err = client.ReadCoils(0x8, 3) if err != ErrIllegalDataAddress { t.Errorf("expected ErrIllegalDataAddress, got: %v", err) } // the coils shouldn't have changed coils, err = client.ReadCoils(0x0000, 10) if err != nil { t.Errorf("client.ReadCoils() should have succeeded, got: %v", err) } for i := 0; i < 10; i++ { if coils[i] != false { t.Errorf("expected coil at addr 0x%04x to be false", i) } } // write to a single coil err = client.WriteCoil(0x0004, true) if err != nil { t.Errorf("client.WriteCoil() should have succeeded, got: %v", err) } // make sure it has been written to coils, err = client.ReadCoils(0x0003, 3) if err != nil { t.Errorf("client.ReadCoils() should have succeeded, got: %v", err) } for i, v := range []bool{false, true, false,} { if coils[i] != v { t.Errorf("expected coil at addr 0x%04x to be %v", 3 + i, v) } } // write to multiple coils at once err = client.WriteCoils(0x0005, []bool{ true, false, true, true, }) if err != nil { t.Errorf("client.WriteCoils() should have succeeded, got: %v", err) } // make sure the write went through coils, err = client.ReadCoils(0x0005, 4) if err != nil { t.Errorf("client.ReadCoils() should have succeeded, got: %v", err) } for i, v := range []bool{true, false, true, true,} { if coils[i] != v { t.Errorf("expected coil at addr 0x%04x to be %v", 3 + i, v) } } // switch to another unit ID and make sure both coil and discrete input operations // return ErrIllegalFunction client.SetUnitId(5) err = client.WriteCoils(0x0005, []bool{ true, false, true, true, }) if err != ErrIllegalFunction { t.Errorf("client.WriteCoils() should have returned ErrIllegalFunction, got: %v", err) } err = client.WriteCoil(0x0005, false) if err != ErrIllegalFunction { t.Errorf("client.WriteCoil() should have returned ErrIllegalFunction, got: %v", err) } coils, err = client.ReadCoils(0x0005, 1) if err != ErrIllegalFunction { t.Errorf("client.ReadCoils() should have returned ErrIllegalFunction, got: %v", err) } coils, err = client.ReadDiscreteInputs(0x0005, 1) if err != ErrIllegalFunction { t.Errorf("client.ReadDiscreteInputs() should have returned ErrIllegalFunction, got: %v", err) } client.Close() server.Stop() return } func TestTCPServerHoldingAndInputRegisters(t *testing.T) { var server *ModbusServer var err error var client *ModbusClient var th *tcpTestHandler var regs []uint16 th = &tcpTestHandler{} server, err = NewServer(&ServerConfiguration{ URL: "tcp://localhost:5504", MaxClients: 2, }, th) if err != nil { t.Errorf("failed to create server: %v", err) } err = server.Start() if err != nil { t.Errorf("failed to start server: %v", err) } client, err = NewClient(&ClientConfiguration{ URL: "tcp://localhost:5504", }) if err != nil { t.Errorf("failed to create client: %v", err) } err = client.Open() if err != nil { t.Errorf("client.Open() should have succeeded, got: %v", err) } client.SetUnitId(9) // all 10 input registers should be 0x0000 regs, err = client.ReadRegisters(0x0000, 10, INPUT_REGISTER) if err != nil { t.Errorf("client.ReadRegisters() should have succeeded, got: %v", err) } for i := 0; i < 10; i++ { if regs[i] != 0x0000 { t.Errorf("expected 0x0000 at position %v, got: 0x%04x", i, regs[i]) } } // assign some values to the handler's input registers for i := range th.input { th.input[i] = 0xa710 + uint16(i) } regs, err = client.ReadRegisters(0x0000, 10, INPUT_REGISTER) if err != nil { t.Errorf("client.ReadRegisters() should have succeeded, got: %v", err) } for i := 0; i < 10; i++ { if regs[i] != 0xa710 + uint16(i) { t.Errorf("expected 0x%04x at position %v, got: 0x%04x", 0xa710 + uint16(i), i, regs[i]) } } // reading addr 0x0009 (the very last register) should succeed regs, err = client.ReadRegisters(0x0009, 1, INPUT_REGISTER) if err != nil { t.Errorf("client.ReadRegisters() should have succeeded, got: %v", err) } if regs[0] != 0xa719 { t.Errorf("expected 0xa719 at address 9, saw: 0x%04x", regs[0]) } // reading past address 0x000a should fail regs, err = client.ReadRegisters(0x0001, 10, INPUT_REGISTER) if err != ErrIllegalDataAddress { t.Errorf("client.ReadRegisters() should have returned ErrIllegalDataAddress, got: %v", err) } regs, err = client.ReadRegisters(0x0000, 11, INPUT_REGISTER) if err != ErrIllegalDataAddress { t.Errorf("client.ReadRegisters() should have returned ErrIllegalDataAddress, got: %v", err) } // all 10 holding registers should still be 0x0000 regs, err = client.ReadRegisters(0x0000, 10, HOLDING_REGISTER) if err != nil { t.Errorf("client.ReadRegisters() should have succeeded, got: %v", err) } for i := 0; i < 10; i++ { if regs[i] != 0x0000 { t.Errorf("expected 0x0000 at position %v, got: 0x%04x", i, regs[i]) } } // write to a single valid register (with opcode 0x06) err = client.WriteRegister(0x0007, 0xfea1) if err != nil { t.Errorf("client.WriteRegister() should have succeeded, got: %v", err) } // make sure it has been written to regs, err = client.ReadRegisters(0x0005, 5, HOLDING_REGISTER) if err != nil { t.Errorf("client.ReadRegisters() should have succeeded, got: %v", err) } for i := 0; i < 5; i++ { if i != 2 && regs[i] != 0x0000 { t.Errorf("expected 0x0000 at position %v, got: 0x%04x", i, regs[i]) } if i == 2 && regs[i] != 0xfea1 { t.Errorf("expected 0xfea1 at position %v, got: 0x%04x", i, regs[i]) } } // check values in the handler as well for i := 0; i < 10; i++ { if i != 7 && th.holding[i] != 0x0000 { t.Errorf("expected 0x0000 at handler index %v, got: 0x%04x", i, regs[i]) } if i == 7 && th.holding[i] != 0xfea1 { t.Errorf("expected 0xfea1 at handler index %v, got: 0x%04x", i, regs[i]) } } // write multiple registers at once (with function code 0x10) err = client.WriteRegisters(0x0001, []uint16{ 0x0c11, 0x0c22, 0x0c33, 0x0c44, 0x0c55, 0x0c66, 0x0c77, 0x0c88, 0x0c99, }) if err != nil { t.Errorf("client.WriteRegisters() should have succeeded, got: %v", err) } // write to a single valid register (with opcode 0x06) err = client.WriteRegister(0x0000, 0x0c00) if err != nil { t.Errorf("client.WriteRegister() should have succeeded, got: %v", err) } // make sure they have all been written to regs, err = client.ReadRegisters(0x0000, 10, HOLDING_REGISTER) if err != nil { t.Errorf("client.ReadRegisters() should have succeeded, got: %v", err) } for i := 0; i < 10; i++ { if regs[i] != 0x0c00 + uint16(0x11 * i) { t.Errorf("expected ox%04x at position %v, got: 0x%04x", 0x0c00 + uint16(0x11 * i), i, regs[i]) } } // check values in the handler as well for i := 0; i < 10; i++ { if th.holding[i] != 0x0c00 + uint16(0x11 * i) { t.Errorf("expected 0xfea1 at handler index %v, got: 0x%04x", i, regs[i]) } } // reading addr 0x0009 (the very last register) should succeed regs, err = client.ReadRegisters(0x0009, 1, HOLDING_REGISTER) if err != nil { t.Errorf("client.ReadRegisters() should have succeeded, got: %v", err) } if regs[0] != 0x0c99 { t.Errorf("expected 0x0c99 at address 9, saw: 0x%04x", regs[0]) } // reading past address 0x000a should fail regs, err = client.ReadRegisters(0x0001, 10, HOLDING_REGISTER) if err != ErrIllegalDataAddress { t.Errorf("client.ReadRegisters() should have returned ErrIllegalDataAddress, got: %v", err) } regs, err = client.ReadRegisters(0x0000, 11, HOLDING_REGISTER) if err != ErrIllegalDataAddress { t.Errorf("client.ReadRegisters() should have returned ErrIllegalDataAddress, got: %v", err) } // switch to another unit ID and make sure both holding and input register operations // return ErrIllegalFunction client.SetUnitId(2) err = client.WriteRegisters(0x0005, []uint16{ 0x0000, 0x0001, }) if err != ErrIllegalFunction { t.Errorf("client.WriteRegisters() should have returned ErrIllegalFunction, got: %v", err) } err = client.WriteRegister(0x0001, 0xffff) if err != ErrIllegalFunction { t.Errorf("client.WriteRegister() should have returned ErrIllegalFunction, got: %v", err) } regs, err = client.ReadRegisters(0x0005, 1, HOLDING_REGISTER) if err != ErrIllegalFunction { t.Errorf("client.ReadRegisters() should have returned ErrIllegalFunction, got: %v", err) } regs, err = client.ReadRegisters(0x0005, 1, INPUT_REGISTER) if err != ErrIllegalFunction { t.Errorf("client.ReadRegisters() should have returned ErrIllegalFunction, got: %v", err) } client.Close() server.Stop() return } type tcpTestHandler struct { coils [10]bool di [10]bool input [10]uint16 holding [10]uint16 } func (th *tcpTestHandler) HandleCoils(req *CoilsRequest) (res []bool, err error) { if req.UnitId != 9 { // only reply to unit ID #9 err = ErrIllegalFunction return } if req.Addr + req.Quantity > uint16(len(th.coils)) { err = ErrIllegalDataAddress return } for i := 0; i < int(req.Quantity); i++ { if req.IsWrite { th.coils[int(req.Addr) + i] = req.Args[i] } res = append(res, th.coils[int(req.Addr) + i]) } return } func (th *tcpTestHandler) HandleDiscreteInputs(req *DiscreteInputsRequest) (res []bool, err error) { if req.UnitId != 9 { // only reply to unit ID #9 err = ErrIllegalFunction return } if req.Addr + req.Quantity > uint16(len(th.di)) { err = ErrIllegalDataAddress return } for i := 0; i < int(req.Quantity); i++ { res = append(res, th.di[int(req.Addr) + i]) } return } func (th *tcpTestHandler) HandleHoldingRegisters(req *HoldingRegistersRequest) (res []uint16, err error) { if req.UnitId != 9 { // only reply to unit ID #9 err = ErrIllegalFunction return } if req.Addr + req.Quantity > uint16(len(th.holding)) { err = ErrIllegalDataAddress return } for i := 0; i < int(req.Quantity); i++ { if req.IsWrite { th.holding[int(req.Addr) + i] = req.Args[i] } res = append(res, th.holding[int(req.Addr) + i]) } return } func (th *tcpTestHandler) HandleInputRegisters(req *InputRegistersRequest) (res []uint16, err error) { if req.UnitId != 9 { // only reply to unit ID #9 err = ErrIllegalFunction return } if req.Addr + req.Quantity > uint16(len(th.input)) { err = ErrIllegalDataAddress return } for i := 0; i < int(req.Quantity); i++ { res = append(res, th.input[int(req.Addr) + i]) } return }