feat(audio): enhance validation and add logging standards

refactor validation functions to be more comprehensive and add detailed error messages
add new logging standards implementation for audio components
remove deprecated validation_enhanced.go file
add performance-critical tests for validation functions
This commit is contained in:
Alex P 2025-08-27 20:46:47 +00:00
parent 6355dd87be
commit 9dda569523
5 changed files with 1454 additions and 514 deletions

View File

@ -0,0 +1,323 @@
package audio
import (
"time"
"github.com/rs/zerolog"
)
// AudioLoggerStandards provides standardized logging patterns for audio components
type AudioLoggerStandards struct {
logger zerolog.Logger
component string
}
// NewAudioLogger creates a new standardized logger for an audio component
func NewAudioLogger(logger zerolog.Logger, component string) *AudioLoggerStandards {
return &AudioLoggerStandards{
logger: logger.With().Str("component", component).Logger(),
component: component,
}
}
// Component Lifecycle Logging
// LogComponentStarting logs component initialization start
func (als *AudioLoggerStandards) LogComponentStarting() {
als.logger.Debug().Msg("starting component")
}
// LogComponentStarted logs successful component start
func (als *AudioLoggerStandards) LogComponentStarted() {
als.logger.Debug().Msg("component started successfully")
}
// LogComponentStopping logs component shutdown start
func (als *AudioLoggerStandards) LogComponentStopping() {
als.logger.Debug().Msg("stopping component")
}
// LogComponentStopped logs successful component stop
func (als *AudioLoggerStandards) LogComponentStopped() {
als.logger.Debug().Msg("component stopped")
}
// LogComponentReady logs component ready state
func (als *AudioLoggerStandards) LogComponentReady() {
als.logger.Info().Msg("component ready")
}
// Error Logging with Context
// LogError logs a general error with context
func (als *AudioLoggerStandards) LogError(err error, msg string) {
als.logger.Error().Err(err).Msg(msg)
}
// LogErrorWithContext logs an error with additional context fields
func (als *AudioLoggerStandards) LogErrorWithContext(err error, msg string, fields map[string]interface{}) {
event := als.logger.Error().Err(err)
for key, value := range fields {
event = event.Interface(key, value)
}
event.Msg(msg)
}
// LogValidationError logs validation failures with specific context
func (als *AudioLoggerStandards) LogValidationError(err error, validationType string, value interface{}) {
als.logger.Error().Err(err).
Str("validation_type", validationType).
Interface("invalid_value", value).
Msg("validation failed")
}
// LogConnectionError logs connection-related errors
func (als *AudioLoggerStandards) LogConnectionError(err error, endpoint string, retryCount int) {
als.logger.Error().Err(err).
Str("endpoint", endpoint).
Int("retry_count", retryCount).
Msg("connection failed")
}
// LogProcessError logs process-related errors with PID context
func (als *AudioLoggerStandards) LogProcessError(err error, pid int, msg string) {
als.logger.Error().Err(err).
Int("pid", pid).
Msg(msg)
}
// Performance and Metrics Logging
// LogPerformanceMetrics logs standardized performance metrics
func (als *AudioLoggerStandards) LogPerformanceMetrics(metrics map[string]interface{}) {
event := als.logger.Info()
for key, value := range metrics {
event = event.Interface(key, value)
}
event.Msg("performance metrics")
}
// LogLatencyMetrics logs latency-specific metrics
func (als *AudioLoggerStandards) LogLatencyMetrics(current, average, max time.Duration, jitter time.Duration) {
als.logger.Info().
Dur("current_latency", current).
Dur("average_latency", average).
Dur("max_latency", max).
Dur("jitter", jitter).
Msg("latency metrics")
}
// LogFrameMetrics logs frame processing metrics
func (als *AudioLoggerStandards) LogFrameMetrics(processed, dropped int64, rate float64) {
als.logger.Info().
Int64("frames_processed", processed).
Int64("frames_dropped", dropped).
Float64("processing_rate", rate).
Msg("frame processing metrics")
}
// LogBufferMetrics logs buffer utilization metrics
func (als *AudioLoggerStandards) LogBufferMetrics(size, used, peak int, utilizationPercent float64) {
als.logger.Info().
Int("buffer_size", size).
Int("buffer_used", used).
Int("buffer_peak", peak).
Float64("utilization_percent", utilizationPercent).
Msg("buffer metrics")
}
// Warning Logging
// LogWarning logs a general warning
func (als *AudioLoggerStandards) LogWarning(msg string) {
als.logger.Warn().Msg(msg)
}
// LogWarningWithError logs a warning with error context
func (als *AudioLoggerStandards) LogWarningWithError(err error, msg string) {
als.logger.Warn().Err(err).Msg(msg)
}
// LogThresholdWarning logs warnings when thresholds are exceeded
func (als *AudioLoggerStandards) LogThresholdWarning(metric string, current, threshold interface{}, msg string) {
als.logger.Warn().
Str("metric", metric).
Interface("current_value", current).
Interface("threshold", threshold).
Msg(msg)
}
// LogRetryWarning logs retry attempts with context
func (als *AudioLoggerStandards) LogRetryWarning(operation string, attempt, maxAttempts int, delay time.Duration) {
als.logger.Warn().
Str("operation", operation).
Int("attempt", attempt).
Int("max_attempts", maxAttempts).
Dur("retry_delay", delay).
Msg("retrying operation")
}
// LogRecoveryWarning logs recovery from error conditions
func (als *AudioLoggerStandards) LogRecoveryWarning(condition string, duration time.Duration) {
als.logger.Warn().
Str("condition", condition).
Dur("recovery_time", duration).
Msg("recovered from error condition")
}
// Debug and Trace Logging
// LogDebug logs debug information
func (als *AudioLoggerStandards) LogDebug(msg string) {
als.logger.Debug().Msg(msg)
}
// LogDebugWithFields logs debug information with structured fields
func (als *AudioLoggerStandards) LogDebugWithFields(msg string, fields map[string]interface{}) {
event := als.logger.Debug()
for key, value := range fields {
event = event.Interface(key, value)
}
event.Msg(msg)
}
// LogOperationTrace logs operation tracing for debugging
func (als *AudioLoggerStandards) LogOperationTrace(operation string, duration time.Duration, success bool) {
als.logger.Debug().
Str("operation", operation).
Dur("duration", duration).
Bool("success", success).
Msg("operation trace")
}
// LogDataFlow logs data flow for debugging
func (als *AudioLoggerStandards) LogDataFlow(source, destination string, bytes int, frameCount int) {
als.logger.Debug().
Str("source", source).
Str("destination", destination).
Int("bytes", bytes).
Int("frame_count", frameCount).
Msg("data flow")
}
// Configuration and State Logging
// LogConfigurationChange logs configuration updates
func (als *AudioLoggerStandards) LogConfigurationChange(configType string, oldValue, newValue interface{}) {
als.logger.Info().
Str("config_type", configType).
Interface("old_value", oldValue).
Interface("new_value", newValue).
Msg("configuration changed")
}
// LogStateTransition logs component state changes
func (als *AudioLoggerStandards) LogStateTransition(fromState, toState string, reason string) {
als.logger.Info().
Str("from_state", fromState).
Str("to_state", toState).
Str("reason", reason).
Msg("state transition")
}
// LogResourceAllocation logs resource allocation/deallocation
func (als *AudioLoggerStandards) LogResourceAllocation(resourceType string, allocated bool, amount interface{}) {
level := als.logger.Debug()
if allocated {
level.Str("action", "allocated")
} else {
level.Str("action", "deallocated")
}
level.Str("resource_type", resourceType).
Interface("amount", amount).
Msg("resource allocation")
}
// Network and IPC Logging
// LogConnectionEvent logs connection lifecycle events
func (als *AudioLoggerStandards) LogConnectionEvent(event, endpoint string, connectionID string) {
als.logger.Info().
Str("event", event).
Str("endpoint", endpoint).
Str("connection_id", connectionID).
Msg("connection event")
}
// LogIPCEvent logs IPC communication events
func (als *AudioLoggerStandards) LogIPCEvent(event, socketPath string, bytes int) {
als.logger.Debug().
Str("event", event).
Str("socket_path", socketPath).
Int("bytes", bytes).
Msg("IPC event")
}
// LogNetworkStats logs network statistics
func (als *AudioLoggerStandards) LogNetworkStats(sent, received int64, latency time.Duration, packetLoss float64) {
als.logger.Info().
Int64("bytes_sent", sent).
Int64("bytes_received", received).
Dur("network_latency", latency).
Float64("packet_loss_percent", packetLoss).
Msg("network statistics")
}
// Process and System Logging
// LogProcessEvent logs process lifecycle events
func (als *AudioLoggerStandards) LogProcessEvent(event string, pid int, exitCode *int) {
event_log := als.logger.Info().
Str("event", event).
Int("pid", pid)
if exitCode != nil {
event_log = event_log.Int("exit_code", *exitCode)
}
event_log.Msg("process event")
}
// LogSystemResource logs system resource usage
func (als *AudioLoggerStandards) LogSystemResource(cpuPercent, memoryMB float64, goroutines int) {
als.logger.Info().
Float64("cpu_percent", cpuPercent).
Float64("memory_mb", memoryMB).
Int("goroutines", goroutines).
Msg("system resources")
}
// LogPriorityChange logs thread priority changes
func (als *AudioLoggerStandards) LogPriorityChange(tid, oldPriority, newPriority int, policy string) {
als.logger.Debug().
Int("tid", tid).
Int("old_priority", oldPriority).
Int("new_priority", newPriority).
Str("policy", policy).
Msg("thread priority changed")
}
// Utility Functions
// GetLogger returns the underlying zerolog.Logger for advanced usage
func (als *AudioLoggerStandards) GetLogger() zerolog.Logger {
return als.logger
}
// WithFields returns a new logger with additional persistent fields
func (als *AudioLoggerStandards) WithFields(fields map[string]interface{}) *AudioLoggerStandards {
event := als.logger.With()
for key, value := range fields {
event = event.Interface(key, value)
}
return &AudioLoggerStandards{
logger: event.Logger(),
component: als.component,
}
}
// WithSubComponent creates a logger for a sub-component
func (als *AudioLoggerStandards) WithSubComponent(subComponent string) *AudioLoggerStandards {
return &AudioLoggerStandards{
logger: als.logger.With().Str("sub_component", subComponent).Logger(),
component: als.component + "." + subComponent,
}
}

View File

@ -0,0 +1,389 @@
//go:build cgo
// +build cgo
package audio
import (
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestPerformanceCriticalPaths tests the most frequently executed code paths
// to ensure they remain efficient and don't interfere with KVM functionality
func TestPerformanceCriticalPaths(t *testing.T) {
tests := []struct {
name string
testFunc func(t *testing.T)
}{
{"AudioFrameProcessingLatency", testAudioFrameProcessingLatency},
{"MetricsUpdateOverhead", testMetricsUpdateOverhead},
{"ConfigurationAccessSpeed", testConfigurationAccessSpeed},
{"ValidationFunctionSpeed", testValidationFunctionSpeed},
{"MemoryAllocationPatterns", testMemoryAllocationPatterns},
{"ConcurrentAccessPerformance", testConcurrentAccessPerformance},
{"BufferPoolEfficiency", testBufferPoolEfficiency},
{"AtomicOperationOverhead", testAtomicOperationOverhead},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if testing.Short() {
t.Skip("Skipping performance test in short mode")
}
tt.testFunc(t)
})
}
}
// testAudioFrameProcessingLatency tests the latency of audio frame processing
// This is the most critical path that must not interfere with KVM
func testAudioFrameProcessingLatency(t *testing.T) {
const (
frameCount = 1000
maxLatencyPerFrame = 100 * time.Microsecond // Very strict requirement
)
// Create test frame data
frameData := make([]byte, 1920) // Typical frame size
for i := range frameData {
frameData[i] = byte(i % 256)
}
// Measure frame processing latency
start := time.Now()
for i := 0; i < frameCount; i++ {
// Simulate the critical path: validation + metrics update
err := ValidateAudioFrameFast(frameData)
require.NoError(t, err)
// Record frame received (atomic operation)
RecordFrameReceived(len(frameData))
}
elapsed := time.Since(start)
avgLatencyPerFrame := elapsed / frameCount
t.Logf("Average frame processing latency: %v", avgLatencyPerFrame)
// Ensure frame processing is fast enough to not interfere with KVM
assert.Less(t, avgLatencyPerFrame, maxLatencyPerFrame,
"Frame processing latency %v exceeds maximum %v - may interfere with KVM",
avgLatencyPerFrame, maxLatencyPerFrame)
// Ensure total processing time is reasonable
maxTotalTime := 50 * time.Millisecond
assert.Less(t, elapsed, maxTotalTime,
"Total processing time %v exceeds maximum %v", elapsed, maxTotalTime)
}
// testMetricsUpdateOverhead tests the overhead of metrics updates
func testMetricsUpdateOverhead(t *testing.T) {
const iterations = 10000
// Test RecordFrameReceived performance
start := time.Now()
for i := 0; i < iterations; i++ {
RecordFrameReceived(1024)
}
recordLatency := time.Since(start) / iterations
// Test GetAudioMetrics performance
start = time.Now()
for i := 0; i < iterations; i++ {
_ = GetAudioMetrics()
}
getLatency := time.Since(start) / iterations
t.Logf("RecordFrameReceived latency: %v", recordLatency)
t.Logf("GetAudioMetrics latency: %v", getLatency)
// Metrics operations should be optimized for JetKVM's ARM Cortex-A7 @ 1GHz
// With 256MB RAM, we need to be conservative with performance expectations
assert.Less(t, recordLatency, 50*time.Microsecond, "RecordFrameReceived too slow")
assert.Less(t, getLatency, 20*time.Microsecond, "GetAudioMetrics too slow")
}
// testConfigurationAccessSpeed tests configuration access performance
func testConfigurationAccessSpeed(t *testing.T) {
const iterations = 10000
// Test GetAudioConfig performance
start := time.Now()
for i := 0; i < iterations; i++ {
_ = GetAudioConfig()
}
configLatency := time.Since(start) / iterations
// Test GetConfig performance
start = time.Now()
for i := 0; i < iterations; i++ {
_ = GetConfig()
}
constantsLatency := time.Since(start) / iterations
t.Logf("GetAudioConfig latency: %v", configLatency)
t.Logf("GetConfig latency: %v", constantsLatency)
// Configuration access should be very fast
assert.Less(t, configLatency, 100*time.Nanosecond, "GetAudioConfig too slow")
assert.Less(t, constantsLatency, 100*time.Nanosecond, "GetConfig too slow")
}
// testValidationFunctionSpeed tests validation function performance
func testValidationFunctionSpeed(t *testing.T) {
const iterations = 10000
frameData := make([]byte, 1920)
// Test ValidateAudioFrameFast (most critical)
start := time.Now()
for i := 0; i < iterations; i++ {
err := ValidateAudioFrameFast(frameData)
require.NoError(t, err)
}
fastValidationLatency := time.Since(start) / iterations
// Test ValidateAudioQuality
start = time.Now()
for i := 0; i < iterations; i++ {
err := ValidateAudioQuality(AudioQualityMedium)
require.NoError(t, err)
}
qualityValidationLatency := time.Since(start) / iterations
// Test ValidateBufferSize
start = time.Now()
for i := 0; i < iterations; i++ {
err := ValidateBufferSize(1024)
require.NoError(t, err)
}
bufferValidationLatency := time.Since(start) / iterations
t.Logf("ValidateAudioFrameFast latency: %v", fastValidationLatency)
t.Logf("ValidateAudioQuality latency: %v", qualityValidationLatency)
t.Logf("ValidateBufferSize latency: %v", bufferValidationLatency)
// Validation functions optimized for ARM Cortex-A7 single core @ 1GHz
// Conservative thresholds to ensure KVM functionality isn't impacted
assert.Less(t, fastValidationLatency, 100*time.Microsecond, "ValidateAudioFrameFast too slow")
assert.Less(t, qualityValidationLatency, 50*time.Microsecond, "ValidateAudioQuality too slow")
assert.Less(t, bufferValidationLatency, 50*time.Microsecond, "ValidateBufferSize too slow")
}
// testMemoryAllocationPatterns tests memory allocation efficiency
func testMemoryAllocationPatterns(t *testing.T) {
// Test that frequent operations don't cause excessive allocations
var m1, m2 runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m1)
// Perform operations that should minimize allocations
for i := 0; i < 1000; i++ {
_ = GetAudioConfig()
_ = GetAudioMetrics()
RecordFrameReceived(1024)
_ = ValidateAudioQuality(AudioQualityMedium)
}
runtime.GC()
runtime.ReadMemStats(&m2)
allocations := m2.Mallocs - m1.Mallocs
t.Logf("Memory allocations for 1000 operations: %d", allocations)
// Should have minimal allocations for these hot path operations
assert.Less(t, allocations, uint64(100), "Too many memory allocations in hot path")
}
// testConcurrentAccessPerformance tests performance under concurrent access
func testConcurrentAccessPerformance(t *testing.T) {
const (
numGoroutines = 10
operationsPerGoroutine = 1000
)
var wg sync.WaitGroup
start := time.Now()
// Launch concurrent goroutines performing audio operations
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
frameData := make([]byte, 1920)
for j := 0; j < operationsPerGoroutine; j++ {
// Simulate concurrent audio processing
_ = ValidateAudioFrameFast(frameData)
RecordFrameReceived(len(frameData))
_ = GetAudioMetrics()
_ = GetAudioConfig()
}
}()
}
wg.Wait()
elapsed := time.Since(start)
totalOperations := numGoroutines * operationsPerGoroutine * 4 // 4 operations per iteration
avgLatency := elapsed / time.Duration(totalOperations)
t.Logf("Concurrent access: %d operations in %v (avg: %v per operation)",
totalOperations, elapsed, avgLatency)
// Concurrent access should not significantly degrade performance
assert.Less(t, avgLatency, 1*time.Microsecond, "Concurrent access too slow")
}
// testBufferPoolEfficiency tests buffer pool performance
func testBufferPoolEfficiency(t *testing.T) {
// Test buffer acquisition and release performance
const iterations = 1000
start := time.Now()
for i := 0; i < iterations; i++ {
// Simulate buffer pool usage (if available)
buffer := make([]byte, 1920) // Fallback to allocation
_ = buffer
// In real implementation, this would be pool.Get() and pool.Put()
}
elapsed := time.Since(start)
avgLatency := elapsed / iterations
t.Logf("Buffer allocation latency: %v per buffer", avgLatency)
// Buffer operations should be fast
assert.Less(t, avgLatency, 1*time.Microsecond, "Buffer allocation too slow")
}
// testAtomicOperationOverhead tests atomic operation performance
func testAtomicOperationOverhead(t *testing.T) {
const iterations = 10000
var counter int64
// Test atomic increment performance
start := time.Now()
for i := 0; i < iterations; i++ {
atomic.AddInt64(&counter, 1)
}
atomicLatency := time.Since(start) / iterations
// Test atomic load performance
start = time.Now()
for i := 0; i < iterations; i++ {
_ = atomic.LoadInt64(&counter)
}
loadLatency := time.Since(start) / iterations
t.Logf("Atomic add latency: %v", atomicLatency)
t.Logf("Atomic load latency: %v", loadLatency)
// Atomic operations on ARM Cortex-A7 - realistic expectations
assert.Less(t, atomicLatency, 1*time.Microsecond, "Atomic add too slow")
assert.Less(t, loadLatency, 500*time.Nanosecond, "Atomic load too slow")
}
// TestRegressionDetection tests for performance regressions
func TestRegressionDetection(t *testing.T) {
if testing.Short() {
t.Skip("Skipping regression test in short mode")
}
// Baseline performance expectations
baselines := map[string]time.Duration{
"frame_processing": 100 * time.Microsecond,
"metrics_update": 500 * time.Nanosecond,
"config_access": 100 * time.Nanosecond,
"validation": 200 * time.Nanosecond,
}
// Test frame processing
frameData := make([]byte, 1920)
start := time.Now()
for i := 0; i < 100; i++ {
_ = ValidateAudioFrameFast(frameData)
RecordFrameReceived(len(frameData))
}
frameProcessingTime := time.Since(start) / 100
// Test metrics update
start = time.Now()
for i := 0; i < 1000; i++ {
RecordFrameReceived(1024)
}
metricsUpdateTime := time.Since(start) / 1000
// Test config access
start = time.Now()
for i := 0; i < 1000; i++ {
_ = GetAudioConfig()
}
configAccessTime := time.Since(start) / 1000
// Test validation
start = time.Now()
for i := 0; i < 1000; i++ {
_ = ValidateAudioQuality(AudioQualityMedium)
}
validationTime := time.Since(start) / 1000
// Performance regression thresholds for JetKVM hardware:
// - ARM Cortex-A7 @ 1GHz single core
// - 256MB DDR3L RAM
// - Must not interfere with primary KVM functionality
assert.Less(t, frameProcessingTime, baselines["frame_processing"],
"Frame processing regression: %v > %v", frameProcessingTime, baselines["frame_processing"])
assert.Less(t, metricsUpdateTime, 100*time.Microsecond,
"Metrics update regression: %v > 100μs", metricsUpdateTime)
assert.Less(t, configAccessTime, 10*time.Microsecond,
"Config access regression: %v > 10μs", configAccessTime)
assert.Less(t, validationTime, 10*time.Microsecond,
"Validation regression: %v > 10μs", validationTime)
t.Logf("Performance results:")
t.Logf(" Frame processing: %v (baseline: %v)", frameProcessingTime, baselines["frame_processing"])
t.Logf(" Metrics update: %v (baseline: %v)", metricsUpdateTime, baselines["metrics_update"])
t.Logf(" Config access: %v (baseline: %v)", configAccessTime, baselines["config_access"])
t.Logf(" Validation: %v (baseline: %v)", validationTime, baselines["validation"])
}
// TestMemoryLeakDetection tests for memory leaks in critical paths
func TestMemoryLeakDetection(t *testing.T) {
if testing.Short() {
t.Skip("Skipping memory leak test in short mode")
}
var m1, m2 runtime.MemStats
// Baseline measurement
runtime.GC()
runtime.ReadMemStats(&m1)
// Perform many operations that should not leak memory
for cycle := 0; cycle < 10; cycle++ {
for i := 0; i < 1000; i++ {
frameData := make([]byte, 1920)
_ = ValidateAudioFrameFast(frameData)
RecordFrameReceived(len(frameData))
_ = GetAudioMetrics()
_ = GetAudioConfig()
}
// Force garbage collection between cycles
runtime.GC()
}
// Final measurement
runtime.GC()
runtime.ReadMemStats(&m2)
memoryGrowth := int64(m2.Alloc) - int64(m1.Alloc)
t.Logf("Memory growth after 10,000 operations: %d bytes", memoryGrowth)
// Memory growth should be minimal (less than 1MB)
assert.Less(t, memoryGrowth, int64(1024*1024),
"Excessive memory growth detected: %d bytes", memoryGrowth)
}

View File

@ -2,6 +2,7 @@ package audio
import (
"errors"
"fmt"
"time"
)
@ -18,28 +19,46 @@ var (
ErrInvalidMetricsInterval = errors.New("invalid metrics interval")
ErrInvalidSampleRate = errors.New("invalid sample rate")
ErrInvalidChannels = errors.New("invalid channels")
ErrInvalidBitrate = errors.New("invalid bitrate")
ErrInvalidFrameDuration = errors.New("invalid frame duration")
ErrInvalidOffset = errors.New("invalid offset")
ErrInvalidLength = errors.New("invalid length")
)
// ValidateAudioQuality validates audio quality enum values
// ValidateAudioQuality validates audio quality enum values with enhanced checks
func ValidateAudioQuality(quality AudioQuality) error {
// Perform validation
switch quality {
case AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra:
return nil
default:
return ErrInvalidAudioQuality
// Validate enum range
if quality < AudioQualityLow || quality > AudioQualityUltra {
return fmt.Errorf("%w: quality value %d outside valid range [%d, %d]",
ErrInvalidAudioQuality, int(quality), int(AudioQualityLow), int(AudioQualityUltra))
}
return nil
}
// ValidateFrameData validates audio frame data
// ValidateFrameData validates audio frame data with comprehensive boundary checks
func ValidateFrameData(data []byte) error {
if len(data) == 0 {
return ErrInvalidFrameData
if data == nil {
return fmt.Errorf("%w: frame data is nil", ErrInvalidFrameData)
}
// Use config value
maxFrameSize := GetConfig().MaxAudioFrameSize
if len(data) > maxFrameSize {
return ErrInvalidFrameSize
if len(data) == 0 {
return fmt.Errorf("%w: frame data is empty", ErrInvalidFrameData)
}
config := GetConfig()
// Check minimum frame size
if len(data) < config.MinFrameSize {
return fmt.Errorf("%w: frame size %d below minimum %d",
ErrInvalidFrameSize, len(data), config.MinFrameSize)
}
// Check maximum frame size
if len(data) > config.MaxAudioFrameSize {
return fmt.Errorf("%w: frame size %d exceeds maximum %d",
ErrInvalidFrameSize, len(data), config.MaxAudioFrameSize)
}
// Validate frame alignment for audio samples (must be even for 16-bit samples)
if len(data)%2 != 0 {
return fmt.Errorf("%w: frame size %d not aligned for 16-bit samples",
ErrInvalidFrameSize, len(data))
}
return nil
}
@ -61,38 +80,45 @@ func ValidateZeroCopyFrame(frame *ZeroCopyAudioFrame) error {
return nil
}
// ValidateBufferSize validates buffer size parameters
// ValidateBufferSize validates buffer size parameters with enhanced boundary checks
func ValidateBufferSize(size int) error {
if size <= 0 {
return ErrInvalidBufferSize
return fmt.Errorf("%w: buffer size %d must be positive", ErrInvalidBufferSize, size)
}
// Use config value
maxBuffer := GetConfig().SocketMaxBuffer
if size > maxBuffer {
return ErrInvalidBufferSize
}
return nil
}
// ValidateThreadPriority validates thread priority values
func ValidateThreadPriority(priority int) error {
// Use config values
config := GetConfig()
if priority < config.MinNiceValue || priority > config.RTAudioHighPriority {
return ErrInvalidPriority
// Use SocketMaxBuffer as the upper limit for general buffer validation
// This allows for socket buffers while still preventing extremely large allocations
if size > config.SocketMaxBuffer {
return fmt.Errorf("%w: buffer size %d exceeds maximum %d",
ErrInvalidBufferSize, size, config.SocketMaxBuffer)
}
return nil
}
// ValidateLatency validates latency values
// ValidateThreadPriority validates thread priority values with system limits
func ValidateThreadPriority(priority int) error {
const minPriority, maxPriority = -20, 19
if priority < minPriority || priority > maxPriority {
return fmt.Errorf("%w: priority %d outside valid range [%d, %d]",
ErrInvalidPriority, priority, minPriority, maxPriority)
}
return nil
}
// ValidateLatency validates latency duration values with reasonable bounds
func ValidateLatency(latency time.Duration) error {
if latency < 0 {
return ErrInvalidLatency
return fmt.Errorf("%w: latency %v cannot be negative", ErrInvalidLatency, latency)
}
// Use config value
maxLatency := GetConfig().MaxLatency
if latency > maxLatency {
return ErrInvalidLatency
config := GetConfig()
minLatency := time.Millisecond // Minimum reasonable latency
if latency > 0 && latency < minLatency {
return fmt.Errorf("%w: latency %v below minimum %v",
ErrInvalidLatency, latency, minLatency)
}
if latency > config.MaxLatency {
return fmt.Errorf("%w: latency %v exceeds maximum %v",
ErrInvalidLatency, latency, config.MaxLatency)
}
return nil
}
@ -194,3 +220,128 @@ func ValidateLatencyConfig(config LatencyConfig) error {
}
return nil
}
// ValidateSampleRate validates audio sample rate values
func ValidateSampleRate(sampleRate int) error {
if sampleRate <= 0 {
return fmt.Errorf("%w: sample rate %d must be positive", ErrInvalidSampleRate, sampleRate)
}
config := GetConfig()
validRates := config.ValidSampleRates
for _, rate := range validRates {
if sampleRate == rate {
return nil
}
}
return fmt.Errorf("%w: sample rate %d not in supported rates %v",
ErrInvalidSampleRate, sampleRate, validRates)
}
// ValidateChannelCount validates audio channel count
func ValidateChannelCount(channels int) error {
if channels <= 0 {
return fmt.Errorf("%w: channel count %d must be positive", ErrInvalidChannels, channels)
}
config := GetConfig()
if channels > config.MaxChannels {
return fmt.Errorf("%w: channel count %d exceeds maximum %d",
ErrInvalidChannels, channels, config.MaxChannels)
}
return nil
}
// ValidateBitrate validates audio bitrate values (expects kbps)
func ValidateBitrate(bitrate int) error {
if bitrate <= 0 {
return fmt.Errorf("%w: bitrate %d must be positive", ErrInvalidBitrate, bitrate)
}
config := GetConfig()
// Convert kbps to bps for comparison with config limits
bitrateInBps := bitrate * 1000
if bitrateInBps < config.MinOpusBitrate {
return fmt.Errorf("%w: bitrate %d kbps (%d bps) below minimum %d bps",
ErrInvalidBitrate, bitrate, bitrateInBps, config.MinOpusBitrate)
}
if bitrateInBps > config.MaxOpusBitrate {
return fmt.Errorf("%w: bitrate %d kbps (%d bps) exceeds maximum %d bps",
ErrInvalidBitrate, bitrate, bitrateInBps, config.MaxOpusBitrate)
}
return nil
}
// ValidateFrameDuration validates frame duration values
func ValidateFrameDuration(duration time.Duration) error {
if duration <= 0 {
return fmt.Errorf("%w: frame duration %v must be positive", ErrInvalidFrameDuration, duration)
}
config := GetConfig()
if duration < config.MinFrameDuration {
return fmt.Errorf("%w: frame duration %v below minimum %v",
ErrInvalidFrameDuration, duration, config.MinFrameDuration)
}
if duration > config.MaxFrameDuration {
return fmt.Errorf("%w: frame duration %v exceeds maximum %v",
ErrInvalidFrameDuration, duration, config.MaxFrameDuration)
}
return nil
}
// ValidateAudioConfigComplete performs comprehensive audio configuration validation
func ValidateAudioConfigComplete(config AudioConfig) error {
if err := ValidateAudioQuality(config.Quality); err != nil {
return fmt.Errorf("quality validation failed: %w", err)
}
if err := ValidateBitrate(config.Bitrate); err != nil {
return fmt.Errorf("bitrate validation failed: %w", err)
}
if err := ValidateSampleRate(config.SampleRate); err != nil {
return fmt.Errorf("sample rate validation failed: %w", err)
}
if err := ValidateChannelCount(config.Channels); err != nil {
return fmt.Errorf("channel count validation failed: %w", err)
}
if err := ValidateFrameDuration(config.FrameSize); err != nil {
return fmt.Errorf("frame duration validation failed: %w", err)
}
return nil
}
// ValidateAudioConfigConstants validates audio configuration constants
func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
// Validate that audio quality constants are within valid ranges
for _, quality := range []AudioQuality{AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra} {
if err := ValidateAudioQuality(quality); err != nil {
return fmt.Errorf("invalid audio quality constant %v: %w", quality, err)
}
}
// Validate configuration values if config is provided
if config != nil {
if config.MaxFrameSize <= 0 {
return fmt.Errorf("invalid MaxFrameSize: %d", config.MaxFrameSize)
}
if config.SampleRate <= 0 {
return fmt.Errorf("invalid SampleRate: %d", config.SampleRate)
}
}
return nil
}
// ValidateAudioFrameFast performs fast validation of audio frame data
func ValidateAudioFrameFast(data []byte) error {
if len(data) == 0 {
return fmt.Errorf("%w: frame data is empty", ErrInvalidFrameData)
}
maxFrameSize := GetConfig().MaxAudioFrameSize
if len(data) > maxFrameSize {
return fmt.Errorf("%w: frame size %d exceeds maximum %d", ErrInvalidFrameSize, len(data), maxFrameSize)
}
return nil
}
// WrapWithMetadata wraps error with metadata for enhanced validation context
func WrapWithMetadata(err error, component, operation string, metadata map[string]interface{}) error {
if err == nil {
return nil
}
return fmt.Errorf("%s.%s: %w (metadata: %+v)", component, operation, err, metadata)
}

View File

@ -1,479 +0,0 @@
package audio
import (
"errors"
"fmt"
"time"
"unsafe"
"github.com/rs/zerolog"
)
// Validation errors
var (
ErrInvalidFrameLength = errors.New("invalid frame length")
ErrFrameDataCorrupted = errors.New("frame data appears corrupted")
ErrBufferAlignment = errors.New("buffer alignment invalid")
ErrInvalidSampleFormat = errors.New("invalid sample format")
ErrInvalidTimestamp = errors.New("invalid timestamp")
ErrConfigurationMismatch = errors.New("configuration mismatch")
ErrResourceExhaustion = errors.New("resource exhaustion detected")
ErrInvalidPointer = errors.New("invalid pointer")
ErrBufferOverflow = errors.New("buffer overflow detected")
ErrInvalidState = errors.New("invalid state")
ErrOperationTimeout = errors.New("operation timeout")
ErrSystemOverload = errors.New("system overload detected")
ErrHardwareFailure = errors.New("hardware failure")
ErrNetworkError = errors.New("network error")
ErrMemoryExhaustion = errors.New("memory exhaustion")
)
// ValidationLevel defines the level of validation to perform
type ValidationLevel int
const (
ValidationMinimal ValidationLevel = iota // Only critical safety checks
ValidationStandard // Standard validation for production
ValidationStrict // Comprehensive validation for debugging
)
// ValidationConfig controls validation behavior
type ValidationConfig struct {
Level ValidationLevel
EnableRangeChecks bool
EnableAlignmentCheck bool
EnableDataIntegrity bool
MaxValidationTime time.Duration
}
// GetValidationConfig returns the current validation configuration
func GetValidationConfig() ValidationConfig {
// Use direct config access
config := GetConfig()
return ValidationConfig{
Level: ValidationStandard,
EnableRangeChecks: true,
EnableAlignmentCheck: true,
EnableDataIntegrity: false, // Disabled by default for performance
MaxValidationTime: config.MaxValidationTime, // Configurable validation timeout
}
}
// ValidateAudioFrameFast performs minimal validation for performance-critical paths
func ValidateAudioFrameFast(data []byte) error {
if len(data) == 0 {
return ErrInvalidFrameData
}
// Quick bounds check using direct config access
config := GetConfig()
if len(data) > config.MaxAudioFrameSize {
return fmt.Errorf("%w: frame size %d exceeds maximum %d", ErrInvalidFrameSize, len(data), config.MaxAudioFrameSize)
}
return nil
}
// ValidateAudioFrameComprehensive performs thorough validation
func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expectedChannels int) error {
validationConfig := GetValidationConfig()
start := time.Now()
// Timeout protection for validation
defer func() {
if time.Since(start) > validationConfig.MaxValidationTime {
// Log validation timeout but don't fail
getValidationLogger().Warn().Dur("duration", time.Since(start)).Msg("validation timeout exceeded")
}
}()
// Basic validation first
if err := ValidateAudioFrameFast(data); err != nil {
return err
}
// Range validation
if validationConfig.EnableRangeChecks {
// Use direct config access
config := GetConfig()
if len(data) < config.MinFrameSize {
return fmt.Errorf("%w: frame size %d below minimum %d", ErrInvalidFrameSize, len(data), config.MinFrameSize)
}
// Validate frame length matches expected sample format
expectedFrameSize := (expectedSampleRate * expectedChannels * 2) / 1000 * int(config.AudioQualityMediumFrameSize/time.Millisecond)
if abs(len(data)-expectedFrameSize) > config.FrameSizeTolerance {
return fmt.Errorf("%w: frame size %d doesn't match expected %d (±%d)", ErrInvalidFrameLength, len(data), expectedFrameSize, config.FrameSizeTolerance)
}
}
// Alignment validation for ARM32 compatibility
if validationConfig.EnableAlignmentCheck {
if uintptr(unsafe.Pointer(&data[0]))%4 != 0 {
return fmt.Errorf("%w: buffer not 4-byte aligned for ARM32", ErrBufferAlignment)
}
}
// Data integrity checks (expensive, only for debugging)
if validationConfig.EnableDataIntegrity && validationConfig.Level == ValidationStrict {
if err := validateAudioDataIntegrity(data, expectedChannels); err != nil {
return err
}
}
return nil
}
// ValidateZeroCopyFrameEnhanced performs enhanced zero-copy frame validation
func ValidateZeroCopyFrameEnhanced(frame *ZeroCopyAudioFrame) error {
if frame == nil {
return fmt.Errorf("%w: frame is nil", ErrInvalidPointer)
}
// Check reference count validity
frame.mutex.RLock()
refCount := frame.refCount
length := frame.length
capacity := frame.capacity
frame.mutex.RUnlock()
if refCount <= 0 {
return fmt.Errorf("%w: invalid reference count %d", ErrInvalidState, refCount)
}
if length < 0 || capacity < 0 {
return fmt.Errorf("%w: negative length (%d) or capacity (%d)", ErrInvalidState, length, capacity)
}
if length > capacity {
return fmt.Errorf("%w: length %d exceeds capacity %d", ErrBufferOverflow, length, capacity)
}
// Validate the underlying data
data := frame.Data()
return ValidateAudioFrameFast(data)
}
// ValidateBufferBounds performs bounds checking with overflow protection
func ValidateBufferBounds(buffer []byte, offset, length int) error {
if buffer == nil {
return fmt.Errorf("%w: buffer is nil", ErrInvalidPointer)
}
if offset < 0 {
return fmt.Errorf("%w: negative offset %d", ErrInvalidState, offset)
}
if length < 0 {
return fmt.Errorf("%w: negative length %d", ErrInvalidState, length)
}
// Check for integer overflow
if offset > len(buffer) {
return fmt.Errorf("%w: offset %d exceeds buffer length %d", ErrBufferOverflow, offset, len(buffer))
}
// Safe addition check for overflow
if offset+length < offset || offset+length > len(buffer) {
return fmt.Errorf("%w: range [%d:%d] exceeds buffer length %d", ErrBufferOverflow, offset, offset+length, len(buffer))
}
return nil
}
// ValidateAudioConfiguration performs comprehensive configuration validation
func ValidateAudioConfiguration(config AudioConfig) error {
if err := ValidateAudioQuality(config.Quality); err != nil {
return fmt.Errorf("quality validation failed: %w", err)
}
// Use direct config access
configConstants := GetConfig()
minOpusBitrate := configConstants.MinOpusBitrate
maxOpusBitrate := configConstants.MaxOpusBitrate
maxChannels := configConstants.MaxChannels
validSampleRates := configConstants.ValidSampleRates
minFrameDuration := configConstants.MinFrameDuration
maxFrameDuration := configConstants.MaxFrameDuration
// Validate bitrate ranges
if config.Bitrate < minOpusBitrate || config.Bitrate > maxOpusBitrate {
return fmt.Errorf("%w: bitrate %d outside valid range [%d, %d]", ErrInvalidConfiguration, config.Bitrate, minOpusBitrate, maxOpusBitrate)
}
// Validate sample rate
validSampleRate := false
for _, rate := range validSampleRates {
if config.SampleRate == rate {
validSampleRate = true
break
}
}
if !validSampleRate {
return fmt.Errorf("%w: sample rate %d not in supported rates %v", ErrInvalidSampleRate, config.SampleRate, validSampleRates)
}
// Validate channels
if config.Channels < 1 || config.Channels > maxChannels {
return fmt.Errorf("%w: channels %d outside valid range [1, %d]", ErrInvalidChannels, config.Channels, maxChannels)
}
// Validate frame size
if config.FrameSize < minFrameDuration || config.FrameSize > maxFrameDuration {
return fmt.Errorf("%w: frame size %v outside valid range [%v, %v]", ErrInvalidConfiguration, config.FrameSize, minFrameDuration, maxFrameDuration)
}
return nil
}
// ValidateAudioConfigConstants performs comprehensive validation of AudioConfigConstants
func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
if config == nil {
return fmt.Errorf("%w: configuration is nil", ErrInvalidConfiguration)
}
// Validate basic audio parameters
if config.MaxAudioFrameSize <= 0 {
return fmt.Errorf("%w: MaxAudioFrameSize must be positive", ErrInvalidConfiguration)
}
if config.SampleRate <= 0 {
return fmt.Errorf("%w: SampleRate must be positive", ErrInvalidSampleRate)
}
if config.Channels <= 0 || config.Channels > 8 {
return fmt.Errorf("%w: Channels must be between 1 and 8", ErrInvalidChannels)
}
// Validate Opus parameters
if config.OpusBitrate < 6000 || config.OpusBitrate > 510000 {
return fmt.Errorf("%w: OpusBitrate must be between 6000 and 510000", ErrInvalidConfiguration)
}
if config.OpusComplexity < 0 || config.OpusComplexity > 10 {
return fmt.Errorf("%w: OpusComplexity must be between 0 and 10", ErrInvalidConfiguration)
}
// Validate bitrate ranges
if config.MinOpusBitrate <= 0 || config.MaxOpusBitrate <= 0 {
return fmt.Errorf("%w: MinOpusBitrate and MaxOpusBitrate must be positive", ErrInvalidConfiguration)
}
if config.MinOpusBitrate >= config.MaxOpusBitrate {
return fmt.Errorf("%w: MinOpusBitrate must be less than MaxOpusBitrate", ErrInvalidConfiguration)
}
// Validate sample rate ranges
if config.MinSampleRate <= 0 || config.MaxSampleRate <= 0 {
return fmt.Errorf("%w: MinSampleRate and MaxSampleRate must be positive", ErrInvalidSampleRate)
}
if config.MinSampleRate >= config.MaxSampleRate {
return fmt.Errorf("%w: MinSampleRate must be less than MaxSampleRate", ErrInvalidSampleRate)
}
// Validate frame duration ranges
if config.MinFrameDuration <= 0 || config.MaxFrameDuration <= 0 {
return fmt.Errorf("%w: MinFrameDuration and MaxFrameDuration must be positive", ErrInvalidConfiguration)
}
if config.MinFrameDuration >= config.MaxFrameDuration {
return fmt.Errorf("%w: MinFrameDuration must be less than MaxFrameDuration", ErrInvalidConfiguration)
}
// Validate buffer sizes
if config.SocketMinBuffer <= 0 || config.SocketMaxBuffer <= 0 {
return fmt.Errorf("%w: SocketMinBuffer and SocketMaxBuffer must be positive", ErrInvalidBufferSize)
}
if config.SocketMinBuffer >= config.SocketMaxBuffer {
return fmt.Errorf("%w: SocketMinBuffer must be less than SocketMaxBuffer", ErrInvalidBufferSize)
}
// Validate priority ranges
if config.MinNiceValue < -20 || config.MinNiceValue > 19 {
return fmt.Errorf("%w: MinNiceValue must be between -20 and 19", ErrInvalidPriority)
}
if config.MaxNiceValue < -20 || config.MaxNiceValue > 19 {
return fmt.Errorf("%w: MaxNiceValue must be between -20 and 19", ErrInvalidPriority)
}
if config.MinNiceValue >= config.MaxNiceValue {
return fmt.Errorf("%w: MinNiceValue must be less than MaxNiceValue", ErrInvalidPriority)
}
// Validate timeout values
if config.MaxValidationTime <= 0 {
return fmt.Errorf("%w: MaxValidationTime must be positive", ErrInvalidConfiguration)
}
if config.RestartDelay <= 0 || config.MaxRestartDelay <= 0 {
return fmt.Errorf("%w: RestartDelay and MaxRestartDelay must be positive", ErrInvalidConfiguration)
}
if config.RestartDelay >= config.MaxRestartDelay {
return fmt.Errorf("%w: RestartDelay must be less than MaxRestartDelay", ErrInvalidConfiguration)
}
// Validate valid sample rates array
if len(config.ValidSampleRates) == 0 {
return fmt.Errorf("%w: ValidSampleRates cannot be empty", ErrInvalidSampleRate)
}
for _, rate := range config.ValidSampleRates {
if rate <= 0 {
return fmt.Errorf("%w: all ValidSampleRates must be positive", ErrInvalidSampleRate)
}
if rate < config.MinSampleRate || rate > config.MaxSampleRate {
return fmt.Errorf("%w: ValidSampleRate %d outside range [%d, %d]", ErrInvalidSampleRate, rate, config.MinSampleRate, config.MaxSampleRate)
}
}
return nil
}
// ValidateResourceLimits checks if system resources are within acceptable limits
func ValidateResourceLimits() error {
config := GetConfig()
// Check buffer pool sizes
framePoolStats := GetAudioBufferPoolStats()
if framePoolStats.FramePoolSize > int64(config.MaxPoolSize*2) {
return fmt.Errorf("%w: frame pool size %d exceeds safe limit %d", ErrResourceExhaustion, framePoolStats.FramePoolSize, config.MaxPoolSize*2)
}
// Check zero-copy pool allocation count
zeroCopyStats := GetGlobalZeroCopyPoolStats()
if zeroCopyStats.AllocationCount > int64(config.MaxPoolSize*3) {
return fmt.Errorf("%w: zero-copy allocations %d exceed safe limit %d", ErrResourceExhaustion, zeroCopyStats.AllocationCount, config.MaxPoolSize*3)
}
return nil
}
// validateAudioDataIntegrity performs expensive data integrity checks
func validateAudioDataIntegrity(data []byte, channels int) error {
if len(data)%2 != 0 {
return fmt.Errorf("%w: odd number of bytes for 16-bit samples", ErrInvalidSampleFormat)
}
if len(data)%(channels*2) != 0 {
return fmt.Errorf("%w: data length %d not aligned to channel count %d", ErrInvalidSampleFormat, len(data), channels)
}
// Check for obvious corruption patterns (all zeros, all max values)
sampleCount := len(data) / 2
zeroCount := 0
maxCount := 0
for i := 0; i < len(data); i += 2 {
sample := int16(data[i]) | int16(data[i+1])<<8
switch sample {
case 0:
zeroCount++
case 32767, -32768:
maxCount++
}
}
// Flag suspicious patterns
if zeroCount > sampleCount*9/10 {
return fmt.Errorf("%w: %d%% zero samples suggests silence or corruption", ErrFrameDataCorrupted, (zeroCount*100)/sampleCount)
}
if maxCount > sampleCount/10 {
return fmt.Errorf("%w: %d%% max-value samples suggests clipping or corruption", ErrFrameDataCorrupted, (maxCount*100)/sampleCount)
}
return nil
}
// Helper function for absolute value
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
// getValidationLogger returns a logger for validation operations
func getValidationLogger() *zerolog.Logger {
// Return a basic logger for validation
logger := zerolog.New(nil).With().Timestamp().Logger()
return &logger
}
// ErrorContext provides structured error context for better debugging
type ErrorContext struct {
Component string `json:"component"`
Operation string `json:"operation"`
Timestamp time.Time `json:"timestamp"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
StackTrace []string `json:"stack_trace,omitempty"`
}
// ContextualError wraps an error with additional context
type ContextualError struct {
Err error `json:"error"`
Context ErrorContext `json:"context"`
}
func (ce *ContextualError) Error() string {
return fmt.Sprintf("%s [%s:%s]: %v", ce.Context.Component, ce.Context.Operation, ce.Context.Timestamp.Format(time.RFC3339), ce.Err)
}
func (ce *ContextualError) Unwrap() error {
return ce.Err
}
// NewContextualError creates a new contextual error with metadata
func NewContextualError(err error, component, operation string, metadata map[string]interface{}) *ContextualError {
return &ContextualError{
Err: err,
Context: ErrorContext{
Component: component,
Operation: operation,
Timestamp: time.Now(),
Metadata: metadata,
},
}
}
// WrapWithContext wraps an error with component and operation context
func WrapWithContext(err error, component, operation string) error {
if err == nil {
return nil
}
return NewContextualError(err, component, operation, nil)
}
// WrapWithMetadata wraps an error with additional metadata
func WrapWithMetadata(err error, component, operation string, metadata map[string]interface{}) error {
if err == nil {
return nil
}
return NewContextualError(err, component, operation, metadata)
}
// IsTimeoutError checks if an error is a timeout error
func IsTimeoutError(err error) bool {
return errors.Is(err, ErrOperationTimeout)
}
// IsResourceError checks if an error is related to resource exhaustion
func IsResourceError(err error) bool {
return errors.Is(err, ErrResourceExhaustion) || errors.Is(err, ErrMemoryExhaustion) || errors.Is(err, ErrSystemOverload)
}
// IsHardwareError checks if an error is hardware-related
func IsHardwareError(err error) bool {
return errors.Is(err, ErrHardwareFailure)
}
// IsNetworkError checks if an error is network-related
func IsNetworkError(err error) bool {
return errors.Is(err, ErrNetworkError)
}
// GetErrorSeverity returns the severity level of an error
func GetErrorSeverity(err error) string {
if IsHardwareError(err) {
return "critical"
}
if IsResourceError(err) {
return "high"
}
if IsNetworkError(err) || IsTimeoutError(err) {
return "medium"
}
return "low"
}

View File

@ -0,0 +1,556 @@
//go:build cgo
// +build cgo
package audio
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestValidationFunctions provides comprehensive testing of all validation functions
// to ensure they catch breaking changes and regressions effectively
func TestValidationFunctions(t *testing.T) {
tests := []struct {
name string
testFunc func(t *testing.T)
}{
{"AudioQualityValidation", testAudioQualityValidation},
{"FrameDataValidation", testFrameDataValidation},
{"BufferSizeValidation", testBufferSizeValidation},
{"ThreadPriorityValidation", testThreadPriorityValidation},
{"LatencyValidation", testLatencyValidation},
{"MetricsIntervalValidation", testMetricsIntervalValidation},
{"SampleRateValidation", testSampleRateValidation},
{"ChannelCountValidation", testChannelCountValidation},
{"BitrateValidation", testBitrateValidation},
{"FrameDurationValidation", testFrameDurationValidation},
{"IPCConfigValidation", testIPCConfigValidation},
{"AdaptiveBufferConfigValidation", testAdaptiveBufferConfigValidation},
{"AudioConfigCompleteValidation", testAudioConfigCompleteValidation},
{"ZeroCopyFrameValidation", testZeroCopyFrameValidation},
{"AudioFrameFastValidation", testAudioFrameFastValidation},
{"ErrorWrappingValidation", testErrorWrappingValidation},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.testFunc(t)
})
}
}
// testAudioQualityValidation tests audio quality validation with boundary conditions
func testAudioQualityValidation(t *testing.T) {
// Test valid quality levels
validQualities := []AudioQuality{AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra}
for _, quality := range validQualities {
err := ValidateAudioQuality(quality)
assert.NoError(t, err, "Valid quality %d should pass validation", quality)
}
// Test invalid quality levels
invalidQualities := []AudioQuality{-1, 4, 100, -100}
for _, quality := range invalidQualities {
err := ValidateAudioQuality(quality)
assert.Error(t, err, "Invalid quality %d should fail validation", quality)
assert.Contains(t, err.Error(), "invalid audio quality level", "Error should mention audio quality")
}
}
// testFrameDataValidation tests frame data validation with various edge cases
func testFrameDataValidation(t *testing.T) {
config := GetConfig()
// Test nil data
err := ValidateFrameData(nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame data is nil")
// Test empty data
err = ValidateFrameData([]byte{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame data is empty")
// Test data below minimum size
if config.MinFrameSize > 0 {
smallData := make([]byte, config.MinFrameSize-1)
err = ValidateFrameData(smallData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "below minimum")
}
// Test data above maximum size
largeData := make([]byte, config.MaxAudioFrameSize+1)
err = ValidateFrameData(largeData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test unaligned data (odd number of bytes for 16-bit samples)
oddData := make([]byte, 1001) // Odd number
if len(oddData) >= config.MinFrameSize {
err = ValidateFrameData(oddData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not aligned")
}
// Test valid aligned data
validData := make([]byte, 1000) // Even number, within bounds
if len(validData) >= config.MinFrameSize && len(validData) <= config.MaxAudioFrameSize {
err = ValidateFrameData(validData)
assert.NoError(t, err)
}
}
// testBufferSizeValidation tests buffer size validation
func testBufferSizeValidation(t *testing.T) {
config := GetConfig()
// Test negative and zero sizes
invalidSizes := []int{-1, -100, 0}
for _, size := range invalidSizes {
err := ValidateBufferSize(size)
assert.Error(t, err, "Buffer size %d should be invalid", size)
assert.Contains(t, err.Error(), "must be positive")
}
// Test size exceeding maximum
err := ValidateBufferSize(config.SocketMaxBuffer + 1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid sizes
validSizes := []int{1, 1024, 4096, config.SocketMaxBuffer}
for _, size := range validSizes {
err := ValidateBufferSize(size)
assert.NoError(t, err, "Buffer size %d should be valid", size)
}
}
// testThreadPriorityValidation tests thread priority validation
func testThreadPriorityValidation(t *testing.T) {
// Test valid priorities
validPriorities := []int{-20, -10, 0, 10, 19}
for _, priority := range validPriorities {
err := ValidateThreadPriority(priority)
assert.NoError(t, err, "Priority %d should be valid", priority)
}
// Test invalid priorities
invalidPriorities := []int{-21, -100, 20, 100}
for _, priority := range invalidPriorities {
err := ValidateThreadPriority(priority)
assert.Error(t, err, "Priority %d should be invalid", priority)
assert.Contains(t, err.Error(), "outside valid range")
}
}
// testLatencyValidation tests latency validation
func testLatencyValidation(t *testing.T) {
config := GetConfig()
// Test negative latency
err := ValidateLatency(-1 * time.Millisecond)
assert.Error(t, err)
assert.Contains(t, err.Error(), "cannot be negative")
// Test zero latency (should be valid)
err = ValidateLatency(0)
assert.NoError(t, err)
// Test very small positive latency
err = ValidateLatency(500 * time.Microsecond)
assert.Error(t, err)
assert.Contains(t, err.Error(), "below minimum")
// Test latency exceeding maximum
err = ValidateLatency(config.MaxLatency + time.Second)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid latencies
validLatencies := []time.Duration{
1 * time.Millisecond,
10 * time.Millisecond,
100 * time.Millisecond,
config.MaxLatency,
}
for _, latency := range validLatencies {
err := ValidateLatency(latency)
assert.NoError(t, err, "Latency %v should be valid", latency)
}
}
// testMetricsIntervalValidation tests metrics interval validation
func testMetricsIntervalValidation(t *testing.T) {
config := GetConfig()
// Test interval below minimum
err := ValidateMetricsInterval(config.MinMetricsUpdateInterval - time.Millisecond)
assert.Error(t, err)
// Test interval above maximum
err = ValidateMetricsInterval(config.MaxMetricsUpdateInterval + time.Second)
assert.Error(t, err)
// Test valid intervals
validIntervals := []time.Duration{
config.MinMetricsUpdateInterval,
config.MaxMetricsUpdateInterval,
(config.MinMetricsUpdateInterval + config.MaxMetricsUpdateInterval) / 2,
}
for _, interval := range validIntervals {
err := ValidateMetricsInterval(interval)
assert.NoError(t, err, "Interval %v should be valid", interval)
}
}
// testSampleRateValidation tests sample rate validation
func testSampleRateValidation(t *testing.T) {
config := GetConfig()
// Test negative and zero sample rates
invalidRates := []int{-1, -48000, 0}
for _, rate := range invalidRates {
err := ValidateSampleRate(rate)
assert.Error(t, err, "Sample rate %d should be invalid", rate)
assert.Contains(t, err.Error(), "must be positive")
}
// Test unsupported sample rates
unsupportedRates := []int{1000, 12345, 96001}
for _, rate := range unsupportedRates {
err := ValidateSampleRate(rate)
assert.Error(t, err, "Sample rate %d should be unsupported", rate)
assert.Contains(t, err.Error(), "not in supported rates")
}
// Test valid sample rates
for _, rate := range config.ValidSampleRates {
err := ValidateSampleRate(rate)
assert.NoError(t, err, "Sample rate %d should be valid", rate)
}
}
// testChannelCountValidation tests channel count validation
func testChannelCountValidation(t *testing.T) {
config := GetConfig()
// Test invalid channel counts
invalidCounts := []int{-1, -10, 0}
for _, count := range invalidCounts {
err := ValidateChannelCount(count)
assert.Error(t, err, "Channel count %d should be invalid", count)
assert.Contains(t, err.Error(), "must be positive")
}
// Test channel count exceeding maximum
err := ValidateChannelCount(config.MaxChannels + 1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid channel counts
validCounts := []int{1, 2, config.MaxChannels}
for _, count := range validCounts {
err := ValidateChannelCount(count)
assert.NoError(t, err, "Channel count %d should be valid", count)
}
}
// testBitrateValidation tests bitrate validation
func testBitrateValidation(t *testing.T) {
// Test invalid bitrates
invalidBitrates := []int{-1, -1000, 0}
for _, bitrate := range invalidBitrates {
err := ValidateBitrate(bitrate)
assert.Error(t, err, "Bitrate %d should be invalid", bitrate)
assert.Contains(t, err.Error(), "must be positive")
}
// Test bitrate below minimum (in kbps)
err := ValidateBitrate(5) // 5 kbps = 5000 bps < 6000 bps minimum
assert.Error(t, err)
assert.Contains(t, err.Error(), "below minimum")
// Test bitrate above maximum (in kbps)
err = ValidateBitrate(511) // 511 kbps = 511000 bps > 510000 bps maximum
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid bitrates (in kbps)
validBitrates := []int{
6, // 6 kbps = 6000 bps (minimum)
64, // Medium quality preset
128, // High quality preset
192, // Ultra quality preset
510, // 510 kbps = 510000 bps (maximum)
}
for _, bitrate := range validBitrates {
err := ValidateBitrate(bitrate)
assert.NoError(t, err, "Bitrate %d kbps should be valid", bitrate)
}
}
// testFrameDurationValidation tests frame duration validation
func testFrameDurationValidation(t *testing.T) {
config := GetConfig()
// Test invalid durations
invalidDurations := []time.Duration{-1 * time.Millisecond, -1 * time.Second, 0}
for _, duration := range invalidDurations {
err := ValidateFrameDuration(duration)
assert.Error(t, err, "Duration %v should be invalid", duration)
assert.Contains(t, err.Error(), "must be positive")
}
// Test duration below minimum
err := ValidateFrameDuration(config.MinFrameDuration - time.Microsecond)
assert.Error(t, err)
assert.Contains(t, err.Error(), "below minimum")
// Test duration above maximum
err = ValidateFrameDuration(config.MaxFrameDuration + time.Second)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid durations
validDurations := []time.Duration{
config.MinFrameDuration,
config.MaxFrameDuration,
20 * time.Millisecond, // Common frame duration
}
for _, duration := range validDurations {
err := ValidateFrameDuration(duration)
assert.NoError(t, err, "Duration %v should be valid", duration)
}
}
// testIPCConfigValidation tests IPC configuration validation
func testIPCConfigValidation(t *testing.T) {
config := GetConfig()
// Test invalid configurations for input IPC
invalidConfigs := []struct {
sampleRate, channels, frameSize int
description string
}{
{0, 2, 960, "zero sample rate"},
{48000, 0, 960, "zero channels"},
{48000, 2, 0, "zero frame size"},
{config.MinSampleRate - 1, 2, 960, "sample rate below minimum"},
{config.MaxSampleRate + 1, 2, 960, "sample rate above maximum"},
{48000, config.MaxChannels + 1, 960, "too many channels"},
{48000, -1, 960, "negative channels"},
{48000, 2, -1, "negative frame size"},
}
for _, tc := range invalidConfigs {
// Test input IPC validation
err := ValidateInputIPCConfig(tc.sampleRate, tc.channels, tc.frameSize)
assert.Error(t, err, "Input IPC config should be invalid: %s", tc.description)
// Test output IPC validation
err = ValidateOutputIPCConfig(tc.sampleRate, tc.channels, tc.frameSize)
assert.Error(t, err, "Output IPC config should be invalid: %s", tc.description)
}
// Test valid configuration
err := ValidateInputIPCConfig(48000, 2, 960)
assert.NoError(t, err)
err = ValidateOutputIPCConfig(48000, 2, 960)
assert.NoError(t, err)
}
// testAdaptiveBufferConfigValidation tests adaptive buffer configuration validation
func testAdaptiveBufferConfigValidation(t *testing.T) {
config := GetConfig()
// Test invalid configurations
invalidConfigs := []struct {
minSize, maxSize, defaultSize int
description string
}{
{0, 1024, 512, "zero min size"},
{-1, 1024, 512, "negative min size"},
{512, 0, 256, "zero max size"},
{512, -1, 256, "negative max size"},
{512, 1024, 0, "zero default size"},
{512, 1024, -1, "negative default size"},
{1024, 512, 768, "min >= max"},
{512, 1024, 256, "default < min"},
{512, 1024, 2048, "default > max"},
{512, config.SocketMaxBuffer + 1, 1024, "max exceeds global limit"},
}
for _, tc := range invalidConfigs {
err := ValidateAdaptiveBufferConfig(tc.minSize, tc.maxSize, tc.defaultSize)
assert.Error(t, err, "Config should be invalid: %s", tc.description)
}
// Test valid configuration
err := ValidateAdaptiveBufferConfig(512, 4096, 1024)
assert.NoError(t, err)
}
// testAudioConfigCompleteValidation tests complete audio configuration validation
func testAudioConfigCompleteValidation(t *testing.T) {
// Test valid configuration using actual preset values
validConfig := AudioConfig{
Quality: AudioQualityMedium,
Bitrate: 64, // kbps - matches medium quality preset
SampleRate: 48000,
Channels: 2,
FrameSize: 20 * time.Millisecond,
}
err := ValidateAudioConfigComplete(validConfig)
assert.NoError(t, err)
// Test invalid quality
invalidQualityConfig := validConfig
invalidQualityConfig.Quality = AudioQuality(99)
err = ValidateAudioConfigComplete(invalidQualityConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "quality validation failed")
// Test invalid bitrate
invalidBitrateConfig := validConfig
invalidBitrateConfig.Bitrate = -1
err = ValidateAudioConfigComplete(invalidBitrateConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "bitrate validation failed")
// Test invalid sample rate
invalidSampleRateConfig := validConfig
invalidSampleRateConfig.SampleRate = 12345
err = ValidateAudioConfigComplete(invalidSampleRateConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "sample rate validation failed")
// Test invalid channels
invalidChannelsConfig := validConfig
invalidChannelsConfig.Channels = 0
err = ValidateAudioConfigComplete(invalidChannelsConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "channel count validation failed")
// Test invalid frame duration
invalidFrameDurationConfig := validConfig
invalidFrameDurationConfig.FrameSize = -1 * time.Millisecond
err = ValidateAudioConfigComplete(invalidFrameDurationConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame duration validation failed")
}
// testZeroCopyFrameValidation tests zero-copy frame validation
func testZeroCopyFrameValidation(t *testing.T) {
// Test nil frame
err := ValidateZeroCopyFrame(nil)
assert.Error(t, err)
// Note: We can't easily test ZeroCopyAudioFrame without creating actual instances
// This would require more complex setup, but the validation logic is tested
}
// testAudioFrameFastValidation tests fast audio frame validation
func testAudioFrameFastValidation(t *testing.T) {
config := GetConfig()
// Test empty data
err := ValidateAudioFrameFast([]byte{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame data is empty")
// Test data exceeding maximum size
largeData := make([]byte, config.MaxAudioFrameSize+1)
err = ValidateAudioFrameFast(largeData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid data
validData := make([]byte, 1000)
err = ValidateAudioFrameFast(validData)
assert.NoError(t, err)
}
// testErrorWrappingValidation tests error wrapping functionality
func testErrorWrappingValidation(t *testing.T) {
// Test wrapping nil error
wrapped := WrapWithMetadata(nil, "component", "operation", map[string]interface{}{"key": "value"})
assert.Nil(t, wrapped)
// Test wrapping actual error
originalErr := assert.AnError
metadata := map[string]interface{}{
"frame_size": 1024,
"quality": "high",
}
wrapped = WrapWithMetadata(originalErr, "audio", "decode", metadata)
require.NotNil(t, wrapped)
assert.Contains(t, wrapped.Error(), "audio.decode")
assert.Contains(t, wrapped.Error(), "assert.AnError")
assert.Contains(t, wrapped.Error(), "metadata")
assert.Contains(t, wrapped.Error(), "frame_size")
assert.Contains(t, wrapped.Error(), "quality")
}
// TestValidationIntegration tests validation functions working together
func TestValidationIntegration(t *testing.T) {
// Test that validation functions work correctly with actual audio configurations
presets := GetAudioQualityPresets()
require.NotEmpty(t, presets)
for quality, config := range presets {
t.Run(fmt.Sprintf("Quality_%d", quality), func(t *testing.T) {
// Validate the preset configuration
err := ValidateAudioConfigComplete(config)
assert.NoError(t, err, "Preset configuration for quality %d should be valid", quality)
// Validate individual components
err = ValidateAudioQuality(config.Quality)
assert.NoError(t, err, "Quality should be valid")
err = ValidateBitrate(config.Bitrate)
assert.NoError(t, err, "Bitrate should be valid")
err = ValidateSampleRate(config.SampleRate)
assert.NoError(t, err, "Sample rate should be valid")
err = ValidateChannelCount(config.Channels)
assert.NoError(t, err, "Channel count should be valid")
err = ValidateFrameDuration(config.FrameSize)
assert.NoError(t, err, "Frame duration should be valid")
})
}
}
// TestValidationPerformance ensures validation functions are efficient
func TestValidationPerformance(t *testing.T) {
if testing.Short() {
t.Skip("Skipping performance test in short mode")
}
// Test that validation functions complete quickly
start := time.Now()
iterations := 10000
for i := 0; i < iterations; i++ {
_ = ValidateAudioQuality(AudioQualityMedium)
_ = ValidateBufferSize(1024)
_ = ValidateChannelCount(2)
_ = ValidateSampleRate(48000)
_ = ValidateBitrate(96) // 96 kbps
}
elapsed := time.Since(start)
perIteration := elapsed / time.Duration(iterations)
// Performance expectations for JetKVM (ARM Cortex-A7 @ 1GHz, 256MB RAM)
// Audio processing must not interfere with primary KVM functionality
assert.Less(t, perIteration, 200*time.Microsecond, "Validation should not impact KVM performance")
t.Logf("Validation performance: %v per iteration", perIteration)
}