commit d1f857a16419fed79811aafae3237b8e14b67529
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-10-07T13:14:53Z |
| subject | 0.0.10 |
commit d1f857a16419fed79811aafae3237b8e14b67529
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-10-07T13:14:53Z
0.0.10
---
build.env | 2 +-
drivers/sesame/client.go | 34 ++++++++++++++++++++++++----------
drivers/sesame/client_test.go | 39 +++++++++++++++++++++++++++++++++++++++
ha-app/CHANGELOG.md | 4 ++++
ha-app/config.yaml | 2 +-
internal/physical/manager.go | 5 ++++-
6 files changed, 73 insertions(+), 13 deletions(-)
diff --git a/build.env b/build.env
index a140b21..d8a01bd 100644
--- a/build.env
+++ b/build.env
@@ -1,4 +1,4 @@
IMAGE_NAME=ghcr.io/tgckpg/homekey-go
-IMAGE_TAG=0.0.9
+IMAGE_TAG=0.0.10
APT_PROXY=
diff --git a/drivers/sesame/client.go b/drivers/sesame/client.go
index 679e2a6..59d3c86 100644
--- a/drivers/sesame/client.go
+++ b/drivers/sesame/client.go
@@ -173,17 +173,22 @@ func login(ctx context.Context, l Link, c Credential, update func(State)) (*sess
// immediately after deriving the credential, before any additional BLE command.
// A registered device must instead be imported; this API never resets a lock.
func Register(ctx context.Context, t Target, dial Dialer, persist func(Credential) error) error {
- l, actual, e := dial(ctx, t)
+ connect, cancel := context.WithTimeout(ctx, 30*time.Second)
+ l, actual, e := dial(connect, t)
+ cancel()
if e != nil {
- return e
+ return fmt.Errorf("connect for Sesame registration: %w", e)
}
defer l.Close()
if actual.Registered {
return errors.New("lock already registered: import its local key instead")
}
s := &session{link: l}
- if _, e = s.initial(ctx); e != nil {
- return e
+ token, done := context.WithTimeout(ctx, 10*time.Second)
+ _, e = s.initial(token)
+ done()
+ if e != nil {
+ return fmt.Errorf("wait for Sesame registration initial token: %w", e)
}
private, e := ecdh.P256().GenerateKey(rand.Reader)
if e != nil {
@@ -193,9 +198,11 @@ func Register(ctx context.Context, t Target, dial Dialer, persist func(Credentia
stamp := make([]byte, 4)
binary.LittleEndian.PutUint32(stamp, uint32(time.Now().Unix()))
payload = append(payload, stamp...)
- reply, e := s.exchange(ctx, 1, payload, false)
+ registration, finish := context.WithTimeout(ctx, 10*time.Second)
+ reply, e := s.exchange(registration, 1, payload, false)
+ finish()
if e != nil {
- return e
+ return fmt.Errorf("Sesame registration exchange (lock may already be registered): %w", e)
}
if len(reply) < 77 {
return errors.New("invalid registration response; lock may need recovery in the Sesame app")
@@ -208,18 +215,25 @@ func Register(ctx context.Context, t Target, dial Dialer, persist func(Credentia
if e != nil {
return e
}
- return persist(Credential{Secret: hex.EncodeToString(shared[:16])})
+ if e = persist(Credential{Secret: hex.EncodeToString(shared[:16])}); e != nil {
+ return fmt.Errorf("save Sesame registration key: %w", e)
+ }
+ return nil
}
func Probe(ctx context.Context, t Target, c Credential, dial Dialer) error {
- l, actual, e := dial(ctx, t)
+ connect, cancel := context.WithTimeout(ctx, 30*time.Second)
+ l, actual, e := dial(connect, t)
+ cancel()
if e != nil {
- return e
+ return fmt.Errorf("connect to verify Sesame key: %w", e)
}
defer l.Close()
if !actual.Registered {
return errors.New("lock is unregistered; register it first")
}
- _, e = login(ctx, l, c, nil)
+ handshake, done := context.WithTimeout(ctx, 10*time.Second)
+ _, e = login(handshake, l, c, nil)
+ done()
return e
}
diff --git a/drivers/sesame/client_test.go b/drivers/sesame/client_test.go
index ce039c5..1652a5e 100644
--- a/drivers/sesame/client_test.go
+++ b/drivers/sesame/client_test.go
@@ -268,6 +268,45 @@ func TestInitialTokenTimeoutIdentifiesStage(t *testing.T) {
}
}
+func TestRegistrationTimeoutIdentifiesStageAndDoesNotRetry(t *testing.T) {
+ for _, stage := range []string{"connect", "initial token", "registration exchange"} {
+ t.Run(stage, func(t *testing.T) {
+ f := newLink()
+ if stage == "initial token" {
+ <-f.packets // No initial notification arrives.
+ }
+ writes := 0
+ f.write = func(byte, []byte) error {
+ writes++ // Registration sent, but no response arrives.
+ return nil
+ }
+ dial := func(ctx context.Context, _ Target) (Link, Target, error) {
+ if stage == "connect" {
+ <-ctx.Done()
+ return nil, Target{}, ctx.Err()
+ }
+ return f, testTarget, nil
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
+ defer cancel()
+ e := Register(ctx, testTarget, dial, func(Credential) error {
+ t.Fatal("saved key without registration response")
+ return nil
+ })
+ if !errors.Is(e, context.DeadlineExceeded) || !strings.Contains(e.Error(), stage) {
+ t.Fatal("missing timeout stage", e)
+ }
+ wantWrites := 0
+ if stage == "registration exchange" {
+ wantWrites = 1
+ }
+ if writes != wantWrites {
+ t.Fatalf("registration attempts = %d, want %d", writes, wantWrites)
+ }
+ })
+ }
+}
+
func TestReconnectUsesNewTokenAndResetsCipher(t *testing.T) {
var attempts atomic.Int32
firstReady := make(chan *fakeLink, 1)
diff --git a/ha-app/CHANGELOG.md b/ha-app/CHANGELOG.md
index c277c46..106d9b6 100644
--- a/ha-app/CHANGELOG.md
+++ b/ha-app/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.0.10
+
+- Better connection management and error messsages
+
## 0.0.9
- Fixed missing remove lock button
diff --git a/ha-app/config.yaml b/ha-app/config.yaml
index de0c9ec..14989a3 100644
--- a/ha-app/config.yaml
+++ b/ha-app/config.yaml
@@ -1,5 +1,5 @@
name: Home Key Go
-version: "0.0.9"
+version: "0.0.10"
slug: homekey_go
description: HomeKit virtual lock with Apple Home Key NFC authentication
url: https://sgit.k8s.astropenguin.net/penguin/homekey-go/
diff --git a/internal/physical/manager.go b/internal/physical/manager.go
index 55e7696..6ca034f 100644
--- a/internal/physical/manager.go
+++ b/internal/physical/manager.go
@@ -287,7 +287,10 @@ func (m *Manager) Add(ctx context.Context, in Enrollment) (Record, error) {
if in.Mode != "register" && in.Mode != "import" {
return Record{}, errors.New("choose register or import")
}
- ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
+ // Leave room for separate BLE discovery/connect, initial-token and
+ // registration budgets, plus persistence. A slow dial must not exhaust
+ // the handshake deadline before the lock can answer.
+ ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
stop := context.AfterFunc(m.ctx, cancel)
defer stop()