diff --git a/control/controlclient/map.go b/control/controlclient/map.go index d44310028..e03e57d7a 100644 --- a/control/controlclient/map.go +++ b/control/controlclient/map.go @@ -664,9 +664,22 @@ func peerChangeDiff(was tailcfg.NodeView, n *tailcfg.Node) (_ *tailcfg.PeerChang pc().Cap = n.Cap } case "CapMap": - if n.CapMap != nil { - pc().CapMap = n.CapMap + if len(n.CapMap) != was.CapMap().Len() { + if n.CapMap == nil { + pc().CapMap = make(tailcfg.NodeCapMap) + } else { + pc().CapMap = n.CapMap + } + break } + was.CapMap().Range(func(k tailcfg.NodeCapability, v views.Slice[tailcfg.RawMessage]) bool { + nv, ok := n.CapMap[k] + if !ok || !views.SliceEqual(v, views.SliceOf(nv)) { + pc().CapMap = n.CapMap + return false + } + return true + }) case "Tags": if !views.SliceEqual(was.Tags(), views.SliceOf(n.Tags)) { return nil, false diff --git a/control/controlclient/map_test.go b/control/controlclient/map_test.go index bdbfaf6e4..817e730ca 100644 --- a/control/controlclient/map_test.go +++ b/control/controlclient/map_test.go @@ -848,7 +848,36 @@ func TestPeerChangeDiff(t *testing.T) { a: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))}, b: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3006"))}, want: nil, - }} + }, + { + name: "patch-capmap-add-value-to-existing-key", + a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, + b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: []tailcfg.RawMessage{"true"}}}, + want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: []tailcfg.RawMessage{"true"}}}, + }, + { + name: "patch-capmap-add-new-key", + a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, + b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}}, + want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}}, + }, { + name: "patch-capmap-remove-key", + a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, + b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{}}, + want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}}, + }, { + name: "patch-capmap-add-key-to-empty-map", + a: &tailcfg.Node{ID: 1}, + b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, + want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, + }, + { + name: "patch-capmap-no-change", + a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, + b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}}, + wantEqual: true, + }, + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pc, ok := peerChangeDiff(tt.a.View(), tt.b)