Skip to content

Instantly share code, notes, and snippets.

@Bashta
Created February 23, 2015 21:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bashta/9ffe33606bbef49bbb7c to your computer and use it in GitHub Desktop.
Save Bashta/9ffe33606bbef49bbb7c to your computer and use it in GitHub Desktop.
import Darwin
/**
Representation of a connected socket or error.
Either a Descriptor with an associated value for the POSIX
socket file descriptor or an Error with a string describing
the error.
*/
enum Connection {
case Descriptor(CInt)
case Error(String)
}
/**
Representation of a socket or error.
Either a Descriptor with an associated value for the POSIX
socket file descriptor or an Error with a string describing
the error.
*/
enum Socket {
case Descriptor(CInt)
case Error(String)
/**
Creates a new, unbound POSIX socket and encapsulates the file descriptor.
*/
init() {
let newSocket = Darwin.socket(AF_INET, SOCK_STREAM, 0)
if newSocket < 0 {
self = .Error(String.fromCString(strerror(errno)))
} else {
self = .Descriptor(newSocket)
}
}
/**
Binds a socket to the specified port, listening on the interface specified by `address`.
*/
func connect(port: CUnsignedShort, address: CString = "127.0.0.1", fn: Socket -> Socket = { $0 }) -> Socket {
switch self {
case let .Descriptor(sock):
let addr = inet_addr(address)
if addr == __uint32_t.max {
return .Error("unable to parse address")
}
var server_addr = sockaddr()
server_addr.sa_family = sa_family_t(AF_INET)
server_addr.sin_port = port
server_addr.sin_addr = addr
let err = bind(sock, &server_addr, socklen_t(sizeof(sockaddr)))
if err != 0 {
return .Error(String.fromCString(strerror(errno)))
}
return fn(self)
case .Error:
return self
}
}
/**
Listens for connections on the socket, pulling from a queue with
a maximum length specified by `limit`. Per the man page, `limit`
is silently limited to 128.
*/
func listen(limit: CInt = 128, fn: Socket -> Socket = { $0 }) -> Socket {
switch self {
case let .Descriptor(sock):
let success = Darwin.listen(sock, limit)
if success != 0 {
return .Error(String.fromCString(strerror(errno)))
} else {
return self
}
case .Error:
return self
}
}
/**
Accepts a connection on a socket. Turns a Socket into a Connection.
*/
func accept(fn: (Connection, sockaddr) -> Connection = { c, _ in c }) -> Connection {
switch self {
case .Descriptor(let sock):
var address = sockaddr()
var length = socklen_t(sizeof(sockaddr))
let newSocket = Darwin.accept(sock, &address, &length)
if newSocket < 0 {
return .Error(String.fromCString(strerror(errno)))
} else {
return fn(.Descriptor(newSocket), address)
}
case .Error(let str):
return .Error(str) // self
}
}
/**
Closes a connection.
*/
func close(fn: Socket -> Socket = { $0 }) -> Socket {
switch self {
case let .Descriptor(sock):
if Darwin.close(sock) != 0 {
return .Error(String.fromCString(strerror(errno)))
} else {
return fn(self)
}
case .Error:
return self
}
}
}
extension Socket: LogicValue {
/**
Gets the logical value of the socket.
Returns `true` if the Socket represents a valid descriptor
and `false` if the Socket represents an error.
*/
func getLogicValue() -> Bool {
switch self {
case .Descriptor:
return true
case .Error:
return false
}
}
}
// Connection Extensions
// ---------------------
// Methods on the Connection enum are here because of the forward declaration
// required for the Socket implementation.
extension Connection {
/**
Reads data from the connection. The data read from the connection is passed as
the second parameter to the success function.
The `success` parameter is a function to be called upon successful reading.
Returns a `Connection` monad
*/
func read(fn: (Connection, String) -> Connection = { c, _ in c }) -> Connection {
// Use this to quickly zero out memory if we reuse the same buffer per read
// memset(&array, CInt(sizeof(CChar)) * CInt(array.count), 0)
//
// This is less Swiftish but might be faster...
//
// var buff = calloc(UInt(sizeof(CChar)), 256)
// var n = read(conn, buff, 255)
// var charBuf = UnsafePointer<CChar>(buff)
// charBuf[n] = 0
// var s: String = String.fromCString(charBuf)
// println(s)
switch self {
case .Descriptor(let sock):
var buffer = new CChar[256]
let bytesRead = Darwin.read(sock, &buffer, UInt(buffer.count))
if bytesRead < 0 {
return .Error(String.fromCString(strerror(errno)))
} else {
buffer[bytesRead] = 0
return fn(self, buffer.withUnsafePointerToElements { String.fromCString($0) })
}
case .Error:
return self
}
}
/**
Writes the contents of a string to the connection.
The `response` parameter is the string to be written; `success`
is the function to be called upon successful writing.
Returns a `Connection` monad.
*/
func write(response: String, fn: Connection -> Connection = { $0 }) -> Connection {
switch self {
case .Descriptor(let sock):
let bytesOut = UInt8[](response.utf8)
let bytesWritten = Darwin.write(sock, bytesOut, UInt(bytesOut.count))
if bytesWritten < 0 {
return .Error(String.fromCString(strerror(errno)))
} else {
return fn(self)
}
case .Error:
return self
}
}
/**
Closes the connection.
The `success` parameter is a function to be called upon successful closure of the connection.
Returns A `Connection` monad.
*/
func close(fn: Connection -> Connection = { $0 }) -> Connection {
switch self {
case let .Descriptor(sock):
if Darwin.close(sock) != 0 {
return .Error(String.fromCString(strerror(errno)))
} else {
return fn(self)
}
case .Error:
return self
}
}
}
extension Connection: LogicValue {
func getLogicValue() -> Bool {
/**
Gets the logical value of the connection.
Returns `true` if the Connection represents a valid descriptor,
and `false` if the Connection represents an error
*/
switch self {
case .Descriptor:
return true
case .Error:
return false
}
}
}
// C sockaddr struct Extension
// ---------------------------
// The Swift type checker doesn't allow us to use sockaddr and sockaddr_in
// interchangably, so the following extension destructures port and address
// types and sets the appropriate bytes in sa_data to use with the socket
// system calls.
extension sockaddr {
init () {
sa_len = 0
sa_family = 0
sa_data = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
}
var sin_port: in_port_t {
/*! Gets the socket's port number by restructuring bytes in the sa_data field.
* \returns The socket's port number as a 16-bit unsigned integer
*/
get {
// TODO: Make sure this is done in a machine-architecture indepenent way.
return (UInt16(sa_data.1.asUnsigned()) << 8) + UInt16(sa_data.0.asUnsigned())
}
/*! Sets the socket's port number by destructuring the first two bytes of the
* sa_data field.
* \param newValue The port number as a 16-bit unsigned integer
*/
set {
// TODO: Make sure this is done in a machine-architecture indepenent way.
sa_data.0 = CChar((newValue & 0xFF00) >> 8)
sa_data.1 = CChar((newValue & 0x00FF) >> 0)
}
}
var sin_addr: in_addr_t {
get {
return (
// Restructures bytes 3 through 6 of sa_data into a 32-bit unsigned
// integer IPv4 address
// TODO: This should probably go through ntohs() first.
in_addr_t(sa_data.2) >> 00 + in_addr_t(sa_data.3) >> 08 +
in_addr_t(sa_data.4) >> 16 + in_addr_t(sa_data.5) >> 24
)
}
set {
// Destructures a 32-bit IPv4 address to set as bytes 3 through 6 of sa_data
// TODO: This should probably go through htons() first.
sa_data.2 = CChar((newValue & 0x000000FF) >> 00)
sa_data.3 = CChar((newValue & 0x0000FF00) >> 08)
sa_data.4 = CChar((newValue & 0x00FF0000) >> 16)
sa_data.5 = CChar((newValue & 0xFF000000) >> 24)
}
}
/**
The human-readable, dotted quad string representation of the socket's IPv4 address.
*/
var addressString: String {
let data = self.sa_data
return "\(data.2).\(data.3).\(data.4).\(data.5)"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment