From d5317b598bb8925692fed31892c6a942277dec86 Mon Sep 17 00:00:00 2001 From: twystd Date: Mon, 27 May 2024 10:26:01 -0700 Subject: [PATCH] Reworked set-listener message and API function to use netip.AddrPort --- Makefile | 2 +- messages/set_listener.go | 5 +- messages/set_listener_test.go | 15 ++--- types/listener.go | 26 ++++---- uhppote/errors.go | 8 +++ uhppote/iuhppote.go | 9 ++- uhppote/set_listener.go | 32 +++++----- uhppote/set_listener_test.go | 110 +++++++++++++++++++++++++++++++--- 8 files changed, 157 insertions(+), 50 deletions(-) create mode 100644 uhppote/errors.go diff --git a/Makefile b/Makefile index 81887b6..1095fc8 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ publish: release gh release create "$(VERSION)" --draft --prerelease --title "$(VERSION)-beta" --notes-file release-notes.md debug: build - go test ./... -run TestControllerAddrMarshalJSON + go test -v ./... -run TestSetListener godoc: godoc -http=:80 -index_interval=60s diff --git a/messages/set_listener.go b/messages/set_listener.go index 8a53a1b..c2a8e86 100644 --- a/messages/set_listener.go +++ b/messages/set_listener.go @@ -2,14 +2,13 @@ package messages import ( "github.com/uhppoted/uhppote-core/types" - "net" + "net/netip" ) type SetListenerRequest struct { MsgType types.MsgType `uhppote:"value:0x90"` SerialNumber types.SerialNumber `uhppote:"offset:4"` - Address net.IP `uhppote:"offset:8"` - Port uint16 `uhppote:"offset:12"` + AddrPort netip.AddrPort `uhppote:"offset:8"` } type SetListenerResponse struct { diff --git a/messages/set_listener_test.go b/messages/set_listener_test.go index 674eda7..b2d8783 100644 --- a/messages/set_listener_test.go +++ b/messages/set_listener_test.go @@ -2,7 +2,7 @@ package messages import ( codec "github.com/uhppoted/uhppote-core/encoding/UTO311-L0x" - "net" + "net/netip" "reflect" "testing" ) @@ -17,16 +17,12 @@ func TestMarshalSetListenerRequest(t *testing.T) { request := SetListenerRequest{ SerialNumber: 423187757, - Address: net.IPv4(192, 168, 1, 100), - Port: 40000, + AddrPort: netip.MustParseAddrPort("192.168.1.100:40000"), } - m, err := codec.Marshal(request) - if err != nil { + if m, err := codec.Marshal(request); err != nil { t.Fatalf("Unexpected error: %v", err) - } - - if !reflect.DeepEqual(m, expected) { + } else if !reflect.DeepEqual(m, expected) { t.Errorf("Invalid byte array:\nExpected:\n%s\nReturned:\n%s", dump(expected, ""), dump(m, "")) } } @@ -42,8 +38,7 @@ func TestFactoryUnmarshalSetListenerRequest(t *testing.T) { expected := SetListenerRequest{ MsgType: 0x90, SerialNumber: 423187757, - Address: net.IPv4(192, 168, 1, 100), - Port: 40000, + AddrPort: netip.MustParseAddrPort("192.168.1.100:40000"), } request, err := UnmarshalRequest(message) diff --git a/types/listener.go b/types/listener.go index e0c8f90..3b72aef 100644 --- a/types/listener.go +++ b/types/listener.go @@ -1,16 +1,16 @@ package types -import ( - "fmt" - "net" -) - +// import ( +// "fmt" +// "net" +// ) +// // FIXME remove (unused) -type Listener struct { - SerialNumber SerialNumber - Address net.UDPAddr -} - -func (l *Listener) String() string { - return fmt.Sprintf("%v %v", l.SerialNumber, l.Address) -} +// type Listener struct { +// SerialNumber SerialNumber +// Address net.UDPAddr +// } +// +// func (l *Listener) String() string { +// return fmt.Sprintf("%v %v", l.SerialNumber, l.Address) +// } diff --git a/uhppote/errors.go b/uhppote/errors.go new file mode 100644 index 0000000..11ddd85 --- /dev/null +++ b/uhppote/errors.go @@ -0,0 +1,8 @@ +package uhppote + +import ( + "errors" +) + +var ErrInvalidListenerAddress = errors.New("invalid listener address") +var ErrIncorrectController = errors.New("response from incorrect controller") diff --git a/uhppote/iuhppote.go b/uhppote/iuhppote.go index 5263d4e..9f6ab20 100644 --- a/uhppote/iuhppote.go +++ b/uhppote/iuhppote.go @@ -15,7 +15,14 @@ type IUHPPOTE interface { SetAddress(deviceID uint32, address, mask, gateway net.IP) (*types.Result, error) GetListener(deviceID uint32) (netip.AddrPort, error) - SetListener(deviceID uint32, address net.UDPAddr) (*types.Result, error) + + // Sets the controller event listener address:port. + // + // The address must be either a valid IPv4 address and the port may not be 0 or + // 0.0.0.0:0. + // Returns true if the controller event listener address was set. + SetListener(deviceID uint32, address netip.AddrPort) (bool, error) + GetTime(deviceID uint32) (*types.Time, error) SetTime(deviceID uint32, datetime time.Time) (*types.Time, error) GetDoorControlState(deviceID uint32, door byte) (*types.DoorControlState, error) diff --git a/uhppote/set_listener.go b/uhppote/set_listener.go index 2aa39cf..00b6d52 100644 --- a/uhppote/set_listener.go +++ b/uhppote/set_listener.go @@ -2,33 +2,35 @@ package uhppote import ( "fmt" - "net" + "net/netip" "github.com/uhppoted/uhppote-core/messages" "github.com/uhppoted/uhppote-core/types" ) -func (u *uhppote) SetListener(serialNumber uint32, address net.UDPAddr) (*types.Result, error) { - if serialNumber == 0 { - return nil, fmt.Errorf("invalid device ID (%v)", serialNumber) +func (u *uhppote) SetListener(controller uint32, address netip.AddrPort) (bool, error) { + if controller == 0 { + return false, fmt.Errorf("invalid device ID (%v)", controller) } - if address.IP.To4() == nil { - return nil, fmt.Errorf("invalid IP address: %v", address) + if !address.IsValid() { + return false, ErrInvalidListenerAddress + } + + if (address != netip.MustParseAddrPort("0.0.0.0:0")) && (!address.Addr().Is4() || (address.Port() == 0)) { + return false, fmt.Errorf("invalid listener address: %v", address) } request := messages.SetListenerRequest{ - SerialNumber: types.SerialNumber(serialNumber), - Address: address.IP, - Port: uint16(address.Port), + SerialNumber: types.SerialNumber(controller), + AddrPort: address, } - if reply, err := sendto[messages.SetListenerResponse](u, serialNumber, request); err != nil { - return nil, err + if reply, err := sendto[messages.SetListenerResponse](u, controller, request); err != nil { + return false, err + } else if uint32(reply.SerialNumber) != controller { + return false, ErrIncorrectController } else { - return &types.Result{ - SerialNumber: reply.SerialNumber, - Succeeded: reply.Succeeded, - }, nil + return reply.Succeeded, nil } } diff --git a/uhppote/set_listener_test.go b/uhppote/set_listener_test.go index 328f3ca..926844a 100644 --- a/uhppote/set_listener_test.go +++ b/uhppote/set_listener_test.go @@ -1,20 +1,116 @@ package uhppote import ( + "errors" + "fmt" "net" + "net/netip" "testing" ) +func TestSetListener(t *testing.T) { + message := []byte{ + 0x17, 0x90, 0x00, 0x00, 0x78, 0x37, 0x2a, 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + u := uhppote{ + driver: &stub{ + broadcastTo: func(addr *net.UDPAddr, request []byte, handler func([]byte) bool) ([]byte, error) { + return message, nil + }, + }, + } + + if ok, err := u.SetListener(405419896, netip.MustParseAddrPort("192.168.1.100:60001")); err != nil { + t.Errorf("unexpected error (%v)", err) + } else if !ok { + t.Errorf("invalid response - expected:%v, got:%v", true, ok) + } +} + +func TestSetListenerWithANYAddr(t *testing.T) { + message := []byte{ + 0x17, 0x90, 0x00, 0x00, 0x78, 0x37, 0x2a, 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + u := uhppote{ + driver: &stub{ + broadcastTo: func(addr *net.UDPAddr, request []byte, handler func([]byte) bool) ([]byte, error) { + return message, nil + }, + }, + } + + if ok, err := u.SetListener(405419896, netip.MustParseAddrPort("0.0.0.0:0")); err != nil { + t.Errorf("unexpected error (%v)", err) + } else if !ok { + t.Errorf("invalid response - expected:%v, got:%v", true, ok) + } +} + +func TestSetListenerWithResponseFromIncorrectController(t *testing.T) { + message := []byte{ + 0x17, 0x90, 0x00, 0x00, 0x2d, 0x55, 0x39, 0x19, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + u := uhppote{ + driver: &stub{ + broadcastTo: func(addr *net.UDPAddr, request []byte, handler func([]byte) bool) ([]byte, error) { + return message, nil + }, + }, + } + + if ok, err := u.SetListener(405419896, netip.MustParseAddrPort("192.168.1.100:60001")); err == nil { + t.Errorf("expected 'invalid controller' error, got %v", err) + } else if fmt.Sprintf("%v", err) != "invalid controller ID - expected:405419896, got:423187757" { + t.Errorf("expected 'woot', got:%v", err) + } else if ok { + t.Errorf("expected 'not ok', got %v", ok) + } +} + func TestSetListenerWithInvalidDeviceID(t *testing.T) { u := uhppote{} - _, err := u.SetListener(0, net.UDPAddr{ - IP: net.IPv4(192, 168, 1, 100), - Port: 60001, - Zone: "", - }) + if _, err := u.SetListener(0, netip.MustParseAddrPort("192.168.1.100:60001")); err == nil { + t.Errorf("Expected 'invalid device ID' error, got %v", err) + } +} + +func TestSetListenerWithInvalidAddrPort(t *testing.T) { + u := uhppote{} + + if _, err := u.SetListener(405419896, netip.AddrPort{}); !errors.Is(err, ErrInvalidListenerAddress) { + t.Errorf("Expected 'invalid listener address' error, got %v", err) + } +} + +func TestSetListenerWithIPv6Address(t *testing.T) { + u := uhppote{} + + if _, err := u.SetListener(405419896, netip.MustParseAddrPort("[2001:db8::68]:60001")); err == nil { + t.Errorf("Expected 'invalid listener address: [2001:db8::68]:60001', got %v", err) + } else if fmt.Sprintf("%v", err) != "invalid listener address: [2001:db8::68]:60001" { + t.Errorf("Expected 'invalid listener address: [2001:db8::68]:60001', got %v", err) + } +} + +func TestSetListenerWithInvalidPort(t *testing.T) { + u := uhppote{} - if err == nil { - t.Fatalf("Expected 'Invalid device ID' error, got %v", err) + if _, err := u.SetListener(405419896, netip.MustParseAddrPort("192.168.1.100:0")); err == nil { + t.Errorf("Expected 'invalid listener address: 192.168.1.100:0', got %v", err) + } else if fmt.Sprintf("%v", err) != "invalid listener address: 192.168.1.100:0" { + t.Errorf("Expected 'invalid listener address: 192.168.1.100:0', got %v", err) } }