summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-09-08 11:02:47 +0200
committerAndrej Mihajlov <and@mullvad.net>2020-09-08 18:36:40 +0200
commit30ba808f89d39b7710a3ea3c95c30de92b932068 (patch)
tree56da972afa8d137cbbcdcdbb5a7ee8b7c0fc70cf
parent21e1155b1d3bfa6474327817f87b2ccc30a21232 (diff)
downloadmullvadvpn-30ba808f89d39b7710a3ea3c95c30de92b932068.tar.xz
mullvadvpn-30ba808f89d39b7710a3ea3c95c30de92b932068.zip
Improve IPAddressRange and add tests
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/IPAddressRange.swift113
-rw-r--r--ios/MullvadVPNTests/IPAddressRangeTests.swift54
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(""))
+ }
+ }
+}