Chris Simpson | a9b6c70 | 2018-04-08 07:11:37 -0400 | [diff] [blame] | 1 | |
| 2 | /* |
| 3 | * Licensed to the Apache Software Foundation (ASF) under one |
| 4 | * or more contributor license agreements. See the NOTICE file |
| 5 | * distributed with this work for additional information |
| 6 | * regarding copyright ownership. The ASF licenses this file |
| 7 | * to you under the Apache License, Version 2.0 (the |
| 8 | * "License"); you may not use this file except in compliance |
| 9 | * with the License. You may obtain a copy of the License at |
| 10 | * |
| 11 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | * |
| 13 | * Unless required by applicable law or agreed to in writing, |
| 14 | * software distributed under the License is distributed on an |
| 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 16 | * KIND, either express or implied. See the License for the |
| 17 | * specific language governing permissions and limitations |
| 18 | * under the License. |
| 19 | */ |
| 20 | |
| 21 | |
| 22 | #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) |
| 23 | import Darwin |
| 24 | #elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) |
| 25 | import Glibc |
| 26 | import Dispatch |
| 27 | #endif |
| 28 | |
| 29 | import Foundation |
| 30 | import CoreFoundation |
| 31 | |
| 32 | private struct Sys { |
| 33 | #if os(Linux) |
| 34 | static let read = Glibc.read |
| 35 | static let write = Glibc.write |
| 36 | static let close = Glibc.close |
| 37 | #else |
| 38 | static let read = Darwin.read |
| 39 | static let write = Darwin.write |
| 40 | static let close = Darwin.close |
| 41 | #endif |
| 42 | } |
| 43 | |
| 44 | extension in_addr { |
| 45 | public init?(hostent: hostent?) { |
| 46 | guard let host = hostent, host.h_addr_list != nil, host.h_addr_list.pointee != nil else { |
| 47 | return nil |
| 48 | } |
| 49 | self.init() |
| 50 | memcpy(&self, host.h_addr_list.pointee!, Int(host.h_length)) |
| 51 | |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | |
| 56 | #if os(Linux) |
| 57 | /// TCFSocketTransport currently unavailable |
| 58 | /// remove comments and build to see why/fix |
| 59 | /// currently CF[Read|Write]Stream's can't cast to [Input|Output]Streams which breaks thigns |
| 60 | #else |
| 61 | extension Stream.PropertyKey { |
| 62 | static let SSLPeerTrust = Stream.PropertyKey(kCFStreamPropertySSLPeerTrust as String) |
| 63 | } |
| 64 | |
| 65 | /// TCFSocketTransport, uses CFSockets and (NS)Stream's |
| 66 | public class TCFSocketTransport: TStreamTransport { |
| 67 | public init?(hostname: String, port: Int, secure: Bool = false) { |
| 68 | |
| 69 | var inputStream: InputStream |
| 70 | var outputStream: OutputStream |
| 71 | |
| 72 | var readStream: Unmanaged<CFReadStream>? |
| 73 | var writeStream: Unmanaged<CFWriteStream>? |
| 74 | CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, |
| 75 | hostname as CFString!, |
| 76 | UInt32(port), |
| 77 | &readStream, |
| 78 | &writeStream) |
| 79 | |
| 80 | if let readStream = readStream?.takeRetainedValue(), |
| 81 | let writeStream = writeStream?.takeRetainedValue() { |
| 82 | CFReadStreamSetProperty(readStream, .shouldCloseNativeSocket, kCFBooleanTrue) |
| 83 | CFWriteStreamSetProperty(writeStream, .shouldCloseNativeSocket, kCFBooleanTrue) |
| 84 | |
| 85 | if secure { |
Chris Simpson | 2566ecd | 2018-08-29 14:40:44 -0400 | [diff] [blame^] | 86 | CFReadStreamSetProperty(readStream, .socketSecurityLevel, StreamSocketSecurityLevel.negotiatedSSL.rawValue as CFString) |
| 87 | CFWriteStreamSetProperty(writeStream, .socketSecurityLevel, StreamSocketSecurityLevel.negotiatedSSL.rawValue as CFString) |
Chris Simpson | a9b6c70 | 2018-04-08 07:11:37 -0400 | [diff] [blame] | 88 | } |
| 89 | |
| 90 | inputStream = readStream as InputStream |
| 91 | inputStream.schedule(in: .current, forMode: .defaultRunLoopMode) |
| 92 | inputStream.open() |
| 93 | |
| 94 | outputStream = writeStream as OutputStream |
| 95 | outputStream.schedule(in: .current, forMode: .defaultRunLoopMode) |
| 96 | outputStream.open() |
| 97 | |
| 98 | } else { |
| 99 | |
| 100 | if readStream != nil { |
| 101 | readStream?.release() |
| 102 | } |
| 103 | if writeStream != nil { |
| 104 | writeStream?.release() |
| 105 | } |
| 106 | super.init(inputStream: nil, outputStream: nil) |
| 107 | return nil |
| 108 | } |
| 109 | |
| 110 | super.init(inputStream: inputStream, outputStream: outputStream) |
| 111 | |
| 112 | self.input?.delegate = self |
| 113 | self.output?.delegate = self |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | extension TCFSocketTransport: StreamDelegate { } |
| 118 | #endif |
| 119 | |
| 120 | |
| 121 | /// TSocketTransport, posix sockets. Supports IPv4 only for now |
| 122 | public class TSocketTransport : TTransport { |
| 123 | public var socketDescriptor: Int32 |
| 124 | |
| 125 | |
| 126 | |
| 127 | /// Initialize from an already set up socketDescriptor. |
| 128 | /// Expects socket thats already bound/connected (i.e. from listening) |
| 129 | /// |
| 130 | /// - parameter socketDescriptor: posix socket descriptor (Int32) |
| 131 | public init(socketDescriptor: Int32) { |
| 132 | self.socketDescriptor = socketDescriptor |
| 133 | } |
| 134 | |
| 135 | |
| 136 | public convenience init(hostname: String, port: Int) throws { |
| 137 | guard let hp = gethostbyname(hostname.cString(using: .utf8)!)?.pointee, |
| 138 | let hostAddr = in_addr(hostent: hp) else { |
| 139 | throw TTransportError(error: .unknown, message: "Invalid address: \(hostname)") |
| 140 | } |
| 141 | |
| 142 | |
| 143 | |
| 144 | #if os(Linux) |
| 145 | let sock = socket(AF_INET, Int32(SOCK_STREAM.rawValue), 0) |
| 146 | var addr = sockaddr_in(sin_family: sa_family_t(AF_INET), |
| 147 | sin_port: in_port_t(htons(UInt16(port))), |
| 148 | sin_addr: hostAddr, |
| 149 | sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) |
| 150 | #else |
| 151 | let sock = socket(AF_INET, SOCK_STREAM, 0) |
| 152 | |
| 153 | var addr = sockaddr_in(sin_len: UInt8(MemoryLayout<sockaddr_in>.size), |
| 154 | sin_family: sa_family_t(AF_INET), |
| 155 | sin_port: in_port_t(htons(UInt16(port))), |
| 156 | sin_addr: in_addr(s_addr: in_addr_t(0)), |
| 157 | sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) |
| 158 | |
| 159 | #endif |
| 160 | |
| 161 | let addrPtr = withUnsafePointer(to: &addr){ UnsafePointer<sockaddr>(OpaquePointer($0)) } |
| 162 | |
| 163 | let connected = connect(sock, addrPtr, UInt32(MemoryLayout<sockaddr_in>.size)) |
| 164 | if connected != 0 { |
| 165 | throw TTransportError(error: .notOpen, message: "Error binding to host: \(hostname) \(port)") |
| 166 | } |
| 167 | |
| 168 | self.init(socketDescriptor: sock) |
| 169 | } |
| 170 | |
| 171 | deinit { |
| 172 | close() |
| 173 | } |
| 174 | |
| 175 | public func readAll(size: Int) throws -> Data { |
| 176 | var out = Data() |
| 177 | while out.count < size { |
| 178 | out.append(try self.read(size: size)) |
| 179 | } |
| 180 | return out |
| 181 | } |
| 182 | |
| 183 | public func read(size: Int) throws -> Data { |
| 184 | var buff = Array<UInt8>.init(repeating: 0, count: size) |
| 185 | let readBytes = Sys.read(socketDescriptor, &buff, size) |
| 186 | |
| 187 | return Data(bytes: buff[0..<readBytes]) |
| 188 | } |
| 189 | |
| 190 | public func write(data: Data) { |
| 191 | var bytesToWrite = data.count |
| 192 | var writeBuffer = data |
| 193 | while bytesToWrite > 0 { |
| 194 | let written = writeBuffer.withUnsafeBytes { |
| 195 | Sys.write(socketDescriptor, $0, writeBuffer.count) |
| 196 | } |
| 197 | writeBuffer = writeBuffer.subdata(in: written ..< writeBuffer.count) |
| 198 | bytesToWrite -= written |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | public func flush() throws { |
| 203 | // nothing to do |
| 204 | } |
| 205 | |
| 206 | public func close() { |
| 207 | shutdown(socketDescriptor, Int32(SHUT_RDWR)) |
| 208 | _ = Sys.close(socketDescriptor) |
| 209 | } |
| 210 | } |