From d82d62e81887fd7bd543b9af00d85c66dbe177a2 Mon Sep 17 00:00:00 2001 From: riccardom Date: Sun, 28 Jun 2026 11:39:51 +0200 Subject: [PATCH] Adds explicit merge call for future map updates --- client/internal/mapsync.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/client/internal/mapsync.go b/client/internal/mapsync.go index 083c841bd..e7fed33f2 100644 --- a/client/internal/mapsync.go +++ b/client/internal/mapsync.go @@ -63,7 +63,7 @@ func (m *mapStateManager) SetTarget(update *mgmProto.SyncResponse) error { if m.target != nil && m.targetGen > m.appliedGen { log.Debugf("sync map (gen %d) superseded before convergence, skipping", m.targetGen) } - m.target = update + m.target = m.mergeTarget(m.target, update) // Bump an internal generation counter, NOT the map serial: config-only updates // (relay token rotation, STUN/TURN) arrive with NetworkMap == nil and carry no // serial, yet must still be applied. Every SetTarget is therefore a distinct @@ -80,6 +80,19 @@ func (m *mapStateManager) SetTarget(update *mgmProto.SyncResponse) error { return nil } +// mergeTarget combines the currently pending target with a freshly received update +// and returns the new desired state. It is called under m.mu from SetTarget and is +// the single seam where the replace-vs-squash decision lives. +// +// Today management always sends a FULL map (the complete desired state), so the +// update simply replaces whatever was pending — prev is ignored. When management +// starts sending incremental/delta updates, squash `update` onto `prev` here; the +// rest of the manager (generation tracking, convergence, signaling) is unaffected +// because it already treats target as "the complete desired state, whatever it is". +func (m *mapStateManager) mergeTarget(prev, update *mgmProto.SyncResponse) *mgmProto.SyncResponse { + return update +} + // run drives convergence until ctx is done. It is meant to run in its own goroutine. func (m *mapStateManager) run(ctx context.Context) { for {