penguin/homekey-go

homekey-go

internal/hkserver/server_test.go

raw ยท 3066 bytes

package hkserver

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"homekey.local/provisioner/internal/provision"
)

func testDevice(t *testing.T) *Device {
	t.Helper()
	s, e := provision.Open(t.TempDir())
	if e != nil {
		t.Fatal(e)
	}
	pub := bytes.Repeat([]byte{42}, 32)
	p, _ := json.Marshal(struct {
		Name       string
		PublicKey  []byte
		Permission byte
	}{"phone", pub, 1})
	if e = s.Set("phone.pairing", p); e != nil {
		t.Fatal(e)
	}
	d, e := New(s, Config{Name: "Test Lock", Serial: "test", Finish: "silver", PIN: "51808582", Address: ":51826"})
	if e != nil {
		t.Fatal(e)
	}
	return d
}
func TestAccessoryAndProvisioningRetry(t *testing.T) {
	d := testDevice(t)
	b, e := json.Marshal(d.Accessory)
	if e != nil {
		t.Fatal(e)
	}
	var a struct {
		Services []struct {
			Type            string `json:"type"`
			Characteristics []struct {
				Type  string   `json:"type"`
				Perms []string `json:"perms"`
				Value any      `json:"value"`
			} `json:"characteristics"`
		} `json:"services"`
	}
	if e = json.Unmarshal(b, &a); e != nil {
		t.Fatal(e)
	}
	seen := map[string]bool{}
	for _, s := range a.Services {
		seen["service:"+s.Type] = true
		for _, c := range s.Characteristics {
			seen[c.Type] = true
			if c.Type == "265" && c.Value != "AQEQAgEQ" {
				t.Fatalf("supported configuration = %v", c.Value)
			}
			if c.Type == "26C" && c.Value != base64.StdEncoding.EncodeToString([]byte{1, 4, 0xe3, 0xe3, 0xe3, 0}) {
				t.Fatal("bad finish")
			}
		}
	}
	for _, k := range []string{"service:266", "service:45", "service:44", "263", "264", "265", "26C"} {
		if !seen[k] {
			t.Fatal("missing", k)
		}
	}
	key := make([]byte, 32)
	key[31] = 1
	body := append(provision.TLV(1, []byte{2}), provision.TLV(2, key)...)
	body = append(body, provision.TLV(3, []byte("reader01"))...)
	raw := append(provision.TLV(1, []byte{2}), provision.TLV(6, body)...)
	encoded := base64.StdEncoding.EncodeToString(raw)
	req := httptest.NewRequest(http.MethodPut, "/characteristics", nil)
	for i, want := range [][]byte{{7, 3, 2, 1, 0}, {7, 3, 2, 1, 2}} {
		v, code := d.Control.SetValueRequest(encoded, req)
		if code != 0 || v != base64.StdEncoding.EncodeToString(want) {
			t.Fatalf("write %d: %v %d", i, v, code)
		}
		if d.Control.Value() != "" {
			t.Fatal("private request retained as characteristic value")
		}
	}
	v, code := d.Control.ValueRequest(req)
	if code != 0 || v != "" {
		t.Fatal("GET leaked command payload")
	}
	if _, code := d.Control.SetValueRequest("not base64!", req); code == 0 {
		t.Fatal("invalid base64 accepted")
	}
}
func TestUnauthenticatedHTTPDenied(t *testing.T) {
	d := testDevice(t)
	mux, ok := d.Server.ServeMux().(http.Handler)
	if !ok {
		t.Fatal("mux is not a handler")
	}
	for _, path := range []string{"/accessories", "/characteristics?id=1.1"} {
		r := httptest.NewRecorder()
		mux.ServeHTTP(r, httptest.NewRequest(http.MethodGet, path, nil))
		if !bytes.Contains(r.Body.Bytes(), []byte("-70401")) {
			t.Fatalf("request not rejected: %d %s", r.Code, r.Body.String())
		}
	}
}