WIP
This commit is contained in:
parent
6b1a0cdf93
commit
6eda162a70
3 changed files with 133 additions and 0 deletions
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module gitea.drjosh.dev/josh/plugctl
|
||||||
|
|
||||||
|
go 1.19
|
76
plug.go
Normal file
76
plug.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// Package plugctl provides an API for Kasa smart home switches.
|
||||||
|
package plugctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Plug provides an API for a single Kasa smart home switch.
|
||||||
|
type Plug struct {
|
||||||
|
Addr net.TCPAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
type msg struct {
|
||||||
|
System *systemMsg `json:"system,omitempty"`
|
||||||
|
EMeter *eMeterMsg `json:"emeter,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type eMeterMsg struct {
|
||||||
|
GetRealtime *getRealtimeMsg `json:"get_realtime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type getRealtimeMsg struct{}
|
||||||
|
|
||||||
|
type systemMsg struct {
|
||||||
|
GetSysInfo *getSysInfoMsg `json:"get_sysinfo"`
|
||||||
|
SetRelayState *setRelayStateMsg `json:"set_relay_state,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type getSysInfoMsg struct{}
|
||||||
|
|
||||||
|
type setRelayStateMsg struct {
|
||||||
|
State int `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type decryptReader struct {
|
||||||
|
r io.Reader
|
||||||
|
c byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDecrypter(r io.Reader) *decryptReader {
|
||||||
|
return &decryptReader{
|
||||||
|
r: r,
|
||||||
|
c: 0xab,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decryptReader) Read(b []byte) (int, error) {
|
||||||
|
n, err := d.r.Read(b)
|
||||||
|
for i, x := range b {
|
||||||
|
b[i] = d.c ^ x
|
||||||
|
d.c = x
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type encryptWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
c byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEncrypter(w io.Writer) *encryptWriter {
|
||||||
|
return &encryptWriter{
|
||||||
|
w: w,
|
||||||
|
c: 0xab,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encryptWriter) Write(b []byte) (int, error) {
|
||||||
|
eb := make([]byte, len(b))
|
||||||
|
for i, x := range b {
|
||||||
|
e.c ^= x
|
||||||
|
eb[i] = e.c
|
||||||
|
}
|
||||||
|
return e.w.Write(eb)
|
||||||
|
}
|
54
plug_test.go
Normal file
54
plug_test.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package plugctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecrypt(t *testing.T) {
|
||||||
|
// on message from hs100.sh
|
||||||
|
in64 := "AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog=="
|
||||||
|
in, err := base64.StdEncoding.DecodeString(in64)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("base64.DecodeString(%q) error: %v", in64, err)
|
||||||
|
}
|
||||||
|
in = in[4:]
|
||||||
|
want := []byte(`{"system":{"set_relay_state":{"state":1}}}`)
|
||||||
|
dec := newDecrypter(bytes.NewReader(in))
|
||||||
|
got, err := io.ReadAll(dec)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("io.ReadAll(decryptReader) error: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("decrypt(%02x) = %q, want %q", in, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncrypt(t *testing.T) {
|
||||||
|
// on message from hs100.sh
|
||||||
|
want64 := "AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog=="
|
||||||
|
want, err := base64.StdEncoding.DecodeString(want64)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("base64.DecodeString(%q) error: %v", want64, err)
|
||||||
|
}
|
||||||
|
want = want[4:]
|
||||||
|
in := []byte(`{"system":{"set_relay_state":{"state":1}}}`)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
newEncrypter(&buf).Write(in)
|
||||||
|
|
||||||
|
if got := buf.Bytes(); !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("encrypt(%q) = %02x, want %02x", in, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func TestEncryptDecrypt(t *testing.T) {
|
||||||
|
// want := make([]byte, 64)
|
||||||
|
// if _, err := rand.Read(want); err != nil {
|
||||||
|
// t.Fatalf("rand.Read(want) error: %v", err)
|
||||||
|
// }
|
||||||
|
// if got := decrypt(encrypt(want)); !bytes.Equal(got, want) {
|
||||||
|
// t.Errorf("decrypt(encrypt(%02x)) = %02x, want %02x", want, got, want)
|
||||||
|
// }
|
||||||
|
// }
|
Loading…
Reference in a new issue