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 }