Files

597 lines
14 KiB
Go
Raw Permalink Normal View History

2026-09-09 21:44:05 -05:00
package frame
import (
"bufio"
"bytes"
"fmt"
"image"
"net"
"strings"
"testing"
"time"
"gocv.io/x/gocv"
)
func ptr[T any](v T) *T { return &v }
func testFrame(t *testing.T, width, height int, matType gocv.MatType, fillVal float64) Frame {
t.Helper()
mat := gocv.NewMatWithSize(height, width, matType)
mat.SetTo(gocv.NewScalar(fillVal, fillVal, fillVal, 0))
return Frame{
PixelBytes: mat.ToBytes(),
Width: uint(mat.Cols()),
Height: uint(mat.Rows()),
GocvImageType: mat.Type(),
Channels: mat.Channels(),
Guid: []byte("test-guid"),
SourceData: "test--cam0",
Timestamp: uint64(time.Now().UnixNano()),
}
}
func TestToMat_Roundtrip(t *testing.T) {
mat := gocv.NewMatWithSize(4, 4, gocv.MatTypeCV8UC1)
mat.SetTo(gocv.NewScalar(128, 0, 0, 0))
original := mat.ToBytes()
f := Frame{
PixelBytes: original,
Width: 4,
Height: 4,
GocvImageType: gocv.MatTypeCV8UC1,
Channels: 1,
}
result, err := f.ToMat()
if err != nil {
t.Fatalf("ToMat() returned error: %v", err)
}
defer result.Close()
if result.Cols() != 4 || result.Rows() != 4 {
t.Errorf("got size %dx%d, want 4x4", result.Cols(), result.Rows())
}
got := result.ToBytes()
if !bytes.Equal(got, original) {
t.Errorf("pixel bytes differ after roundtrip")
}
}
func TestToMat_NonNilResult(t *testing.T) {
f := Frame{
PixelBytes: []byte{0, 1, 2, 3},
Width: 2,
Height: 2,
GocvImageType: gocv.MatTypeCV8UC1,
Channels: 1,
}
mat, err := f.ToMat()
if err != nil {
t.Fatalf("ToMat() error: %v", err)
}
defer mat.Close()
if mat.Empty() {
t.Error("expected non-empty Mat")
}
}
func TestToMat_EmptyBytes(t *testing.T) {
f := Frame{
PixelBytes: nil,
Width: 0,
Height: 0,
GocvImageType: gocv.MatTypeCV8UC1,
Channels: 1,
}
_, err := f.ToMat()
if err == nil {
t.Error("expected error for empty pixel data, got nil")
}
}
func TestEncryptDecrypt_Roundtrip(t *testing.T) {
original := []byte("these are my pixel bytes, there are many like them but these are mine")
f := Frame{
PixelBytes: original,
Width: 8,
Height: 2,
GocvImageType: gocv.MatTypeCV8UC3,
Channels: 3,
}
if err := f.Encrypt("secret"); err != nil {
t.Fatalf("Encrypt() error: %v", err)
}
if bytes.Equal(f.PixelBytes, original) {
t.Error("PixelBytes unchanged after encryption")
}
if err := f.Decrypt("secret"); err != nil {
t.Fatalf("Decrypt() error: %v", err)
}
if !bytes.Equal(f.PixelBytes, original) {
t.Error("PixelBytes differ after decrypt roundtrip")
}
}
func TestEncryptDecrypt_WrongPassphrase(t *testing.T) {
original := []byte("sensitive pixel data")
f := Frame{
PixelBytes: original,
Width: 4,
Height: 1,
GocvImageType: gocv.MatTypeCV8UC3,
Channels: 3,
}
if err := f.Encrypt("correct-horse-battery-staple"); err != nil {
t.Fatalf("Encrypt() error: %v", err)
}
if err := f.Decrypt("wrong-passphrase"); err == nil {
t.Error("expected error for wrong passphrase, got nil")
}
}
func TestEncryptDecrypt_EmptyBytes(t *testing.T) {
f := Frame{PixelBytes: nil}
if err := f.Encrypt("pass"); err != nil {
t.Errorf("Encrypt on empty bytes should succeed: %v", err)
}
if err := f.Decrypt("pass"); err != nil {
t.Errorf("Decrypt on empty bytes should succeed: %v", err)
}
}
func TestDecrypt_TooShort(t *testing.T) {
f := Frame{PixelBytes: []byte{0, 1, 2}}
if err := f.Decrypt("pass"); err == nil {
t.Error("expected error for too-short data, got nil")
}
}
func TestEncrypt_UniqueSaltPerCall(t *testing.T) {
data := []byte("same data every time")
f1 := Frame{PixelBytes: bytes.Clone(data)}
f2 := Frame{PixelBytes: bytes.Clone(data)}
f1.Encrypt("pass")
f2.Encrypt("pass")
if bytes.Equal(f1.PixelBytes, f2.PixelBytes) {
t.Error("two encryptions of same data should differ (salt)")
}
}
func testClip() Clip {
return Clip{
PixelMats: [][]byte{{1, 2, 3}, {4, 5, 6}},
Width: 1,
Height: 1,
Types: gocv.MatTypeCV8UC3,
Channels: 3,
Guids: [][]byte{{0x01}, {0x02}},
Timestamps: []uint64{100, 200},
}
}
func TestClipEncryptDecrypt_Roundtrip(t *testing.T) {
clp := testClip()
if err := clp.Encrypt("secret"); err != nil {
t.Fatalf("Encrypt() error: %v", err)
}
if len(clp.Salt) != saltSize {
t.Fatalf("clip salt is %d bytes, want %d", len(clp.Salt), saltSize)
}
for i, px := range clp.PixelMats {
if len(px) != 3+clipEncryptedOverhead {
t.Fatalf("frame %d is %d bytes, want %d encrypted", i, len(px), 3+clipEncryptedOverhead)
}
}
if err := clp.Decrypt("secret"); err != nil {
t.Fatalf("Decrypt() error: %v", err)
}
want := testClip()
for i := range clp.PixelMats {
if !bytes.Equal(clp.PixelMats[i], want.PixelMats[i]) {
t.Errorf("frame %d differs after clip decrypt roundtrip", i)
}
}
}
func TestClipEncryptDecrypt_WrongPassphrase(t *testing.T) {
clp := testClip()
if err := clp.Encrypt("correct-horse"); err != nil {
t.Fatalf("Encrypt() error: %v", err)
}
if err := clp.Decrypt("wrong-passphrase"); err == nil {
t.Error("expected error for wrong passphrase, got nil")
}
}
func TestClipDecrypt_LegacyPerFrameFallback(t *testing.T) {
want := testClip()
legacy := testClip()
for i := range legacy.PixelMats {
f := Frame{PixelBytes: legacy.PixelMats[i]}
if err := f.Encrypt("secret"); err != nil {
t.Fatalf("frame %d Encrypt() error: %v", i, err)
}
legacy.PixelMats[i] = f.PixelBytes
}
if len(legacy.Salt) != 0 {
t.Fatal("legacy clip must not carry a clip-level salt")
}
if err := legacy.Decrypt("secret"); err != nil {
t.Fatalf("Decrypt() error: %v", err)
}
for i := range legacy.PixelMats {
if !bytes.Equal(legacy.PixelMats[i], want.PixelMats[i]) {
t.Errorf("frame %d differs after legacy per-frame decrypt", i)
}
}
}
func TestClipEncrypt_RecordsIterationCount(t *testing.T) {
orig := DefaultPBKDF2Iter
defer SetDefaultPBKDF2Iter(orig)
SetDefaultPBKDF2Iter(12345)
clp := testClip()
if err := clp.Encrypt("secret"); err != nil {
t.Fatalf("Encrypt() error: %v", err)
}
if clp.PBKDF2Iter != 12345 {
t.Fatalf("clip records PBKDF2Iter=%d, want 12345", clp.PBKDF2Iter)
}
}
func TestClipDecrypt_HonorsStoredIterationCount(t *testing.T) {
orig := DefaultPBKDF2Iter
defer SetDefaultPBKDF2Iter(orig)
SetDefaultPBKDF2Iter(12345)
want := testClip()
clp := testClip()
if err := clp.Encrypt("secret"); err != nil {
t.Fatalf("Encrypt() error: %v", err)
}
if clp.PBKDF2Iter != 12345 {
t.Fatalf("clip PBKDF2Iter=%d, want 12345", clp.PBKDF2Iter)
}
// Decrypting node is configured with a different default; it must still
// use the count recorded on the clip.
SetDefaultPBKDF2Iter(600000)
if err := clp.Decrypt("secret"); err != nil {
t.Fatalf("Decrypt with mismatched default: %v", err)
}
for i := range clp.PixelMats {
if !bytes.Equal(clp.PixelMats[i], want.PixelMats[i]) {
t.Errorf("frame %d differs after decrypt honoring stored iterations", i)
}
}
}
func TestClipDecrypt_TamperedIterationCountFails(t *testing.T) {
orig := DefaultPBKDF2Iter
defer SetDefaultPBKDF2Iter(orig)
clp := testClip()
if err := clp.Encrypt("secret"); err != nil {
t.Fatalf("Encrypt() error: %v", err)
}
// Changing the count invalidates the derived key.
clp.PBKDF2Iter = 600001
if err := clp.Decrypt("secret"); err == nil {
t.Error("expected decrypt error after tampering with PBKDF2Iter, got nil")
}
}
func TestClipEncrypt_Empty(t *testing.T) {
clp := Clip{}
if err := clp.Encrypt("secret"); err != nil {
t.Errorf("Encrypt on empty clip should succeed: %v", err)
}
if len(clp.Salt) != 0 {
t.Error("empty clip should not be assigned a salt")
}
}
func TestAuthHandshake_Match(t *testing.T) {
SetAuthToken("s3cret")
t.Cleanup(func() { SetAuthToken("") })
server, client := net.Pipe()
defer server.Close()
defer client.Close()
delivered := make(chan Clip, 1)
go HandleClipConn(server, NewMalformedAttempts(), func(clp Clip) {
delivered <- clp
})
if err := writeAuthToken(client); err != nil {
t.Fatalf("writeAuthToken: %v", err)
}
clp := validTestClip()
if _, err := writeWireClip(client, &clp); err != nil {
t.Fatalf("encode: %v", err)
}
select {
case <-delivered:
case <-time.After(3 * time.Second):
t.Fatal("timed out waiting for authenticated clip")
}
}
func TestAuthHandshake_Mismatch(t *testing.T) {
SetAuthToken("s3cret")
t.Cleanup(func() { SetAuthToken("") })
server, client := net.Pipe()
defer server.Close()
defer client.Close()
done := make(chan struct{})
go func() {
HandleClipConn(server, NewMalformedAttempts(), func(Clip) {
t.Error("callback must not run for a bad token")
})
close(done)
}()
if _, err := fmt.Fprintf(client, "wrong-token\n"); err != nil {
t.Fatalf("write: %v", err)
}
client.Close()
select {
case <-done:
case <-time.After(3 * time.Second):
t.Fatal("HandleClipConn did not return for bad token")
}
}
func TestAuthSend_EndToEnd(t *testing.T) {
SetAuthToken("s3cret")
t.Cleanup(func() { SetAuthToken("") })
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
defer ln.Close()
received := make(chan Clip, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
return
}
HandleClipConn(conn, NewMalformedAttempts(), func(clp Clip) {
received <- clp
})
}()
clp := testClip()
if err := clp.Send(ln.Addr().String()); err != nil {
t.Fatalf("Send() error: %v", err)
}
select {
case got := <-received:
if !bytes.Equal(got.PixelMats[0], []byte{1, 2, 3}) {
t.Error("received wrong pixel data")
}
case <-time.After(3 * time.Second):
t.Fatal("timed out waiting for clip")
}
}
func TestSublimate_Basic(t *testing.T) {
clp := Clip{
PixelMats: [][]byte{
{1, 2, 3},
{4, 5, 6},
},
Width: 1,
Height: 1,
Types: gocv.MatTypeCV8UC3,
Channels: 3,
Guids: [][]byte{{0x01}, {0x02}},
Timestamps: []uint64{100, 200},
SourceData: "test--cam0",
}
frames := clp.Sublimate()
if len(frames) != 2 {
t.Fatalf("got %d frames, want 2", len(frames))
}
for i, f := range frames {
if f.Width != 1 || f.Height != 1 {
t.Errorf("frame %d: got %dx%d, want 1x1", i, f.Width, f.Height)
}
if f.Channels != 3 {
t.Errorf("frame %d: got %d channels, want 3", i, f.Channels)
}
if f.Guid[0] != byte(i+1) {
t.Errorf("frame %d: guid = %x, want %x", i, f.Guid[0], byte(i+1))
}
if f.Timestamp != uint64(100*(i+1)) {
t.Errorf("frame %d: timestamp = %d, want %d", i, f.Timestamp, uint64(100*(i+1)))
}
if f.SourceData != "test--cam0" {
t.Errorf("frame %d: sourceData = %q", i, f.SourceData)
}
}
}
func TestSublimate_EmptyClip(t *testing.T) {
clp := Clip{}
frames := clp.Sublimate()
if len(frames) != 0 {
t.Errorf("got %d frames, want 0", len(frames))
}
}
func TestSublimate_DetectionsCarryOver(t *testing.T) {
dets := []map[string][]Detection{
{"human": {{DetectionTitle: "human", DetectionRegion: image.Rect(0, 0, 10, 10)}}},
nil,
}
clp := Clip{
PixelMats: [][]byte{{0}, {0}},
Guids: [][]byte{{1}, {2}},
Timestamps: []uint64{0, 0},
Detections: dets,
}
frames := clp.Sublimate()
if len(frames[0].Detections["human"]) != 1 {
t.Error("detections not carried over to frame 0")
}
if frames[1].Detections != nil {
t.Error("expected nil detections for frame 1")
}
}
func TestCheckLenCorrelations_Match(t *testing.T) {
clp := Clip{
PixelMats: [][]byte{{0}, {0}, {0}},
Guids: [][]byte{{1}, {2}, {3}},
Timestamps: []uint64{10, 20, 30},
}
ok, err := clp.CheckLenCorrelations()
if !ok {
t.Errorf("expected match, got error: %v", err)
}
}
func TestCheckLenCorrelations_PixelMatsGuidsMismatch(t *testing.T) {
clp := Clip{
PixelMats: [][]byte{{0}, {0}},
Guids: [][]byte{{1}},
Timestamps: []uint64{10, 20},
}
ok, _ := clp.CheckLenCorrelations()
if ok {
t.Error("expected mismatch")
}
}
func TestCheckLenCorrelations_TimestampsMismatch(t *testing.T) {
clp := Clip{
PixelMats: [][]byte{{0}, {0}},
Guids: [][]byte{{1}, {2}},
Timestamps: []uint64{10},
}
ok, _ := clp.CheckLenCorrelations()
if ok {
t.Error("expected mismatch")
}
}
func TestCheckLenCorrelations_Empty(t *testing.T) {
clp := Clip{}
ok, err := clp.CheckLenCorrelations()
if !ok {
t.Errorf("empty clip should match: %v", err)
}
}
func TestClipSend(t *testing.T) {
SetAuthToken("test-token")
t.Cleanup(func() { SetAuthToken("") })
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
defer ln.Close()
addr := ln.Addr().String()
received := make(chan Clip, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
return
}
defer conn.Close()
br := bufio.NewReader(conn)
line, err := br.ReadString('\n')
if err != nil || strings.TrimSpace(line) != "test-token" {
return
}
clp, err := DecodeWireClip(br)
if err == nil {
received <- clp
}
}()
sent := Clip{
PixelMats: [][]byte{{1, 2, 3}},
Width: 1,
Height: 1,
Types: gocv.MatTypeCV8UC3,
Channels: 3,
Guids: [][]byte{{42}},
Timestamps: []uint64{99},
SourceData: "test--cam0",
}
if err := sent.Send(addr); err != nil {
t.Fatalf("Send() error: %v", err)
}
select {
case got := <-received:
if !bytes.Equal(got.PixelMats[0], []byte{1, 2, 3}) {
t.Error("received wrong pixel data")
}
if got.Guids[0][0] != 42 {
t.Error("received wrong guid")
}
if got.Timestamps[0] != 99 {
t.Error("received wrong timestamp")
}
case <-time.After(3 * time.Second):
t.Fatal("timed out waiting for clip")
}
}
func TestClipSend_InvalidAddress(t *testing.T) {
clp := Clip{}
err := clp.Send("256.0.0.1:1")
if err == nil {
t.Error("expected error for unreachable address, got nil")
}
}
func TestFrameFields_ZeroValues(t *testing.T) {
f := Frame{}
if f.Detections != nil {
t.Error("expected nil Detections for zero-value Frame")
}
}
func TestClipFields_ZeroValues(t *testing.T) {
c := Clip{}
if c.Comparisons != nil {
t.Error("expected nil Comparisons for zero-value Clip")
}
}