diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-09-08 11:02:47 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-09-08 18:36:40 +0200 |
| commit | 30ba808f89d39b7710a3ea3c95c30de92b932068 (patch) | |
| tree | 56da972afa8d137cbbcdcdbb5a7ee8b7c0fc70cf | |
| parent | 21e1155b1d3bfa6474327817f87b2ccc30a21232 (diff) | |
| download | mullvadvpn-30ba808f89d39b7710a3ea3c95c30de92b932068.tar.xz mullvadvpn-30ba808f89d39b7710a3ea3c95c30de92b932068.zip | |
Improve IPAddressRange and add tests
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/IPAddressRange.swift | 113 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/IPAddressRangeTests.swift | 54 |
3 files changed, 136 insertions, 35 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index acb84d969c..75e3c99ad9 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ 582BB1B1229569620055B6EF /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B0229569620055B6EF /* CustomNavigationBar.swift */; }; 582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */; }; 582BB1B52295780F0055B6EF /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B42295780F0055B6EF /* AccountExpiry.swift */; }; + 58341D9D2507826300D2BB19 /* IPAddressRangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58341D9C2507826300D2BB19 /* IPAddressRangeTests.swift */; }; 5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5835B7CB233B76CB0096D79F /* TunnelManager.swift */; }; 583BC70724FE4DC500C9DE04 /* Optional+DispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583BC70624FE4DC400C9DE04 /* Optional+DispatchQueue.swift */; }; 583BC70824FE4DC500C9DE04 /* Optional+DispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583BC70624FE4DC400C9DE04 /* Optional+DispatchQueue.swift */; }; @@ -288,6 +289,7 @@ 582BB1B0229569620055B6EF /* CustomNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.swift; sourceTree = "<group>"; }; 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountCell.swift; sourceTree = "<group>"; }; 582BB1B42295780F0055B6EF /* AccountExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiry.swift; sourceTree = "<group>"; }; + 58341D9C2507826300D2BB19 /* IPAddressRangeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPAddressRangeTests.swift; sourceTree = "<group>"; }; 5835B7CB233B76CB0096D79F /* TunnelManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManager.swift; sourceTree = "<group>"; }; 583BC70624FE4DC400C9DE04 /* Optional+DispatchQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+DispatchQueue.swift"; sourceTree = "<group>"; }; 5840250022B1124600E4CFEC /* IpAddress+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IpAddress+Codable.swift"; sourceTree = "<group>"; }; @@ -489,6 +491,7 @@ 58B0A2A1238EE67E00BC001D /* MullvadVPNTests */ = { isa = PBXGroup; children = ( + 58341D9C2507826300D2BB19 /* IPAddressRangeTests.swift */, 582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */, 58B0A2A4238EE67E00BC001D /* Info.plist */, 584B26F3237434D00073B10E /* RelaySelectorTests.swift */, @@ -929,6 +932,7 @@ 5857F23B24C8448600CF6F47 /* OperationProtocol.swift in Sources */, 5857F22F24C8404C00CF6F47 /* MullvadRest.swift in Sources */, 58B0A2AA238EE6A900BC001D /* RelaySelector.swift in Sources */, + 58341D9D2507826300D2BB19 /* IPAddressRangeTests.swift in Sources */, 5857F23924C8446A00CF6F47 /* AnyOperationObserver.swift in Sources */, 5896AE86246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift in Sources */, 5807E2C3243203E700F5FF30 /* String+Split.swift in Sources */, diff --git a/ios/MullvadVPN/IPAddressRange.swift b/ios/MullvadVPN/IPAddressRange.swift index fda496714e..712dc4d39e 100644 --- a/ios/MullvadVPN/IPAddressRange.swift +++ b/ios/MullvadVPN/IPAddressRange.swift @@ -10,9 +10,45 @@ import Foundation import Network +/// A struct describing an IP address range struct IPAddressRange { let address: IPAddress - var networkPrefixLength: UInt8 + let networkPrefixLength: UInt8 + + init(address: IPAddress, networkPrefixLength: UInt8) { + self.address = address + self.networkPrefixLength = min(networkPrefixLength, address.maxNetworkPrefixLength) + } + + init(string: String) throws { + let separatorIndex = string.lastIndex(of: "/") ?? string.endIndex + let prefixStartIndex = string.index(separatorIndex, offsetBy: 1, limitedBy: string.endIndex) + + let prefixSubstring = prefixStartIndex.flatMap { string[$0...] } + var prefix: UInt8? + if let prefixSubstring = prefixSubstring { + if let parsedPrefix = UInt8(prefixSubstring) { + prefix = parsedPrefix + } else { + throw IPAddressRangeParseError.parsePrefix(String(prefixSubstring)) + } + } + + let addressString = String(string[..<separatorIndex]) + if let ipv4Address = IPv4Address(addressString) { + self = IPAddressRange( + address: ipv4Address, + networkPrefixLength: prefix ?? ipv4Address.maxNetworkPrefixLength + ) + } else if let ipv6Address = IPv6Address(addressString) { + self = IPAddressRange( + address: ipv6Address, + networkPrefixLength: prefix ?? ipv6Address.maxNetworkPrefixLength + ) + } else { + throw IPAddressRangeParseError.parseAddress(addressString) + } + } } extension IPAddressRange: Equatable { @@ -35,44 +71,31 @@ extension IPAddressRange: CustomStringConvertible { } } -extension IPAddressRange { - - init?(from string: String) { - guard let parsed = IPAddressRange.parseAddressString(string) else { return nil } - address = parsed.0 - networkPrefixLength = parsed.1 +private extension IPv4Address { + var maxNetworkPrefixLength: UInt8 { + return 32 } +} - private static func parseAddressString(_ string: String) -> (IPAddress, UInt8)? { - let endOfIPAddress = string.lastIndex(of: "/") ?? string.endIndex - let addressString = String(string[string.startIndex ..< endOfIPAddress]) - let address: IPAddress - if let addr = IPv4Address(addressString) { - address = addr - } else if let addr = IPv6Address(addressString) { - address = addr - } else { - return nil - } +private extension IPv6Address { + var maxNetworkPrefixLength: UInt8 { + return 128 + } +} - let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128 - var networkPrefixLength: UInt8 - if endOfIPAddress < string.endIndex { // "/" was located - let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress) - guard indexOfNetworkPrefixLength < string.endIndex else { return nil } - let networkPrefixLengthSubstring = string[indexOfNetworkPrefixLength ..< string.endIndex] - guard let npl = UInt8(networkPrefixLengthSubstring) else { return nil } - networkPrefixLength = min(npl, maxNetworkPrefixLength) +private extension IPAddress { + var maxNetworkPrefixLength: UInt8 { + if let ipv4Address = self as? IPv4Address { + return ipv4Address.maxNetworkPrefixLength + } else if let ipv6Address = self as? IPv6Address { + return ipv6Address.maxNetworkPrefixLength } else { - networkPrefixLength = maxNetworkPrefixLength + fatalError() } - - return (address, networkPrefixLength) } } extension IPAddressRange: Codable { - func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -83,11 +106,31 @@ extension IPAddressRange: Codable { let container = try decoder.singleValueContainer() let value = try container.decode(String.self) - if let addressRange = IPAddressRange(from: value) { - self = addressRange - } else { - throw DecodingError.dataCorruptedError( - in: container, debugDescription: "Invalid IPAddressRange representation") + do { + self = try IPAddressRange(string: value) + } catch { + let context = DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Invalid IPAddressRange representation", + underlyingError: error) + throw DecodingError.dataCorrupted(context) + } + } +} + +enum IPAddressRangeParseError: LocalizedError, Equatable { + /// A failure to parse the IP address + case parseAddress(String) + + /// A failure to parse the network prefix + case parsePrefix(String) + + var errorDescription: String? { + switch self { + case .parseAddress(let addressString): + return "Failure to parse the IP address: \(addressString)" + case .parsePrefix(let prefixString): + return "Failure to parse the network prefix: \(prefixString)" } } } diff --git a/ios/MullvadVPNTests/IPAddressRangeTests.swift b/ios/MullvadVPNTests/IPAddressRangeTests.swift new file mode 100644 index 0000000000..37dc3415a2 --- /dev/null +++ b/ios/MullvadVPNTests/IPAddressRangeTests.swift @@ -0,0 +1,54 @@ +// +// IPAddressRangeTests.swift +// MullvadVPNTests +// +// Created by pronebird on 08/09/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import XCTest + +class IPAddressRangeTests: XCTestCase { + + func testParsingValidIPv4AddressRange() throws { + let addr = try IPAddressRange(string: "127.0.0.1/32") + XCTAssertEqual("\(addr)", "127.0.0.1/32") + } + + func testParsingValidIPv6AddressRange() throws { + let addr = try IPAddressRange(string: "::1/128") + XCTAssertEqual("\(addr)", "::1/128") + } + + func testParsingIPv4AddressWithoutNetworkPrefix() throws { + let addr = try IPAddressRange(string: "127.0.0.1") + XCTAssertEqual("\(addr)", "127.0.0.1/32") + } + + func testParsingIPv6AddressWithoutNetworkPrefix() throws { + let addr = try IPAddressRange(string: "::1") + XCTAssertEqual("\(addr)", "::1/128") + } + + func testParsingInvalidIPv4AddressNetworkPrefix() throws { + let addr = try IPAddressRange(string: "127.0.0.1/33") + XCTAssertEqual("\(addr)", "127.0.0.1/32") + } + + func testParsingInvalidIPv6AddressNetworkPrefix() throws { + let addr = try IPAddressRange(string: "::1/129") + XCTAssertEqual("\(addr)", "::1/128") + } + + func testParsingInvalidIPAddress() throws { + XCTAssertThrowsError(try IPAddressRange(string: "1.2.3.4.5/32")) { (error) in + XCTAssertEqual(error as? IPAddressRangeParseError, IPAddressRangeParseError.parseAddress("1.2.3.4.5")) + } + } + + func testParsingEmptyNetworkPrefix() throws { + XCTAssertThrowsError(try IPAddressRange(string: "::1/")) { (error) in + XCTAssertEqual(error as? IPAddressRangeParseError, IPAddressRangeParseError.parsePrefix("")) + } + } +} |
