Fixes 3 Dialyzer warnings about impossible comparisons when checking for NaN
and infinity values in sensor validation.
Problem:
Dialyzer correctly identified that comparing a float with atoms (:nan,
:infinity, :neg_infinity) using == can never evaluate to true. The previous
code attempted to validate sensor values by comparing with these atoms, which
is incorrect in Erlang/Elixir.
Solution:
Use proper float comparison techniques:
- NaN detection: value != value (NaN is the only value that doesn't equal itself)
- Infinity detection: value > 1.0e308 or value < -1.0e308 (beyond max float range)
This approach correctly identifies special float values without impossible
comparisons that Dialyzer flags.
Dialyzer results:
- Before: Total errors: 509, Skipped: 506
- After: Total errors: 506, Skipped: 506
- Status: done (passed successfully), EXIT CODE: 0
All 3434 tests passing. Validation logic now correctly identifies NaN and
infinity values while satisfying Dialyzer type analysis.
Adds @spec annotations to all Oban worker perform/1 functions to improve
static analysis and documentation. Return types are specific to each worker's
actual behavior:
Workers returning 🆗
- BackupSummaryWorker
- BackupTimeoutWorker
- DeviceMonitorWorker
- DevicePollerWorker
- JobHealthCheckWorker
- NeighborCleanupWorker
Workers with complex returns:
- DiscoveryWorker: :ok | :discard
- FirmwareVersionFetcherWorker: :ok | {:error, term()}
- LoginHistoryCleanupWorker: {:ok, %{deleted: non_neg_integer(), anonymized_deleted: non_neg_integer()}}
- MikrotikBackupWorker: :ok | {:error, String.t()}
- SessionCleanupWorker: {:ok, %{sessions_deleted: non_neg_integer()}}
Analysis found that none of these workers have complex job parameters that
would benefit from embedded schemas - all use either no parameters or simple
device_id strings passed in job args.
StaleAgentWorker already had a typespec on find_stale_agents/0 from previous work.
All 98 worker tests passing after changes.
Continues the pattern of using custom Ecto types to create rich domain objects
and avoid primitive obsession. Adds two new custom types following the
established pattern from IPAddress.
## MacAddress Custom Type
Provides structured MAC address handling with:
- Multiple input format support (colon, hyphen, dot-separated, compact)
- Normalization to colon-separated lowercase (canonical format)
- SNMP binary format conversion (6-byte binary)
- Format conversion helpers (:colon, :hyphen, :dot, :compact)
- Comprehensive validation and error handling
### Supported Formats
- Colon: `00:11:22:33:44:55`
- Hyphen: `00-11-22-33-44-55`
- Dot (Cisco): `0011.2233.4455`
- Compact: `001122334455`
- SNMP binary: `<<0, 17, 34, 51, 68, 85>>`
All formats normalize to lowercase colon-separated format for consistency.
## SnmpOid Custom Type
Provides structured SNMP OID handling with:
- OID format validation (numeric and named)
- Normalization to dotted-decimal with leading dot
- OID resolution via SnmpKit (named → numeric)
- Helper functions (parent, child_of?, append)
- Integer list representation support
### Supported Formats
- Dotted-decimal: `.1.3.6.1.2.1.1.1.0`
- Numeric (no leading dot): `1.3.6.1.2.1.1.1.0`
- Named OID: `sysDescr.0` (with automatic resolution)
- Integer list: `[1, 3, 6, 1, 2, 1, 1, 1, 0]`
## Benefits
- **Type Safety**: Structured data with validation at cast time
- **Normalization**: Consistent storage format regardless of input
- **Rich API**: Helper functions for common operations
- **Zero Migration Cost**: No database changes needed
- **Better Errors**: Clear validation errors instead of DB constraint violations
- **Domain Modeling**: Code works with %MacAddress{} and %SnmpOid{} instead of strings
## Database Storage
Both types store as VARCHAR in the database:
- MacAddress: VARCHAR(17) - colon-separated format
- SnmpOid: VARCHAR(255) - dotted-decimal format with leading dot
## Test Coverage
Added comprehensive test suites:
- MacAddress: 47 tests covering all formats, roundtrips, edge cases
- SnmpOid: 49 tests covering OID operations, validation, helpers
- All 96 tests passing ✅
## Usage Example
```elixir
# Before (primitive obsession)
field :mac_address, :string
field :sensor_oid, :string
# After (rich domain types)
field :mac_address, Towerops.EctoTypes.MacAddress
field :sensor_oid, Towerops.EctoTypes.SnmpOid
# Usage
changeset
|> cast(attrs, [:mac_address, :sensor_oid])
# Validation happens automatically in cast/1
```
Files:
- lib/towerops/ecto_types/mac_address.ex (330 lines)
- lib/towerops/ecto_types/snmp_oid.ex (340 lines)
- test/towerops/ecto_types/mac_address_test.exs (380 lines)
- test/towerops/ecto_types/snmp_oid_test.exs (390 lines)
Adds @spec annotations to all public functions in agent-related modules for
better static analysis and documentation.
Changes:
- Added 17 typespecs to Towerops.Agents context module
- All CRUD operations (create, read, update, delete, revoke)
- Assignment management functions
- Agent token resolution and lookup functions
- PubSub broadcast function
- Added 9 typespecs to Towerops.Agents.Stats module
- Agent health and metrics statistics
- Device assignment breakdowns
- Latency analysis and reassignment candidate detection
- Added typespec to Towerops.Workers.StaleAgentWorker
- find_stale_agents/0 function
- Preserved existing typespecs in AgentChannel (socket types)
Benefits:
- Improved code documentation with clear function signatures
- Better static analysis via dialyzer
- Clearer API contracts for all agent management functions
- Type-safe UUID handling throughout agent system
Implements comprehensive validation layer on top of protobuf schema to
prevent malformed data, DoS attacks, and business logic violations.
Changes:
- Created Towerops.Agent.Validator module with strict validation for all
agent message types (AgentHeartbeat, MetricBatch, SnmpResult, AgentError,
CredentialTestResult, MikrotikResult)
- Added validation limits to prevent memory exhaustion and DoS attacks
(max string lengths, collection sizes, numeric ranges)
- Added format validation for UUIDs, IP addresses, versions, hostnames
- Added enum validation for status and protocol fields
- Modified AgentChannel handlers to validate all incoming messages before
processing
- Added comprehensive typespecs to AgentChannel for better static analysis
- Created safe_base64_decode/1 helper with error handling
- Added 32 comprehensive tests covering valid cases, invalid inputs, and
edge cases
Security improvements:
- Prevents memory exhaustion via oversized strings and collections
- Validates all numeric fields are within expected ranges
- Checks UUID format for all ID fields
- Validates IP addresses and hostnames against RFC standards
- Rejects future timestamps and excessive values
Backward compatibility:
- Empty version and hostname strings allowed for older agents
Improve coverage from 37.97% to 40.64% with tests for:
- PDU types in SNMPv3 messages (get_next, get_response, set, getbulk)
- Context engine ID and context name (empty, non-empty, both)
- Message max size variations (minimum 484, custom values)
- Multiple varbinds in v3 messages
- Error responses with various error_status values (0-5)
- Reportable flag variations (true/false)
- USM security model
- All SNMP value types (counter64, gauge32, ip_address, opaque, oid)
- Exception values (no_such_object, no_such_instance, end_of_mib_view)
- Message ID edge cases (0, max 2147483647)
- Discovery message consistency
Tests verify message encoding completes without crashes
even when V3 security processing is involved.
Coverage: 1,391 → 1,419 tests, all passing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added complete test coverage for MIB.Error module (16.13% -> target 100%):
**Error Creation:**
- All supported error types (syntax, semantic, import, type, constraint, etc.)
- Error creation with line and column information
- Error creation with context maps
- Suggestion generation for common mistakes
**Error Types Tested:**
- unexpected_token with various combinations (expected/actual/value)
- unexpected_eof with expected values
- unterminated_string
- invalid_number and invalid_identifier
- file_not_found with path
- import_error with symbol and module
- duplicate_definition with name
- Generic error types
**Suggestion Generation:**
- MAX-ACCESS vs ACCESS typo
- deprecated 'mandatory' vs 'current'
- OBJECT-TYPE vs OBJECT-IDENTITY confusion
- Unterminated string hints
- File not found with RFC/MIB name detection
- Import error dependency hints
**Error Formatting:**
- Format with/without line and column
- Format with/without suggestions
- Multiple suggestion formatting
- Edge cases (nil values, empty strings, non-standard types)
**Edge Cases:**
- All nil fields
- Large line numbers
- Zero/negative line/column values
- Various context data types
- Mixed token value types
- Empty options handling
53 new tests added
Test count: 1,211 -> 1,264 (all passing)
Added comprehensive test coverage for previously untested functionality:
- IP address parsing (valid IPs, invalid inputs, IPv6 rejection)
- Zero-filling behavior for incomplete IPs (Erlang :inet standard)
- Type inference for all SNMP types (integers, strings, lists, booleans, nulls)
- Value decoding for all SNMP types (strings, integers, IPs, OIDs, exceptions)
- Value encoding with type inference and validation controls
- Unsigned32 validation (valid ranges, overflow, underflow)
- Coercion edge cases (IP addresses, unsigned32, octet_string, OIDs, booleans)
Fixes:
- Corrected type inference expectation for large integers (returns :unsigned32)
- Fixed boolean assertion to use equality check instead of pattern match
- Documented :inet.parse_address zero-fill behavior for incomplete IPs
Total: 97 tests, all passing
Fixed 2 failing tests in HostParser that were using unreliable hostname resolution. Changed tests to use malformed IP addresses that will always fail validation consistently.
Added tests for SnmpKit.SNMP bang methods (get_bulk!, bulk_walk!) which have actual implementation logic beyond delegation.
All 954 SNMPkit tests now pass (0 failures).
- Remove unused Ecto.Query import from device_poller_worker_test
- Update route paths from /orgs/:slug to /dashboard after navigation refactor
- Fix HostParser tests to match actual hostname resolution behavior
- Update UserAuth redirect assertions to expect /dashboard instead of /devices
- Fix DashboardLiveTest missing organization context
- Fix OrgLive.NewTest to verify organization creation correctly
All 4850 tests now passing with zero failures and zero warnings.
Changes:
- Fix traffic graph to handle nil interface octets gracefully
- Add comprehensive tests for nil octets scenarios
- Fix agent polling query to support devices assigned directly to org (left join on sites)
- Fix test SNMP connection to inherit credentials from site/org
- Refactor agent_channel process_polling_result to reduce nesting (Credo)
- Add alias for JobCleanupTask in application.ex (Credo)
This fixes crashes when interface stats have nil octets and ensures
devices without a site assignment can be polled by agents.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added Logger.require for debug logging
- Refactored fetch_latest_agent_data to reduce nesting depth
- Added debug logging to trace agent assignment detection
- Check for nil/stale sensor readings and log appropriately
- Use actual reading timestamp instead of current time for data points
This will help diagnose why live polling shows 0 values and why
Phoenix SNMP disabled messages appear for agent-assigned devices.
Modified GraphLive live polling to check for agent assignments and use
agent-collected sensor data from the database instead of doing direct
SNMP polling. This respects the agent assignment and prevents Phoenix
from bypassing the agent for live graph updates.
Also refactored handle_params to reduce nesting depth.
Devices can belong directly to organizations without sites. Fixed
perform_live_poll to check device.organization_id instead of
device.site.organization_id which caused BadMapError when site is nil.
Also removed unused Repo alias.
Discovery is now working successfully with SNMPv3 after increasing
agent timeout to 60s. Removing the temporary debug logging that was
added to verify credentials were being properly decrypted and sent
to the agent.
Added logging to show what SNMPv3 parameters are being sent from Phoenix
to the agent, including password lengths to verify they're decrypted
properly.
Added JobCleanupTask that runs on production startup to cancel all
existing polling jobs and reschedule them with the latest worker code.
This ensures:
- No stale jobs run with old code after deployment (e.g., missing SNMPv3 credentials)
- All devices get fresh polling jobs with current logic
- Production deployments are seamless without manual intervention
The task:
- Only runs in production environment (skipped in dev/test)
- Runs asynchronously after supervisor initialization
- Cancels all DevicePollerWorker and DeviceMonitorWorker jobs
- Reschedules polling for all SNMP-enabled devices
- Is idempotent and safe to run multiple times
Removed debug logging from Client and DevicePollerWorker as SNMPv3
implementation is now verified and working correctly.
Added logging to show what options are being built in DevicePollerWorker,
including whether security_name (v3) or community (v2c) credentials are
being included.
The DevicePollerWorker was only passing SNMPv2c credentials (community
string) to the SNMP client, causing KeyError when polling SNMPv3 devices.
Changes:
- Updated build_client_opts in device_poller_worker.ex to detect version "3"
- Added same SNMPv3 credential handling as discovery.ex
- Includes: security_name, security_level, auth_protocol, auth_password,
priv_protocol, priv_password
This completes the SNMPv3 implementation - both discovery and polling now
support v3 authentication alongside the existing v2c support.
The discovery process was only passing SNMPv2c credentials (community
string) to the SNMP client, causing SNMPv3 devices to fail discovery
even when v3 credentials were configured.
Changes:
- Updated build_client_opts in discovery.ex to include SNMPv3 credentials
(security_name, security_level, auth_protocol, auth_password,
priv_protocol, priv_password) when version is "3"
- Updated build_snmp_opts in client.ex to pass v3 credentials to SnmpKit
- Added parse functions for security_level, auth_protocol, and priv_protocol
- Updated connection_opts typespec to include v3 parameters
- Fixed parameter names to match SnmpKit expectations (security_name vs sec_name)
This enables full SNMPv3 discovery on devices like MikroTik routers that
use v3 authentication, allowing them to discover the same sensors and
data that SNMPv2c would show.
Fixes BadMapError when loading device show page for devices without
an assigned site. Changed to use device.organization_id directly
instead of device.site.organization_id since site can be nil but
organization is always present.
Extract common pattern from perform_site_reorder/4 and perform_device_reorder/4
into a single perform_reorder/4 helper function.
Both reorder handlers now delegate to the shared helper, reducing duplication
and making the reload/regroup/flash pattern consistent.
- Reduces code from 40 lines to 28 lines
- Maintains identical behavior (all tests pass)
- Uses static gettext strings to satisfy compile-time requirements
Remove duplicate device counting logic by consolidating three identical
implementations into a single reusable function.
**Changes:**
- Refactor agent_live/index.ex mount/3 to use calculate_device_counts/1
- Delete duplicate calculate_cloud_poller_counts/1 function
- Replace all usages with calculate_device_counts/1
**Before:**
- Lines 31-47: Inline counting for agent_tokens and cloud_pollers
- Lines 312-318: calculate_device_counts/1
- Lines 320-326: calculate_cloud_poller_counts/1 (identical logic)
**After:**
- Single calculate_device_counts/1 function used everywhere
- ~20 lines removed
**Benefits:**
- Eliminates triple duplication of counting logic
- Single source of truth for device counting
- Easier to maintain and test
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extract duplicate access control logic from multiple LiveView files into
a reusable AccessControl helper module.
**Changes:**
- NEW: lib/towerops_web/live/helpers/access_control.ex
- NEW: test/towerops_web/live/helpers/access_control_test.exs
- Refactor device_live/index.ex to use AccessControl.verify_site_access/2
and verify_device_access/2
- Refactor device_live/form.ex to use AccessControl.verify_device_access/2
- Refactor alert_live/index.ex to use AccessControl.verify_alert_access/2
- Refactor device_live/show.ex to use AccessControl.verify_device_access/2
- Remove unused Repo aliases from refactored files
**Benefits:**
- Reduces code duplication across 7+ locations
- Centralizes security-critical access checks
- Improves testability (helper module has >90% coverage)
- Consistent error handling across all LiveViews
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>