|
|
@@ -9,121 +9,183 @@ import Foundation
|
|
|
/* Low level routines for POSIX sockets */
|
|
|
|
|
|
enum SocketError: ErrorType {
|
|
|
- case SocketInitializationFailed(String?)
|
|
|
- case SocketOptionInitializationFailed(String?)
|
|
|
- case BindFailed(String?)
|
|
|
- case ListenFailed(String?)
|
|
|
- case WriteFailed(String?)
|
|
|
- case GetPeerNameFailed(String?)
|
|
|
- case GetNameInfoFailed(String?)
|
|
|
- case AcceptFailed(String?)
|
|
|
+ case SocketInitializationFailed(String)
|
|
|
+ case SocketOptionInitializationFailed(String)
|
|
|
+ case BindFailed(String)
|
|
|
+ case ListenFailed(String)
|
|
|
+ case WriteFailed(String)
|
|
|
+ case GetPeerNameFailed(String)
|
|
|
+ case ConvertingPeerNameFailed
|
|
|
+ case GetNameInfoFailed(String)
|
|
|
+ case AcceptFailed(String)
|
|
|
+ case RecvFailed(String)
|
|
|
+
|
|
|
}
|
|
|
|
|
|
let maxPendingConnection: Int32 = 20
|
|
|
|
|
|
-struct Socket {
|
|
|
+class Socket : Hashable {
|
|
|
+ let socketId: CInt
|
|
|
+
|
|
|
+ var hashValue: Int {
|
|
|
+ return Int(self.socketId)
|
|
|
+ }
|
|
|
|
|
|
- static func tcpForListen(port: in_port_t = 8080) throws -> CInt {
|
|
|
- let s = socket(AF_INET, SOCK_STREAM, 0)
|
|
|
- if s == -1 {
|
|
|
- throw SocketError.SocketInitializationFailed(String.fromCString(UnsafePointer(strerror(errno))))
|
|
|
+ init(port: in_port_t = 8080) throws {
|
|
|
+ self.socketId = socket(AF_INET, SOCK_STREAM, 0)
|
|
|
+ if self.socketId == -1 {
|
|
|
+ throw SocketError.SocketInitializationFailed(ErrorHandle.errorText)
|
|
|
}
|
|
|
|
|
|
var value: Int32 = 1
|
|
|
- if setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(sizeof(Int32))) == -1 {
|
|
|
- let details = String.fromCString(UnsafePointer(strerror(errno)))
|
|
|
- release(s)
|
|
|
+ if setsockopt(self.socketId, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(sizeof(Int32))) == -1 {
|
|
|
+ let details = ErrorHandle.errorText
|
|
|
+// self.release()
|
|
|
throw SocketError.SocketOptionInitializationFailed(details)
|
|
|
}
|
|
|
|
|
|
- nosigpipe(s)
|
|
|
+ self.nosigpipe()
|
|
|
|
|
|
var addr = sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)),
|
|
|
- sin_family: sa_family_t(AF_INET),
|
|
|
- sin_port: port_htons(port),
|
|
|
- sin_addr: in_addr(s_addr: inet_addr("0.0.0.0")),
|
|
|
- sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
+ sin_family: sa_family_t(AF_INET),
|
|
|
+ sin_port: Socket.port_htons(port),
|
|
|
+ sin_addr: in_addr(s_addr: inet_addr("0.0.0.0")),
|
|
|
+ sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
var sock_addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
memcpy(&sock_addr, &addr, Int(sizeof(sockaddr_in)))
|
|
|
|
|
|
- if bind(s, &sock_addr, socklen_t(sizeof(sockaddr_in))) == -1 {
|
|
|
- let details = String.fromCString(UnsafePointer(strerror(errno)))
|
|
|
- release(s)
|
|
|
+ if bind(self.socketId, &sock_addr, socklen_t(sizeof(sockaddr_in))) == -1 {
|
|
|
+ let details = ErrorHandle.errorText
|
|
|
+// self.release()
|
|
|
throw SocketError.BindFailed(details)
|
|
|
}
|
|
|
|
|
|
- if listen(s, maxPendingConnection ) == -1 {
|
|
|
- let details = String.fromCString(UnsafePointer(strerror(errno)))
|
|
|
- release(s)
|
|
|
+ if listen(self.socketId, maxPendingConnection ) == -1 {
|
|
|
+ let details = ErrorHandle.errorText
|
|
|
+// self.release()
|
|
|
throw SocketError.ListenFailed(details)
|
|
|
}
|
|
|
- return s
|
|
|
}
|
|
|
|
|
|
- static func writeUTF8(socket: CInt, string: String) throws {
|
|
|
- if let nsdata = string.dataUsingEncoding(NSUTF8StringEncoding) {
|
|
|
- try writeData(socket, data: nsdata)
|
|
|
- } else {
|
|
|
- throw SocketError.WriteFailed("dataUsingEncoding(NSUTF8StringEncoding) failed")
|
|
|
- }
|
|
|
+ private init(socketId: CInt) {
|
|
|
+ self.socketId = socketId
|
|
|
+ }
|
|
|
+
|
|
|
+ deinit {
|
|
|
+ print("deinit socket")
|
|
|
+ shutdown(self.socketId, SHUT_RDWR)
|
|
|
+ close(self.socketId)
|
|
|
+ }
|
|
|
+
|
|
|
+ func nosigpipe() {
|
|
|
+ // prevents crashes when blocking calls are pending and the app is paused ( via Home button )
|
|
|
+ var no_sig_pipe: Int32 = 1;
|
|
|
+ setsockopt(self.socketId, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(sizeof(Int32)));
|
|
|
}
|
|
|
|
|
|
- static func writeASCII(socket: CInt, string: String) throws {
|
|
|
- if let nsdata = string.dataUsingEncoding(NSASCIIStringEncoding) {
|
|
|
- try writeData(socket, data: nsdata)
|
|
|
+ class func port_htons(port: in_port_t) -> in_port_t {
|
|
|
+ let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian
|
|
|
+ return isLittleEndian ? _OSSwapInt16(port) : port
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // MARK: - write methods
|
|
|
+
|
|
|
+ func writeUTF8(string: String) throws {
|
|
|
+ try self.writeString(string, withEncoding: NSUTF8StringEncoding)
|
|
|
+ }
|
|
|
+
|
|
|
+ func writeASCII(string: String) throws {
|
|
|
+ try self.writeString(string, withEncoding: NSASCIIStringEncoding)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func writeString(string: String, withEncoding encoding: NSStringEncoding) throws {
|
|
|
+ if let nsdata = string.dataUsingEncoding(encoding) {
|
|
|
+ try self.writeData(nsdata)
|
|
|
} else {
|
|
|
- throw SocketError.WriteFailed("dataUsingEncoding(NSASCIIStringEncoding) failed")
|
|
|
+ throw SocketError.WriteFailed("dataUsingEncoding(\(encoding)) failed")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static func writeData(socket: CInt, data: NSData) throws {
|
|
|
+ func writeData(data: NSData) throws {
|
|
|
var sent = 0
|
|
|
let unsafePointer = UnsafePointer<UInt8>(data.bytes)
|
|
|
while sent < data.length {
|
|
|
- let s = write(socket, unsafePointer + sent, Int(data.length - sent))
|
|
|
+ let s = write(self.socketId, unsafePointer + sent, Int(data.length - sent))
|
|
|
if s <= 0 {
|
|
|
- throw SocketError.WriteFailed(String.fromCString(UnsafePointer(strerror(errno))))
|
|
|
+ throw SocketError.WriteFailed(ErrorHandle.errorText)
|
|
|
}
|
|
|
sent += s
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static func acceptClientSocket(socket: CInt) throws -> CInt {
|
|
|
+ // MARK: -
|
|
|
+
|
|
|
+ func acceptClientSocket() throws -> Socket {
|
|
|
var addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
var len: socklen_t = 0
|
|
|
- let clientSocket = accept(socket, &addr, &len)
|
|
|
+ let clientSocket = accept(self.socketId, &addr, &len)
|
|
|
if clientSocket == -1 {
|
|
|
- throw SocketError.AcceptFailed(String.fromCString(UnsafePointer(strerror(errno))))
|
|
|
+ throw SocketError.AcceptFailed(ErrorHandle.errorText)
|
|
|
}
|
|
|
- Socket.nosigpipe(clientSocket)
|
|
|
- return clientSocket
|
|
|
+ self.nosigpipe()
|
|
|
+ return Socket(socketId: clientSocket)
|
|
|
}
|
|
|
|
|
|
- static func nosigpipe(socket: CInt) {
|
|
|
- // prevents crashes when blocking calls are pending and the app is paused ( via Home button )
|
|
|
- var no_sig_pipe: Int32 = 1;
|
|
|
- setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(sizeof(Int32)));
|
|
|
+ func peername() throws -> String {
|
|
|
+ var addr = sockaddr(), len: socklen_t = socklen_t(sizeof(sockaddr))
|
|
|
+ if getpeername(self.socketId, &addr, &len) != 0 {
|
|
|
+ throw SocketError.GetPeerNameFailed(ErrorHandle.errorText)
|
|
|
+ }
|
|
|
+ var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
|
|
|
+ if getnameinfo(&addr, len, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, NI_NUMERICHOST) != 0 {
|
|
|
+ throw SocketError.GetNameInfoFailed(ErrorHandle.errorText)
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let name = String.fromCString(hostBuffer) else {
|
|
|
+ throw SocketError.ConvertingPeerNameFailed
|
|
|
+ }
|
|
|
+
|
|
|
+ return name
|
|
|
}
|
|
|
+
|
|
|
+// func release() {
|
|
|
+// print("release")
|
|
|
+// shutdown(self.socketId, SHUT_RDWR)
|
|
|
+// close(self.socketId)
|
|
|
+// }
|
|
|
|
|
|
- static func port_htons(port: in_port_t) -> in_port_t {
|
|
|
- let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian
|
|
|
- return isLittleEndian ? _OSSwapInt16(port) : port
|
|
|
- }
|
|
|
+ // MARK: - basic receiving
|
|
|
|
|
|
- static func release(socket: CInt) {
|
|
|
- shutdown(socket, SHUT_RDWR)
|
|
|
- close(socket)
|
|
|
+ func nextInt8() -> Int {
|
|
|
+ var buffer = [UInt8](count: 1, repeatedValue: 0);
|
|
|
+ let next = recv(self.socketId as Int32, &buffer, Int(buffer.count), 0)
|
|
|
+ if next <= 0 {
|
|
|
+ return next
|
|
|
+ }
|
|
|
+ return Int(buffer[0])
|
|
|
}
|
|
|
|
|
|
- static func peername(socket: CInt) throws -> String? {
|
|
|
- var addr = sockaddr(), len: socklen_t = socklen_t(sizeof(sockaddr))
|
|
|
- if getpeername(socket, &addr, &len) != 0 {
|
|
|
- throw SocketError.GetPeerNameFailed(String.fromCString(UnsafePointer(strerror(errno))))
|
|
|
+ func nextLine() throws -> String {
|
|
|
+ var characters: String = ""
|
|
|
+ var n = 0
|
|
|
+ repeat {
|
|
|
+ n = self.nextInt8()
|
|
|
+ if ( n > 13 /* CR */ ) { characters.append(Character(UnicodeScalar(n))) }
|
|
|
+ } while n > 0 && n != 10 /* NL */
|
|
|
+ if n == -1 {
|
|
|
+ throw SocketError.RecvFailed(ErrorHandle.errorText)
|
|
|
}
|
|
|
- var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
|
|
|
- if getnameinfo(&addr, len, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, NI_NUMERICHOST) != 0 {
|
|
|
- throw SocketError.GetNameInfoFailed(String.fromCString(UnsafePointer(strerror(errno))))
|
|
|
- }
|
|
|
- return String.fromCString(hostBuffer)
|
|
|
+ return characters
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+func ==(socket1: Socket, socket2: Socket) -> Bool {
|
|
|
+ return socket1.socketId == socket2.socketId
|
|
|
+}
|
|
|
+
|
|
|
+class ErrorHandle {
|
|
|
+ class var errorText: String {
|
|
|
+ return String.fromCString(UnsafePointer(strerror(errno))) ?? "error converting error text from C String"
|
|
|
+ }
|
|
|
+}
|