diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..96e912b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,250 @@ +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + name: Build Release Binaries + runs-on: ubuntu-latest + strategy: + matrix: + include: + # Linux + - goos: linux + goarch: amd64 + - goos: linux + goarch: arm64 + - goos: linux + goarch: arm + goarm: 7 + + # Windows + - goos: windows + goarch: amd64 + - goos: windows + goarch: arm64 + + # macOS + - goos: darwin + goarch: amd64 + - goos: darwin + goarch: arm64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Get version + id: version + run: | + echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Build binaries + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + GOARM: ${{ matrix.goarm }} + CGO_ENABLED: 0 + run: | + VERSION=${{ steps.version.outputs.VERSION }} + LDFLAGS="-s -w -X main.Version=${VERSION} -X main.Commit=${{ steps.version.outputs.SHORT_SHA }}" + + # Set file extension for Windows + EXT="" + if [ "${{ matrix.goos }}" = "windows" ]; then + EXT=".exe" + fi + + # Build all CLI tools + mkdir -p dist + + echo "Building onvif-cli..." + go build -ldflags="${LDFLAGS}" -o "dist/onvif-cli-${{ matrix.goos }}-${{ matrix.goarch }}${EXT}" ./cmd/onvif-cli + + echo "Building onvif-quick..." + go build -ldflags="${LDFLAGS}" -o "dist/onvif-quick-${{ matrix.goos }}-${{ matrix.goarch }}${EXT}" ./cmd/onvif-quick + + echo "Building onvif-server..." + go build -ldflags="${LDFLAGS}" -o "dist/onvif-server-${{ matrix.goos }}-${{ matrix.goarch }}${EXT}" ./cmd/onvif-server + + echo "Building onvif-diagnostics..." + go build -ldflags="${LDFLAGS}" -o "dist/onvif-diagnostics-${{ matrix.goos }}-${{ matrix.goarch }}${EXT}" ./cmd/onvif-diagnostics + + - name: Create archive + run: | + VERSION=${{ steps.version.outputs.VERSION }} + PLATFORM="${{ matrix.goos }}-${{ matrix.goarch }}" + ARCHIVE_NAME="go-onvif-${VERSION}-${PLATFORM}" + + mkdir -p releases + + if [ "${{ matrix.goos }}" = "windows" ]; then + # Create ZIP for Windows + cd dist + zip -j "../releases/${ARCHIVE_NAME}.zip" *-${{ matrix.goos }}-${{ matrix.goarch }}.exe ../README.md ../LICENSE + cd .. + else + # Create tar.gz for Unix-like systems + cd dist + tar czf "../releases/${ARCHIVE_NAME}.tar.gz" *-${{ matrix.goos }}-${{ matrix.goarch }} -C .. README.md LICENSE + cd .. + fi + + - name: Generate checksums + run: | + cd releases + if command -v sha256sum >/dev/null 2>&1; then + sha256sum * > checksums-${{ matrix.goos }}-${{ matrix.goarch }}.txt + else + shasum -a 256 * > checksums-${{ matrix.goos }}-${{ matrix.goarch }}.txt + fi + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: release-${{ matrix.goos }}-${{ matrix.goarch }} + path: releases/* + retention-days: 5 + + release: + name: Create GitHub Release + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: all-releases + pattern: release-* + merge-multiple: true + + - name: Generate combined checksums + run: | + cd all-releases + # Combine all checksum files + cat checksums-*.txt > checksums.txt + # Remove individual checksum files + rm checksums-*.txt + + - name: Get version and changelog + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/} + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + + # Generate changelog from commits since last tag + PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + if [ -n "$PREV_TAG" ]; then + echo "CHANGELOG<> $GITHUB_OUTPUT + git log --pretty=format:"- %s (%h)" ${PREV_TAG}..HEAD >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: all-releases/* + draft: false + prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-beta') || contains(github.ref, '-alpha') }} + generate_release_notes: true + body: | + ## Release ${{ steps.version.outputs.VERSION }} + + ### Installation + + Download the appropriate binary for your platform below. + + #### Linux/macOS + ```bash + # Download and extract + wget https://github.com/${{ github.repository }}/releases/download/${{ steps.version.outputs.VERSION }}/go-onvif-${{ steps.version.outputs.VERSION }}-linux-amd64.tar.gz + tar xzf go-onvif-${{ steps.version.outputs.VERSION }}-linux-amd64.tar.gz + + # Make executable and move to PATH + chmod +x onvif-cli-linux-amd64 + sudo mv onvif-cli-linux-amd64 /usr/local/bin/onvif-cli + ``` + + #### Windows + Download the `.zip` file for your architecture and extract it. + + #### Go Library + ```bash + go get github.com/${{ github.repository }}@${{ steps.version.outputs.VERSION }} + ``` + + ### Checksums + + SHA256 checksums are available in `checksums.txt` + + ### Changes + + ${{ steps.version.outputs.CHANGELOG }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + docker: + name: Build and Push Docker Image + needs: build + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + continue-on-error: true + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get version + id: version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: | + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000..ebee498 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,226 @@ +# Building and Releasing go-onvif + +This document describes how to build binaries for multiple platforms and create releases. + +## Quick Start + +### Build for Your Current Platform + +```bash +make build-cli +``` + +This builds all CLI tools for your current OS/architecture in the `bin/` directory. + +### Build for All Platforms + +```bash +make build-all +``` + +This creates binaries for: +- **Linux**: amd64, arm64, arm (32-bit) +- **Windows**: amd64, arm64 +- **macOS**: amd64 (Intel), arm64 (Apple Silicon) + +Binaries are output to `bin/` directory. + +### Create Release Archives + +```bash +make release +``` + +This: +1. Builds for all platforms +2. Creates `.tar.gz` archives (Linux/macOS) and `.zip` files (Windows) +3. Generates SHA256 checksums +4. Places everything in `releases/` directory + +## Manual Building + +### Using the Build Script + +```bash +# Build with automatic version detection +./build-release.sh + +# Build with specific version +./build-release.sh v1.0.1 +``` + +### Using Go Directly + +```bash +# Set platform and architecture +export GOOS=linux +export GOARCH=amd64 + +# Build a specific tool +go build -o bin/onvif-cli-linux-amd64 ./cmd/onvif-cli +``` + +## Supported Platforms + +| OS | Architecture | Binary Suffix | Notes | +|---------|-------------|------------------------|----------------------------| +| Linux | amd64 | `linux-amd64` | 64-bit Intel/AMD | +| Linux | arm64 | `linux-arm64` | 64-bit ARM (Raspberry Pi 4)| +| Linux | arm | `linux-arm` | 32-bit ARM (Raspberry Pi 3)| +| Windows | amd64 | `windows-amd64.exe` | 64-bit Windows | +| Windows | arm64 | `windows-arm64.exe` | ARM Windows (Surface Pro X)| +| macOS | amd64 | `darwin-amd64` | Intel Macs | +| macOS | arm64 | `darwin-arm64` | Apple Silicon (M1/M2/M3) | + +## CLI Tools + +The following binaries are built: + +1. **onvif-cli** - Comprehensive ONVIF client with full feature set +2. **onvif-quick** - Quick tool for common operations +3. **onvif-server** - ONVIF mock server for testing +4. **onvif-diagnostics** - Diagnostic and debugging tools + +## Automated Releases via GitHub Actions + +Releases are automatically created when you push a tag: + +```bash +# Create and push a new version tag +git tag -a v1.0.1 -m "Release version 1.0.1" +git push origin v1.0.1 +``` + +The GitHub Actions workflow will: +1. Build binaries for all platforms +2. Create release archives +3. Generate checksums +4. Create a GitHub release with all artifacts +5. Build and push Docker images (multi-arch) + +### Release Workflow Features + +- ✅ Builds for 7 platform/architecture combinations +- ✅ Creates compressed archives (`.tar.gz` and `.zip`) +- ✅ Generates SHA256 checksums for verification +- ✅ Auto-generates release notes from commits +- ✅ Supports pre-releases (tags with `-rc`, `-beta`, `-alpha`) +- ✅ Builds multi-architecture Docker images +- ✅ Pushes to GitHub Container Registry + +## Docker Images + +Docker images are automatically built for: +- `linux/amd64` +- `linux/arm64` +- `linux/arm/v7` + +Available at: +``` +ghcr.io/0x524a/go-onvif:latest +ghcr.io/0x524a/go-onvif:v1.0.0 +``` + +## Manual GitHub Release + +If you prefer to create releases manually: + +```bash +# Build release archives +make release + +# Create GitHub release using gh CLI +gh release create v1.0.1 releases/* \ + --title "Release v1.0.1" \ + --notes "Release notes here" +``` + +## Version Numbering + +Follow [Semantic Versioning](https://semver.org/): + +- `v1.0.0` - Major release (breaking changes) +- `v1.1.0` - Minor release (new features, backward compatible) +- `v1.1.1` - Patch release (bug fixes) +- `v1.0.0-rc1` - Release candidate +- `v1.0.0-beta1` - Beta release +- `v1.0.0-alpha1` - Alpha release + +## Build Flags + +The build process uses the following flags: + +```bash +-ldflags="-s -w -X main.Version= -X main.Commit=" +``` + +- `-s` - Omit symbol table (smaller binary) +- `-w` - Omit DWARF debug info (smaller binary) +- `-X main.Version` - Inject version string +- `-X main.Commit` - Inject git commit SHA + +## Size Optimization + +Binaries are built with `CGO_ENABLED=0` and stripped flags, resulting in: +- Smaller binary sizes +- No external dependencies +- Portable across systems + +Typical sizes: +- onvif-cli: ~10-15 MB +- onvif-quick: ~8-12 MB +- onvif-server: ~10-14 MB + +## Troubleshooting + +### Build Fails for Specific Platform + +Some platforms may not be supported by all dependencies. Check: +```bash +go tool dist list # List all supported platforms +``` + +### Large Binary Sizes + +Ensure you're using the build flags: +```bash +go build -ldflags="-s -w" -o binary ./cmd/tool +``` + +### Missing Dependencies + +```bash +go mod download +go mod tidy +``` + +## Distribution + +Once built, binaries can be distributed via: + +1. **GitHub Releases** (automatic) +2. **Package managers** (homebrew, apt, etc.) +3. **Container registries** (Docker Hub, GHCR) +4. **Direct download** from your server + +## Verification + +Users can verify downloads using checksums: + +```bash +# Download binary and checksum +wget https://github.com/0x524A/go-onvif/releases/download/v1.0.0/go-onvif-v1.0.0-linux-amd64.tar.gz +wget https://github.com/0x524A/go-onvif/releases/download/v1.0.0/checksums.txt + +# Verify +sha256sum -c checksums.txt --ignore-missing +``` + +## Next Steps + +After building: +1. Test binaries on target platforms +2. Update CHANGELOG.md with release notes +3. Create GitHub release +4. Announce on relevant channels +5. Update documentation with new features diff --git a/CAMERA_ANALYSIS.md b/CAMERA_ANALYSIS.md deleted file mode 100644 index 5c9147a..0000000 --- a/CAMERA_ANALYSIS.md +++ /dev/null @@ -1,706 +0,0 @@ -# ONVIF Camera Analysis Report - -Generated: November 7, 2025 - -## Executive Summary - -Analysis of 5 ONVIF-compliant cameras from 3 manufacturers (REOLINK, AXIS, Bosch) reveals diverse implementations and capabilities. All cameras successfully responded to ONVIF commands with varying feature sets. - ---- - -## Camera Inventory - -### 1. REOLINK E1 Zoom -- **Firmware**: v3.1.0.2649_23083101 -- **Serial**: 192168261 -- **IP**: 192.168.2.61:8000 -- **Type**: PTZ Indoor Camera -- **Key Features**: PTZ support, dual stream, basic imaging - -### 2. AXIS Q3819-PVE -- **Firmware**: 10.12.153 -- **Serial**: B8A44F9DC7ED -- **IP**: 192.168.2.190 -- **Type**: Panoramic Fixed Dome -- **Key Features**: Ultra-wide 8192x1728 resolution, analytics, advanced imaging - -### 3. AXIS P3818-PVE -- **Firmware**: 11.9.60 -- **Serial**: B8A44FA04F26 -- **IP**: 192.168.2.82 -- **Type**: Panoramic Fixed Dome -- **Key Features**: 5120x2560 resolution, analytics, dual encoding (H264/JPEG) - -### 4. Bosch FLEXIDOME panoramic 5100i -- **Firmware**: 9.00.0210 -- **Serial**: 404705923918060213 -- **IP**: 192.168.2.24 -- **Type**: 360° Panoramic Dome -- **Key Features**: 16 profiles, dewarping, circular image (2112x2112) - -### 5. Bosch FLEXIDOME IP starlight 8000i -- **Firmware**: 7.70.0126 -- **Serial**: 044518807925140011 -- **IP**: 192.168.2.200 -- **Type**: Fixed Dome with Low-Light Performance -- **Key Features**: Starlight imaging, I/O connectors, relay output - ---- - -## Comparative Analysis - -### Resolution Capabilities - -| Camera | Max Resolution | Aspect Ratio | Primary Use Case | -|--------|---------------|--------------|------------------| -| REOLINK E1 Zoom | 2048x1536 | 4:3 | Standard surveillance | -| AXIS Q3819-PVE | 8192x1728 | ~4.7:1 | 180° panoramic | -| AXIS P3818-PVE | 5120x2560 | 2:1 | 180° panoramic | -| Bosch panoramic 5100i | 2112x2112 | 1:1 | 360° fisheye | -| Bosch starlight 8000i | 1536x864 | 16:9 | Low-light environments | - -### Profile Count - -| Camera | Total Profiles | Video Profiles | Notes | -|--------|----------------|----------------|-------| -| REOLINK E1 Zoom | 2 | 2 | MainStream + SubStream | -| AXIS Q3819-PVE | 2 | 2 | H264 + JPEG | -| AXIS P3818-PVE | 2 | 2 | H264 + JPEG | -| Bosch panoramic 5100i | 16 | 9 valid | Includes metadata/audio profiles | -| Bosch starlight 8000i | 3 | 3 | 2x H264 + 1x JPEG | - -### ONVIF Service Support - -| Service | REOLINK | AXIS Q3819 | AXIS P3818 | Bosch Panoramic | Bosch Starlight | -|---------|---------|------------|------------|-----------------|-----------------| -| Device | ✓ | ✓ | ✓ | ✓ | ✓ | -| Media | ✓ | ✓ | ✓ | ✓ | ✓ | -| Imaging | ✓ | ✓ | ✓ | ✓ | ✓ | -| Events | ✓ | ✓ | ✓ | ✓ | ✓ | -| Analytics | ✗ | ✓ | ✓ | ✓ | ✗ | -| PTZ | ✓ | ✗ | ✗ | ✓ | ✗ | - -### Video Encoding - -| Camera | H264 | JPEG | MPEG4 | Notes | -|--------|------|------|-------|-------| -| REOLINK | ✓ | ✗ | ✗ | H264 only | -| AXIS Q3819 | ✓ | ✓ | ✗ | Dual encoding | -| AXIS P3818 | ✓ | ✓ | ✗ | Dual encoding | -| Bosch Panoramic | ✓ | ✗ | ✗ | H264 only | -| Bosch Starlight | ✓ | ✓ | ✗ | Dual encoding | - -### Network Capabilities - -| Feature | REOLINK | AXIS Q3819 | AXIS P3818 | Bosch Panoramic | Bosch Starlight | -|---------|---------|------------|------------|-----------------|-----------------| -| RTP Multicast | ✗ | ✓ | ✓ | ✓ | ✓ | -| RTP/TCP | ✓ | ✓ | ✓ | ✗ | ✗ | -| RTP/RTSP/TCP | ✓ | ✓ | ✓ | ✓ | ✓ | -| IPv6 Support | ✗ | ✓ | ✓ | ✗ | ✗ | -| TLS 1.2 | ✗ | ✓ | ✓ | ✓ | ✓ | - -### Imaging Features - -| Feature | REOLINK | AXIS Q3819 | AXIS P3818 | Bosch Panoramic | Bosch Starlight | -|---------|---------|------------|------------|-----------------|-----------------| -| Brightness Control | ✓ (128) | ✓ (50) | ✓ (50) | ✓ (127) | ✓ (128) | -| Saturation Control | ✓ (128) | ✓ (50) | ✓ (50) | ✓ (127) | ✓ (128) | -| Contrast Control | ✓ (128) | ✓ (50) | ✓ (50) | ✓ (127) | ✓ (128) | -| Sharpness Control | ✓ (128) | ✓ (50) | ✓ (50) | ✗ | ✗ | -| IrCutFilter | AUTO | AUTO | AUTO | ✗ | ✗ | -| WDR | ✗ | ON | ON | ✗ | ✗ | -| WhiteBalance | ✗ | AUTO | AUTO | ✗ | ✗ | -| Exposure Control | ✗ | AUTO | AUTO | ✗ | ✗ | - -### I/O and Security - -| Feature | REOLINK | AXIS Q3819 | AXIS P3818 | Bosch Panoramic | Bosch Starlight | -|---------|---------|------------|------------|-----------------|-----------------| -| Input Connectors | 0 | 2 | 2 | 0 | 2 | -| Relay Outputs | 0 | 0 | 0 | 0 | 1 | -| IP Filter | ✗ | ✓ | ✓ | ✗ | ✗ | -| TLS 1.1 | ✗ | ✓ | ✓ | ✗ | ✓ | -| TLS 1.2 | ✗ | ✓ | ✓ | ✓ | ✓ | - ---- - -## Manufacturer-Specific Findings - -### REOLINK -- **Strengths**: - - Simple, straightforward ONVIF implementation - - PTZ support with status reporting - - Good value camera with basic features -- **Limitations**: - - Limited imaging controls (no WDR, exposure, focus) - - Only H264 encoding (no JPEG profile) - - No analytics support - - Lower security features (no TLS) -- **RTSP Pattern**: `rtsp://IP:554/` (main), `rtsp://IP:554/h264Preview_01_sub` (sub) -- **Snapshot Pattern**: `http://IP:80/cgi-bin/api.cgi?cmd=onvifSnapPic&channel=0` - -### AXIS -- **Strengths**: - - Excellent ONVIF compliance and feature richness - - Ultra-high resolution panoramic cameras - - Advanced imaging with WDR, exposure control, white balance - - Strong security (TLS 1.1/1.2, IP filtering, access policy) - - Analytics and rule-based event support -- **Consistent Implementation**: - - Both cameras share similar ONVIF structure - - Dual H264/JPEG encoding profiles - - Same URL patterns and capabilities -- **RTSP Pattern**: `rtsp://IP/onvif-media/media.amp?profile=X&sessiontimeout=60&streamtype=unicast` -- **Snapshot Pattern**: `http://IP/onvif-cgi/jpg/image.cgi?resolution=WxH&compression=30` -- **Notable**: Q3819 has wider aspect ratio (8192x1728 vs 5120x2560) - -### Bosch -- **Strengths**: - - Specialized cameras with unique features - - Panoramic 5100i has comprehensive dewarping profiles - - Starlight 8000i optimized for low-light - - Good I/O options (starlight model has relay output) -- **Quirks**: - - Panoramic model has 16 profiles (many without video encoders) - - Some profiles return "IncompleteConfiguration" errors - - Less standardized RTSP URLs (tunnel-based) -- **RTSP Pattern**: `rtsp://IP/rtsp_tunnel?p=X&line=Y&inst=Z` (various parameters) -- **Snapshot Pattern**: `http://IP/snap.jpg?JpegCam=X` -- **Notable**: - - Panoramic uses circular (2112x2112) and dewarped (3072x1728) views - - 3 profiles failed GetStreamURI with incomplete configuration - ---- - -## Performance Metrics - -### Response Times (Average) - -| Operation | REOLINK | AXIS Q3819 | AXIS P3818 | Bosch Panoramic | Bosch Starlight | -|-----------|---------|------------|------------|-----------------|-----------------| -| DeviceInfo | 117.7ms | 5.0ms | 4.9ms | 8.5ms | 7.9ms | -| Capabilities | 85.6ms | 72.7ms | 69.3ms | 21.9ms | 27.1ms | -| GetProfiles | 832.1ms | 70.9ms | 8.0ms | 706.2ms | 258.3ms | -| GetStreamURI | ~129ms avg | ~20ms avg | ~4ms avg | ~11ms avg | ~10ms avg | -| GetSnapshot | ~170ms avg | ~20ms avg | ~4ms avg | ~11ms avg | ~6ms avg | -| Imaging | 111.8ms | 55.8ms | 67.2ms | 57.3ms | 14.8ms | - -**Key Observations**: -- AXIS cameras have fastest response times overall -- REOLINK has higher latency (likely due to port 8000, may be proxy/gateway) -- Bosch cameras have moderate, consistent response times -- GetProfiles is slowest operation for most cameras - -### Error Analysis - -| Camera | Total Errors | Error Types | -|--------|--------------|-------------| -| REOLINK E1 Zoom | 0 | None | -| AXIS Q3819-PVE | 0 | None | -| AXIS P3818-PVE | 0 | None | -| Bosch panoramic 5100i | 3 | GetStreamURI: IncompleteConfiguration (profiles 9,10,11) | -| Bosch starlight 8000i | 0 | None | - -**Bosch Panoramic Errors**: Profiles 9, 10, 11 have no VideoEncoderConfiguration, causing legitimate failures. These appear to be metadata-only or incomplete profiles. - ---- - -## Stream URI Patterns - -### REOLINK Pattern -``` -rtsp://192.168.2.61:554/ # MainStream -rtsp://192.168.2.61:554/h264Preview_01_sub # SubStream -``` - -### AXIS Pattern -``` -rtsp://IP/onvif-media/media.amp?profile=profile_1_h264&sessiontimeout=60&streamtype=unicast -rtsp://IP/onvif-media/media.amp?profile=profile_1_jpeg&sessiontimeout=60&streamtype=unicast -``` - -### Bosch Patterns - -**Indoor 5100i IR** (from previous report): -``` -rtsp://IP/rtsp_tunnel?p=0&line=1&inst=1&vcd=2 -``` - -**Panoramic 5100i**: -``` -rtsp://192.168.2.24/rtsp_tunnel?p=0&line=3&inst=4 # E_PTZ view -rtsp://192.168.2.24/rtsp_tunnel?p=1&line=2&inst=1 # Dewarped view -rtsp://192.168.2.24/rtsp_tunnel?p=2&line=1&inst=4 # Full circle -rtsp://192.168.2.24/rtsp_tunnel?von=0&aon=1&aud=1 # Audio only -rtsp://192.168.2.24/rtsp_tunnel?von=0&vcd=2&line=1 # Metadata -``` - -**Starlight 8000i**: -``` -rtsp://192.168.2.200/rtsp_tunnel?p=0&h26x=4&vcd=2 -rtsp://192.168.2.200/rtsp_tunnel?p=1&inst=2&h26x=4 -rtsp://192.168.2.200/rtsp_tunnel?h26x=0 # JPEG -``` - -**Parameter Meanings**: -- `p`: Profile index -- `line`: Video line/source (1=full, 2=dewarped, 3=ePTZ) -- `inst`: Instance number -- `vcd`: Video codec (2=metadata) -- `h26x`: H.26x codec (0=JPEG, 4=H264) -- `von`: Video on/off -- `aon`: Audio on/off - ---- - -## PTZ Capabilities - -### REOLINK E1 Zoom (PTZ Enabled) -- **PTZ Service**: http://192.168.2.61:8000/onvif/ptz_service -- **Status**: Both profiles report IDLE for PanTilt and Zoom -- **Presets**: 0 configured -- **Configuration**: PTZ config present but with empty position spaces -- **Notes**: PTZ capability exists but requires further testing for movement commands - -### Bosch Panoramic 5100i (ePTZ) -- **PTZ Service**: http://192.168.2.24/onvif/ptz_service -- **Type**: Electronic PTZ (digital zoom/pan on panoramic image) -- **Profile**: Dedicated ePTZ profile (token "0", 1920x1080) -- **Notes**: Digital PTZ on dewarped 360° image, not mechanical movement - -### Other Cameras -- AXIS Q3819-PVE, P3818-PVE, Bosch starlight 8000i: No PTZ support - ---- - -## Snapshot URI Patterns - -| Manufacturer | Pattern | Authentication Required | -|--------------|---------|------------------------| -| REOLINK | `http://IP:80/cgi-bin/api.cgi?cmd=onvifSnapPic&channel=0` | Yes | -| AXIS | `http://IP/onvif-cgi/jpg/image.cgi?resolution=WxH&compression=30` | Yes | -| Bosch | `http://IP/snap.jpg?JpegCam=N` | Yes | - -**InvalidAfterConnect/Reboot**: -- REOLINK: InvalidAfterConnect=true, InvalidAfterReboot=true -- AXIS: All false (persistent URIs) -- Bosch: InvalidAfterReboot=true - ---- - -## Bitrate and Frame Rate Analysis - -### REOLINK E1 Zoom -- **MainStream**: 1024 kbps @ 15fps (2048x1536) -- **SubStream**: 512 kbps @ 15fps (640x480) -- **Quality**: 0 (main), 2 (sub) - -### AXIS Q3819-PVE -- **H264**: Max bitrate @ 30fps (8192x1728) -- **JPEG**: Max bitrate @ 30fps (8192x1728) -- **Quality**: 70 for both -- **Bitrate Limit**: 2147483647 (max int32 = unlimited) - -### AXIS P3818-PVE -- **H264**: Max bitrate @ 30fps (1920x960) -- **JPEG**: Max bitrate @ 30fps (5120x2560) -- **Quality**: 70 for both -- **Bitrate Limit**: 2147483647 (unlimited) - -### Bosch Panoramic 5100i -- **Highest**: 13000 kbps @ 30fps (3072x1728 dewarped) -- **Lowest**: 400 kbps @ 30fps (512x288) -- **Standard**: 5200 kbps @ 30fps (1920x1080) -- **Quality**: 50 across all profiles - -### Bosch Starlight 8000i -- **H264**: 1400 kbps @ 30fps (1536x864) -- **JPEG**: 6000 kbps @ 1fps (1536x864) -- **Quality**: 50 (H264), 70 (JPEG) - ---- - -## Testing Recommendations - -### Priority 1: Create Camera-Specific Tests - -Each manufacturer has distinct patterns worthy of dedicated test files: - -1. **reolink_e1_zoom_test.go** - - Test PTZ status retrieval - - Verify dual-stream profiles - - Test CGI-based snapshot URLs - - Validate 15fps frame rate limits - -2. **axis_q3819_test.go** - - Test ultra-wide resolution (8192x1728) - - Verify analytics service - - Test dual H264/JPEG encoding - - Validate WDR and exposure settings - - Test multicast support - -3. **axis_p3818_test.go** - - Test 5120x2560 panoramic resolution - - Similar to Q3819 but different aspect ratio - - Benchmark performance differences - -4. **bosch_panoramic_5100i_test.go** - - Test circular (2112x2112) image profiles - - Test dewarped profiles - - Handle IncompleteConfiguration errors gracefully - - Test metadata and audio-only profiles - - Test 16 different profiles - -5. **bosch_starlight_8000i_test.go** - - Test low-light imaging capabilities - - Test I/O connectors (2 inputs, 1 relay output) - - Test JPEG motion (1fps) vs H264 (30fps) - -### Priority 2: Cross-Manufacturer Tests - -Create tests that verify common ONVIF compliance: - -1. **stream_uri_compatibility_test.go** - - Parse and validate different RTSP URL formats - - Test RTSP connection to each pattern - - Verify authentication handling - -2. **imaging_settings_test.go** - - Test brightness/contrast/saturation ranges - - Test optional features (WDR, exposure, white balance) - - Verify manufacturer-specific defaults - -3. **profile_enumeration_test.go** - - Test handling of 2-16 profiles - - Verify profile names and tokens - - Test resolution validation - -### Priority 3: Edge Case Tests - -1. **incomplete_profile_handling_test.go** - - Test cameras with profiles lacking video encoders - - Verify graceful error handling for IncompleteConfiguration - - Test metadata-only and audio-only profiles - -2. **performance_benchmark_test.go** - - Benchmark GetProfiles (100ms to 800ms variation) - - Test response time consistency - - Measure concurrent request handling - ---- - -## Code Patterns for Tests - -### Example: Testing AXIS Cameras - -```go -func TestAXISQ3819PVE_UltraWideResolution(t *testing.T) { - skipIfNoCamera(t) - - client := createTestClient(t) - profiles, err := client.GetProfiles() - require.NoError(t, err) - - // AXIS Q3819 should have H264 and JPEG profiles - assert.Equal(t, 2, len(profiles)) - - // Find H264 profile - var h264Profile *onvif.Profile - for _, p := range profiles { - if p.VideoEncoderConfiguration != nil && - p.VideoEncoderConfiguration.Encoding == "H264" { - h264Profile = &p - break - } - } - - require.NotNil(t, h264Profile, "H264 profile should exist") - - // Verify ultra-wide resolution - assert.Equal(t, 8192, h264Profile.VideoEncoderConfiguration.Resolution.Width) - assert.Equal(t, 1728, h264Profile.VideoEncoderConfiguration.Resolution.Height) - - // Verify 30fps - assert.Equal(t, 30, h264Profile.VideoEncoderConfiguration.RateControl.FrameRateLimit) -} -``` - -### Example: Testing Bosch Panoramic Profiles - -```go -func TestBoschPanoramic5100i_MultipleProfiles(t *testing.T) { - skipIfNoCamera(t) - - client := createTestClient(t) - profiles, err := client.GetProfiles() - require.NoError(t, err) - - // Should have 16 profiles - assert.Equal(t, 16, len(profiles)) - - // Count profiles with valid video encoders - validVideoProfiles := 0 - for _, p := range profiles { - if p.VideoEncoderConfiguration != nil { - validVideoProfiles++ - } - } - - assert.Equal(t, 9, validVideoProfiles, "Should have 9 video profiles") - - // Test that incomplete profiles fail gracefully - for _, p := range profiles { - uri, err := client.GetStreamURI(p.Token, "RTP-Unicast") - - if p.VideoEncoderConfiguration != nil { - // Valid profiles should succeed - if err != nil { - t.Logf("Profile %s failed: %v", p.Token, err) - } - } else { - // Incomplete profiles should fail - assert.Error(t, err, "Profile %s should fail (no video encoder)", p.Token) - } - } -} -``` - -### Example: Testing PTZ Status - -```go -func TestREOLINKE1Zoom_PTZStatus(t *testing.T) { - skipIfNoCamera(t) - - client := createTestClient(t) - profiles, err := client.GetProfiles() - require.NoError(t, err) - - for _, profile := range profiles { - if profile.PTZConfiguration != nil { - status, err := client.GetPTZStatus(profile.Token) - require.NoError(t, err) - - // Should report IDLE when not moving - assert.NotNil(t, status.MoveStatus) - assert.Contains(t, []string{"IDLE", "MOVING"}, status.MoveStatus.PanTilt) - assert.Contains(t, []string{"IDLE", "MOVING"}, status.MoveStatus.Zoom) - } - } -} -``` - ---- - -## Integration Test Suite Structure - -``` -tests/ -├── manufacturers/ -│ ├── reolink/ -│ │ └── e1_zoom_test.go -│ ├── axis/ -│ │ ├── q3819_pve_test.go -│ │ └── p3818_pve_test.go -│ └── bosch/ -│ ├── flexidome_indoor_5100i_ir_test.go (existing) -│ ├── flexidome_panoramic_5100i_test.go -│ └── flexidome_starlight_8000i_test.go -├── compliance/ -│ ├── stream_uri_test.go -│ ├── imaging_test.go -│ └── profile_test.go -├── benchmarks/ -│ └── response_time_test.go -└── edge_cases/ - ├── incomplete_profiles_test.go - └── error_handling_test.go -``` - ---- - -## Implementation Insights - -### RTSP Tunnel Parameters (Bosch) - -Bosch uses a proprietary `rtsp_tunnel` endpoint with various parameters: - -- **p**: Profile index (0-15) -- **line**: Video source line - - 1 = Full image circle - - 2 = Dewarped view mode - - 3 = Electronic PTZ -- **inst**: Stream instance (1-4, corresponds to bitrate tiers) -- **h26x**: Codec selection - - 0 = JPEG - - 4 = H.264 -- **vcd**: Video coding - - 2 = Metadata stream -- **von**: Video on (0/1) -- **aon**: Audio on (0/1) -- **aud**: Audio stream identifier -- **JpegCam**: Camera number for snapshots - -### AXIS URL Parameters - -- **profile**: Profile token -- **sessiontimeout**: Session timeout in seconds -- **streamtype**: unicast or multicast -- **resolution**: Snapshot resolution (WxH) -- **compression**: JPEG compression quality (0-100, lower = better) - -### REOLINK CGI API - -Uses proprietary CGI commands: -- `cmd=onvifSnapPic`: Get ONVIF-compliant snapshot -- `channel=0`: Camera channel - ---- - -## Security Considerations - -### Authentication -All cameras require HTTP Digest Authentication for ONVIF requests. - -### TLS Support - -| Camera | TLS 1.1 | TLS 1.2 | Notes | -|--------|---------|---------|-------| -| REOLINK E1 Zoom | ✗ | ✗ | HTTP only | -| AXIS Q3819-PVE | ✓ | ✓ | Full TLS support | -| AXIS P3818-PVE | ✓ | ✓ | Full TLS support | -| Bosch Panoramic 5100i | ✗ | ✓ | TLS 1.2 only | -| Bosch Starlight 8000i | ✓ | ✓ | Full TLS support | - -**Recommendation**: AXIS cameras provide the strongest security posture with IP filtering, access policy config, and TLS support. - -### WS-Security -All cameras support WS-Security UsernameToken with digest authentication, as evidenced by successful ONVIF communication. - ---- - -## Compatibility Matrix - -### ONVIF Profile Compliance - -Based on feature analysis, likely ONVIF profile compliance: - -| Camera | Profile S | Profile T | Profile G | Profile M | -|--------|-----------|-----------|-----------|-----------| -| REOLINK E1 Zoom | ✓ | ✓ (PTZ) | ✗ | ✗ | -| AXIS Q3819-PVE | ✓ | ✗ | ✓ (Analytics) | ✓ (Metadata) | -| AXIS P3818-PVE | ✓ | ✗ | ✓ (Analytics) | ✓ (Metadata) | -| Bosch Panoramic 5100i | ✓ | ✓ (ePTZ) | ✓ (Analytics) | ✓ (Metadata) | -| Bosch Starlight 8000i | ✓ | ✗ | ✗ | Partial | - -**Profiles**: -- **S**: Streaming (basic video) -- **T**: PTZ control -- **G**: Video analytics -- **M**: Metadata streaming - ---- - -## Conclusions - -### Best Practices Discovered - -1. **Profile Enumeration**: Always check VideoEncoderConfiguration before calling GetStreamURI -2. **Error Handling**: Bosch cameras may return IncompleteConfiguration for metadata profiles -3. **Response Times**: Expect 5-800ms for GetProfiles depending on camera complexity -4. **URL Patterns**: Cannot assume consistent RTSP URL format across manufacturers -5. **Imaging Defaults**: Manufacturers use different scales (0-255 vs 0-100 vs 0-128) - -### Client Library Improvements Needed - -1. **URL Parser**: Helper to parse and validate different RTSP URL formats -2. **Profile Filter**: Method to filter profiles by capability (video, audio, metadata) -3. **Retry Logic**: Handle transient errors and timeouts -4. **TLS Support**: Enable HTTPS for cameras supporting TLS -5. **Batch Operations**: Parallel GetStreamURI calls for cameras with many profiles - -### Test Coverage Recommendations - -Based on this analysis, create test files covering: - -1. ✅ Bosch FLEXIDOME indoor 5100i IR (already exists) -2. 🔲 REOLINK E1 Zoom (PTZ, dual stream) -3. 🔲 AXIS Q3819-PVE (ultra-wide, analytics) -4. 🔲 AXIS P3818-PVE (panoramic, analytics) -5. 🔲 Bosch FLEXIDOME panoramic 5100i (16 profiles, dewarping) -6. 🔲 Bosch FLEXIDOME IP starlight 8000i (low-light, I/O) - -### Interoperability Score - -Based on ONVIF compliance, feature richness, and ease of integration: - -| Camera | Score | Rationale | -|--------|-------|-----------| -| AXIS P3818-PVE | 9.5/10 | Excellent compliance, fast, feature-rich | -| AXIS Q3819-PVE | 9.5/10 | Same as P3818, ultra-wide resolution | -| Bosch Starlight 8000i | 8.0/10 | Good compliance, moderate features | -| Bosch Panoramic 5100i | 7.5/10 | Complex profile structure, some errors | -| REOLINK E1 Zoom | 7.0/10 | Basic features, slower responses, limited imaging | - ---- - -## Next Steps - -1. **Create manufacturer-specific test files** for each camera model -2. **Implement helper functions** for common patterns (URL parsing, profile filtering) -3. **Add benchmark tests** to track performance regression -4. **Document manufacturer quirks** in code comments -5. **Create CI/CD pipeline** to test against real cameras (when available) -6. **Expand coverage** for PTZ operations on REOLINK -7. **Test analytics** on AXIS cameras -8. **Validate TLS connections** on supported cameras - ---- - -## Appendix: Raw Data Summary - -### REOLINK E1 Zoom -- Profiles: 2 -- Stream URIs: 2/2 successful -- Snapshot URIs: 2/2 successful -- Video Encoders: 2/2 successful -- Imaging Settings: 1/1 successful -- PTZ Status: 2/2 successful (both IDLE) -- PTZ Presets: 0 -- Total Errors: 0 - -### AXIS Q3819-PVE -- Profiles: 2 -- Stream URIs: 2/2 successful -- Snapshot URIs: 2/2 successful -- Video Encoders: 2/2 successful -- Imaging Settings: 1/1 successful -- Total Errors: 0 - -### AXIS P3818-PVE -- Profiles: 2 -- Stream URIs: 2/2 successful -- Snapshot URIs: 2/2 successful -- Video Encoders: 2/2 successful -- Imaging Settings: 1/1 successful -- Total Errors: 0 - -### Bosch FLEXIDOME panoramic 5100i -- Profiles: 16 -- Stream URIs: 13/16 successful (3 IncompleteConfiguration errors) -- Snapshot URIs: 16/16 successful -- Video Encoders: 9/9 successful (only tested valid profiles) -- Imaging Settings: 1/1 successful -- Total Errors: 3 (expected for incomplete profiles) - -### Bosch FLEXIDOME IP starlight 8000i -- Profiles: 3 -- Stream URIs: 3/3 successful -- Snapshot URIs: 3/3 successful -- Video Encoders: 3/3 successful -- Imaging Settings: 1/1 successful -- Total Errors: 0 - ---- - -**End of Analysis Report** diff --git a/LINTING_FIXES.md b/LINTING_FIXES.md deleted file mode 100644 index 9e2a1c9..0000000 --- a/LINTING_FIXES.md +++ /dev/null @@ -1,129 +0,0 @@ -# Linting Fixes - golangci-lint Issues Resolved - -## Summary -All 7 linting errors reported by golangci-lint have been successfully fixed. - -## Issues Fixed - -### 1. Unchecked Error Return: `rand.Read` -**File:** `soap/soap.go:174` -**Fix:** Added explicit error handling with comment explaining that `rand.Read` from `crypto/rand` always succeeds for valid buffer sizes. -```go -// Before -rand.Read(nonceBytes) - -// After -_, _ = rand.Read(nonceBytes) // rand.Read always returns len(nonceBytes), nil -``` - -### 2. Unchecked Error Return: `w.Write` -**File:** `client_test.go:102` -**Fix:** Added explicit error handling for `http.ResponseWriter.Write()` with explanatory comment. -```go -// Before -w.Write([]byte(response)) - -// After -_, _ = w.Write([]byte(response)) // Writing to ResponseWriter; error is handled by http package -``` - -### 3-5. Unchecked Error Return: `client.Initialize` -**Files:** -- `cmd/onvif-quick/main.go:121` -- `cmd/onvif-quick/main.go:164` -- `cmd/onvif-quick/main.go:269` - -**Fix:** Added explicit error ignoring with explanatory comments. Errors are caught in subsequent operations. -```go -// Before -client.Initialize(ctx) - -// After -_ = client.Initialize(ctx) // Ignore initialization errors, we'll catch them on GetProfiles -``` - -### 6. Unchecked Error Return: `client.Stop` -**File:** `cmd/onvif-quick/main.go:226` -**Fix:** Added explicit error handling for PTZ stop operation. -```go -// Before -client.Stop(ctx, profileToken, true, false) - -// After -_ = client.Stop(ctx, profileToken, true, false) // Stop PTZ movement -``` - -### 7. Unused Field: `deviceEndpoint` -**File:** `client.go:21` -**Fix:** Removed the unused field from the `Client` struct. -```go -// Before -type Client struct { - deviceEndpoint string - mediaEndpoint string - ptzEndpoint string - imagingEndpoint string - eventEndpoint string -} - -// After -type Client struct { - mediaEndpoint string - ptzEndpoint string - imagingEndpoint string - eventEndpoint string -} -``` - -### 8-10. Unchecked Error Return: Deferred `Close()` calls -**Files:** -- `client_test.go:59` - `r.Body.Close()` -- `discovery/discovery.go:81` - `conn.Close()` -- `soap/soap.go:128` - `resp.Body.Close()` - -**Fix:** Wrapped deferred close calls in anonymous functions to properly handle errors. -```go -// Before -defer conn.Close() - -// After -defer func() { _ = conn.Close() }() -``` - -## Verification - -### Linting Results -```bash -$ golangci-lint run --timeout=5m -0 issues. -``` - -### Test Results -All tests continue to pass: -```bash -$ go test -v ./... -PASS -ok github.com/0x524A/go-onvif 30.008s -``` - -### Build Results -Both CLI tools build successfully: -```bash -$ make build -🔨 Building ONVIF CLI... -🔨 Building ONVIF Quick Tool... -``` - -## Best Practices Applied - -1. **Explicit Error Handling:** All error returns are now explicitly handled or documented why they're ignored -2. **Deferred Close Patterns:** Properly wrapped `Close()` calls in anonymous functions for defer statements -3. **Code Cleanliness:** Removed unused struct fields to reduce code bloat -4. **Documentation:** Added inline comments explaining why certain errors are explicitly ignored - -## Impact -- ✅ No functional changes to the library behavior -- ✅ All tests still pass -- ✅ CLI tools compile and work correctly -- ✅ Code now follows Go best practices and linting standards -- ✅ Ready for CI/CD pipelines with strict linting requirements \ No newline at end of file diff --git a/Makefile b/Makefile index 44a9122..2f5f108 100644 --- a/Makefile +++ b/Makefile @@ -96,29 +96,87 @@ examples: go build -o $(BINARY_DIR)/examples/ptz ./examples/ptz # Build for multiple platforms +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") +LDFLAGS := -ldflags "-s -w -X main.Version=$(VERSION)" + build-all: - @echo "🌍 Building for multiple platforms..." + @echo "🌍 Building for multiple platforms (version: $(VERSION))..." @mkdir -p $(BINARY_DIR) # Linux AMD64 - GOOS=linux GOARCH=amd64 go build -o $(BINARY_DIR)/onvif-cli-linux-amd64 ./cmd/onvif-cli - GOOS=linux GOARCH=amd64 go build -o $(BINARY_DIR)/onvif-quick-linux-amd64 ./cmd/onvif-quick + @echo "Building Linux AMD64..." + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-cli-linux-amd64 ./cmd/onvif-cli + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-quick-linux-amd64 ./cmd/onvif-quick + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-server-linux-amd64 ./cmd/onvif-server + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-diagnostics-linux-amd64 ./cmd/onvif-diagnostics # Linux ARM64 - GOOS=linux GOARCH=arm64 go build -o $(BINARY_DIR)/onvif-cli-linux-arm64 ./cmd/onvif-cli - GOOS=linux GOARCH=arm64 go build -o $(BINARY_DIR)/onvif-quick-linux-arm64 ./cmd/onvif-quick + @echo "Building Linux ARM64..." + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-cli-linux-arm64 ./cmd/onvif-cli + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-quick-linux-arm64 ./cmd/onvif-quick + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-server-linux-arm64 ./cmd/onvif-server + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-diagnostics-linux-arm64 ./cmd/onvif-diagnostics + + # Linux ARM (32-bit) + @echo "Building Linux ARM..." + GOOS=linux GOARCH=arm CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-cli-linux-arm ./cmd/onvif-cli + GOOS=linux GOARCH=arm CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-quick-linux-arm ./cmd/onvif-quick # Windows AMD64 - GOOS=windows GOARCH=amd64 go build -o $(BINARY_DIR)/onvif-cli-windows-amd64.exe ./cmd/onvif-cli - GOOS=windows GOARCH=amd64 go build -o $(BINARY_DIR)/onvif-quick-windows-amd64.exe ./cmd/onvif-quick + @echo "Building Windows AMD64..." + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-cli-windows-amd64.exe ./cmd/onvif-cli + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-quick-windows-amd64.exe ./cmd/onvif-quick + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-server-windows-amd64.exe ./cmd/onvif-server + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-diagnostics-windows-amd64.exe ./cmd/onvif-diagnostics - # macOS AMD64 - GOOS=darwin GOARCH=amd64 go build -o $(BINARY_DIR)/onvif-cli-darwin-amd64 ./cmd/onvif-cli - GOOS=darwin GOARCH=amd64 go build -o $(BINARY_DIR)/onvif-quick-darwin-amd64 ./cmd/onvif-quick + # Windows ARM64 + @echo "Building Windows ARM64..." + GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-cli-windows-arm64.exe ./cmd/onvif-cli + GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-quick-windows-arm64.exe ./cmd/onvif-quick + + # macOS AMD64 (Intel) + @echo "Building macOS AMD64..." + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-cli-darwin-amd64 ./cmd/onvif-cli + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-quick-darwin-amd64 ./cmd/onvif-quick + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-server-darwin-amd64 ./cmd/onvif-server + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-diagnostics-darwin-amd64 ./cmd/onvif-diagnostics # macOS ARM64 (Apple Silicon) - GOOS=darwin GOARCH=arm64 go build -o $(BINARY_DIR)/onvif-cli-darwin-arm64 ./cmd/onvif-cli - GOOS=darwin GOARCH=arm64 go build -o $(BINARY_DIR)/onvif-quick-darwin-arm64 ./cmd/onvif-quick + @echo "Building macOS ARM64..." + GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-cli-darwin-arm64 ./cmd/onvif-cli + GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-quick-darwin-arm64 ./cmd/onvif-quick + GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-server-darwin-arm64 ./cmd/onvif-server + GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(BINARY_DIR)/onvif-diagnostics-darwin-arm64 ./cmd/onvif-diagnostics + + @echo "✅ All binaries built successfully in $(BINARY_DIR)/" + @echo "" + @ls -lh $(BINARY_DIR)/ + +# Create release archives with checksums +release: build-all + @echo "📦 Creating release archives..." + @mkdir -p releases + + # Create archives for each platform + @cd $(BINARY_DIR) && \ + for os in linux darwin windows; do \ + for arch in amd64 arm64 arm; do \ + if [ "$$os" = "windows" ] && [ "$$arch" != "arm" ]; then \ + if [ -f onvif-cli-$$os-$$arch.exe ]; then \ + zip -j ../releases/go-onvif-$(VERSION)-$$os-$$arch.zip onvif-*-$$os-$$arch.exe ../README.md ../LICENSE 2>/dev/null || true; \ + fi; \ + elif [ "$$os" != "windows" ]; then \ + if [ -f onvif-cli-$$os-$$arch ]; then \ + tar czf ../releases/go-onvif-$(VERSION)-$$os-$$arch.tar.gz onvif-*-$$os-$$arch ../README.md ../LICENSE 2>/dev/null || true; \ + fi; \ + fi; \ + done; \ + done + + # Generate checksums + @cd releases && sha256sum * > checksums.txt 2>/dev/null || shasum -a 256 * > checksums.txt + @echo "✅ Release archives created in releases/" + @ls -lh releases/ # Create Docker image docker: diff --git a/QUICKSTART_SERVER.md b/QUICKSTART_SERVER.md deleted file mode 100644 index e69de29..0000000 diff --git a/SERVER_IMPLEMENTATION.md b/SERVER_IMPLEMENTATION.md deleted file mode 100644 index e69de29..0000000 diff --git a/TEST_COVERAGE_REPORT.md b/TEST_COVERAGE_REPORT.md deleted file mode 100644 index 9cb2666..0000000 --- a/TEST_COVERAGE_REPORT.md +++ /dev/null @@ -1,174 +0,0 @@ -# Unit Test Coverage Report - -## Summary -Added comprehensive unit tests to increase code coverage across the go-onvif library. - -## Coverage Improvements - -### Before -- Main package (`onvif`): 8.1% -- Discovery package: 0% -- SOAP package: 0% -- **Overall**: ~3% average - -### After -- Main package (`onvif`): **19.9%** ✅ (+11.8%) -- Discovery package: **67.2%** ✅ (+67.2%) -- SOAP package: **81.5%** ✅ (+81.5%) -- **Overall**: ~56% average (+53%) - -## Test Files Created - -### 1. `/workspaces/go-onvif/soap/soap_test.go` (297 lines) -Comprehensive tests for the SOAP client package: -- `TestNewClient` - Client creation with/without credentials -- `TestBuildEnvelope` - SOAP envelope generation -- `TestClientCall` - HTTP request handling with multiple scenarios: - - Successful request - - Unauthorized request (401) - - HTTP error status (500) -- `TestClientCallWithTimeout` - Context timeout behavior -- `TestSecurityHeaderCreation` - WS-Security header validation -- `BenchmarkNewClient` - Performance: Client creation -- `BenchmarkBuildEnvelope` - Performance: Envelope building -- `BenchmarkCall` - Performance: SOAP calls - -**Coverage**: 81.5% - -### 2. `/workspaces/go-onvif/discovery/discovery_test.go` (194 lines) -Unit tests for the WS-Discovery package: -- `TestDevice_GetName` - Device name extraction from scopes -- `TestDevice_GetDeviceEndpoint` - Endpoint extraction from XAddrs -- `TestDevice_GetLocation` - Location extraction from scopes -- `TestDiscover_WithTimeout` - Discovery with timeout -- `TestDiscover_InvalidDuration` - Edge case: zero duration -- `TestParseSpaceSeparated` - Utility function testing -- `TestDevice_GetTypes` - Device type validation -- `TestDevice_GetScopes` - Scope parsing -- `BenchmarkDeviceGetName` - Performance: Name extraction -- `BenchmarkDeviceGetDeviceEndpoint` - Performance: Endpoint extraction - -**Coverage**: 67.2% - -### 3. `/workspaces/go-onvif/device_test.go` (398 lines) -Unit tests for the main ONVIF device service: -- `TestGetDeviceInformation` - Device info retrieval (success & fault cases) -- `TestGetCapabilities` - Capabilities retrieval -- `TestGetHostname` - Hostname retrieval -- `TestSetHostname` - Hostname modification -- `TestGetDNS` - DNS configuration retrieval -- `TestGetUsers` - User account listing -- `TestCreateUsers` - User creation -- `TestDeleteUsers` - User deletion -- `TestGetNetworkInterfaces` - Network interface configuration -- `BenchmarkDeviceGetDeviceInformation` - Performance: Device info - -**Coverage**: 19.9% (main package also includes media, ptz, imaging which need additional tests) - -## Test Patterns Used - -### 1. Table-Driven Tests -```go -tests := []struct { - name string - handler http.HandlerFunc - wantErr bool -}{ - {"success case", successHandler, false}, - {"error case", errorHandler, true}, -} -``` - -### 2. Mock HTTP Servers -```go -server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - response := `...` - w.WriteHeader(http.StatusOK) - w.Write([]byte(response)) -})) -defer server.Close() -``` - -### 3. Context Testing -```go -ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) -defer cancel() -``` - -### 4. Benchmark Tests -```go -func BenchmarkOperation(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - operation() - } -} -``` - -## Next Steps (Optional) - -To achieve higher coverage (>80% overall), consider adding tests for: - -1. **Media Service** (`media.go`) - - GetProfiles - - GetStreamURI - - GetSnapshotURI - - Video encoder configuration - -2. **PTZ Service** (`ptz.go`) - - ContinuousMove - - AbsoluteMove - - RelativeMove - - Presets management - -3. **Imaging Service** (`imaging.go`) - - Imaging settings - - Video source configuration - -4. **Server Package** (`server/`) - - Server initialization - - SOAP handler - - Service endpoints - -5. **Integration Tests** - - End-to-end workflows - - Multi-service interactions - - Real camera simulation - -## Testing Commands - -```bash -# Run all tests -go test ./... - -# Run tests with coverage -go test -cover ./... - -# Generate detailed coverage report -go test -coverprofile=coverage.out ./... -go tool cover -html=coverage.out - -# Run specific package tests -go test ./soap/ -go test ./discovery/ -go test . - -# Run benchmarks -go test -bench=. ./soap/ -go test -bench=. ./discovery/ -``` - -## Impact - -✅ **Linting**: Clean (all previous linting errors fixed) -✅ **Build**: Passes -✅ **Tests**: All passing -✅ **Coverage**: Increased from ~3% to ~56% average -✅ **Quality**: Production-ready with comprehensive test coverage - -The library now has: -- Strong test coverage for core SOAP functionality -- Good coverage for device discovery -- Foundation for device service testing -- Benchmark tests for performance monitoring -- Patterns that can be extended to other services diff --git a/build-release.sh b/build-release.sh new file mode 100755 index 0000000..cfe410f --- /dev/null +++ b/build-release.sh @@ -0,0 +1,112 @@ +#!/bin/bash +# build-release.sh - Build release binaries locally + +set -e + +VERSION=${1:-$(git describe --tags --always --dirty 2>/dev/null || echo "dev")} +echo "Building release binaries for version: $VERSION" + +# Clean previous builds +rm -rf bin releases +mkdir -p bin releases + +# Platforms to build +PLATFORMS=( + "linux/amd64" + "linux/arm64" + "linux/arm" + "windows/amd64" + "windows/arm64" + "darwin/amd64" + "darwin/arm64" +) + +# Binaries to build +BINARIES=( + "onvif-cli" + "onvif-quick" + "onvif-server" + "onvif-diagnostics" +) + +LDFLAGS="-s -w -X main.Version=${VERSION} -X main.Commit=$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" + +echo "Building binaries..." +for platform in "${PLATFORMS[@]}"; do + OS="${platform%/*}" + ARCH="${platform#*/}" + + echo "" + echo "Building for $OS/$ARCH..." + + for binary in "${BINARIES[@]}"; do + OUTPUT="bin/${binary}-${OS}-${ARCH}" + + if [ "$OS" = "windows" ]; then + OUTPUT="${OUTPUT}.exe" + fi + + echo " - ${binary}" + GOOS=$OS GOARCH=$ARCH CGO_ENABLED=0 go build -ldflags="${LDFLAGS}" -o "$OUTPUT" "./cmd/${binary}" 2>/dev/null || { + echo " ⚠️ Skipped (build failed)" + continue + } + done +done + +echo "" +echo "Creating release archives..." + +cd bin + +for platform in "${PLATFORMS[@]}"; do + OS="${platform%/*}" + ARCH="${platform#*/}" + ARCHIVE_NAME="go-onvif-${VERSION}-${OS}-${ARCH}" + + # Check if any binary exists for this platform + if [ "$OS" = "windows" ]; then + FILES=(*-${OS}-${ARCH}.exe) + else + FILES=(*-${OS}-${ARCH}) + fi + + # Skip if no files found + if [ "${FILES[0]}" = "*-${OS}-${ARCH}" ] || [ "${FILES[0]}" = "*-${OS}-${ARCH}.exe" ]; then + continue + fi + + echo " Creating archive for ${OS}/${ARCH}..." + + if [ "$OS" = "windows" ]; then + # ZIP for Windows + zip -q "../releases/${ARCHIVE_NAME}.zip" *-${OS}-${ARCH}.exe ../README.md ../LICENSE + else + # tar.gz for Unix-like + tar czf "../releases/${ARCHIVE_NAME}.tar.gz" *-${OS}-${ARCH} -C .. README.md LICENSE + fi +done + +cd .. + +echo "" +echo "Generating checksums..." +cd releases +if command -v sha256sum >/dev/null 2>&1; then + sha256sum * > checksums.txt +else + shasum -a 256 * > checksums.txt +fi +cd .. + +echo "" +echo "✅ Build complete!" +echo "" +echo "Binaries in: $(pwd)/bin/" +echo "Archives in: $(pwd)/releases/" +echo "" +ls -lh releases/ + +echo "" +echo "To create a GitHub release, run:" +echo " gh release create ${VERSION} releases/* --title \"Release ${VERSION}\" --notes \"Release notes here\""