From 8fde42dadb01bf8a1401345a7faaf92bfa848eb9 Mon Sep 17 00:00:00 2001 From: Austin Chang Date: Fri, 6 Feb 2026 14:52:47 +0800 Subject: [PATCH 1/2] fix: Migrate missing fixes from dev-1.2.7 NOW-713: Update OpenDNS Family Shield IPs - Changed from 208.67.222.123/208.67.220.123 to 208.67.222.222/208.67.220.220 - Updated service, test data, and test assertions QUALITY-439: Allow /31 subnet masks for WAN Static IP - Added max: 31 parameter to SubnetMaskValidator in: - internet_settings_form_validator.dart - pnp_static_ip_view.dart Co-Authored-By: Claude Opus 4.5 --- .../utils/internet_settings_form_validator.dart | 3 ++- lib/page/instant_safety/services/instant_safety_service.dart | 5 +++-- .../views/isp_settings/pnp_static_ip_view.dart | 3 ++- test/mocks/test_data/instant_safety_test_data.dart | 5 +++-- .../instant_safety/services/instant_safety_service_test.dart | 5 +++-- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/page/advanced_settings/internet_settings/utils/internet_settings_form_validator.dart b/lib/page/advanced_settings/internet_settings/utils/internet_settings_form_validator.dart index 282c68af3..fba7d4afb 100644 --- a/lib/page/advanced_settings/internet_settings/utils/internet_settings_form_validator.dart +++ b/lib/page/advanced_settings/internet_settings/utils/internet_settings_form_validator.dart @@ -43,7 +43,8 @@ class InternetSettingsFormValidator { if (value == null || value.isEmpty) { return ValidationError.invalidSubnetMask; } - final subnetMaskValidator = SubnetMaskValidator(); + // QUALITY-439: Override default max (30) to allow /31 subnet masks for WAN Static IP + final subnetMaskValidator = SubnetMaskValidator(max: 31); if (subnetMaskValidator.validate(value)) { return null; } else { diff --git a/lib/page/instant_safety/services/instant_safety_service.dart b/lib/page/instant_safety/services/instant_safety_service.dart index dc7ac8fcf..8bfed5654 100644 --- a/lib/page/instant_safety/services/instant_safety_service.dart +++ b/lib/page/instant_safety/services/instant_safety_service.dart @@ -49,8 +49,9 @@ class InstantSafetyService { // DNS Configuration Constants static const _fortinetDns1 = '208.91.114.155'; - static const _openDnsDns1 = '208.67.222.123'; - static const _openDnsDns2 = '208.67.220.123'; + // NOW-713: Updated OpenDNS Family Shield IPs + static const _openDnsDns1 = '208.67.222.222'; + static const _openDnsDns2 = '208.67.220.220'; /// Fetches current safe browsing configuration from router. /// diff --git a/lib/page/instant_setup/troubleshooter/views/isp_settings/pnp_static_ip_view.dart b/lib/page/instant_setup/troubleshooter/views/isp_settings/pnp_static_ip_view.dart index aa9302d43..a6cde6ae8 100644 --- a/lib/page/instant_setup/troubleshooter/views/isp_settings/pnp_static_ip_view.dart +++ b/lib/page/instant_setup/troubleshooter/views/isp_settings/pnp_static_ip_view.dart @@ -34,7 +34,8 @@ class _PnpStaticIpViewState extends ConsumerState { var _hasExtraDNS = false; bool _isLoading = false; - final subnetMaskValidator = SubnetMaskValidator(); + // QUALITY-439: Override default max (30) to allow /31 subnet masks for WAN Static IP + final subnetMaskValidator = SubnetMaskValidator(max: 31); final ipAddressValidator = IpAddressValidator(); final requiredIpAddressValidator = IpAddressRequiredValidator(); String? _ipError; diff --git a/test/mocks/test_data/instant_safety_test_data.dart b/test/mocks/test_data/instant_safety_test_data.dart index d340436f5..f3e248110 100644 --- a/test/mocks/test_data/instant_safety_test_data.dart +++ b/test/mocks/test_data/instant_safety_test_data.dart @@ -7,8 +7,9 @@ import 'package:privacy_gui/core/jnap/result/jnap_result.dart'; class InstantSafetyTestData { // DNS Configuration Constants (matching service) static const fortinetDns1 = '208.91.114.155'; - static const openDnsDns1 = '208.67.222.123'; - static const openDnsDns2 = '208.67.220.123'; + // NOW-713: Updated OpenDNS Family Shield IPs + static const openDnsDns1 = '208.67.222.222'; + static const openDnsDns2 = '208.67.220.220'; /// Create default LAN settings response with no safe browsing configured static JNAPSuccess createLANSettingsSuccess({ diff --git a/test/page/instant_safety/services/instant_safety_service_test.dart b/test/page/instant_safety/services/instant_safety_service_test.dart index 0a7fc28cf..400416ab0 100644 --- a/test/page/instant_safety/services/instant_safety_service_test.dart +++ b/test/page/instant_safety/services/instant_safety_service_test.dart @@ -224,8 +224,9 @@ void main() { final data = captured.first as Map; final dhcpSettings = data['dhcpSettings'] as Map; - expect(dhcpSettings['dnsServer1'], '208.67.222.123'); - expect(dhcpSettings['dnsServer2'], '208.67.220.123'); + // NOW-713: Updated OpenDNS Family Shield IPs + expect(dhcpSettings['dnsServer1'], '208.67.222.222'); + expect(dhcpSettings['dnsServer2'], '208.67.220.220'); }); test('with off clears DNS servers', () async { From 899a8b2ed6058c7e4f642538fe67dcb970b9eb91 Mon Sep 17 00:00:00 2001 From: Austin Chang Date: Fri, 6 Feb 2026 15:14:26 +0800 Subject: [PATCH 2/2] fix: Address Qodo review feedback - Add /31 subnet mask tests for QUALITY-439 - Test validates /31 mask (255.255.255.254) accepted with max: 31 - Test validates /31 mask rejected with default max (30) - Update spec documents with new OpenDNS IPs (NOW-713) - spec.md: Update assumptions section - contracts/instant_safety_service_contract.md: Update DNS constants and examples Co-Authored-By: Claude Opus 4.5 --- .../contracts/instant_safety_service_contract.md | 10 +++++----- specs/009-instant-safety-service/spec.md | 2 +- test/validator_rules/input_validators_test.dart | 13 +++++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/specs/009-instant-safety-service/contracts/instant_safety_service_contract.md b/specs/009-instant-safety-service/contracts/instant_safety_service_contract.md index 6f2463dc2..14b871fb2 100644 --- a/specs/009-instant-safety-service/contracts/instant_safety_service_contract.md +++ b/specs/009-instant-safety-service/contracts/instant_safety_service_contract.md @@ -125,7 +125,7 @@ Future saveSettings(InstantSafetyType safeBrowsingType) async 1. Validates cached LAN settings exist (throws `InvalidInputError` if not) 2. Constructs DHCP settings with appropriate DNS servers based on type: - `fortinet`: DNS1=208.91.114.155 - - `openDNS`: DNS1=208.67.222.123, DNS2=208.67.220.123 + - `openDNS`: DNS1=208.67.222.222, DNS2=208.67.220.220 - `off`: Clear all DNS servers 3. Calls `setLANSettings` JNAP action 4. Allows `JNAPSideEffectError` to propagate (handled by UI layer) @@ -191,7 +191,7 @@ InstantSafetyType _determineSafeBrowsingType(RouterLANSettings lanSettings) final dnsServer1 = lanSettings.dhcpSettings.dnsServer1; if (dnsServer1 == '208.91.114.155') { return InstantSafetyType.fortinet; -} else if (dnsServer1 == '208.67.222.123') { +} else if (dnsServer1 == '208.67.222.222') { return InstantSafetyType.openDNS; } else { return InstantSafetyType.off; @@ -225,9 +225,9 @@ Private constants within service: // Fortinet Safe Browsing DNS static const _fortinetDns1 = '208.91.114.155'; -// OpenDNS Family Shield -static const _openDnsDns1 = '208.67.222.123'; -static const _openDnsDns2 = '208.67.220.123'; +// OpenDNS Family Shield (NOW-713: Updated IPs) +static const _openDnsDns1 = '208.67.222.222'; +static const _openDnsDns2 = '208.67.220.220'; ``` --- diff --git a/specs/009-instant-safety-service/spec.md b/specs/009-instant-safety-service/spec.md index 4144e9fbc..0b5323042 100644 --- a/specs/009-instant-safety-service/spec.md +++ b/specs/009-instant-safety-service/spec.md @@ -115,6 +115,6 @@ The system determines whether the router hardware/firmware supports Fortinet saf ## Assumptions - The existing `PreservableNotifierMixin` pattern will continue to be used for dirty state management -- DNS server IP addresses for Fortinet (208.91.114.155) and OpenDNS (208.67.222.123, 208.67.220.123) are fixed and do not require configuration +- DNS server IP addresses for Fortinet (208.91.114.155) and OpenDNS Family Shield (208.67.222.222, 208.67.220.220) are fixed and do not require configuration - The compatibility map for Fortinet support (currently empty) will remain managed within the service layer - The InstantSafety feature currently has no unit tests, so new tests will be created from scratch diff --git a/test/validator_rules/input_validators_test.dart b/test/validator_rules/input_validators_test.dart index 7241bbbb6..fafa34cba 100644 --- a/test/validator_rules/input_validators_test.dart +++ b/test/validator_rules/input_validators_test.dart @@ -378,6 +378,19 @@ void main() { expect(validator.validate(invalidMask2), false); }); + // QUALITY-439: Test /31 subnet mask support for WAN Static IP + test('validate method - /31 subnet mask with max: 31', () { + final validator = SubnetMaskValidator(max: 31); + const mask31 = '255.255.255.254'; // /31 subnet mask + expect(validator.validate(mask31), true); + }); + + test('validate method - /31 subnet mask rejected with default max', () { + final validator = SubnetMaskValidator(); // default max is 30 + const mask31 = '255.255.255.254'; // /31 subnet mask + expect(validator.validate(mask31), false); + }); + test('validate method - invalid subnet mask (leading whitespace)', () { final validator = SubnetMaskValidator(); const invalidMask = ' 255.255.255.128';