From af3aa637d2d18258e6a7a9f8ab62b09ff77c7737 Mon Sep 17 00:00:00 2001 From: a Date: Fri, 10 Mar 2023 16:46:39 -0600 Subject: [PATCH] wip --- client.go | 12 +++++++ codec/codec.go | 63 ++++++++++++++++++++++++++++++++++++ codec/codec_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 11 +++++++ go.sum | 17 ++++++++++ msgs.go | 7 ++++ 6 files changed, 188 insertions(+) create mode 100644 client.go create mode 100644 codec/codec.go create mode 100644 codec/codec_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 msgs.go diff --git a/client.go b/client.go new file mode 100644 index 0000000..f45c79c --- /dev/null +++ b/client.go @@ -0,0 +1,12 @@ +package tplink + +type Client struct { +} + +type DiscoveryOptions struct { +} + +func (c *Client) StartDiscovery(do *DiscoveryOptions) error { + + return nil +} diff --git a/codec/codec.go b/codec/codec.go new file mode 100644 index 0000000..51a9584 --- /dev/null +++ b/codec/codec.go @@ -0,0 +1,63 @@ +package codec + +import ( + "bufio" + "io" +) + +const DefaultFirstByte = 0xab + +type XorEncoder struct { + wr io.Writer + wb *bufio.Writer + last byte +} + +func (x *XorEncoder) Write(p []byte) (n int, err error) { + var next byte + for _, v := range p { + next = v ^ x.last + x.last = next + err = x.wb.WriteByte(next) + if err != nil { + return + } + n = n + 1 + } + err = x.wb.Flush() + return +} + +func NewXorEncoder(wr io.Writer, first byte) *XorEncoder { + return &XorEncoder{ + wr: wr, + wb: bufio.NewWriter(wr), + last: first, + } +} + +type XorDecoder struct { + rd io.Reader + last byte +} + +func (x *XorDecoder) Read(p []byte) (n int, err error) { + n, err = x.rd.Read(p) + if err != nil { + return n, err + } + var next byte + for idx := 0; idx < n; idx++ { + next = p[idx] + p[idx] ^= x.last + x.last = next + } + return n, nil +} + +func NewXorDecoder(rd io.Reader, first byte) *XorDecoder { + return &XorDecoder{ + rd: rd, + last: first, + } +} diff --git a/codec/codec_test.go b/codec/codec_test.go new file mode 100644 index 0000000..b4833b9 --- /dev/null +++ b/codec/codec_test.go @@ -0,0 +1,78 @@ +package codec_test + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "tuxpa.in/a/tplink/codec" +) + +type testCase struct { + plain string + encryptedBuf []byte + encryptedBufWithHeader []byte +} + +func mustDecode64(xs string) []byte { + val, err := base64.StdEncoding.DecodeString(xs) + if err != nil { + panic(err) + } + return val +} + +var testCases = map[string]testCase{ + "setPowerStateOn": { + plain: `{"system":{"set_relay_state":{"state":1}}}`, + encryptedBuf: mustDecode64(`0PKB+Iv/mvfV75S2xaDUi/mc8JHot8Sw0aXA4tijgfKG55P21O7fot+i`), + encryptedBufWithHeader: mustDecode64(`AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog==`), + }, + "setPowerStateOff": { + plain: `{"system":{"set_relay_state":{"state":0}}}`, + encryptedBuf: mustDecode64(`0PKB+Iv/mvfV75S2xaDUi/mc8JHot8Sw0aXA4tijgfKG55P21O7eo96j`), + encryptedBufWithHeader: mustDecode64(`AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu3qPeow==`), + }, + "getSysInfo": { + plain: `{ "system":{ "get_sysinfo":null } }`, + encryptedBuf: mustDecode64(`0PDSodir37rX9c+0lLbRtMCf7JXmj+GH6MrwnuuH68u2lus=`), + encryptedBufWithHeader: mustDecode64(`AAAAI9Dw0qHYq9+61/XPtJS20bTAn+yV5o/hh+jK8J7rh+vLtpbr`), + }, + "getConsumption": { + plain: `{ "emeter":{ "get_realtime":null } }`, + encryptedBuf: mustDecode64(`0PDSt9q/y67c/sS/n73av8uU5oPijvqT/pu5g+2Y9Ji4xeWY`), + encryptedBufWithHeader: mustDecode64(`AAAAJNDw0rfav8uu3P7Ev5+92r/LlOaD4o76k/6buYPtmPSYuMXlmA==`), + }, + "specialChars": { + plain: `right single quotation mark:’ left double quotation mark:“ right double quotation mark:” kissing cat face with closed eyes:😽`, + encryptedBuf: mustDecode64(`2bDXv8vrmPGf+JTx0aDVus6v27Lds5P+n+2GvF7eR2cLbgh8XDhXIkAsSWkYbQJ2F2MKZQsrRidVPgTmZvraqMGmzrqa/pHkhuqPr96rxLDRpcyjze2A4ZP4wiCgPR12H2wfdhh/XzxdKQlvDm0IKF82QioKaQVqGXwYOF0kQTII+Gf/Qg==`), + encryptedBufWithHeader: mustDecode64(`AAAAhdmw17/L65jxn/iU8dGg1brOr9uy3bOT/p/thrxe3kdnC24IfFw4VyJALElpGG0CdhdjCmULK0YnVT4E5mb62qjBps66mv6R5Ibqj6/eq8Sw0aXMo83tgOGT+MIgoD0ddh9sH3YYf188XSkJbw5tCChfNkIqCmkFahl8GDhdJEEyCPhn/0I=`), + }, +} + +func TestEncoder(t *testing.T) { + for k, v := range testCases { + t.Run(k, func(t *testing.T) { + ans := new(bytes.Buffer) + enc := codec.NewXorEncoder(ans, codec.DefaultFirstByte) + _, err := enc.Write([]byte(v.plain)) + assert.NoError(t, err) + assert.EqualValues(t, v.encryptedBuf, ans.Bytes()) + lenb := binary.BigEndian.AppendUint32(nil, uint32(ans.Len())) + assert.EqualValues(t, v.encryptedBufWithHeader, append(lenb, ans.Bytes()...)) + }) + } +} +func TestDecoder(t *testing.T) { + for k, v := range testCases { + t.Run(k, func(t *testing.T) { + enc := codec.NewXorDecoder(bytes.NewBuffer(v.encryptedBuf), codec.DefaultFirstByte) + pkt, err := io.ReadAll(enc) + assert.NoError(t, err) + assert.EqualValues(t, v.plain, string(pkt)) + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0c9a32a --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module tuxpa.in/a/tplink + +go 1.19 + +require github.com/stretchr/testify v1.8.2 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6a56e69 --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/msgs.go b/msgs.go new file mode 100644 index 0000000..9993da8 --- /dev/null +++ b/msgs.go @@ -0,0 +1,7 @@ +package tplink + +type MsgDiscovery struct { + System struct { + GetSysInfo struct{} `json:"get_sysinfo"` + } `json:"system"` +}