Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Last active February 15, 2023 12:14
Show Gist options
  • Save chriseidhof/fa706201ced29a492932 to your computer and use it in GitHub Desktop.
Save chriseidhof/fa706201ced29a492932 to your computer and use it in GitHub Desktop.
// Created by Chris Eidhof on 04-01-16.
// Copyright © 2016 Chris Eidhof. All rights reserved.
//
// Large parts copy/pasted from https://github.com/Eonil/TCPIPSocket.Swift
import Foundation
struct TCPIPSocketAddress {
init(_ a:UInt8, _ b:UInt8, _ c:UInt8, _ d:UInt8) {
let a1 = UInt32(a) << 24
let b1 = UInt32(b) << 16
let c1 = UInt32(c) << 8
let d1 = UInt32(d)
number = a1 + b1 + c1 + d1
}
var number:UInt32 /// Uses host-endian.
static let localhost = TCPIPSocketAddress(127,0,0,1)
}
final class TCPIPConnection {
static let bufLen = 1024
private var data: [UInt8] = Array(count: bufLen, repeatedValue: 0) // Keep it around for efficiency
private let _conn: Int32
private init(_ conn: Int32) throws {
_conn = conn
try postconditionDarwinAPICallResultCodeState(_conn > 0)
}
func read() -> ReadResult {
while true {
let n = Darwin.read(_conn, &data, TCPIPConnection.bufLen)
guard n != 0 else { return .Empty }
guard n > 0 else { return .Error("Read error \(n)") }
return .Chunk(AnySequence(data.prefix(n)))
}
}
func write(bytes: [UInt8]) throws {
let r = Darwin.write(_conn, bytes, bytes.count)
try postconditionDarwinAPICallResultCodeState(r == bytes.count)
}
func close() {
Darwin.close(_conn)
}
}
enum ReadResult {
case Error(ErrorType)
case Empty
case Chunk(AnySequence<UInt8>)
}
final class TCPIPSocket {
let socketDescriptor:Int32
private var ds : dispatch_source_t?
var handler: TCPIPConnection throws -> () = { _ in () }
init(address: TCPIPSocketAddress, port: UInt16) throws {
socketDescriptor = Darwin.socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)
try postconditionDarwinAPICallResultCodeState(socketDescriptor != -1)
try bind(address, port)
}
deinit {
let r = close(socketDescriptor)
try! postconditionDarwinAPICallResultCodeState(r == 0)
}
private func bind(address: TCPIPSocketAddress, _ port: UInt16) throws {
let f = sa_family_t(AF_INET)
let p = in_port_t(port.bigEndian)
let a = in_addr(s_addr: address.number.bigEndian)
var addr = sockaddr_in(sin_len: 0, sin_family: f, sin_port: p, sin_addr: a, sin_zero: (0,0,0,0,0,0,0,0))
let sz = socklen_t(sizeofValue(addr))
let r = Darwin.bind(socketDescriptor, unsafePointerCast(&addr), sz)
try postconditionDarwinAPICallResultCodeState(r == 0)
}
func listen(handler: TCPIPConnection throws -> ()) throws {
self.handler = handler
let r = Darwin.listen(socketDescriptor, SOMAXCONN)
try postconditionDarwinAPICallResultCodeState(r == 0)
ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, UInt(socketDescriptor), 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))
guard let source = ds else { throw "Couldn't create dispatch source" }
dispatch_source_set_event_handler(source, self.handleConnection)
dispatch_resume(source)
}
func handleConnection() {
do {
let connection = try TCPIPConnection(Darwin.accept(socketDescriptor, nil, nil))
try handler(connection)
} catch {
print(error)
}
}
}
extension String: ErrorType { }
private func postconditionDarwinAPICallResultCodeState(ok:Bool) throws {
if ok { return }
let n = Darwin.errno
let s = String(UTF8String: strerror(n))
throw "Darwin API call error: (\(n)) \(s)"
}
private func unsafePointerCast<T,U>(p:UnsafePointer<T>) -> UnsafePointer<U> {
return UnsafePointer<U>(p)
}
// Actually using the socket
let socket = try TCPIPSocket(address: TCPIPSocketAddress.localhost, port: 2016)
try socket.listen { connection in
var result: [UInt8] = []
var done = false
while !done {
switch connection.read() {
case .Empty: done = true
case .Chunk(let chunk): result += chunk
case .Error(let error): throw error
}
}
print(result)
sleep(10)
try connection.write(result.reverse())
connection.close()
}
sleep(100)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment