Files
go-xcipher/xcipher_test.go

743 lines
22 KiB
Go

package xcipher
import (
"bytes"
"context"
"crypto/rand"
"errors"
"fmt"
"golang.org/x/crypto/chacha20poly1305"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
)
// Generate a random key
func generateRandomKey() ([]byte, error) {
key := make([]byte, chacha20poly1305.KeySize)
_, err := rand.Read(key)
return key, err
}
// Generate random data of specified size
func generateRandomData(size int) ([]byte, error) {
data := make([]byte, size)
_, err := rand.Read(data)
return data, err
}
// Create temporary file and write data to it
func createTempFile(t *testing.T, data []byte) string {
tempDir := t.TempDir()
tempFile := filepath.Join(tempDir, "test_data")
if err := os.WriteFile(tempFile, data, 0644); err != nil {
t.Fatalf("Failed to create temporary file: %v", err)
}
return tempFile
}
// TestStreamEncryptDecrypt tests basic stream encryption/decryption functionality
func TestStreamEncryptDecrypt(t *testing.T) {
// Generate random key
key, err := generateRandomKey()
if err != nil {
t.Fatalf("Failed to generate key: %v", err)
}
// Initialize cipher
xcipher := NewXCipher(key)
// Generate random test data (1MB)
testSize := 1 * 1024 * 1024
testData, err := generateRandomData(testSize)
if err != nil {
t.Fatalf("Failed to generate test data: %v", err)
}
// Additional data
additionalData := []byte("Test additional data")
// Create input and output buffers
var encryptedBuf bytes.Buffer
encryptedReader := bytes.NewReader(testData)
// Perform stream encryption
err = xcipher.EncryptStream(encryptedReader, &encryptedBuf, additionalData)
if err != nil {
t.Fatalf("Stream encryption failed: %v", err)
}
// Create decryption buffer
var decryptedBuf bytes.Buffer
decryptReader := bytes.NewReader(encryptedBuf.Bytes())
// Perform stream decryption
err = xcipher.DecryptStream(decryptReader, &decryptedBuf, additionalData)
if err != nil {
t.Fatalf("Stream decryption failed: %v", err)
}
// Verify decrypted data matches original data
if !bytes.Equal(testData, decryptedBuf.Bytes()) {
t.Fatal("Stream encrypted/decrypted data does not match")
}
t.Logf("Successfully stream processed %d bytes of data", testSize)
}
// TestStreamEncryptDecryptWithOptions tests stream encryption/decryption with options
func TestStreamEncryptDecryptWithOptions(t *testing.T) {
// Generate random key
key, err := generateRandomKey()
if err != nil {
t.Fatalf("Failed to generate key: %v", err)
}
// Initialize cipher
xcipher := NewXCipher(key)
// Generate random test data (2MB)
testSize := 2 * 1024 * 1024
testData, err := generateRandomData(testSize)
if err != nil {
t.Fatalf("Failed to generate test data: %v", err)
}
// Create temporary file for testing large data
inputFile := createTempFile(t, testData)
defer os.Remove(inputFile)
// Additional data
additionalData := []byte("Test additional data")
// Test different buffer size options
bufferSizes := []int{8 * 1024, 32 * 1024, 128 * 1024}
for _, bufSize := range bufferSizes {
t.Run(fmt.Sprintf("BufferSize=%dKB", bufSize/1024), func(t *testing.T) {
// Create input and output files
encryptedFile := inputFile + ".enc"
decryptedFile := inputFile + ".dec"
defer os.Remove(encryptedFile)
defer os.Remove(decryptedFile)
// Open input file
inFile, err := os.Open(inputFile)
if err != nil {
t.Fatalf("Failed to open input file: %v", err)
}
defer inFile.Close()
// Create encrypted output file
outFile, err := os.Create(encryptedFile)
if err != nil {
t.Fatalf("Failed to create encrypted output file: %v", err)
}
defer outFile.Close()
// Create options
options := DefaultStreamOptions()
options.BufferSize = bufSize
options.AdditionalData = additionalData
options.CollectStats = true
// Perform stream encryption
stats, err := xcipher.EncryptStreamWithOptions(inFile, outFile, options)
if err != nil {
t.Fatalf("Stream encryption failed: %v", err)
}
// Output encryption performance statistics
t.Logf("Encryption performance statistics (buffer size=%dKB):", bufSize/1024)
t.Logf("- Bytes processed: %d", stats.BytesProcessed)
t.Logf("- Blocks processed: %d", stats.BlocksProcessed)
t.Logf("- Average block size: %.2f bytes", stats.AvgBlockSize)
t.Logf("- Processing time: %v", stats.Duration())
t.Logf("- Throughput: %.2f MB/s", stats.Throughput)
// Prepare for decryption
encData, err := ioutil.ReadFile(encryptedFile)
if err != nil {
t.Fatalf("Failed to read encrypted file: %v", err)
}
encFile := bytes.NewReader(encData)
decFile, err := os.Create(decryptedFile)
if err != nil {
t.Fatalf("Failed to create decrypted output file: %v", err)
}
defer decFile.Close()
// Perform parallel stream decryption
_, err = xcipher.DecryptStreamWithOptions(encFile, decFile, options)
if err != nil {
t.Fatalf("Parallel stream decryption failed: %v", err)
}
// Close file to ensure data is written
decFile.Close()
// Read decrypted data for verification
decryptedData, err := ioutil.ReadFile(decryptedFile)
if err != nil {
t.Fatalf("Failed to read decrypted file: %v", err)
}
// Verify data
if !bytes.Equal(testData, decryptedData) {
t.Fatal("Stream encrypted/decrypted data does not match")
}
t.Logf("Successfully stream processed %d bytes of data (buffer=%dKB)", testSize, bufSize/1024)
})
}
}
// TestStreamParallelProcessing tests the parallel stream encryption/decryption
func TestStreamParallelProcessing(t *testing.T) {
// Generate random key
key, err := generateRandomKey()
if err != nil {
t.Fatalf("Failed to generate key: %v", err)
}
// Initialize cipher
xcipher := NewXCipher(key)
// Generate smaller test data
testSize := 1 * 1024 * 1024 // 1MB
testData, err := generateRandomData(testSize)
if err != nil {
t.Fatalf("Failed to generate test data: %v", err)
}
// Create processing options - first test with non-parallel mode
options := DefaultStreamOptions()
options.UseParallel = false // Disable parallel processing
options.CollectStats = true
// Use memory buffer for testing
t.Log("Starting encryption")
var encryptedBuffer bytes.Buffer
// Perform stream encryption
stats, err := xcipher.EncryptStreamWithOptions(
bytes.NewReader(testData), &encryptedBuffer, options)
if err != nil {
t.Fatalf("Stream encryption failed: %v", err)
}
// Output encryption performance statistics
t.Logf("Encryption performance statistics:")
t.Logf("- Bytes processed: %d", stats.BytesProcessed)
t.Logf("- Blocks processed: %d", stats.BlocksProcessed)
t.Logf("- Average block size: %.2f bytes", stats.AvgBlockSize)
t.Logf("- Processing time: %v", stats.Duration())
t.Logf("- Throughput: %.2f MB/s", stats.Throughput)
// Get encrypted data
encryptedData := encryptedBuffer.Bytes()
t.Logf("Encrypted data size: %d bytes", len(encryptedData))
// Check if encrypted data is valid
if len(encryptedData) <= nonceSize {
t.Fatalf("Invalid encrypted data, length too short: %d bytes", len(encryptedData))
}
// Start decryption
t.Log("Starting decryption")
var decryptedBuffer bytes.Buffer
// Perform stream decryption
decStats, err := xcipher.DecryptStreamWithOptions(
bytes.NewReader(encryptedData), &decryptedBuffer, options)
if err != nil {
t.Fatalf("Stream decryption failed: %v (encrypted data size: %d bytes)", err, len(encryptedData))
}
// Output decryption performance statistics
t.Logf("Decryption performance statistics:")
t.Logf("- Bytes processed: %d", decStats.BytesProcessed)
t.Logf("- Blocks processed: %d", decStats.BlocksProcessed)
t.Logf("- Average block size: %.2f bytes", decStats.AvgBlockSize)
t.Logf("- Processing time: %v", decStats.Duration())
t.Logf("- Throughput: %.2f MB/s", decStats.Throughput)
// Get decrypted data
decryptedData := decryptedBuffer.Bytes()
// Verify data
if !bytes.Equal(testData, decryptedData) {
t.Fatal("Stream encrypted/decrypted data does not match")
}
t.Logf("Successfully completed stream processing of %d bytes", testSize)
}
// TestStreamCancellation tests cancellation of stream encryption/decryption operations
func TestStreamCancellation(t *testing.T) {
// Generate random key
key, err := generateRandomKey()
if err != nil {
t.Fatalf("Failed to generate key: %v", err)
}
// Initialize cipher
xcipher := NewXCipher(key)
// Generate large test data (50MB, enough time to cancel)
testSize := 50 * 1024 * 1024
testData, err := generateRandomData(testSize)
if err != nil {
t.Fatalf("Failed to generate test data: %v", err)
}
// Create an unlimited data source to simulate large file
infiniteReader := &infiniteDataReader{data: testData}
// Create output buffer
var outputBuf bytes.Buffer
// Create context with cancellation
ctx, cancel := context.WithCancel(context.Background())
// Create options with cancel channel
options := DefaultStreamOptions()
options.CancelChan = ctx.Done()
// Cancel operation after a short time
go func() {
time.Sleep(100 * time.Millisecond) // Let encryption run for a short time
cancel()
}()
// Perform stream encryption, should be cancelled
_, err = xcipher.EncryptStreamWithOptions(infiniteReader, &outputBuf, options)
// Verify error is cancellation error
if !errors.Is(err, ErrOperationCancelled) {
t.Fatalf("Expected cancellation error, but got: %v", err)
}
t.Log("Successfully tested stream encryption cancellation")
}
// TestStreamErrors tests error handling in stream encryption/decryption
func TestStreamErrors(t *testing.T) {
// Generate random key
key, err := generateRandomKey()
if err != nil {
t.Fatalf("Failed to generate key: %v", err)
}
// Initialize cipher
xcipher := NewXCipher(key)
t.Run("InvalidBufferSize", func(t *testing.T) {
// Generate test data
testData, err := generateRandomData(1024)
if err != nil {
t.Fatalf("Failed to generate test data: %v", err)
}
// Test case with too small buffer (1 byte)
t.Run("BufferTooSmall", func(t *testing.T) {
// Create new options for each subtest to avoid shared state
options := DefaultStreamOptions()
options.BufferSize = 1 // Extremely small buffer
options.CollectStats = true // Ensure stats are collected
var buffer bytes.Buffer
stats, err := xcipher.EncryptStreamWithOptions(
bytes.NewReader(testData), &buffer, options)
// Verify that buffer size was automatically adjusted instead of returning error
if err != nil {
t.Errorf("Expected automatic buffer size adjustment, but got error: %v", err)
}
// Check if buffer was adjusted to minimum valid size
if stats != nil && stats.BufferSize < minBufferSize {
t.Errorf("Buffer size should be greater than or equal to minimum %d, but got %d",
minBufferSize, stats.BufferSize)
}
if stats != nil {
t.Logf("Requested buffer size: %d, actually used: %d", options.BufferSize, stats.BufferSize)
}
})
// Test case with too large buffer (10MB)
t.Run("BufferTooLarge", func(t *testing.T) {
// Create new options for each subtest to avoid shared state
options := DefaultStreamOptions()
options.BufferSize = 10 * 1024 * 1024 // 10MB, potentially too large
options.CollectStats = true // Ensure stats are collected
var buffer bytes.Buffer
stats, err := xcipher.EncryptStreamWithOptions(
bytes.NewReader(testData), &buffer, options)
// Verify that buffer size was automatically adjusted instead of returning error
if err != nil {
t.Errorf("Expected automatic adjustment of oversized buffer, but got error: %v", err)
}
// Check if buffer was adjusted to a reasonable size
if stats != nil && stats.BufferSize > maxBufferSize {
t.Errorf("Buffer size should be less than or equal to maximum %d, but got %d",
maxBufferSize, stats.BufferSize)
}
if stats != nil {
t.Logf("Requested buffer size: %d, actually used: %d", options.BufferSize, stats.BufferSize)
}
})
})
// Test authentication failure
t.Run("AuthenticationFailure", func(t *testing.T) {
// First encrypt some data
plaintext := []byte("Test authentication failure")
var encBuf bytes.Buffer
err := xcipher.EncryptStream(bytes.NewReader(plaintext), &encBuf, nil)
if err != nil {
t.Fatalf("Encryption failed: %v", err)
}
// Tamper with encrypted data
encryptedData := encBuf.Bytes()
if len(encryptedData) > nonceSize+10 {
// Modify one byte in ciphertext part
encryptedData[nonceSize+10]++
}
// Try to decrypt tampered data
var decBuf bytes.Buffer
err = xcipher.DecryptStream(bytes.NewReader(encryptedData), &decBuf, nil)
if err == nil || !errors.Is(err, ErrAuthenticationFailed) {
t.Fatalf("Expected authentication failure error, but got: %v", err)
}
})
// Test read error
t.Run("ReadError", func(t *testing.T) {
reader := &errorReader{err: fmt.Errorf("simulated read error")}
var buf bytes.Buffer
err := xcipher.EncryptStream(reader, &buf, nil)
if err == nil || !errors.Is(err, ErrReadFailed) {
t.Fatalf("Expected read failure error, but got: %v", err)
}
})
// Test write error
t.Run("WriteError", func(t *testing.T) {
writer := &errorWriter{err: fmt.Errorf("simulated write error")}
err := xcipher.EncryptStream(bytes.NewReader([]byte("test")), writer, nil)
if err == nil || !errors.Is(err, ErrWriteFailed) {
t.Fatalf("Expected write failure error, but got: %v", err)
}
})
}
// Infinite data reader for testing cancellation
type infiniteDataReader struct {
data []byte
pos int
}
func (r *infiniteDataReader) Read(p []byte) (n int, err error) {
if r.pos >= len(r.data) {
r.pos = 0 // Cycle through data
}
n = copy(p, r.data[r.pos:])
r.pos += n
return n, nil
}
// Reader that simulates read errors
type errorReader struct {
err error
}
func (r *errorReader) Read(p []byte) (n int, err error) {
return 0, r.err
}
// Writer that simulates write errors
type errorWriter struct {
err error
}
func (w *errorWriter) Write(p []byte) (n int, err error) {
return 0, w.err
}
// TestCPUFeatureDetection tests CPU feature detection functionality
func TestCPUFeatureDetection(t *testing.T) {
// Get system optimization info
info := GetSystemOptimizationInfo()
// Output detected CPU features
t.Logf("CPU architecture: %s", info.Architecture)
t.Logf("CPU core count: %d", info.NumCPUs)
t.Logf("AVX support: %v", info.HasAVX)
t.Logf("AVX2 support: %v", info.HasAVX2)
t.Logf("SSE4.1 support: %v", info.HasSSE41)
t.Logf("NEON support: %v", info.HasNEON)
t.Logf("Estimated L1 cache size: %d KB", info.EstimatedL1Cache/1024)
t.Logf("Estimated L2 cache size: %d KB", info.EstimatedL2Cache/1024)
t.Logf("Estimated L3 cache size: %d MB", info.EstimatedL3Cache/1024/1024)
// Check recommended parameters
t.Logf("Recommended buffer size: %d KB", info.RecommendedBufferSize/1024)
t.Logf("Recommended worker count: %d", info.RecommendedWorkers)
// Simple validation of recommended parameters
if info.RecommendedBufferSize < minBufferSize || info.RecommendedBufferSize > maxBufferSize {
t.Errorf("Recommended buffer size %d outside valid range [%d, %d]",
info.RecommendedBufferSize, minBufferSize, maxBufferSize)
}
if info.RecommendedWorkers < minWorkers || info.RecommendedWorkers > maxWorkers {
t.Errorf("Recommended worker count %d outside valid range [%d, %d]",
info.RecommendedWorkers, minWorkers, maxWorkers)
}
}
// TestDynamicParameterAdjustment tests dynamic parameter adjustment system
func TestDynamicParameterAdjustment(t *testing.T) {
// Test different buffer size requests
testCases := []struct {
requestedSize int
description string
}{
{0, "Zero request (use auto-optimization)"},
{4 * 1024, "Below minimum"},
{16 * 1024, "Normal small value"},
{64 * 1024, "Medium value"},
{256 * 1024, "Larger value"},
{2 * 1024 * 1024, "Above maximum"},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
// Get adjusted buffer size
adjustedSize := adaptiveBufferSize(tc.requestedSize)
t.Logf("Requested size: %d, adjusted size: %d", tc.requestedSize, adjustedSize)
// Validate adjusted size is within valid range
if adjustedSize < minBufferSize {
t.Errorf("Adjusted buffer size %d less than minimum %d", adjustedSize, minBufferSize)
}
if adjustedSize > maxBufferSize {
t.Errorf("Adjusted buffer size %d greater than maximum %d", adjustedSize, maxBufferSize)
}
})
}
// Test different worker thread count requests
workerTestCases := []struct {
requestedWorkers int
bufferSize int
description string
}{
{0, 16 * 1024, "Auto-select (small buffer)"},
{0, 512 * 1024, "Auto-select (large buffer)"},
{1, 64 * 1024, "Single thread request"},
{12, 64 * 1024, "Multi-thread request"},
}
for _, tc := range workerTestCases {
t.Run(tc.description, func(t *testing.T) {
// Get adjusted worker count
adjustedWorkers := adaptiveWorkerCount(tc.requestedWorkers, tc.bufferSize)
t.Logf("Requested workers: %d, buffer size: %d, adjusted workers: %d",
tc.requestedWorkers, tc.bufferSize, adjustedWorkers)
// Validate adjusted worker count is within valid range
if adjustedWorkers < minWorkers {
t.Errorf("Adjusted worker count %d less than minimum %d", adjustedWorkers, minWorkers)
}
if adjustedWorkers > maxWorkers {
t.Errorf("Adjusted worker count %d greater than maximum %d", adjustedWorkers, maxWorkers)
}
})
}
}
// TestOptimizedStreamOptions tests optimized stream options
func TestOptimizedStreamOptions(t *testing.T) {
// Get optimized stream options
options := GetOptimizedStreamOptions()
t.Logf("Optimized stream options:")
t.Logf("- Buffer size: %d KB", options.BufferSize/1024)
t.Logf("- Use parallel: %v", options.UseParallel)
t.Logf("- Max workers: %d", options.MaxWorkers)
// Validate options are within valid ranges
if options.BufferSize < minBufferSize || options.BufferSize > maxBufferSize {
t.Errorf("Buffer size %d outside valid range [%d, %d]",
options.BufferSize, minBufferSize, maxBufferSize)
}
if options.MaxWorkers < minWorkers || options.MaxWorkers > maxWorkers {
t.Errorf("Max worker count %d outside valid range [%d, %d]",
options.MaxWorkers, minWorkers, maxWorkers)
}
}
// TestZeroCopyMechanism tests zero-copy mechanism
func TestZeroCopyMechanism(t *testing.T) {
// Test zero-copy string conversion between string and byte slice
original := "测试零拷贝字符串转换"
byteData := stringToBytes(original)
restored := bytesToString(byteData)
if original != restored {
t.Errorf("Zero-copy string conversion failed: %s != %s", original, restored)
}
// Test buffer reuse
data := []byte("测试缓冲区重用")
// Request a buffer larger than original data
largerCap := len(data) * 2
newBuf := reuseBuffer(data, largerCap)
// Verify data was copied correctly
if !bytes.Equal(data, newBuf[:len(data)]) {
t.Error("Data mismatch after buffer reuse")
}
// Verify capacity was increased
if cap(newBuf) < largerCap {
t.Errorf("Buffer capacity not properly increased: %d < %d", cap(newBuf), largerCap)
}
// Test reuse when original buffer is large enough
largeBuf := make([]byte, 100)
copy(largeBuf, data)
// Request capacity smaller than original buffer
smallerCap := 50
reusedBuf := reuseBuffer(largeBuf, smallerCap)
// Verify it's the same underlying array (by comparing length)
if len(reusedBuf) != smallerCap {
t.Errorf("Reused buffer length incorrect: %d != %d", len(reusedBuf), smallerCap)
}
// Verify data integrity
if !bytes.Equal(largeBuf[:len(data)], data) {
t.Error("Original data corrupted after reuse")
}
}
// TestAutoParallelDecision tests automatic parallel processing decision
func TestAutoParallelDecision(t *testing.T) {
// Generate random key
key, err := generateRandomKey()
if err != nil {
t.Fatalf("Failed to generate key: %v", err)
}
// Initialize cipher
xcipher := NewXCipher(key)
testCases := []struct {
name string
dataSize int // Data size in bytes
forceParallel bool // Whether to force parallel mode
}{
{"Small data", 10 * 1024, false}, // 10KB
{"Medium data", 500 * 1024, false}, // 500KB
{"Large data", 2 * 1024 * 1024, true}, // 2MB - force parallel mode
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Generate test data
testData, err := generateRandomData(tc.dataSize)
if err != nil {
t.Fatalf("Failed to generate test data: %v", err)
}
// Create default options and enable stats collection
options := DefaultStreamOptions()
options.CollectStats = true
options.UseParallel = tc.forceParallel // For large data, force parallel mode
// Create temporary file for testing
var encBuffer bytes.Buffer
var stats *StreamStats
// For large data, use file IO instead of memory buffer to ensure parallel mode is triggered
if tc.dataSize >= parallelThreshold {
// Create temporary file
tempFile := createTempFile(t, testData)
defer os.Remove(tempFile)
// Create temporary output file
tempOutFile, err := os.CreateTemp("", "xcipher-test-*")
if err != nil {
t.Fatalf("Failed to create temporary output file: %v", err)
}
tempOutPath := tempOutFile.Name()
tempOutFile.Close()
defer os.Remove(tempOutPath)
// Open file for encryption
inFile, err := os.Open(tempFile)
if err != nil {
t.Fatalf("Failed to open temporary file: %v", err)
}
defer inFile.Close()
outFile, err := os.Create(tempOutPath)
if err != nil {
t.Fatalf("Failed to open output file: %v", err)
}
defer outFile.Close()
// Perform encryption
stats, err = xcipher.EncryptStreamWithOptions(inFile, outFile, options)
if err != nil {
t.Fatalf("Encryption failed: %v", err)
}
} else {
// Use memory buffer for small data
stats, err = xcipher.EncryptStreamWithOptions(
bytes.NewReader(testData), &encBuffer, options)
if err != nil {
t.Fatalf("Encryption failed: %v", err)
}
}
// Output decision results
t.Logf("Data size: %d bytes", tc.dataSize)
t.Logf("Auto decision: Use parallel=%v, workers=%d, buffer size=%d",
stats.ParallelProcessing, stats.WorkerCount, stats.BufferSize)
t.Logf("Performance: Time=%v, throughput=%.2f MB/s",
stats.Duration(), stats.Throughput)
// Verify parallel processing state matches expectation
if tc.forceParallel && !stats.ParallelProcessing {
t.Errorf("Forced parallel processing was set, but system did not use parallel mode")
}
})
}
}