package usbgadget import ( "context" "testing" "time" "github.com/stretchr/testify/assert" ) // Unit tests for USB gadget configuration logic without hardware dependencies // These tests follow the pattern of audio tests - testing business logic and validation func TestUsbGadgetConfigValidation(t *testing.T) { tests := []struct { name string config *Config devices *Devices expected bool }{ { name: "ValidConfig", config: &Config{ VendorId: "0x1d6b", ProductId: "0x0104", Manufacturer: "JetKVM", Product: "USB Emulation Device", }, devices: &Devices{ Keyboard: true, AbsoluteMouse: true, RelativeMouse: true, MassStorage: true, }, expected: true, }, { name: "InvalidVendorId", config: &Config{ VendorId: "invalid", ProductId: "0x0104", Manufacturer: "JetKVM", Product: "USB Emulation Device", }, devices: &Devices{ Keyboard: true, }, expected: false, }, { name: "EmptyManufacturer", config: &Config{ VendorId: "0x1d6b", ProductId: "0x0104", Manufacturer: "", Product: "USB Emulation Device", }, devices: &Devices{ Keyboard: true, }, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateUsbGadgetConfiguration(tt.config, tt.devices) if tt.expected { assert.NoError(t, err, "Configuration should be valid") } else { assert.Error(t, err, "Configuration should be invalid") } }) } } func TestUsbGadgetDeviceConfiguration(t *testing.T) { tests := []struct { name string devices *Devices expectedConfigs []string }{ { name: "AllDevicesEnabled", devices: &Devices{ Keyboard: true, AbsoluteMouse: true, RelativeMouse: true, MassStorage: true, Audio: true, }, expectedConfigs: []string{"keyboard", "absolute_mouse", "relative_mouse", "mass_storage_base", "audio"}, }, { name: "OnlyKeyboard", devices: &Devices{ Keyboard: true, }, expectedConfigs: []string{"keyboard"}, }, { name: "MouseOnly", devices: &Devices{ AbsoluteMouse: true, RelativeMouse: true, }, expectedConfigs: []string{"absolute_mouse", "relative_mouse"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { configs := getEnabledGadgetConfigs(tt.devices) assert.ElementsMatch(t, tt.expectedConfigs, configs, "Enabled configs should match expected") }) } } func TestUsbGadgetStateTransition(t *testing.T) { if testing.Short() { t.Skip("Skipping state transition test in short mode") } tests := []struct { name string initialDevices *Devices newDevices *Devices expectedTransition string }{ { name: "EnableAudio", initialDevices: &Devices{ Keyboard: true, AbsoluteMouse: true, Audio: false, }, newDevices: &Devices{ Keyboard: true, AbsoluteMouse: true, Audio: true, }, expectedTransition: "audio_enabled", }, { name: "DisableKeyboard", initialDevices: &Devices{ Keyboard: true, AbsoluteMouse: true, }, newDevices: &Devices{ Keyboard: false, AbsoluteMouse: true, }, expectedTransition: "keyboard_disabled", }, { name: "NoChange", initialDevices: &Devices{ Keyboard: true, AbsoluteMouse: true, }, newDevices: &Devices{ Keyboard: true, AbsoluteMouse: true, }, expectedTransition: "no_change", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() transition := simulateUsbGadgetStateTransition(ctx, tt.initialDevices, tt.newDevices) assert.Equal(t, tt.expectedTransition, transition, "State transition should match expected") }) } } func TestUsbGadgetConfigurationTimeout(t *testing.T) { if testing.Short() { t.Skip("Skipping timeout test in short mode") } ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() // Test that configuration validation completes within reasonable time start := time.Now() // Simulate multiple rapid configuration changes for i := 0; i < 20; i++ { devices := &Devices{ Keyboard: i%2 == 0, AbsoluteMouse: i%3 == 0, RelativeMouse: i%4 == 0, MassStorage: i%5 == 0, Audio: i%6 == 0, } config := &Config{ VendorId: "0x1d6b", ProductId: "0x0104", Manufacturer: "JetKVM", Product: "USB Emulation Device", } err := validateUsbGadgetConfiguration(config, devices) assert.NoError(t, err, "Configuration validation should not fail") // Ensure we don't timeout select { case <-ctx.Done(): t.Fatal("USB gadget configuration test timed out") default: // Continue } } elapsed := time.Since(start) t.Logf("USB gadget configuration test completed in %v", elapsed) assert.Less(t, elapsed, 2*time.Second, "Configuration validation should complete quickly") } func TestReportDescriptorValidation(t *testing.T) { tests := []struct { name string reportDesc []byte expected bool }{ { name: "ValidKeyboardReportDesc", reportDesc: keyboardReportDesc, expected: true, }, { name: "ValidAbsoluteMouseReportDesc", reportDesc: absoluteMouseCombinedReportDesc, expected: true, }, { name: "ValidRelativeMouseReportDesc", reportDesc: relativeMouseCombinedReportDesc, expected: true, }, { name: "EmptyReportDesc", reportDesc: []byte{}, expected: false, }, { name: "InvalidReportDesc", reportDesc: []byte{0xFF, 0xFF, 0xFF}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateReportDescriptor(tt.reportDesc) if tt.expected { assert.NoError(t, err, "Report descriptor should be valid") } else { assert.Error(t, err, "Report descriptor should be invalid") } }) } } // Helper functions for simulation (similar to audio tests) // validateUsbGadgetConfiguration simulates the validation that happens in production func validateUsbGadgetConfiguration(config *Config, devices *Devices) error { if config == nil { return assert.AnError } // Validate vendor ID format if config.VendorId == "" || len(config.VendorId) < 4 { return assert.AnError } if config.VendorId != "" && config.VendorId[:2] != "0x" { return assert.AnError } // Validate product ID format if config.ProductId == "" || len(config.ProductId) < 4 { return assert.AnError } if config.ProductId != "" && config.ProductId[:2] != "0x" { return assert.AnError } // Validate required fields if config.Manufacturer == "" { return assert.AnError } if config.Product == "" { return assert.AnError } // Note: Allow configurations with no devices enabled for testing purposes // In production, this would typically be validated at a higher level return nil } // getEnabledGadgetConfigs returns the list of enabled gadget configurations func getEnabledGadgetConfigs(devices *Devices) []string { var configs []string if devices.Keyboard { configs = append(configs, "keyboard") } if devices.AbsoluteMouse { configs = append(configs, "absolute_mouse") } if devices.RelativeMouse { configs = append(configs, "relative_mouse") } if devices.MassStorage { configs = append(configs, "mass_storage_base") } if devices.Audio { configs = append(configs, "audio") } return configs } // simulateUsbGadgetStateTransition simulates the state management during USB reconfiguration func simulateUsbGadgetStateTransition(ctx context.Context, initial, new *Devices) string { // Check for audio changes if initial.Audio != new.Audio { if new.Audio { // Simulate enabling audio device time.Sleep(5 * time.Millisecond) return "audio_enabled" } else { // Simulate disabling audio device time.Sleep(5 * time.Millisecond) return "audio_disabled" } } // Check for keyboard changes if initial.Keyboard != new.Keyboard { if new.Keyboard { time.Sleep(5 * time.Millisecond) return "keyboard_enabled" } else { time.Sleep(5 * time.Millisecond) return "keyboard_disabled" } } // Check for mouse changes if initial.AbsoluteMouse != new.AbsoluteMouse || initial.RelativeMouse != new.RelativeMouse { time.Sleep(5 * time.Millisecond) return "mouse_changed" } // Check for mass storage changes if initial.MassStorage != new.MassStorage { time.Sleep(5 * time.Millisecond) return "mass_storage_changed" } return "no_change" } // validateReportDescriptor simulates HID report descriptor validation func validateReportDescriptor(reportDesc []byte) error { if len(reportDesc) == 0 { return assert.AnError } // Basic HID report descriptor validation // Check for valid usage page (0x05) found := false for i := 0; i < len(reportDesc)-1; i++ { if reportDesc[i] == 0x05 { found = true break } } if !found { return assert.AnError } return nil } // Benchmark tests func BenchmarkValidateUsbGadgetConfiguration(b *testing.B) { config := &Config{ VendorId: "0x1d6b", ProductId: "0x0104", Manufacturer: "JetKVM", Product: "USB Emulation Device", } devices := &Devices{ Keyboard: true, AbsoluteMouse: true, RelativeMouse: true, MassStorage: true, } b.ResetTimer() for i := 0; i < b.N; i++ { _ = validateUsbGadgetConfiguration(config, devices) } } func BenchmarkGetEnabledGadgetConfigs(b *testing.B) { devices := &Devices{ Keyboard: true, AbsoluteMouse: true, RelativeMouse: true, MassStorage: true, Audio: true, } b.ResetTimer() for i := 0; i < b.N; i++ { _ = getEnabledGadgetConfigs(devices) } } func BenchmarkValidateReportDescriptor(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = validateReportDescriptor(keyboardReportDesc) } }