Commit Graph
34 Commits
Author SHA1 Message Date
Tobias GesellchenandClaude Opus 4.7 bb11b9d48c feat(health): add DNS interception sanity check
Queries this service's own DNS server for every intercepted
Bose hostname (api.bose.com, content.api.bose.io, etc.) and
verifies the answer is the configured service IP. Catches:

  - DNS subsystem disabled or unbound (speakers using us as
    their resolver get NXDOMAIN).
  - DNS running but answers point at a stale IP (operator
    changed the LAN address without restarting).
  - Subset of intercepts silently failing — emits the failing
    hostname list explicitly so it's obvious which patterns are
    falling through shouldIntercept.

For the mismatch case the finding includes a copyable
`nslookup … <our-dns-bind>` so operators can verify the same
behaviour from the speaker's network.

To avoid duplicating the intercept list, exports it as
`discovery.InterceptedBoseHosts` instead — same string slice
that DNSDiscovery.shouldIntercept walks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 23:20:40 +02:00
Tobias GesellchenandClaude Opus 4.7 feadc478d5 test: sweep example data in test files to RFC-5737 + placeholders
Mirrors the .md/.txt sweep across all tracked _test.go, testdata XML,
and .http integration files. Test files are self-contained (producer
+ assertion in the same file), so the matched-pair swap stays green
under `go test ./...`.

Mapping applied:
  192.168.178.[0-9]+   → 192.0.2.[same]
  192.168.1.[0-9]+     → 192.0.2.[same]
  Sound Machinechen    → Living Room SoundTouch
  A Sound Machine      → Kitchen SoundTouch
  A81B6A536A98 + case/separator variants → AABBCCDDEEFF (etc.)
  A81B6A849D99         → AABBCCDDEE01
  A81B6A849D88         → AABBCCDDEE03
  A81B6A536A09         → AABBCCDDEE04
  884AEAEEBD27         → AABBCCDDEE02
  3230304              → 1000001
  9569497              → 1000002

Two semantic fixes alongside the bulk swap:

- pkg/service/zeroconf/zeroconf_test.go: the "private 192" and
  "strips query" cases pin acceptance of RFC-1918 192.168/16. They
  must use a real 192.168 value; doc-range IPs would (correctly) be
  rejected by validateZcBaseURL. Switched to 192.168.10.10 — generic
  enough not to match any home LAN default, real enough for the
  validator. Added a comment explaining why this single test still
  carries a 192.168 literal.

- pkg/service/setup/setup_test.go: TestTestDNSRedirection mocks the
  device's `od -An -tu1` byte output, which is space-separated
  octets ("192 168 1 100"). My sed only matched the dot-separated
  form, so the mock was returning the old IP while the test
  assertions had moved to the doc range. Updated to " 192 0 2 100".

go build ./... clean. go test ./... clean (only TestDocsConsistency
remains failing, which is a pre-existing/untracked-file issue).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:05:13 +02:00
Tobias GesellchenandClaude Opus 4.7 cb071c9b1b feat(discovery): allow pinning mDNS and UPnP to a specific interface
On a multi-homed host the discovery layer used to walk net.Interfaces()
and pick the first non-loopback IPv4 NIC, while UPnP/SSDP bound a
wildcard UDP socket and let the kernel route the multicast send. That
meant --bind on soundtouch-web only moved the HTTP listener; the
discovery still went out whatever interface the kernel preferred (often
the wrong one on hosts where the speakers sit behind a secondary NIC).

Introduce a separate DiscoveryInterface knob:

  * pkg/config: DiscoveryInterface field + DISCOVERY_INTERFACE env var.
  * pkg/discovery/mdns: NewMDNSDiscoveryServiceWithInterface; the
    interface resolver now honours an explicit name and validates it
    has a usable IPv4 address before handing it to hashicorp/mdns.
  * pkg/discovery/upnp: when an interface is configured, bind the UDP
    socket's source IP to the NIC's IPv4 and call
    ipv4.PacketConn.SetMulticastInterface so M-SEARCH leaves the right
    NIC. Without an interface, behaviour is unchanged.
  * cmd/soundtouch-web: new --interface flag (DISCOVERY_INTERFACE env)
    plumbed into the config before the discovery service is built.

go.mod/go.sum reflect promoting golang.org/x/net from indirect to a
direct dependency (now imported for ipv4.PacketConn).

Refs #264.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 19:36:06 +02:00
e99c04888c feat: implement missing endpoints and serve static resources from downloads/media hosts (#199)
Endpoints:

- POST /streaming/music/musicprovider/{id}/trial/is_eligible (reuses
is_eligible handler)
- POST /bmx/tunein/v1/favorite/{stationID} with datastore persistence
(SaveTuneInFavorite)
- DELETE /bmx/tunein/v1/favorite/{stationID} (DeleteTuneInFavorite)
- POST /bmx/core02/svc-bmx-adapter-orion/prod/orion/token (anonymous
Orion token)
- GET /bmx-icons/* serving embedded static/media assets (media.bose.io)
- GET /ced/* serving embedded firmware index, release notes, and 10
app-help XMLs (downloads.bose.com)

Add media.bose.io and downloads.bose.com to DNS redirect lists (setup.go
both domain slices, dns.go shouldIntercept list, main.go getDomains
map). Document implemented endpoints in
tests/interactions_20260502_missing_external.md; mark rows 0246–0247 as
self/☑ in the interactions table.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 21:22:36 +02:00
Tobias Gesellchen 509f613e34 Make test less dependent on the environment 2026-03-29 19:14:48 +02:00
Tobias Gesellchen 6211e34050 Improve parity with upstream Bose services 2026-02-26 21:08:14 +01:00
Tobias Gesellchen b4c015ef75 Restrict UPnP timeout 2026-02-26 21:08:14 +01:00
Tobias Gesellchen a1d0213f92 refactor: reorganize device directories to use true deviceId from /info endpoint
- Replace serial number-based directory structure with deviceId from device /info
- Extract migration logic to handle transition from old to new directory structure
- Fix directory resolution bug that prevented proper migration to deviceId-based paths
- Ensure all device data (Presets.xml, Sources.xml, Recents.xml) preserved during transition
- Add configurable migration with --migration-enabled and --migration-dry-run flags
- Update DeviceInfo.xml to reflect authoritative deviceId from device's /info endpoint
- Directory structure now: /devices/{deviceId}/ instead of /devices/{serialNumber}/

This aligns the directory structure with the device's self-declared identity
and ensures data consistency with the device's /info endpoint.
2026-02-24 21:45:40 +01:00
Tobias Gesellchen 0b75a2f70d feat: implement robust MAC address to serial number mapping
Enhances device identification by adding MAC address normalization and comprehensive documentation.

- Add `MAC-ADDRESS-MAPPING.md` guide explaining device identification and troubleshooting.
- Implement `normalizeMAC` in `DataStore` to handle various MAC formats (case-insensitive, with/without separators).
- Export `EnrichDeviceInfo` in UPnP discovery to allow better integration and testing.
- Update `TROUBLESHOOTING.md` with a new section on device identification issues.
- Add comprehensive integration and diagnostic tests for MAC mapping, case sensitivity, and UPnP discovery.
- Update documentation structure (`README.md`, `SUMMARY.md`) to include the new mapping guide.
2026-02-24 21:45:40 +01:00
Tobias Gesellchen be762dbc22 test(discovery): optimize discovery tests for faster execution
Reduces `pkg/discovery` test suite runtime by ~75% (from ~17s to ~4s) by eliminating unnecessary network timeouts and reducing wait intervals.

- Refactor `discovery.Service` to use an injectable `http.Client`, allowing UPnP enrichment tests to use `httptest.Server` instead of waiting for 5s network timeouts.
- Make `DNSDiscovery` forward timeout configurable and reduce it from 2s to 100ms in unit tests.
- Decrease discovery and context timeouts in mDNS and Unified discovery tests to the minimum required for stable verification (typically 100-200ms).
2026-02-22 23:40:51 +01:00
Tobias Gesellchen 403e2275dc fix(datastore): resolve local data directory using MAC address mapping
Fixes an issue where device data (e.g., Presets.xml) could not be located when accessed via MAC address because the internal directory structure is organized by serial number.

- Add a `macToSerial` mapping in `DataStore` to bridge MAC addresses from API requests to internal serial-numbered directories.
- Implement automatic mapping population during `DataStore` initialization by scanning `DeviceInfo.xml` files.
- Update `AccountDeviceDir` to transparently resolve MAC addresses to serial numbers for file path construction.
- Enhance UPnP discovery to capture the MAC address (as `serialNumber` in the device description) for better device identification.
- Include automated tests for MAC-to-serial resolution and UPnP enrichment.
2026-02-22 23:40:51 +01:00
Tobias Gesellchen f50ee1131e Fix migration check 2026-02-22 18:58:20 +01:00
Tobias Gesellchen 6a65376784 Attempt resolution if it's not a numeric IP 2026-02-22 14:17:01 +01:00
Tobias Gesellchen 0f802e65c6 Fallback to the system's dns resolver by default 2026-02-22 13:36:58 +01:00
Tobias Gesellchen 743ff5e061 Add streamingoauth.bose.com to the intercepted DNS records 2026-02-21 11:22:46 +01:00
Tobias Gesellchen 72d75133c4 Capture server references before releasing mutex to avoid race condition 2026-02-19 08:44:26 +01:00
Tobias Gesellchen e4c12471b4 Add more upstream domains to the intercept list 2026-02-19 08:44:26 +01:00
Tobias Gesellchen 523ff0eb17 Fix deadlock in settings update and add efficient DNS settings validation 2026-02-16 21:02:42 +01:00
Tobias Gesellchen 025e15d65c Implement log throttling, loop prevention, and empty upstream handling in DNS discovery server 2026-02-16 21:02:42 +01:00
Tobias Gesellchen ab2bf0731a Add DNS-based discovery and migration via /etc/resolv.conf 2026-02-16 12:18:06 +01:00
Tobias Gesellchen c5a3911104 Fix SSDP discovery and enhance device discovery consistency
Major improvements to device discovery system:

🔧 **SSDP Discovery Fixed**:
- Fixed networking issue where SSDP used connected UDP socket instead of UDP listener
- SSDP now properly receives unicast responses from multicast requests
- UPnP discovery now works reliably and finds all MediaRenderer devices

 **Enhanced DiscoveredDevice Model**:
- Added consistent URL fields (APIBaseURL, InfoURL) for all discovery methods
- Added protocol-specific fields (UPnPLocation, UPnPUSN, MDNSHostname, etc.)
- Added DiscoveryMethod tracking to show how devices were found
- Added device merging support for same device found via multiple protocols

🚀 **Unified Discovery Improvements**:
- Fixed device merging logic to properly combine protocol-specific data
- Discovery methods now correctly show combinations like 'Configuration+SSDP/UPnP+mDNS/Bonjour'
- Removed duplicate configuration device loading in individual services
- All three discovery methods (SSDP, mDNS, Configuration) work together seamlessly

🛠 **Updated Tools & Examples**:
- Updated soundtouch-cli to display new consistent field structure
- Enhanced all example programs with better device information display
- Added new unified discovery example demonstrating all three methods
- Fixed context timeout issues in example programs

📋 **Comprehensive Testing**:
- All tests updated and passing
- Real-world validation with actual Bose SoundTouch devices
- Confirmed discovery methods properly merge device data

Every discovered device now has consistent http://host:port/info URLs regardless
of discovery method, while preserving valuable protocol-specific metadata.
2026-01-10 23:01:22 +01:00
Tobias Gesellchen ad43cdaf88 Fix mDNS discovery IPv6 issues and improve timeout handling
- Force IPv4-only mDNS queries with DisableIPv6=true to avoid routing issues
- Add automatic IPv4 interface selection for better compatibility
- Filter mDNS results to only include SoundTouch devices
- Clean up device names by unescaping mDNS characters
- Fix timeout flag handling to respect DISCOVERY_TIMEOUT from .env file
- Only override discovery timeout when --timeout flag is explicitly provided
- Add file operations safety guidelines to docs/CLAUDE.md
- Remove duplicate timeout flags from discover command, use global flags

Fixes IPv6 'no route to host' errors that prevented mDNS discovery.
Now discovers same devices as native dns-sd and dig tools.
2026-01-10 21:56:19 +01:00
Tobias Gesellchen fc9decedd7 fix: make examples non-testable to prevent network operations during tests
- Change '// Output:' to '// Example output:' in all examples
- Examples will still appear in pkg.go.dev documentation
- Prevents examples from running as tests and trying to connect to real devices
- Examples are for documentation purposes, not runtime testing
2026-01-10 12:21:19 +01:00
Tobias Gesellchen 29cbcf48b9 fix: correct API method names and field references in examples
- Fix GetInfo() to GetDeviceInfo() in client examples
- Update discovery examples to use proper constructor patterns
- Fix Volume.Muted to Volume.MuteEnabled field reference
- Correct DiscoveredDevice field names (remove non-existent MACAddress)
- Fix ZoneMember to use IP field instead of IPAddress
- Update Presets examples to use Preset slice and proper methods
- Replace non-existent SubscribeToEvents with NewWebSocketClient pattern
- Fix Capabilities to use Capability field instead of Sources
- Remove duplicate example function names
- Ensure all examples compile and use correct API surface
2026-01-10 12:17:39 +01:00
Tobias Gesellchen 2a9f219d40 docs: enhance pkg.go.dev documentation with comprehensive examples
- Add root package documentation with quick start guide and feature overview
- Enhance client package with detailed usage examples and API coverage
- Add comprehensive discovery package documentation with protocol explanations
- Create models package documentation explaining all data structures
- Add extensive example functions for all major use cases:
  * Basic device control and playback
  * Volume, bass, and balance management
  * Source selection and preset handling
  * Multiroom zone management
  * Real-time WebSocket event monitoring
  * Device discovery with UPnP and mDNS
  * Error handling and context cancellation
- Include code examples for pkg.go.dev's example rendering
- Document API endpoints, data structures, and best practices
- Add hardware compatibility and implementation notes
2026-01-10 12:03:29 +01:00
Tobias Gesellchen ccd19d97a5 docs: add attribution to official Bose SoundTouch Web API documentation
- Reference original API documentation source from Bose Corporation
- Link to official Bose SoundTouch End-of-Life page
- Clarify this is an independent implementation
- Add disclaimer about non-affiliation with Bose Corporation
- Provide both online and local documentation references
2026-01-10 00:34:54 +01:00
Tobias Gesellchen 62a67818d2 Fix golangci-lint issues: resolve range copy, naming, and whitespace violations
- Fix range copy issues in cmd_network.go (use indexing instead of copying 168-byte structs)
- Rename DiscoveryService to Service to avoid package name stuttering
- Update all references to use new Service constructor names
- Apply automatic whitespace fixes using golangci-lint --fix
- Reduce linting issues from 32 to 7 (only cyclomatic complexity remains)

Remaining issues are architectural complexity violations that require manual refactoring.
2026-01-10 00:02:59 +01:00
Tobias Gesellchen 1729fa5d35 fix: resolve all staticcheck issues (SA5011)
- Fixed nil pointer dereference warnings by using t.Fatal instead of t.Error
- In unified_test.go: Changed service nil check to use t.Fatal
- In clockdisplay_test.go: Changed request nil check to use t.Fatal

Using t.Fatal ensures test execution stops if pointer is nil,
eliminating possibility of subsequent nil pointer dereference.

Progress: Eliminated all 5 staticcheck issues
Total issues: 21 → 16 (24% improvement)

Remaining:
- gocyclo: 14 (complexity - requires refactoring)
- revive: 1 (DiscoveryService naming)
- unparam: 1 (client.post result parameter)
2026-01-09 23:21:33 +01:00
Tobias Gesellchen 6f27a559e4 fix: auto-resolve whitespace and formatting issues using golangci-lint --fix
- Used 'golangci-lint run --fix' to automatically resolve formatting issues
- Fixed all 52 remaining wsl_v5 (whitespace) issues automatically
- Applied go fmt to ensure consistent formatting across codebase
- Touched 49 files with automatic formatting improvements

MAJOR PROGRESS: Reduced total issues from 79 to 27 (66% reduction!)
Remaining issues:
- gocyclo: 14 (complexity - requires manual refactoring)
- revive: 5 (style/naming)
- staticcheck: 5 (static analysis)
- unparam: 3 (unused parameters)
2026-01-09 23:12:08 +01:00
Tobias Gesellchen 089bba48b4 fix: resolve more whitespace (wsl_v5) issues - part 2
- Fix whitespace issues in test files (bass_test.go, client_test.go, source_selection_test.go)
- Fix whitespace issues in discovery/mdns.go
- Fix whitespace issues in config/config_test.go
- Fix whitespace issues in soundtouch-cli main.go ranges and loops
- Fix whitespace issues in models/websocket_test.go

Progress: Reduced wsl_v5 issues from 50 to 45 (10% improvement)
Total remaining: 79 issues (down from 84)
2026-01-09 23:11:25 +01:00
Tobias Gesellchen 9ed8182278 fix: resolve golangci-lint issues
- Fix all errcheck issues by properly checking error return values
- Fix gocritic exitAfterDefer issues by replacing log.Fatalf with return statements
- Fix rangeValCopy issues by using index-based iteration for large structs
- Add missing package comments for all packages
- Fix unused parameter issues by renaming to underscore
- Fix empty block issues by adding explicit error handling
- Add documentation for exported methods and constants
- Fix shadow variable issues
- Replace deprecated strings.Title with manual implementation
- Fix defer function error handling

Reduced lint issues from 108 to 84 (22% improvement)
All critical error handling and code quality issues resolved
2026-01-09 22:59:21 +01:00
Tobias Gesellchen a8d9ab99c4 Fix linting issues and test failures
- Fix bodyclose issues by properly closing WebSocket response body
- Fix errcheck issues by checking errors on resp.Body.Close(), conn.Close(), etc.
- Fix errorlint issue by using errors.As() instead of type assertion
- Fix nilerr issue by adding proper logging for UPnP discovery failures
- Fix gocritic issues:
  - Convert if-else chains to switch statements
  - Fix parameter type combining (paramTypeCombine)
  - Fix range value copying (rangeValCopy)
  - Fix exitAfterDefer by calling cancel() before log.Fatalf()
- Add package comments to fix revive package-comments issues
- Rename ClientConfig to Config to avoid type name stuttering
- Add missing exported constant comments
- Fix unused parameter issues by renaming to _
- Fix empty block issues
- Add t.Helper() to test helper functions
- Update User-Agent and fix GetNetworkSummary behavior to match test expectations

Reduces linting issues from 151 to 108 (28% improvement).
All tests now pass.
2026-01-09 13:52:57 +01:00
Tobias Gesellchen f4c71eaa53 Add comprehensive mDNS/Bonjour discovery with unified service and diagnostic tools
Features Added:
• mDNS/Bonjour discovery using hashicorp/mdns library
• Unified discovery service combining UPnP + mDNS + configuration
• Parallel discovery execution for optimal performance
• Comprehensive logging for both UPnP and mDNS discovery
• Network diagnostic tools for troubleshooting

New Discovery Methods:
• Configuration-based (fastest, most reliable)
• UPnP/SSDP discovery (widely supported, enhanced logging)
• mDNS/Bonjour discovery (Apple ecosystem friendly)

New Programs & Tools:
• cmd/example-mdns - Standalone mDNS discovery testing
• cmd/example-upnp - Isolated UPnP/SSDP discovery testing
• cmd/mdns-scanner - Network diagnostic tool for mDNS services

Enhanced Build System:
• make dev-mdns / dev-mdns-verbose (mDNS testing)
• make dev-upnp / dev-upnp-verbose (UPnP testing)
• make dev-scan-all (scan all network services)
• make dev-scan-soundtouch (scan for SoundTouch services)

Documentation:
• docs/DISCOVERY.md - Comprehensive discovery guide
• Updated README.md with new features and commands
• Full API documentation and troubleshooting guide

Technical Improvements:
• Detailed request/response logging for UPnP M-SEARCH
• Step-by-step mDNS service discovery tracking
• IP address resolution with IPv4/IPv6 handling
• Service name parsing and device info extraction
• Robust error handling and network diagnostics

Backward Compatibility:
• No breaking changes to existing APIs
• All existing tests pass
• CLI interface unchanged but enhanced
• Legacy UPnP-only service still available
2026-01-09 08:49:33 +01:00
Tobias Gesellchen 7de6b8246b Initial commit: Bose SoundTouch API Client PoC
- Implement HTTP client with XML support for SoundTouch Web API
- Add UPnP device discovery with SSDP protocol
- Create type-safe Go models for API responses
- Build CLI tool with device discovery and info commands
- Add comprehensive configuration management via .env and env vars
- Include extensive documentation (API endpoints, patterns, development guide)
- Translate all German documentation to English
- Set up modern Go project structure with testing framework
- Add Makefile for cross-platform builds and development workflow

Features:
 Device discovery (UPnP + manual configuration)
 Device information retrieval
 XML request/response handling
 CLI interface with flexible device targeting
 Cross-platform compatibility
 Comprehensive test coverage with mock data
 Production-ready configuration management
2026-01-08 23:01:32 +01:00