summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadREST/Transport/Socks5/Socks5Endpoint.swift
blob: c11936e4536dcb534561986e166920c357836747 (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
//
//  Socks5Endpoint.swift
//  MullvadTransport
//
//  Created by pronebird on 20/10/2023.
//

import Foundation
import MullvadTypes
import Network

/// A network endpoint specified by DNS name and port.
public struct Socks5HostEndpoint: Sendable {
    /// The endpoint's hostname.
    public let hostname: String

    /// The endpoint's port.
    public let port: UInt16

    /**
     Initializes a new host endpoint.
    
     Returns `nil` when the hostname is either empty or longer than 255 bytes, because it cannot be represented in socks protocol.
    
     - Parameters:
        - hostname: the endpoint's hostname
        - port: the endpoint's port
     */
    public init?(hostname: String, port: UInt16) {
        // The maximum length of domain name in bytes.
        let maxHostnameLength = UInt8.max
        let hostnameByteLength = Data(hostname.utf8).count

        // Empty hostname is meaningless.
        guard hostnameByteLength > 0 else { return nil }

        // The length larger than 255 bytes cannot be represented in socks protocol.
        guard hostnameByteLength <= maxHostnameLength else { return nil }

        self.hostname = hostname
        self.port = port
    }
}

/// The endpoint type used by objects implementing socks protocol.
public enum Socks5Endpoint: Sendable {
    /// IPv4 endpoint.
    case ipv4(IPv4Endpoint)

    /// IPv6 endpoint.
    case ipv6(IPv6Endpoint)

    /// Domain name endpoint.
    case domain(Socks5HostEndpoint)

    /// The corresponding raw socks address type.
    var addressType: Socks5AddressType {
        switch self {
        case .ipv4:
            return .ipv4
        case .ipv6:
            return .ipv6
        case .domain:
            return .domainName
        }
    }

    /// The port associated with the underlying endpoint.
    var port: UInt16 {
        switch self {
        case let .ipv4(endpoint):
            endpoint.port
        case let .ipv6(endpoint):
            endpoint.port
        case let .domain(endpoint):
            endpoint.port
        }
    }

    /// The byte representation in socks protocol.
    var rawData: Data {
        var data = Data()

        switch self {
        case let .ipv4(endpoint):
            data.append(contentsOf: endpoint.ip.rawValue)

        case let .ipv6(endpoint):
            data.append(contentsOf: endpoint.ip.rawValue)

        case let .domain(endpoint):
            // Convert hostname to byte data without nul terminator.
            let domainNameBytes = Data(endpoint.hostname.utf8)

            // Append the length of domain name.
            // Host endpoint already ensures that the length of domain name does not exceed the maximum value that
            // single byte can hold.
            data.append(UInt8(domainNameBytes.count))

            // Append the domain name.
            data.append(contentsOf: domainNameBytes)
        }

        // Append port in network byte order.
        withUnsafeBytes(of: port.bigEndian) { buffer in
            data.append(contentsOf: buffer)
        }

        return data
    }
}