feat: add Go API library
This commit is contained in:
+171
@@ -0,0 +1,171 @@
|
||||
package mijia
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
testSsecurity = "MDEyMzQ1Njc4OWFiY2RlZg=="
|
||||
testNonce = "AAECAwQFBgcICQoL"
|
||||
testSignedNonce = "16/CeTzC9IqVVbiZ01Hy/Qd8rtVo5ybLo+ph/Vvh52k="
|
||||
)
|
||||
|
||||
func TestGenerateNonce(t *testing.T) {
|
||||
before := time.Now().Unix() / 60
|
||||
nonce, err := generateNonce()
|
||||
if err != nil {
|
||||
t.Fatalf("generateNonce() error = %v", err)
|
||||
}
|
||||
after := time.Now().Unix() / 60
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("generated nonce is not base64: %v", err)
|
||||
}
|
||||
if len(decoded) <= 8 {
|
||||
t.Fatalf("decoded nonce length = %d, want more than 8", len(decoded))
|
||||
}
|
||||
|
||||
minute := decodeBigEndian(decoded[8:])
|
||||
if minute < before || minute > after {
|
||||
t.Fatalf("nonce minute = %d, want between %d and %d", minute, before, after)
|
||||
}
|
||||
if len(decoded[8:]) > 1 && decoded[8] == 0 {
|
||||
t.Fatal("nonce minute is not minimally encoded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedNonce(t *testing.T) {
|
||||
got, err := signedNonce(testSsecurity, testNonce)
|
||||
if err != nil {
|
||||
t.Fatalf("signedNonce() error = %v", err)
|
||||
}
|
||||
if got != testSignedNonce {
|
||||
t.Fatalf("signedNonce() = %q, want %q", got, testSignedNonce)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRC4Drop1024(t *testing.T) {
|
||||
ciphertext, err := encryptRC4(testSignedNonce, "hello world")
|
||||
if err != nil {
|
||||
t.Fatalf("encryptRC4() error = %v", err)
|
||||
}
|
||||
if ciphertext != "9ve6riTrkW1oJUE=" {
|
||||
t.Fatalf("encryptRC4() = %q, want fixed ciphertext", ciphertext)
|
||||
}
|
||||
|
||||
plaintext, err := decryptRC4(testSignedNonce, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("decryptRC4() error = %v", err)
|
||||
}
|
||||
if string(plaintext) != "hello world" {
|
||||
t.Fatalf("decryptRC4() = %q, want %q", plaintext, "hello world")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateEncryptedParams(t *testing.T) {
|
||||
params, err := generateEncryptedParams("/miotspec/prop/get", "post", testSignedNonce, testNonce, "{\"k\":\"v\"}", testSsecurity)
|
||||
if err != nil {
|
||||
t.Fatalf("generateEncryptedParams() error = %v", err)
|
||||
}
|
||||
|
||||
want := encryptedParams{
|
||||
Data: "5bC94HHpkCBn",
|
||||
RC4Hash: "19WFsROvqTVVE0wemsEqz8GI7Ib8ZRiiLJS5hQ==",
|
||||
Signature: "uRPqk6IHkFCAg+Skfo+5l/KbTpM=",
|
||||
Ssecurity: testSsecurity,
|
||||
Nonce: testNonce,
|
||||
}
|
||||
if params != want {
|
||||
t.Fatalf("generateEncryptedParams() = %#v, want %#v", params, want)
|
||||
}
|
||||
|
||||
firstSignature := encryptedSignature("/miotspec/prop/get", "post", []orderedParam{{Key: "data", Value: "{\"k\":\"v\"}"}}, testSignedNonce)
|
||||
if firstSignature != "IGSsXdO7OZiyN58pywgUYELur6g=" {
|
||||
t.Fatalf("first signature = %q, want fixed signature", firstSignature)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptPayload(t *testing.T) {
|
||||
plaintext, err := decryptPayload(testSsecurity, testNonce, "9ve6riTrkW1oJUE=")
|
||||
if err != nil {
|
||||
t.Fatalf("decryptPayload() plain error = %v", err)
|
||||
}
|
||||
if plaintext != "hello world" {
|
||||
t.Fatalf("decryptPayload() plain = %q, want %q", plaintext, "hello world")
|
||||
}
|
||||
|
||||
var compressed bytes.Buffer
|
||||
writer := gzip.NewWriter(&compressed)
|
||||
if _, err := writer.Write([]byte("gzip response")); err != nil {
|
||||
t.Fatalf("gzip write: %v", err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatalf("gzip close: %v", err)
|
||||
}
|
||||
ciphertext, err := encryptRC4Bytes(testSignedNonce, compressed.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("encrypt compressed response: %v", err)
|
||||
}
|
||||
|
||||
decrypted, err := decryptPayload(testSsecurity, testNonce, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("decryptPayload() gzip error = %v", err)
|
||||
}
|
||||
if decrypted != "gzip response" {
|
||||
t.Fatalf("decryptPayload() gzip = %q, want %q", decrypted, "gzip response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptPayloadInvalidBase64(t *testing.T) {
|
||||
_, err := decryptPayload(testSsecurity, testNonce, "not-base64")
|
||||
if err == nil || !strings.Contains(err.Error(), "decode RC4 payload") {
|
||||
t.Fatalf("decryptPayload() error = %v, want invalid base64 error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptPayloadDamagedGzip(t *testing.T) {
|
||||
ciphertext, err := encryptRC4Bytes(testSignedNonce, []byte{0x1f, 0x8b, 0xff})
|
||||
if err != nil {
|
||||
t.Fatalf("encrypt damaged gzip response: %v", err)
|
||||
}
|
||||
|
||||
_, err = decryptPayload(testSsecurity, testNonce, ciphertext)
|
||||
if err == nil || !strings.Contains(err.Error(), "open gzip response") {
|
||||
t.Fatalf("decryptPayload() error = %v, want damaged gzip error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptPayloadDecompressedLimit(t *testing.T) {
|
||||
var compressed bytes.Buffer
|
||||
writer := gzip.NewWriter(&compressed)
|
||||
oversized := bytes.Repeat([]byte{0xff}, maxDecompressedResponseBytes+1)
|
||||
if _, err := writer.Write(oversized); err != nil {
|
||||
t.Fatalf("gzip write: %v", err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatalf("gzip close: %v", err)
|
||||
}
|
||||
ciphertext, err := encryptRC4Bytes(testSignedNonce, compressed.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("encrypt oversized gzip response: %v", err)
|
||||
}
|
||||
|
||||
_, err = decryptPayload(testSsecurity, testNonce, ciphertext)
|
||||
if err == nil || !strings.Contains(err.Error(), "decompressed response exceeds") {
|
||||
t.Fatalf("decryptPayload() error = %v, want decompressed limit error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeBigEndian(value []byte) int64 {
|
||||
var result int64
|
||||
for _, current := range value {
|
||||
result = result<<8 | int64(current)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user