diff --git a/derp/derp_server.go b/derp/derp_server.go index b7cdf2524..f38ae6621 100644 --- a/derp/derp_server.go +++ b/derp/derp_server.go @@ -47,6 +47,7 @@ "tailscale.com/types/key" "tailscale.com/types/logger" "tailscale.com/util/set" + "tailscale.com/util/slicesx" "tailscale.com/version" ) @@ -1323,7 +1324,7 @@ func (s *Server) noteClientActivity(c *sclient) { cs.activeClient.Store(c) } - if sh := dup.sendHistory; len(sh) != 0 && sh[len(sh)-1] == c { + if slicesx.LastEqual(dup.sendHistory, c) { // The client c was the last client to make activity // in this set and it was already recorded. Nothing to // do. diff --git a/ssh/tailssh/tailssh.go b/ssh/tailssh/tailssh.go index 56d6e63c9..7187b5b59 100644 --- a/ssh/tailssh/tailssh.go +++ b/ssh/tailssh/tailssh.go @@ -45,6 +45,7 @@ "tailscale.com/util/clientmetric" "tailscale.com/util/httpm" "tailscale.com/util/mak" + "tailscale.com/util/slicesx" ) var ( @@ -330,7 +331,7 @@ func (c *conn) nextAuthMethodCallback(cm gossh.ConnMetadata, prevErrors []error) switch { case c.anyPasswordIsOkay: nextMethod = append(nextMethod, "password") - case len(prevErrors) > 0 && prevErrors[len(prevErrors)-1] == errPubKeyRequired: + case slicesx.LastEqual(prevErrors, errPubKeyRequired): nextMethod = append(nextMethod, "publickey") } diff --git a/util/linuxfw/helpers.go b/util/linuxfw/helpers.go index 5d76adac6..a4b9fdf40 100644 --- a/util/linuxfw/helpers.go +++ b/util/linuxfw/helpers.go @@ -10,11 +10,13 @@ "fmt" "strings" "unicode" + + "tailscale.com/util/slicesx" ) func formatMaybePrintable(b []byte) string { - // Remove a single trailing null, if any - if len(b) > 0 && b[len(b)-1] == 0 { + // Remove a single trailing null, if any. + if slicesx.LastEqual(b, 0) { b = b[:len(b)-1] } diff --git a/util/slicesx/slicesx.go b/util/slicesx/slicesx.go index 8abf2bd64..e0b820eb7 100644 --- a/util/slicesx/slicesx.go +++ b/util/slicesx/slicesx.go @@ -136,3 +136,15 @@ func CutSuffix[E comparable](s, suffix []E) (after []E, found bool) { } return s[:len(s)-len(suffix)], true } + +// FirstEqual reports whether len(s) > 0 and +// its first element == v. +func FirstEqual[T comparable](s []T, v T) bool { + return len(s) > 0 && s[0] == v +} + +// LastEqual reports whether len(s) > 0 and +// its last element == v. +func LastEqual[T comparable](s []T, v T) bool { + return len(s) > 0 && s[len(s)-1] == v +} diff --git a/util/slicesx/slicesx_test.go b/util/slicesx/slicesx_test.go index be136d288..597b22b83 100644 --- a/util/slicesx/slicesx_test.go +++ b/util/slicesx/slicesx_test.go @@ -197,3 +197,28 @@ func TestCutSuffix(t *testing.T) { }) } } + +func TestFirstLastEqual(t *testing.T) { + tests := []struct { + name string + in string + v byte + f func([]byte, byte) bool + want bool + }{ + {"first-empty", "", 'f', FirstEqual[byte], false}, + {"first-true", "foo", 'f', FirstEqual[byte], true}, + {"first-false", "foo", 'b', FirstEqual[byte], false}, + {"last-empty", "", 'f', LastEqual[byte], false}, + {"last-true", "bar", 'r', LastEqual[byte], true}, + {"last-false", "bar", 'o', LastEqual[byte], false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.f([]byte(tt.in), tt.v); got != tt.want { + t.Errorf("got %v; want %v", got, tt.want) + } + }) + } + +}