🎉 live server seems to be working now
This commit is contained in:
130
third_party/sendspin-go/pkg/sendspin/client_id.go
vendored
Normal file
130
third_party/sendspin-go/pkg/sendspin/client_id.go
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// ABOUTME: Stable client_id resolution (override -> config value -> MAC -> generated UUID)
|
||||
// ABOUTME: Persistence of new values is delegated to a caller-supplied callback
|
||||
package sendspin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type clientIDSource string
|
||||
|
||||
const (
|
||||
sourceOverride clientIDSource = "override"
|
||||
sourceConfig clientIDSource = "config"
|
||||
sourceMAC clientIDSource = "mac"
|
||||
sourceGenerated clientIDSource = "generated"
|
||||
)
|
||||
|
||||
// PersistFn is called by ResolveClientID whenever a new client_id needs to be
|
||||
// written back to the config file. Implementations typically call
|
||||
// WriteStringKey(path, "client_id", id). Returning an error is logged but not
|
||||
// fatal — the player will still boot with the resolved id; the id just won't
|
||||
// survive a restart.
|
||||
type PersistFn func(id string) error
|
||||
|
||||
// ResolveClientID returns a stable client_id, following this precedence:
|
||||
// 1. override — used as-is and persisted via persist (so a one-time seed sticks)
|
||||
// 2. fromConfig — used as-is, no persistence (value came from the file we'd write to)
|
||||
// 3. primary network interface MAC (xx:xx:xx:xx:xx:xx) — not persisted; MAC is inherently stable
|
||||
// 4. freshly generated UUID — persisted so the next launch takes path 2
|
||||
//
|
||||
// Persisted value beating MAC is deliberate: once Music Assistant has
|
||||
// registered a player under one id, switching to another creates a duplicate
|
||||
// entry. Deleting client_id from the config file is the explicit "re-derive".
|
||||
//
|
||||
// persist may be nil if the caller cannot write back (e.g. no config path
|
||||
// resolvable). In that case generated/override ids still work for the session
|
||||
// but won't survive a restart.
|
||||
func ResolveClientID(override, fromConfig string, persist PersistFn) (string, error) {
|
||||
if override != "" {
|
||||
if override != fromConfig {
|
||||
tryPersist(persist, override, "override")
|
||||
}
|
||||
logClientID(override, sourceOverride, persist != nil && override != fromConfig)
|
||||
return override, nil
|
||||
}
|
||||
|
||||
if fromConfig != "" {
|
||||
logClientID(fromConfig, sourceConfig, false)
|
||||
return fromConfig, nil
|
||||
}
|
||||
|
||||
if mac, ok := pickStableMAC(allInterfaces()); ok {
|
||||
logClientID(mac, sourceMAC, false)
|
||||
return mac, nil
|
||||
}
|
||||
|
||||
generated := uuid.New().String()
|
||||
tryPersist(persist, generated, "generated id")
|
||||
logClientID(generated, sourceGenerated, persist != nil)
|
||||
return generated, nil
|
||||
}
|
||||
|
||||
func tryPersist(persist PersistFn, id, what string) {
|
||||
if persist == nil {
|
||||
return
|
||||
}
|
||||
if err := persist(id); err != nil {
|
||||
log.Printf("client_id: could not persist %s: %v", what, err)
|
||||
}
|
||||
}
|
||||
|
||||
func logClientID(id string, source clientIDSource, persisted bool) {
|
||||
if persisted {
|
||||
log.Printf("client_id: %s (source: %s, persisted)", id, source)
|
||||
return
|
||||
}
|
||||
log.Printf("client_id: %s (source: %s)", id, source)
|
||||
}
|
||||
|
||||
// allInterfaces is a package-level var so tests can stub it.
|
||||
var allInterfaces = func() []net.Interface {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return ifaces
|
||||
}
|
||||
|
||||
// pickStableMAC returns the MAC of the alphabetically-first eligible interface.
|
||||
// Eligible means: up, not loopback, 6-byte hardware address, not zero, not broadcast.
|
||||
// Alphabetical ordering gives deterministic selection across reboots and OSes
|
||||
// (eth0 < wlan0, Ethernet < Wi-Fi).
|
||||
func pickStableMAC(ifaces []net.Interface) (string, bool) {
|
||||
eligible := make([]net.Interface, 0, len(ifaces))
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
if len(iface.HardwareAddr) != 6 {
|
||||
continue
|
||||
}
|
||||
if isZeroMAC(iface.HardwareAddr) || isBroadcastMAC(iface.HardwareAddr) {
|
||||
continue
|
||||
}
|
||||
eligible = append(eligible, iface)
|
||||
}
|
||||
if len(eligible) == 0 {
|
||||
return "", false
|
||||
}
|
||||
sort.Slice(eligible, func(i, j int) bool {
|
||||
return eligible[i].Name < eligible[j].Name
|
||||
})
|
||||
return eligible[0].HardwareAddr.String(), true
|
||||
}
|
||||
|
||||
func isZeroMAC(mac net.HardwareAddr) bool {
|
||||
return bytes.Equal(mac, []byte{0, 0, 0, 0, 0, 0})
|
||||
}
|
||||
|
||||
func isBroadcastMAC(mac net.HardwareAddr) bool {
|
||||
return bytes.Equal(mac, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
|
||||
}
|
||||
Reference in New Issue
Block a user