From 36b603620310d2ba0b706c38b5340a2415d3a956 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Tue, 17 Jun 2025 10:58:14 +0200 Subject: [PATCH] fix: on demand plant id generation (#21873) --- hems/semp/semp.go | 5 +--- server/eebus/eebus.go | 5 +--- util/machine/machine.go | 42 +++++++++++++++++++---------- util/machine/machine_test.go | 51 +++++++++++++++++++++++++++++++++--- util/telemetry/charge.go | 29 ++++---------------- 5 files changed, 82 insertions(+), 50 deletions(-) diff --git a/hems/semp/semp.go b/hems/semp/semp.go index ad58b7710..94d57488a 100644 --- a/hems/semp/semp.go +++ b/hems/semp/semp.go @@ -342,10 +342,7 @@ func (s *SEMP) serialNumber(id int) string { func UniqueDeviceID() ([]byte, error) { bytes := 6 - mid, err := machine.ProtectedID("evcc-semp") - if err != nil { - return nil, err - } + mid := machine.ProtectedID("evcc-semp") b, err := hex.DecodeString(mid) if err != nil { diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go index 931343fb1..67a7ab0b1 100644 --- a/server/eebus/eebus.go +++ b/server/eebus/eebus.go @@ -79,10 +79,7 @@ func NewServer(other Config) (*EEBus, error) { log := util.NewLogger("eebus") - protectedID, err := machine.ProtectedID("evcc-eebus") - if err != nil { - return nil, err - } + protectedID := machine.ProtectedID("evcc-eebus") serial := fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8]) if len(cc.ShipID) != 0 { diff --git a/util/machine/machine.go b/util/machine/machine.go index e74c6901c..4639781a1 100644 --- a/util/machine/machine.go +++ b/util/machine/machine.go @@ -8,10 +8,15 @@ import ( "strings" "github.com/denisbrodbeck/machineid" + "github.com/evcc-io/evcc/core/keys" + "github.com/evcc-io/evcc/server/db/settings" "github.com/samber/lo" ) -var id string +var ( + id string + getMachineID = machineid.ID +) // CustomID sets the machine id to a custom value func CustomID(cid string) error { @@ -37,28 +42,37 @@ func RandomID() string { } // ID returns the platform specific machine id of the current host OS. -// If ID cannot be generated, a random value is suggested. -func ID() (string, error) { +// If ID cannot be generated, a random one from settings will be used (generated on demand) +func ID() string { if id == "" { var err error - if id, err = machineid.ID(); err != nil { - rid := RandomID() - return "", fmt.Errorf("could not get %w; for manual configuration use plant: %s", err, rid) + if id, err = getMachineID(); err == nil && id != "" { + return id } + + // no machine id found, use is from settings + return getOrCreateIDFromSettings() } - return id, nil + return id +} + +// getOrCreateIDFromSettings return instance id from settings if exists, otherwise creates and stores a new one +func getOrCreateIDFromSettings() string { + if id, err := settings.String(keys.Plant); err == nil && id != "" { + return id + } + + id := RandomID() + settings.SetString(keys.Plant, id) + + return id } // ProtectedID returns a hashed version of the machine id // using a fixed, application-specific key. -func ProtectedID(key string) (string, error) { - id, err := ID() - if err != nil { - return id, err - } - - return protect(key, id), nil +func ProtectedID(key string) string { + return protect(key, ID()) } // protect calculates HMAC-SHA256 of the id, keyed by key and returns a hex-encoded string diff --git a/util/machine/machine_test.go b/util/machine/machine_test.go index 43a852af7..0ab894ae3 100644 --- a/util/machine/machine_test.go +++ b/util/machine/machine_test.go @@ -1,22 +1,65 @@ package machine import ( + "errors" "testing" "github.com/denisbrodbeck/machineid" + "github.com/evcc-io/evcc/core/keys" + "github.com/evcc-io/evcc/server/db/settings" ) func TestProtectedMachineId(t *testing.T) { const key = "foo" if mid, err := machineid.ProtectedID(key); err == nil { - id, err := ProtectedID(key) - if err != nil { - t.Error(err) - } + id := ProtectedID(key) if mid != id { t.Errorf("machine id mismatch. expected %s, got %s", mid, id) } + } else { + t.Skip("cannot get machineid, skipping test") + } +} + +func TestIdFromSettings(t *testing.T) { + // reset machine id cache + id = "" + + // reset settings + settings.Delete(keys.Plant) + + // force machineid.ID() to fail + getMachineID = func() (string, error) { + return "", errors.New("dummy error") + } + t.Cleanup(func() { + getMachineID = machineid.ID + }) + + // generate new random id + generatedId := ID() + if len(generatedId) != 64 { + t.Errorf("expected 64 char id, got %d", len(generatedId)) + } + + // check that id is stored in settings + settingsId, _ := settings.String(keys.Plant) + if generatedId != settingsId { + t.Errorf("expected id %s, got %s from settings", generatedId, settingsId) + } + + // check reproducability + idA1 := ProtectedID("A") + idA2 := ProtectedID("A") + idB := ProtectedID("B") + + if idA1 != idA2 { + t.Errorf("expected same id, got %s and %s", idA1, idA2) + } + + if idA1 == idB { + t.Errorf("expected different id, got %s and %s", idA1, idB) } } diff --git a/util/telemetry/charge.go b/util/telemetry/charge.go index d3f5c21e4..86cd4b27a 100644 --- a/util/telemetry/charge.go +++ b/util/telemetry/charge.go @@ -39,7 +39,7 @@ func Enable(enable bool) error { return errors.New("telemetry requires sponsorship") } if instanceID == "" { - return fmt.Errorf("using docker? Telemetry requires a unique instance ID. Add this to your config: `plant: %s`", machine.RandomID()) + return fmt.Errorf("instance id not set") } } @@ -48,31 +48,12 @@ func Enable(enable bool) error { return nil } -// getOrCreateID return instance id from settings if exists, otherwise creates and stores a new one -func getOrCreateID() string { - if id, err := settings.String(keys.Plant); err == nil { - return id - } - - id := machine.RandomID() - settings.SetString(keys.Plant, id) - - return id -} - func Create(machineID string) { - // no machine id from config - if machineID == "" { - if id, err := machine.ProtectedID("evcc-api"); err == nil { - // use id from hardware - machineID = id - } else { - // use id from database, generate if needed (e.g. in docker environment) - machineID = getOrCreateID() - } - } - instanceID = machineID + + if machineID == "" { + instanceID = machine.ProtectedID("evcc-api") + } } // UpdateChargeProgress uploads power and energy data every 30 seconds