aboutsummaryrefslogtreecommitdiffstats
path: root/Shared/Validators.swift
blob: e979aad53f9608f677b472925fa364b96813d9fa (plain) (blame)
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
137
138
139
140
//
//  IPValidator.swift
//  WireGuard
//
//  Created by Jeroen Leenarts on 15-08-18.
//  Copyright © 2018 WireGuard LLC. All rights reserved.
//

import Foundation

enum AddressType {
    case IPv6, IPv4, other
}

public enum EndpointValidationError: Error {
    case noIpAndPort(String)
    case invalidIP(String)
    case invalidPort(String)

    var localizedDescription: String {
        switch self {
        case .noIpAndPort:
            return NSLocalizedString("EndpointValidationError.noIpAndPort", comment: "Error message for malformed endpoint.")
        case .invalidIP:
            return NSLocalizedString("EndpointValidationError.invalidIP", comment: "Error message for invalid endpoint ip.")
        case .invalidPort:
            return NSLocalizedString("EndpointValidationError.invalidPort", comment: "Error message invalid endpoint port.")
        }
    }
}

struct Endpoint {
    var ipAddress: String
    var port: Int32?
    var addressType: AddressType

    init?(endpointString: String, needsPort: Bool = true) throws {
        var ipString: String
        if needsPort {
            guard let range = endpointString.range(of: ":", options: .backwards, range: nil, locale: nil) else {
                throw EndpointValidationError.noIpAndPort(endpointString)
            }
            ipString = String(endpointString[..<range.lowerBound])

            let portString = endpointString[range.upperBound...]

            guard let port = Int32(portString), port > 0 else {
                throw EndpointValidationError.invalidPort(String(portString/*parts[1]*/))
            }
            self.port = port
        } else {
            ipString = endpointString
        }

        ipString = ipString.replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "")

        ipAddress = String(ipString)
        let addressType = validateIpAddress(ipToValidate: ipAddress)
        guard addressType == .IPv4 || addressType == .IPv6 else {
            throw EndpointValidationError.invalidIP(ipAddress)
        }
        self.addressType = addressType
    }
}

func validateIpAddress(ipToValidate: String) -> AddressType {

    var sin = sockaddr_in()
    if ipToValidate.withCString({ cstring in inet_pton(AF_INET, cstring, &sin.sin_addr) }) == 1 {
        // IPv4 peer.
        return .IPv4
    }

    var sin6 = sockaddr_in6()
    if ipToValidate.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1 {
        // IPv6 peer.
        return .IPv6
    }

    return .other
}

public enum CIDRAddressValidationError: Error {
    case noIpAndSubnet(String)
    case invalidIP(String)
    case invalidSubnet(String)

    var localizedDescription: String {
        switch self {
        case .noIpAndSubnet:
            return NSLocalizedString("CIDRAddressValidationError", comment: "Error message for malformed CIDR address.")
        case .invalidIP:
            return NSLocalizedString("CIDRAddressValidationError", comment: "Error message for invalid address ip.")
        case .invalidSubnet:
            return NSLocalizedString("CIDRAddressValidationError", comment: "Error message invalid address subnet.")
        }
    }
}

struct CIDRAddress {
    var ipAddress: String
    var subnet: Int32
    var addressType: AddressType

    init?(stringRepresentation: String) throws {
        guard let range = stringRepresentation.range(of: "/", options: .backwards, range: nil, locale: nil) else {
            throw CIDRAddressValidationError.noIpAndSubnet(stringRepresentation)
        }

        let ipString = stringRepresentation[..<range.lowerBound].replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "")
        let subnetString = stringRepresentation[range.upperBound...]

        guard let subnet = Int32(subnetString) else {
            throw CIDRAddressValidationError.invalidSubnet(String(subnetString))
        }

        ipAddress = String(ipString)
        let addressType = validateIpAddress(ipToValidate: ipAddress)
        guard addressType == .IPv4 || addressType == .IPv6 else {
            throw CIDRAddressValidationError.invalidIP(ipAddress)
        }
        self.addressType = addressType

        self.subnet = subnet
    }

    var subnetString: String {
        // We could calculate these.

        var bitMask: UInt32 = 0b11111111111111111111111111111111
        bitMask = bitMask << (32 - subnet)

        let first = UInt8(truncatingIfNeeded: bitMask >> 24)
        let second = UInt8(truncatingIfNeeded: bitMask >> 16 )
        let third = UInt8(truncatingIfNeeded: bitMask >> 8)
        let fourth = UInt8(truncatingIfNeeded: bitMask)

        return "\(first).\(second).\(third).\(fourth)"
    }
}