mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-24 07:28:07 +01:00
152 lines
5.4 KiB
Swift
152 lines
5.4 KiB
Swift
// BlueWallet
|
|
//
|
|
// Created by Marcos Rodriguez on 3/23/23.
|
|
// Copyright © 2023 BlueWallet. All rights reserved.
|
|
|
|
import Foundation
|
|
|
|
/**
|
|
`SwiftTCPClient` is a simple TCP client class that allows for establishing a TCP connection,
|
|
sending data, and receiving data over the network. It supports both plain TCP and SSL-secured connections.
|
|
|
|
The class uses `InputStream` and `OutputStream` for network communication, encapsulating the complexity of stream management and data transfer.
|
|
|
|
- Note: When using SSL, this implementation disables certificate chain validation for simplicity. This is not recommended for production code due to security risks.
|
|
|
|
## Examples
|
|
|
|
### Creating an instance and connecting to a server:
|
|
|
|
```swift
|
|
let client = SwiftTCPClient()
|
|
let success = client.connect(to: "example.com", port: 12345, useSSL: false)
|
|
|
|
if success {
|
|
print("Connected successfully.")
|
|
} else {
|
|
print("Failed to connect.")
|
|
}
|
|
**/
|
|
|
|
class SwiftTCPClient: NSObject {
|
|
private var inputStream: InputStream?
|
|
private var outputStream: OutputStream?
|
|
private let bufferSize = 1024
|
|
private var readData = Data()
|
|
private let readTimeout = 5.0 // Timeout in seconds
|
|
|
|
func connect(to host: String, port: UInt32, useSSL: Bool = false) -> Bool {
|
|
var readStream: Unmanaged<CFReadStream>?
|
|
var writeStream: Unmanaged<CFWriteStream>?
|
|
|
|
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host as CFString, port, &readStream, &writeStream)
|
|
|
|
guard let read = readStream?.takeRetainedValue(), let write = writeStream?.takeRetainedValue() else {
|
|
return false
|
|
}
|
|
|
|
inputStream = read as InputStream
|
|
outputStream = write as OutputStream
|
|
|
|
if useSSL {
|
|
// Configure SSL settings for the streams
|
|
let sslSettings: [NSString: Any] = [
|
|
kCFStreamSSLLevel as NSString: kCFStreamSocketSecurityLevelNegotiatedSSL as Any,
|
|
kCFStreamSSLValidatesCertificateChain as NSString: kCFBooleanFalse as Any
|
|
// Note: Disabling certificate chain validation (kCFStreamSSLValidatesCertificateChain: kCFBooleanFalse)
|
|
// is typically not recommended for production code as it introduces significant security risks.
|
|
]
|
|
inputStream?.setProperty(sslSettings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
|
|
outputStream?.setProperty(sslSettings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
|
|
}
|
|
|
|
inputStream?.delegate = self
|
|
outputStream?.delegate = self
|
|
|
|
inputStream?.schedule(in: .current, forMode: RunLoop.Mode.default)
|
|
outputStream?.schedule(in: .current, forMode: RunLoop.Mode.default)
|
|
|
|
inputStream?.open()
|
|
outputStream?.open()
|
|
|
|
return true
|
|
}
|
|
|
|
|
|
func send(data: Data) -> Bool {
|
|
guard let outputStream = outputStream else {
|
|
return false
|
|
}
|
|
|
|
let bytesWritten = data.withUnsafeBytes { bufferPointer -> Int in
|
|
guard let baseAddress = bufferPointer.baseAddress else {
|
|
return 0
|
|
}
|
|
return outputStream.write(baseAddress.assumingMemoryBound(to: UInt8.self), maxLength: data.count)
|
|
}
|
|
|
|
return bytesWritten == data.count
|
|
}
|
|
|
|
func receive() throws -> Data {
|
|
guard let inputStream = inputStream else {
|
|
throw NSError(domain: "SwiftTCPClientError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Input stream is nil."])
|
|
}
|
|
|
|
// Check if the input stream is ready for reading
|
|
if inputStream.streamStatus != .open && inputStream.streamStatus != .reading {
|
|
throw NSError(domain: "SwiftTCPClientError", code: 3, userInfo: [NSLocalizedDescriptionKey: "Stream is not ready for reading."])
|
|
}
|
|
|
|
readData = Data()
|
|
|
|
// Wait for data to be available or timeout
|
|
let timeoutDate = Date().addingTimeInterval(readTimeout)
|
|
repeat {
|
|
RunLoop.current.run(mode: RunLoop.Mode.default, before: Date(timeIntervalSinceNow: 0.1))
|
|
if readData.count > 0 || Date() > timeoutDate {
|
|
break
|
|
}
|
|
} while inputStream.streamStatus == .open || inputStream.streamStatus == .reading
|
|
|
|
if readData.count == 0 && Date() > timeoutDate {
|
|
throw NSError(domain: "SwiftTCPClientError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Read timed out."])
|
|
}
|
|
|
|
return readData
|
|
}
|
|
|
|
|
|
func close() {
|
|
inputStream?.close()
|
|
outputStream?.close()
|
|
inputStream?.remove(from: .current, forMode: RunLoop.Mode.default)
|
|
outputStream?.remove(from: .current, forMode: RunLoop.Mode.default)
|
|
inputStream = nil
|
|
outputStream = nil
|
|
}
|
|
}
|
|
|
|
extension SwiftTCPClient: StreamDelegate {
|
|
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
|
switch eventCode {
|
|
case .hasBytesAvailable:
|
|
if aStream == inputStream, let inputStream = inputStream {
|
|
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
|
|
while inputStream.hasBytesAvailable {
|
|
let bytesRead = inputStream.read(buffer, maxLength: bufferSize)
|
|
if bytesRead > 0 {
|
|
readData.append(buffer, count: bytesRead)
|
|
}
|
|
}
|
|
buffer.deallocate()
|
|
}
|
|
case .errorOccurred:
|
|
print("Stream error occurred")
|
|
case .endEncountered:
|
|
close()
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|