Quellcode durchsuchen

types/views,cmd/viewer: add ByteSlice[T] to replace mem.RO

Add a new views.ByteSlice[T ~[]byte] to provide a better API to use
with views.

Updates #cleanup

Signed-off-by: Maisem Ali <[email protected]>
Maisem Ali vor 2 Jahren
Ursprung
Commit
2548496cef
5 geänderte Dateien mit 118 neuen und 30 gelöschten Zeilen
  1. 1 2
      cmd/viewer/tests/tests_view.go
  2. 3 3
      cmd/viewer/viewer.go
  3. 2 3
      ipn/ipnlocal/network-lock.go
  4. 29 20
      tailcfg/tailcfg_view.go
  5. 83 2
      types/views/views.go

+ 1 - 2
cmd/viewer/tests/tests_view.go

@@ -10,7 +10,6 @@ import (
 	"errors"
 	"net/netip"
 
-	"go4.org/mem"
 	"tailscale.com/types/views"
 )
 
@@ -312,7 +311,7 @@ func (v StructWithSlicesView) Slice() views.Slice[string] { return views.SliceOf
 func (v StructWithSlicesView) Prefixes() views.Slice[netip.Prefix] {
 	return views.SliceOf(v.ж.Prefixes)
 }
-func (v StructWithSlicesView) Data() mem.RO { return mem.B(v.ж.Data) }
+func (v StructWithSlicesView) Data() views.ByteSlice[[]byte] { return views.ByteSliceOf(v.ж.Data) }
 
 // A compilation failure here means this code must be regenerated, with the command at the top of this file.
 var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {

+ 3 - 3
cmd/viewer/viewer.go

@@ -67,7 +67,7 @@ func (v *{{.ViewName}}) UnmarshalJSON(b []byte) error {
 {{end}}
 {{define "valueField"}}func (v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} { return v.ж.{{.FieldName}} }
 {{end}}
-{{define "byteSliceField"}}func (v {{.ViewName}}) {{.FieldName}}() mem.RO { return mem.B(v.ж.{{.FieldName}}) }
+{{define "byteSliceField"}}func (v {{.ViewName}}) {{.FieldName}}() views.ByteSlice[{{.FieldType}}] { return views.ByteSliceOf(v.ж.{{.FieldName}}) }
 {{end}}
 {{define "sliceField"}}func (v {{.ViewName}}) {{.FieldName}}() views.Slice[{{.FieldType}}] { return views.SliceOf(v.ж.{{.FieldName}}) }
 {{end}}
@@ -169,12 +169,12 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
 		case *types.Slice:
 			slice := underlying
 			elem := slice.Elem()
-			args.FieldType = it.QualifiedName(elem)
 			switch elem.String() {
 			case "byte":
-				it.Import("go4.org/mem")
+				args.FieldType = it.QualifiedName(fieldType)
 				writeTemplate("byteSliceField")
 			default:
+				args.FieldType = it.QualifiedName(elem)
 				it.Import("tailscale.com/types/views")
 				shallow, deep, base := requiresCloning(elem)
 				if deep {

+ 2 - 3
ipn/ipnlocal/network-lock.go

@@ -73,12 +73,11 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
 			// Not subject to tailnet lock.
 			continue
 		}
-		keySig := tkatype.MarshaledSignature(p.KeySignature().StringCopy()) // TODO(bradfitz,maisem): this is unfortunate. Change tkatype.MarshaledSignature to a string for viewer?
-		if len(keySig) == 0 {
+		if p.KeySignature().Len() == 0 {
 			b.logf("Network lock is dropping peer %v(%v) due to missing signature", p.ID(), p.StableID())
 			mak.Set(&toDelete, i, true)
 		} else {
-			if err := b.tka.authority.NodeKeyAuthorized(p.Key(), keySig); err != nil {
+			if err := b.tka.authority.NodeKeyAuthorized(p.Key(), p.KeySignature().AsSlice()); err != nil {
 				b.logf("Network lock is dropping peer %v(%v) due to failed signature check: %v", p.ID(), p.StableID(), err)
 				mak.Set(&toDelete, i, true)
 			}

+ 29 - 20
tailcfg/tailcfg_view.go

@@ -11,7 +11,6 @@ import (
 	"net/netip"
 	"time"
 
-	"go4.org/mem"
 	"tailscale.com/types/dnstype"
 	"tailscale.com/types/key"
 	"tailscale.com/types/opt"
@@ -129,14 +128,16 @@ func (v *NodeView) UnmarshalJSON(b []byte) error {
 	return nil
 }
 
-func (v NodeView) ID() NodeID                               { return v.ж.ID }
-func (v NodeView) StableID() StableNodeID                   { return v.ж.StableID }
-func (v NodeView) Name() string                             { return v.ж.Name }
-func (v NodeView) User() UserID                             { return v.ж.User }
-func (v NodeView) Sharer() UserID                           { return v.ж.Sharer }
-func (v NodeView) Key() key.NodePublic                      { return v.ж.Key }
-func (v NodeView) KeyExpiry() time.Time                     { return v.ж.KeyExpiry }
-func (v NodeView) KeySignature() mem.RO                     { return mem.B(v.ж.KeySignature) }
+func (v NodeView) ID() NodeID             { return v.ж.ID }
+func (v NodeView) StableID() StableNodeID { return v.ж.StableID }
+func (v NodeView) Name() string           { return v.ж.Name }
+func (v NodeView) User() UserID           { return v.ж.User }
+func (v NodeView) Sharer() UserID         { return v.ж.Sharer }
+func (v NodeView) Key() key.NodePublic    { return v.ж.Key }
+func (v NodeView) KeyExpiry() time.Time   { return v.ж.KeyExpiry }
+func (v NodeView) KeySignature() views.ByteSlice[tkatype.MarshaledSignature] {
+	return views.ByteSliceOf(v.ж.KeySignature)
+}
 func (v NodeView) Machine() key.MachinePublic               { return v.ж.Machine }
 func (v NodeView) DiscoKey() key.DiscoPublic                { return v.ж.DiscoKey }
 func (v NodeView) Addresses() views.Slice[netip.Prefix]     { return views.SliceOf(v.ж.Addresses) }
@@ -610,13 +611,15 @@ func (v *RegisterResponseView) UnmarshalJSON(b []byte) error {
 	return nil
 }
 
-func (v RegisterResponseView) User() UserView           { return v.ж.User.View() }
-func (v RegisterResponseView) Login() Login             { return v.ж.Login }
-func (v RegisterResponseView) NodeKeyExpired() bool     { return v.ж.NodeKeyExpired }
-func (v RegisterResponseView) MachineAuthorized() bool  { return v.ж.MachineAuthorized }
-func (v RegisterResponseView) AuthURL() string          { return v.ж.AuthURL }
-func (v RegisterResponseView) NodeKeySignature() mem.RO { return mem.B(v.ж.NodeKeySignature) }
-func (v RegisterResponseView) Error() string            { return v.ж.Error }
+func (v RegisterResponseView) User() UserView          { return v.ж.User.View() }
+func (v RegisterResponseView) Login() Login            { return v.ж.Login }
+func (v RegisterResponseView) NodeKeyExpired() bool    { return v.ж.NodeKeyExpired }
+func (v RegisterResponseView) MachineAuthorized() bool { return v.ж.MachineAuthorized }
+func (v RegisterResponseView) AuthURL() string         { return v.ж.AuthURL }
+func (v RegisterResponseView) NodeKeySignature() views.ByteSlice[tkatype.MarshaledSignature] {
+	return views.ByteSliceOf(v.ж.NodeKeySignature)
+}
+func (v RegisterResponseView) Error() string { return v.ж.Error }
 
 // A compilation failure here means this code must be regenerated, with the command at the top of this file.
 var _RegisterResponseViewNeedsRegeneration = RegisterResponse(struct {
@@ -749,8 +752,10 @@ func (v RegisterRequestView) Expiry() time.Time              { return v.ж.Expir
 func (v RegisterRequestView) Followup() string               { return v.ж.Followup }
 func (v RegisterRequestView) Hostinfo() HostinfoView         { return v.ж.Hostinfo.View() }
 func (v RegisterRequestView) Ephemeral() bool                { return v.ж.Ephemeral }
-func (v RegisterRequestView) NodeKeySignature() mem.RO       { return mem.B(v.ж.NodeKeySignature) }
-func (v RegisterRequestView) SignatureType() SignatureType   { return v.ж.SignatureType }
+func (v RegisterRequestView) NodeKeySignature() views.ByteSlice[tkatype.MarshaledSignature] {
+	return views.ByteSliceOf(v.ж.NodeKeySignature)
+}
+func (v RegisterRequestView) SignatureType() SignatureType { return v.ж.SignatureType }
 func (v RegisterRequestView) Timestamp() *time.Time {
 	if v.ж.Timestamp == nil {
 		return nil
@@ -759,8 +764,12 @@ func (v RegisterRequestView) Timestamp() *time.Time {
 	return &x
 }
 
-func (v RegisterRequestView) DeviceCert() mem.RO { return mem.B(v.ж.DeviceCert) }
-func (v RegisterRequestView) Signature() mem.RO  { return mem.B(v.ж.Signature) }
+func (v RegisterRequestView) DeviceCert() views.ByteSlice[[]byte] {
+	return views.ByteSliceOf(v.ж.DeviceCert)
+}
+func (v RegisterRequestView) Signature() views.ByteSlice[[]byte] {
+	return views.ByteSliceOf(v.ж.Signature)
+}
 
 // A compilation failure here means this code must be regenerated, with the command at the top of this file.
 var _RegisterRequestViewNeedsRegeneration = RegisterRequest(struct {

+ 83 - 2
types/views/views.go

@@ -6,9 +6,12 @@
 package views
 
 import (
+	"bytes"
 	"encoding/json"
 	"errors"
 	"maps"
+
+	"go4.org/mem"
 )
 
 func unmarshalSliceFromJSON[T any](b []byte, x *[]T) error {
@@ -21,6 +24,83 @@ func unmarshalSliceFromJSON[T any](b []byte, x *[]T) error {
 	return json.Unmarshal(b, x)
 }
 
+// ByteSlice is a read-only accessor for types that are backed by a []byte.
+type ByteSlice[T ~[]byte] struct {
+	// ж is the underlying mutable value, named with a hard-to-type
+	// character that looks pointy like a pointer.
+	// It is named distinctively to make you think of how dangerous it is to escape
+	// to callers. You must not let callers be able to mutate it.
+	ж T
+}
+
+// ByteSliceOf returns a ByteSlice for the provided slice.
+func ByteSliceOf[T ~[]byte](x T) ByteSlice[T] {
+	return ByteSlice[T]{x}
+}
+
+// Len returns the length of the slice.
+func (v ByteSlice[T]) Len() int {
+	return len(v.ж)
+}
+
+// IsNil reports whether the underlying slice is nil.
+func (v ByteSlice[T]) IsNil() bool {
+	return v.ж == nil
+}
+
+// Mem returns a read-only view of the underlying slice.
+func (v ByteSlice[T]) Mem() mem.RO {
+	return mem.B(v.ж)
+}
+
+// Equal reports whether the underlying slice is equal to b.
+func (v ByteSlice[T]) Equal(b T) bool {
+	return bytes.Equal(v.ж, b)
+}
+
+// EqualView reports whether the underlying slice is equal to b.
+func (v ByteSlice[T]) EqualView(b ByteSlice[T]) bool {
+	return bytes.Equal(v.ж, b.ж)
+}
+
+// AsSlice returns a copy of the underlying slice.
+func (v ByteSlice[T]) AsSlice() T {
+	return v.AppendTo(v.ж[:0:0])
+}
+
+// AppendTo appends the underlying slice values to dst.
+func (v ByteSlice[T]) AppendTo(dst T) T {
+	return append(dst, v.ж...)
+}
+
+// LenIter returns a slice the same length as the v.Len().
+// The caller can then range over it to get the valid indexes.
+// It does not allocate.
+func (v ByteSlice[T]) LenIter() []struct{} { return make([]struct{}, len(v.ж)) }
+
+// At returns the byte at index `i` of the slice.
+func (v ByteSlice[T]) At(i int) byte { return v.ж[i] }
+
+// SliceFrom returns v[i:].
+func (v ByteSlice[T]) SliceFrom(i int) ByteSlice[T] { return ByteSlice[T]{v.ж[i:]} }
+
+// SliceTo returns v[:i]
+func (v ByteSlice[T]) SliceTo(i int) ByteSlice[T] { return ByteSlice[T]{v.ж[:i]} }
+
+// Slice returns v[i:j]
+func (v ByteSlice[T]) Slice(i, j int) ByteSlice[T] { return ByteSlice[T]{v.ж[i:j]} }
+
+// MarshalJSON implements json.Marshaler.
+func (v ByteSlice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (v *ByteSlice[T]) UnmarshalJSON(b []byte) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	return json.Unmarshal(b, &v.ж)
+}
+
 // StructView represents the corresponding StructView of a Viewable. The concrete types are
 // typically generated by tailscale.com/cmd/viewer.
 type StructView[T any] interface {
@@ -47,8 +127,9 @@ func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] {
 	return SliceView[T, V]{x}
 }
 
-// SliceView is a read-only wrapper around a struct which should only be exposed
-// as a View.
+// SliceView wraps []T to provide accessors which return an immutable view V of
+// T. It is used to provide the equivalent of SliceOf([]V) without having to
+// allocate []V from []T.
 type SliceView[T ViewCloner[T, V], V StructView[T]] struct {
 	// ж is the underlying mutable value, named with a hard-to-type
 	// character that looks pointy like a pointer.