summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/IPAddressRange.swift
blob: 712dc4d39e7d8675e6ca924d986a1087614e66c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//
//  IPAddressRange.swift
//  MullvadVPN
//
//  Created by pronebird on 20/06/2019.
//  Copyright © 2019 Mullvad VPN AB. All rights reserved.
//  Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
//

import Foundation
import Network

/// A struct describing an IP address range
struct IPAddressRange {
    let address: IPAddress
    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 {
    static func == (lhs: IPAddressRange, rhs: IPAddressRange) -> Bool {
        return lhs.address.rawValue == rhs.address.rawValue &&
            lhs.networkPrefixLength == rhs.networkPrefixLength
    }
}

extension IPAddressRange: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(address.rawValue)
        hasher.combine(networkPrefixLength)
    }
}

extension IPAddressRange: CustomStringConvertible {
    var description: String {
        return "\(address)/\(networkPrefixLength)"
    }
}

private extension IPv4Address {
    var maxNetworkPrefixLength: UInt8 {
        return 32
    }
}

private extension IPv6Address {
    var maxNetworkPrefixLength: UInt8 {
        return 128
    }
}

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 {
            fatalError()
        }
    }
}

extension IPAddressRange: Codable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()

        try container.encode("\(self)")
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let value = try container.decode(String.self)

        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)"
        }
    }
}