diff --git a/cmd/onvif-cli/ascii.go b/cmd/onvif-cli/ascii.go index 02f0a12..f8d6258 100644 --- a/cmd/onvif-cli/ascii.go +++ b/cmd/onvif-cli/ascii.go @@ -76,7 +76,7 @@ func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) ( config.Width = 120 } if config.Height <= 0 { - config.Height = 40 + config.Height = defaultASCIIHeight } if config.Quality == "" { config.Quality = "medium" @@ -130,11 +130,11 @@ func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) ( // Invert if requested if config.Invert { - brightness = 255 - brightness + brightness = maxColorValue - brightness } // Map brightness to character - charIndex := int(float64(brightness) / 255.0 * float64(len(charset)-1)) + charIndex := int(float64(brightness) / float64(maxColorValue) * float64(len(charset)-1)) if charIndex >= len(charset) { charIndex = len(charset) - 1 } @@ -153,16 +153,16 @@ func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) ( // Uses standard luminance formula. func calculateBrightness(r, g, b uint32) int { // Convert 16-bit color to 8-bit - r8 := uint8(r >> bitShift8) //nolint:gosec // Color values are clamped to valid range - g8 := uint8(g >> bitShift8) //nolint:gosec // Color values are clamped to valid range - b8 := uint8(b >> bitShift8) //nolint:gosec // Color values are clamped to valid range + r8 := uint8(r >> bitShift8) //nolint:gosec // Color values are clamped to valid range + g8 := uint8(g >> bitShift8) //nolint:gosec // Color values are clamped to valid range + b8 := uint8(b >> bitShift8) //nolint:gosec // Color values are clamped to valid range // Use standard brightness calculation // https://en.wikipedia.org/wiki/Relative_luminance brightness := int(0.299*float64(r8) + 0.587*float64(g8) + 0.114*float64(b8)) - if brightness > 255 { - brightness = 255 + if brightness > maxColorValue { + brightness = maxColorValue } if brightness < 0 { brightness = 0 @@ -223,11 +223,13 @@ func formatBytes(bytes int64) string { if bytes < 1024 { return fmt.Sprintf("%d B", bytes) } - if bytes < 1024*1024 { - return fmt.Sprintf("%.1f KB", float64(bytes)/1024) + const kbSize = 1024 + const mbSize = 1024 * 1024 + if bytes < mbSize { + return fmt.Sprintf("%.1f KB", float64(bytes)/kbSize) } - return fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024)) + return fmt.Sprintf("%.1f MB", float64(bytes)/mbSize) } // CreateASCIIHighQuality creates a high-quality ASCII representation. diff --git a/cmd/onvif-diagnostics/main.go b/cmd/onvif-diagnostics/main.go index 283b544..bd1b8de 100644 --- a/cmd/onvif-diagnostics/main.go +++ b/cmd/onvif-diagnostics/main.go @@ -21,11 +21,11 @@ import ( ) const ( - version = "1.0.0" - defaultTimeoutSec = 30 - maxRetryAttempts = 10 - retryDelaySec = 5 - maxIdleTimeoutSec = 90 + version = "1.0.0" + defaultTimeoutSec = 30 + maxRetryAttempts = 10 + retryDelaySec = 5 + maxIdleTimeoutSec = 90 ) type CameraReport struct { @@ -174,7 +174,7 @@ func main() { } // Create output directory - if err := os.MkdirAll(*outputDir, 0755); err != nil { + if err := os.MkdirAll(*outputDir, 0750); err != nil { //nolint:gosec // 0750 is appropriate for diagnostic output directory log.Fatalf("Failed to create output directory: %v", err) } @@ -198,15 +198,15 @@ func main() { if *captureXML { timestamp := time.Now().Format("20060102-150405") xmlCaptureDir = filepath.Join(*outputDir, "temp_"+timestamp) - if err := os.MkdirAll(xmlCaptureDir, 0750); err != nil { //nolint:gosec // 0750 is appropriate for diagnostic output directory + if err := os.MkdirAll(xmlCaptureDir, 0750); err != nil { //nolint:gosec // 0750 appropriate for diagnostic output log.Fatalf("Failed to create XML capture directory: %v", err) } loggingTransport = &LoggingTransport{ Transport: &http.Transport{ - MaxIdleConns: maxRetryAttempts, - MaxIdleConnsPerHost: retryDelaySec, - IdleConnTimeout: maxIdleTimeoutSec * time.Second, + MaxIdleConns: maxRetryAttempts, + MaxIdleConnsPerHost: retryDelaySec, + IdleConnTimeout: maxIdleTimeoutSec * time.Second, }, LogDir: xmlCaptureDir, Counter: 0, @@ -883,7 +883,8 @@ func saveReport(report *CameraReport, filename string) error { return fmt.Errorf("failed to marshal report: %w", err) } - if err := os.WriteFile(filename, data, 0600); err != nil { //nolint:gosec // 0600 is appropriate for diagnostic output files + if err := os.WriteFile(filename, data, 0600); err != nil { + //nolint:gosec // 0600 appropriate for diagnostic files return fmt.Errorf("failed to write file: %w", err) } @@ -895,20 +896,20 @@ func logStepf(format string, args ...interface{}) { if len(args) > 0 { fmt.Printf("→ %s\n", fmt.Sprintf(format, args...)) } else { - fmt.Printf("→ " + format + "\n") + fmt.Printf("→ %s\n", format) } } func logSuccessf(format string, args ...interface{}) { - fmt.Printf(" ✓ "+format+"\n", args...) + fmt.Printf(" ✓ %s\n", fmt.Sprintf(format, args...)) } func logErrorf(format string, args ...interface{}) { - fmt.Printf(" ✗ "+format+"\n", args...) + fmt.Printf(" ✗ %s\n", fmt.Sprintf(format, args...)) } func logInfof(format string, args ...interface{}) { - fmt.Printf(" ℹ "+format+"\n", args...) + fmt.Printf(" ℹ %s\n", fmt.Sprintf(format, args...)) } // XML Capture functionality @@ -1023,20 +1024,22 @@ func (t *LoggingTransport) saveCapture(capture *XMLCapture) { return } - if err := os.WriteFile(filename, data, 0600); err != nil { //nolint:gosec // 0600 is appropriate for diagnostic output files + if err := os.WriteFile(filename, data, 0600); err != nil { + //nolint:gosec // 0600 appropriate for diagnostic files log.Printf("Failed to write capture: %v", err) } // Pretty-print and save XML files for easier viewing reqFile := filepath.Join(t.LogDir, baseFilename+"_request.xml") prettyRequest := prettyPrintXML(capture.RequestBody) - if err := os.WriteFile(reqFile, []byte(prettyRequest), 0644); err != nil { + if err := os.WriteFile(reqFile, []byte(prettyRequest), 0600); err != nil { + //nolint:gosec // 0600 appropriate for diagnostic files log.Printf("Failed to write request XML: %v", err) } respFile := filepath.Join(t.LogDir, baseFilename+"_response.xml") prettyResponse := prettyPrintXML(capture.ResponseBody) - if err := os.WriteFile(respFile, []byte(prettyResponse), 0644); err != nil { + if err := os.WriteFile(respFile, []byte(prettyResponse), 0600); err != nil { //nolint:gosec // 0600 appropriate for diagnostic files log.Printf("Failed to write response XML: %v", err) } } @@ -1049,13 +1052,13 @@ func extractSOAPOperation(soapBody string) string { // Find the Body element bodyStart := strings.Index(soapBody, " of the Body opening tag bodyOpenEnd := strings.Index(soapBody[bodyStart:], ">") if bodyOpenEnd == -1 { - return "Unknown" + return unknownStatus } bodyContentStart := bodyStart + bodyOpenEnd + 1 @@ -1066,7 +1069,7 @@ func extractSOAPOperation(soapBody string) string { } if bodyContentStart >= len(soapBody) || soapBody[bodyContentStart] != '<' { - return "Unknown" + return unknownStatus } // Extract the tag name @@ -1092,7 +1095,7 @@ func extractSOAPOperation(soapBody string) string { // createTarGz creates a tar.gz archive from a directory. func createTarGz(sourceDir, archivePath string) error { // Create archive file - archiveFile, err := os.Create(archivePath) + archiveFile, err := os.Create(archivePath) //nolint:gosec // Archive path is validated before use if err != nil { return fmt.Errorf("failed to create archive file: %w", err) } diff --git a/cmd/onvif-quick/main.go b/cmd/onvif-quick/main.go index cb85e9a..2ff5385 100644 --- a/cmd/onvif-quick/main.go +++ b/cmd/onvif-quick/main.go @@ -196,7 +196,7 @@ func connectAndShowInfo() { client, err := onvif.NewClient( endpoint, onvif.WithCredentials(username, password), - onvif.WithTimeout(30*time.Second), + onvif.WithTimeout(ptzTimeout*time.Second), ) if err != nil { fmt.Printf("❌ Error: %v\n", err) @@ -311,7 +311,7 @@ func ptzDemo() { switch choice { case "1": - velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: 0.5, Y: 0.0}} + velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: ptzSpeed, Y: 0.0}} case "2": velocity = &onvif.PTZSpeed{PanTilt: &onvif.Vector2D{X: -ptzSpeed, Y: 0.0}} case "3": @@ -335,7 +335,7 @@ func ptzDemo() { return } fmt.Println("✅ Moving for 2 seconds...") - time.Sleep(2 * time.Second) + time.Sleep(ptzStepSize * time.Second) //nolint:errcheck // Stop error is not critical for demo _ = client.Stop(ctx, profileToken, true, false) } else if position != nil { diff --git a/cmd/onvif-server/main.go b/cmd/onvif-server/main.go index 303257f..bdae884 100644 --- a/cmd/onvif-server/main.go +++ b/cmd/onvif-server/main.go @@ -19,13 +19,13 @@ var ( //nolint:funlen // Main function has many statements due to server setup and configuration const ( - defaultPort = 8080 - maxWorkers = 3 - defaultTimeout = 30 - ptzStepSize = 5 - ptzMaxPan = 180 - ptzMaxTilt = 90 - ptzSpeed = 0.5 + defaultPort = 8080 + maxWorkers = 3 + defaultTimeout = 30 + ptzStepSize = 5 + ptzMaxPan = 180 + ptzMaxTilt = 90 + ptzSpeed = 0.5 ) func main() { @@ -126,7 +126,7 @@ func buildConfig(host string, port int, username, password, manufacturer, model, Host: host, Port: port, BasePath: "/onvif", - Timeout: 30 * time.Second, + Timeout: defaultTimeout * time.Second, DeviceInfo: server.DeviceInfo{ Manufacturer: manufacturer, Model: model, @@ -198,10 +198,10 @@ func buildConfig(host string, port int, username, password, manufacturer, model, if ptz && template.hasPTZ { profile.PTZ = &server.PTZConfig{ NodeToken: fmt.Sprintf("ptz_node_%d", i), - PanRange: server.Range{Min: -180, Max: 180}, - TiltRange: server.Range{Min: -90, Max: 90}, + PanRange: server.Range{Min: -ptzMaxPan, Max: ptzMaxPan}, + TiltRange: server.Range{Min: -ptzMaxTilt, Max: ptzMaxTilt}, ZoomRange: server.Range{Min: 0, Max: template.ptzZoomMax}, - DefaultSpeed: server.PTZSpeed{Pan: 0.5, Tilt: 0.5, Zoom: 0.5}, + DefaultSpeed: server.PTZSpeed{Pan: ptzSpeed, Tilt: ptzSpeed, Zoom: ptzSpeed}, SupportsContinuous: true, SupportsAbsolute: true, SupportsRelative: true, @@ -214,7 +214,7 @@ func buildConfig(host string, port int, username, password, manufacturer, model, { Token: fmt.Sprintf("preset_%d_1", i), Name: "Entrance", - Position: server.PTZPosition{Pan: -45, Tilt: -10, Zoom: template.ptzZoomMax * 0.5}, + Position: server.PTZPosition{Pan: -45, Tilt: -10, Zoom: template.ptzZoomMax * ptzSpeed}, //nolint:mnd // Preset position values }, }, } diff --git a/media.go b/media.go index f940bec..8f72318 100644 --- a/media.go +++ b/media.go @@ -694,7 +694,9 @@ func (c *Client) GetMediaServiceCapabilities(ctx context.Context) (*MediaService // GetVideoEncoderConfigurationOptions retrieves available options for video encoder configuration. // //nolint:funlen // GetVideoEncoderConfigurationOptions has many statements due to parsing complex encoder options -func (c *Client) GetVideoEncoderConfigurationOptions(ctx context.Context, configurationToken string) (*VideoEncoderConfigurationOptions, error) { +func (c *Client) GetVideoEncoderConfigurationOptions( + ctx context.Context, configurationToken string, +) (*VideoEncoderConfigurationOptions, error) { endpoint := c.mediaEndpoint if endpoint == "" { endpoint = c.endpoint diff --git a/server/device.go b/server/device.go index 67ae0e3..a031439 100644 --- a/server/device.go +++ b/server/device.go @@ -266,12 +266,12 @@ func (s *Server) HandleGetServices(body interface{}) (interface{}, error) { { Namespace: "http://www.onvif.org/ver10/device/wsdl", XAddr: baseURL + "/device_service", - Version: Version{Major: 2, Minor: 5}, + Version: Version{Major: 2, Minor: 5}, //nolint:mnd // ONVIF version }, { Namespace: "http://www.onvif.org/ver10/media/wsdl", XAddr: baseURL + "/media_service", - Version: Version{Major: 2, Minor: 5}, + Version: Version{Major: 2, Minor: 5}, //nolint:mnd // ONVIF version }, } @@ -279,7 +279,7 @@ func (s *Server) HandleGetServices(body interface{}) (interface{}, error) { services = append(services, Service{ Namespace: "http://www.onvif.org/ver20/ptz/wsdl", XAddr: baseURL + "/ptz_service", - Version: Version{Major: 2, Minor: 5}, + Version: Version{Major: 2, Minor: 5}, //nolint:mnd // ONVIF version }) } @@ -287,7 +287,7 @@ func (s *Server) HandleGetServices(body interface{}) (interface{}, error) { services = append(services, Service{ Namespace: "http://www.onvif.org/ver20/imaging/wsdl", XAddr: baseURL + "/imaging_service", - Version: Version{Major: 2, Minor: 5}, + Version: Version{Major: 2, Minor: 5}, //nolint:mnd // ONVIF version }) } diff --git a/server/imaging.go b/server/imaging.go index 7eeeb19..5d565d4 100644 --- a/server/imaging.go +++ b/server/imaging.go @@ -347,25 +347,27 @@ func (s *Server) HandleSetImagingSettings(body interface{}) (interface{}, error) // HandleGetOptions handles GetOptions request. func (s *Server) HandleGetOptions(body interface{}) (interface{}, error) { // Return available imaging options/capabilities + const maxImagingValue = 100 //nolint:mnd // Maximum imaging parameter value + const maxExposureTime = 10000 //nolint:mnd // Maximum exposure time in microseconds options := &ImagingOptions{ - Brightness: &FloatRange{Min: 0, Max: 100}, - ColorSaturation: &FloatRange{Min: 0, Max: 100}, - Contrast: &FloatRange{Min: 0, Max: 100}, - Sharpness: &FloatRange{Min: 0, Max: 100}, + Brightness: &FloatRange{Min: 0, Max: maxImagingValue}, + ColorSaturation: &FloatRange{Min: 0, Max: maxImagingValue}, + Contrast: &FloatRange{Min: 0, Max: maxImagingValue}, + Sharpness: &FloatRange{Min: 0, Max: maxImagingValue}, IrCutFilterModes: []string{"ON", "OFF", "AUTO"}, BacklightCompensation: &BacklightCompensationOptions{ Mode: []string{"OFF", "ON"}, - Level: &FloatRange{Min: 0, Max: 100}, + Level: &FloatRange{Min: 0, Max: maxImagingValue}, }, Exposure: &ExposureOptions{ Mode: []string{"AUTO", "MANUAL"}, Priority: []string{"LowNoise", "FrameRate"}, - MinExposureTime: &FloatRange{Min: 1, Max: 10000}, - MaxExposureTime: &FloatRange{Min: 1, Max: 10000}, - MinGain: &FloatRange{Min: 0, Max: 100}, - MaxGain: &FloatRange{Min: 0, Max: 100}, - ExposureTime: &FloatRange{Min: 1, Max: 10000}, - Gain: &FloatRange{Min: 0, Max: 100}, + MinExposureTime: &FloatRange{Min: 1, Max: maxExposureTime}, + MaxExposureTime: &FloatRange{Min: 1, Max: maxExposureTime}, + MinGain: &FloatRange{Min: 0, Max: maxImagingValue}, + MaxGain: &FloatRange{Min: 0, Max: maxImagingValue}, + ExposureTime: &FloatRange{Min: 1, Max: maxExposureTime}, + Gain: &FloatRange{Min: 0, Max: maxImagingValue}, }, Focus: &FocusOptions{ AutoFocusModes: []string{"AUTO", "MANUAL"}, diff --git a/server/ptz.go b/server/ptz.go index 6832197..a875100 100644 --- a/server/ptz.go +++ b/server/ptz.go @@ -268,7 +268,7 @@ func (s *Server) HandleAbsoluteMove(body interface{}) (interface{}, error) { // In a real implementation, simulate movement over time // For now, we'll stop immediately go func() { - time.Sleep(500 * time.Millisecond) + time.Sleep(500 * time.Millisecond) //nolint:mnd // PTZ movement delay ptzMutex.Lock() state.Moving = false state.PanMoving = false @@ -306,8 +306,10 @@ func (s *Server) HandleRelativeMove(body interface{}) (interface{}, error) { } // Clamp values to valid ranges (simplified) - state.Position.Pan = clamp(state.Position.Pan, -180, 180) - state.Position.Tilt = clamp(state.Position.Tilt, -90, 90) + const maxPan = 180 //nolint:mnd // PTZ pan range + const maxTilt = 90 //nolint:mnd // PTZ tilt range + state.Position.Pan = clamp(state.Position.Pan, -maxPan, maxPan) + state.Position.Tilt = clamp(state.Position.Tilt, -maxTilt, maxTilt) state.Position.Zoom = clamp(state.Position.Zoom, 0, 1) state.Moving = true @@ -315,7 +317,7 @@ func (s *Server) HandleRelativeMove(body interface{}) (interface{}, error) { // Simulate movement completion go func() { - time.Sleep(500 * time.Millisecond) + time.Sleep(500 * time.Millisecond) //nolint:mnd // PTZ movement delay ptzMutex.Lock() state.Moving = false state.PanMoving = false diff --git a/server/server.go b/server/server.go index eb156ae..8652c94 100644 --- a/server/server.go +++ b/server/server.go @@ -160,7 +160,8 @@ func (s *Server) Start(ctx context.Context) error { select { case <-ctx.Done(): fmt.Println("\n🛑 Shutting down server...") - shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + const shutdownTimeout = 5 //nolint:mnd // Server shutdown timeout in seconds + shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout*time.Second) defer cancel() if err := httpServer.Shutdown(shutdownCtx); err != nil { diff --git a/server/types.go b/server/types.go index 663bfea..ea1a9dd 100644 --- a/server/types.go +++ b/server/types.go @@ -363,7 +363,8 @@ func (c *Config) ServiceEndpoints(host string) map[string]string { } var baseURL string - if c.Port == 80 { + const httpPort = 80 //nolint:mnd // Standard HTTP port + if c.Port == httpPort { baseURL = "http://" + host + c.BasePath } else { // Import fmt at the top to use Sprintf