A POSIX Socket API Wrapper in Swift
This blog post is the first in a five part series about POSIX Sockets in Swift and our SocketWrapper Library:
A while back I held a talk at the CocoaHeads Austria meetup hosted at our office about some lessons we learned while writing a protocol oriented wrapper in Swift around the POSIX socket API. Maybe some of these findings are of interest to others, so I thought I’d blog about them here since there’s no recording of the talk and even if there were, the talk was in German. Instead of dumping a long transcript, I’m planning on writing multiple more structured blog posts that each document a specific point I personally find interesting about the code. You can find the code for all this in our SocketWrapper repository on GitHub.
The resulting library itself probably isn’t particularly useful to most Swift developers because far more complete and far better general purpose libraries exist out there. In fact, you probably shouldn’t use it in production code (INSERT LIABILITY DISCLAIMER HERE). Also, it is far from complete in terms of features, e.g. right now it only supports TCP.
The reason we wrote it is because we wanted a library to test very specific behavior of Little Snitch’s network traffic filtering and for that we wanted complete control over the low-level calls like connect()
or send()
as defined in the POSIX API.
With that out of the way, let’s dive into the topic of this first blog post: writing an object oriented wrapper around the POSIX socket API in Swift. (The protocol oriented stuff that builds on that is covered in Using Swift Protocols to Separate Socket Functionality.)
Why a wrapper?
First off, why do we even want a wrapper around the POSIX socket API? Can’t we just use the C API directly? We certainly could but there are a couple of very desirable things that can be achieved by a thin wrapper that I’ll simply call Socket
:
- A POSIX socket is a file descriptor, which is represented as an
Int32
in Swift (int
in C) and there’s nothing special about it, i.e. given anyInt32
, we can’t tell whether it is a socket file descriptor, the count of an array or the number of cupcackes I ate today (so far). We’d have to read the documentation of each function to know that theseInt32
parameters and/or return values are special. It sure would be nice if we had a separate type that the compiler knows about and could enforce in the code we’re writing. Also, the self-documenting nature of function and method signatures is greatly enhanced by this:
// ⛔️ Bad:
func foo(socket: Int) { ... }
foo(42) // Works just fine.
// ✅ Good:
func bar(socket: Socket) { ... }
bar(42) // Compiler error.
- The POSIX API is a series of C functions defined in
/usr/include/sys/socket.h
. With a separateSocket
type in Swift, we can add instance methods that call through to these free functions, which makes it clear what we can do with a givenSocket
instance:
// ⛔️ Bad:
send(socket, data)
// ✅ Good:
socket.send(data) // Bonus: Code completion can help here.
- Errors are reported by the POSIX socket API by returning
-1
from the functions and setting the globalerrno
to a value that must be checked against pre-defined constants to find out what error actually occurred. This means that each function call must be accompanied by at least a check against the magic-1
, hopefully followed by some more error handling code. Again, we’d have to take a look at the documentation to know how the error handling works. By leveraging Swift’s error handling model we can provide a better API that is self-documenting:
// ⛔️ Bad:
func send1(socket: Int, data: NSData) -> Int { ... }
let bytesSent = send1(socket, data)
if bytesSent == -1 {
let error = errno // The magic global error number.
print("Error \(error) happened.")
} else {
print("\(bytesSent) bytes were sent.")
}
// ✅ Good:
func send2(socket: Int, data: NSData) throws -> Int { ... }
do {
let bytesSent = try send2(socket, data)
print("\(bytesSent) bytes were sent.")
} catch {
print("Error \(error) happened.")
}
class Socket
or struct Socket
?
With these advantages laid out, we only have to decide what kind of type our Socket
should be before we can start implementing our wrapper. Swift offers four different options we could use for this: class
, enum
, protocol
, and struct
.
While maybe there might exist some theoretically possible implementations with an enum
or a protocol
for this, class
and struct
are the two candidates that seem most practical to me. Which one of these two to pick always makes for an interesting discussion, but I chose to go with a struct
because there’s no reason why our Socket
should be a reference type, therefore it shouldn’t be a class
.
In my mind, the magic Int32
that is the file descriptor is itself really just a reference: There’s no real difference between an Int32
we get from a call to socket()
that must be deleted by a call to close()
and a void *
we get from a call to malloc()
that must be deleted by a call to free()
. The Int32
/void *
is just a value type that represents a – well – reference to a reference type, i.e. the socket and chunk of memory, respectively.
Yes, having a class
that manages a pointer to memory and that frees it in its deinit
is a sensible thing to do, but that is not exactly what we have here. Instead, we want exact control over when close()
is called and we don’t want ARC to decide when all references to the Socket
go away before deinit
is called and closes the underlying socket. Therefore, we need a Socket.close
method anyways that can be called at any point, rendering the “close in deinit
” argument moot.
Another advantage: I like to imagine that since our struct Socket
will only have a single Int32
property (the low-level magic socket file descriptor) it’s very easy for the compiler to optimize all of the wrapper away because it can just pass around that integer on the stack or in a register and never has to deal with memory on the heap that could be accessed by other threads. That sounds like optimal performance to me. Let’s hope it’s true.
Basic Implementation
After all this discussion, the implementation should be quite unsurprising:
import Darwin
struct Socket {
let fileDescriptor: Int32
init(fileDescriptor: Int32) {
self.fileDescriptor = fileDescriptor
}
}
We actually don’t have to implement the initializer because Swift automatically generates struct
initializers as long as we don’t write any of our own. But we will write a custom initializer in another blog post, so we might as well implement it right away.
Socket Functionality as Methods
Like Natasha The Robot, one of the things I love using Swift extensions for is grouping related functionality together. The methods for sending, receiving, server stuff or client stuff are therefore in their own separate extensions. First up, connecting a TCP client socket to a server:
extension Socket {
/// Connects the socket to a peer.
///
/// - SeeAlso: `connect(2)`
func connect(address address: UnsafePointer<sockaddr>, length: socklen_t) throws {
guard Darwin.connect(fileDescriptor, address, length) != -1 else {
throw Error.ConnectFailed(code: errno)
}
}
}
Don’t mind the types of the parameters just now. After all, this is only a thin wrapper around the POSIX socket API, so we’ll use the exact same parameters as the functions we’re wrapping for now.
Error Handling in Action
In the above code you can see the error handling in action. I omitted the error type used until now, so here it is – in a separate extension
, of course:
extension Socket {
enum Error: ErrorType {
case ConnectFailed(code: errno_t)
case SendFailed(code: errno_t)
// More error cases.
...
}
}
(Note: Not all possible errors that are thrown in our library have an associated errno_t
, so every case that has one must declare an associated value. I don’t particularly like that and it would be possible to use a struct
for the errors that have an errno_t
and a separate enum
for those that don’t, but that has other implications that I like even less.)
By using an enum
within the struct
(it’s in an extension
but it still belongs to the struct
) the complete type name is Socket.Error
, so it’s clear from looking at it that these errors are related to the Socket
struct without spelling it out in the name. At the call site it looks like this:
let socket: Socket = ...
do {
try socket.connect()
// Other throwing calls.
...
} catch Socket.Error.ConnectFailed {
print("A very specific error occurred: connect() call failed")
} catch let error as Socket.Error {
print("Some other Socket.Error occurred: \(error)")
} catch {
print("Any other error occurred: \(error)")
}
There’s a C function called strerror()
that converts these errno_t
types into a human-readable string. That sounds like a good fit for a CustomStringConvertible
conformance of Socket.Error
:
extension Socket.Error: CustomStringConvertible {
var description: String {
// Local helper function:
func errorString(code: errno_t) -> String {
return String(UTF8String: strerror(code))!
}
switch self {
case .ConnectFailed(let code):
return "connect() failed: " + errorString(code)
case .SendFailed(let code):
return "send() failed: " + errorString(code)
// More error cases.
...
}
}
}
Improved Parameters
Let’s take a look at the method for sending data:
extension Socket {
/// Sends the data in the given `buffer`.
///
/// - SeeAlso: `send(2)`
func send(buffer: UnsafeBufferPointer<Byte>, flags: Int32 = 0) throws -> Int {
let result = Darwin.send(fileDescriptor, buffer.baseAddress, buffer.count, flags)
guard result != -1 else {
throw Error.SendFailed(code: errno)
}
return result
}
}
This looks quite similar to Socket.connect
above, but there are a couple of things to note about the type signature here:
Unlike the implementation of Socket.connect
above, this method does not use the exact same parameter types as the wrapped function, which comes through to Swift as:
func send(socket: Int32, buffer: UnsafePointer<Void>, length: Int, flags: Int32) -> Int
Let’s go through them:
socket: Int32
: This is self.fileDescriptor
in our wrapper, so we don’t need that.
buffer: UnsafePointer<Void>
and length: Int
: These two middle parameters are something that is common enough – at least in C APIs – that there’s a separate type in Swift just for interoperating with these kinds of functions: UnsafeBufferPointer
. That’s just an UnsafePointer
plus a count (a.k.a. length). If it seems wasteful to introduce a new type just to combine these two things, think again: An UnsafePointer
is generic over the type of elements it contains, so knowing the count of elements makes it possible for UnsafeBufferPointer
to conform to CollectionType
, supercharging it with all kinds of functionality for free like subscripting to get an element at a given offset, using it in a for
loop, or using methods like map
, filter
, prefix
or split
.
Also, the buffer
parameter is a void *
in the C API and comes through as UnsafePointer<Void>
to Swift, so you’d probably expect our wrapper to use UnsafeBufferPointer<Void>
here, but instead it’s UnsafeBufferPointer<Byte>
. Which begs the question: What is Byte
? In short (no pun intended): It’s just a placeholder with an expressive name for anything that is a single byte in size. In long (…): It’s a topic for the next blog post.
flags: Int32
: Many C APIs have something like this to modify the behavior of the function. These flags take magic values that you have to look up in the documentation, which then either lists an arcane set of options or (surprisingly often) simply states that the flags are “reserved for future use”. For us, that means we often pass 0
or the only sensible combination we can find. That leaves us with one of three things we can do in our wrapper:
- Add a
flags
parameter to the method and let the caller decide what to use (just like in C). - Don’t add a
flags
parameter and hard-code whatever seems to fit most cases into the method. - Add a
flags
parameter and give it a default value that fits most cases. The caller can then still override that if necessary.
The choice is obvious to me.
Wrapping up
Most of the rest of the wrapper code isn’t of much interest, it’s just more of what you saw above. You can find the struct Socket
code that I described in this post in Socket.swift (commit edcd570).
(There are subscript implementations for socket and file descriptor options in there that I don’t like because they log errors instead of reporting them properly. I should rewrite them as methods that throw
at some point.)
What we have now is a thin wrapper around the POSIX socket API that makes it a bit more convenient to use in Swift. But there are still some open questions about the implementation of what I’ve shown here, not to speak of what kind of API can be created with just a little bit of protocol oriented awesomeness.
There are three things I promised to address in future blog posts:
- The nature of the
Byte
type used as the generic parameter of one ofSocket.send
’s parameters. (Update: Modeling Simple Data in Swift: struct versus typealias) - A mysterious initializer for
struct Socket
that wasn’t shown here. (Update: Low-Level Network Address Resolving in Swift) - A protocol oriented API that builds on this
struct Socket
wrapper. (Update: Using Swift Protocols to Separate Socket Functionality)
Let’s hope I’ll get to it soon…