aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Shared/NetSocket.swift
blob: 263487bb6fce8467c25015ca3b407cb4c2e1cf0d (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269

// NetSocket.swift
// A simple delegate based buffered read/write TCP socket.
// Created by Dustin Mierau

import Foundation

protocol NetSocketDelegate: AnyObject {
  @MainActor func netsocketConnected(socket: NetSocket)
  @MainActor func netsocketDisconnected(socket: NetSocket, error: Error?)
  @MainActor func netsocketReceived(socket: NetSocket, bytes: [UInt8])
  func netsocketSent(socket: NetSocket, count: Int)
}

extension NetSocketDelegate {
  func netsocketConnected(socket: NetSocket) {}
  func netsocketDisconnected(socket: NetSocket, error: Error?) {}
  func netsocketReceived(socket: NetSocket, bytes: [UInt8]) {}
  func netsocketSent(socket: NetSocket, count: Int) {}
}

enum NetSocketStatus {
  case disconnected
  case connecting
  case connected
}

final class NetSocket: NSObject, StreamDelegate {
  weak var delegate: NetSocketDelegate? = nil
  
  private var output: OutputStream? = nil
  private var input: InputStream? = nil
  
  private var outputBuffer: [UInt8] = []
  private var inputBuffer: [UInt8] = []
  
  private var readBuffer: [UInt8] = Array(repeating: 0, count: 4 * 1024)
  
  public func peek() -> [UInt8] { self.inputBuffer }
  public var available: Int { self.inputBuffer.count }
  
  private var status: NetSocketStatus = .disconnected
  
  @MainActor public func has(_ length: Int) -> Bool {
    return (self.available >= length)
  }
  
  override init() {}
  
  @MainActor public func connect(host: String, port: Int) {
    self.close()
    
    var outputStream: OutputStream? = nil
    var inputStream: InputStream? = nil
    
    self.status = .connecting
    
    Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)
    
    self.input = inputStream
    self.output = outputStream
    
    inputStream?.delegate = self
    outputStream?.delegate = self
    
    inputStream?.schedule(in: .current, forMode: .default)
    outputStream?.schedule(in: .current, forMode: .default)
    
    inputStream?.open()
    outputStream?.open()
  }
  
  @MainActor public func close(_ err: Error? = nil) {
    print("NetSocket: Closed")
    
    let disconnected = (self.status != .disconnected)
    
    self.status = .disconnected
    
    self.input?.delegate = nil
    self.output?.delegate = nil
    self.input?.close()
    self.output?.close()
    self.input?.remove(from: .current, forMode: .default)
    self.output?.remove(from: .current, forMode: .default)
    self.input = nil
    self.output = nil
    self.inputBuffer = []
    self.outputBuffer = []
    
    if disconnected {
      self.delegate?.netsocketDisconnected(socket: self, error: err)
    }
  }
  
  @MainActor public func write(_ data: Data) {
    guard let output = self.output else {
      return
    }
    
    self.outputBuffer.append(contentsOf: data)
    
    if output.hasSpaceAvailable {
      self.writeBufferToStream()
    }
  }
  
  @MainActor public func write(_ data: [UInt8]) {
    guard let output = self.output else {
      return
    }
    
    self.outputBuffer.append(contentsOf: data)
    
    if output.hasSpaceAvailable {
      self.writeBufferToStream()
    }
  }
  
  @MainActor public func read(count: Int) -> [UInt8] {
    guard self.inputBuffer.count > 0, count > 0 else {
      return []
    }
    
    let amountToRead = min(count, self.inputBuffer.count)
    let dataRead: [UInt8] = Array(self.inputBuffer[0..<amountToRead])
    self.inputBuffer.removeFirst(amountToRead)
    
    return dataRead
  }
  
  @MainActor public func read(count: Int) -> Data {
    guard self.inputBuffer.count > 0, count > 0 else {
      return Data()
    }
    
    let amountToRead = min(count, self.inputBuffer.count)
    
    let dataRead: Data = Data(self.inputBuffer[0..<amountToRead])
    self.inputBuffer.removeFirst(amountToRead)
    
    return dataRead
  }
  
  @MainActor public func readAll() -> [UInt8] {
    guard self.inputBuffer.count > 0 else {
      return []
    }
    
    let dataRead: [UInt8] = Array(self.inputBuffer)
    self.inputBuffer = []
    
    return dataRead
  }
  
  @MainActor public func readAll() -> Data {
    guard self.inputBuffer.count > 0 else {
      return Data()
    }
    
    let dataRead: Data = Data(self.inputBuffer)
    self.inputBuffer = []
    
    return dataRead
  }
  
  @MainActor private func writeBufferToStream() {
    guard let output = self.output, self.outputBuffer.count > 0 else {
      return
    }
    
    let bytesWritten = output.write(self.outputBuffer, maxLength: self.outputBuffer.count)
    print("NetSocket => \(bytesWritten) bytes")
    if bytesWritten > 0 {
      self.outputBuffer.removeFirst(bytesWritten)
      self.delegate?.netsocketSent(socket: self, count: bytesWritten)
    }
    else if bytesWritten == -1 {
      self.close(output.streamError)
    }
  }
  
  @MainActor private func readStreamToBuffer() {
    guard let input = self.input else {
      return
    }
    
    let bytesRead = input.read(&self.readBuffer, maxLength: 4 * 1024)
    print("NetSocket <= \(bytesRead) bytes")
    if bytesRead > 0 {
      self.inputBuffer.append(contentsOf: self.readBuffer[0..<bytesRead])
      self.delegate?.netsocketReceived(socket: self, bytes: self.inputBuffer)
    }
    else if bytesRead == -1 {
      self.close(input.streamError)
    }
  }
  
  // MARK: -
    
  @MainActor func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    guard let input = self.input, let output = self.output else {
      return
    }
    
    switch eventCode {
    case .openCompleted:
      if aStream == input {
        self.setupStreamOptions()
      }
      if input.streamStatus == .open && output.streamStatus == .open {
        if self.status == .connecting {
          print("NetSocket: Connected")
          self.status = .connected
          self.delegate?.netsocketConnected(socket: self)
        }
      }
      break
    case .hasBytesAvailable:
      print("NetSocket: Data available")
      self.readStreamToBuffer()
    case .hasSpaceAvailable:
      print("NetSocket: Space available")
      self.writeBufferToStream()
    case .endEncountered:
      print("NetSocket: End encounterd")
      self.close()
    case .errorOccurred:
      print("NetSocket: Error encounterd", input.streamError.debugDescription, output.streamError.debugDescription)
      let err = input.streamError ?? output.streamError
      self.close(err)
    default:
      break
    }
  }
  
  // MARK: -
  
  private func setupStreamOptions() {
    if let input = self.input {
      let socketData: Data = CFReadStreamCopyProperty(input as CFReadStream, CFStreamPropertyKey.socketNativeHandle) as! Data;
      var socketHandle: CFSocketNativeHandle = 0;
      (socketData as NSData).getBytes(&socketHandle, length: MemoryLayout.size(ofValue: socketHandle));
      
      var value: Int = 0;
      let size = UInt32(MemoryLayout.size(ofValue: value));
      
      value = 1;
      if setsockopt(socketHandle, IPPROTO_TCP, TCP_NODELAY, &value, size) != 0 {
        print("NetSocket: failed to set TCP_NODELAY");
      }
      // Enable keepalive
      value = 1;
      if setsockopt(socketHandle, SOL_SOCKET, SO_KEEPALIVE, &value, size) != 0 {
        print("NetSocket: failed to set SO_KEEPALIVE");
      }
//      // Number of keepalives before close (including first keepalive packet)
//      value = 5
//      if setsockopt(socketHandle, IPPROTO_TCP, TCP_KEEPCNT, &value, size) != 0 {
//        print("NetSocket: failed to set TCP_KEEPCNT");
//      }
//      // Idle time used when SO_KEEPALIVE is enabled. Sets how long connection must be idle before keepalive is sent.
//      value = 60
//      if setsockopt(socketHandle, IPPROTO_TCP, TCP_KEEPALIVE, &value, size) != 0 {
//        print("NetSocket: failed to set TCP_KEEPALIVE")
//      }
    }
  }
}