Protobuf Server

Quick Links:
iOS AppVapor ServerPerfect Server

One of the primary challenges in learning to work with Protocol Buffers is finding an API to communicate with. Adoption is currently not wide-spread and I had trouble finding public APIs willing to communicate via protobufs. So, I decided to create my own API using server-side Swift, thus fulfilling the requirements (tenuously) for calling myself a full-stack developer. I looked at two of the most popular Swift web server frameworks currently available Vapor and Perfect.

The Contenders

Both offer easy setup via assistant tools (Vapor Toolbox and Perfect Assistant, respectively). However, the default projects’ setups are philosophically quite different. Vapor’s default setup is a fully-fledge web server with example HTML files, CSS, communication over multiple protocols, etc. Perfect’s default setup is extremely spartan, relying on the developer to add features as needed. Going head-to-head on documentation, I’d give the slight edge to Vapor, but both clearly explain how to handle requests and responses. Vapor has the reputation for having a larger and more approachable support community if you have questions, but I didn’t engage with either community so I cannot verify this.

Adding Protobufs to either default project is as simple as adding a dependency for it to the Package.swift file and running swift build:

.Package(url: "https://github.com/apple/swift-protobuf.git", Version(0,9,29))

Note: At the time of writing, the Swift Protobuf team considers 0.9.29 to be their release candidate, and may soon move the project to a full 1.0 release.

Once that is done, running swift build in the terminal from the root directory of the project will download the Swift Protobuf library and integrate it with your project. At this point, you’re ready to include the model files created from the .proto definitions. If you are unfamiliar with how to compile .proto files into Swift, I recommend this article as a primer. Once the models are in your Sources/ directory, you can use them in your request handling code to serialize and deserialize binary messages.

Working with Protobufs

Making a simple API server actually involves gutting the default implementation of both the Vapor and Perfect default projects, which are both set up to serve HTML responses. If you want to send Protobuf data to the server from your client app, you will need to use a POST route, as GET cannot transmit binary data. If you are simply going to request data from the server then GET is appropriate. If you’re receiving data, simply access the body data of the POST request and feed it into the init(serializedData:) initializer of your Protobuf model object to get copy you can manipulate as you see fit.

To send a Protobuf response to the client app, just follow these general steps:

  1. Create a new instance of the Protobuf model object.
  2. Assign values to the properties.
  3. Call try object.serializedData() to get the Data representation of the object.
  4. Assign the data to the body of the response.
  5. Set the content-type header to application/octet-stream. (This is optional, but is a good practice.)
  6. Send the response with a 200 OK response code.

The iOS app linked above shows the basics of using Protobufs with URLSession to parse the object(s) being returned by the server.

Protobufs 💕 BLE

Bluetooth Low Energy + Protocol Buffers

a.k.a. BLEProtobufs

Proto-whats?

Protocol buffers (protobufs) are the hot new data transport scheme created and open-sourced by Google. At it’s core, it is a way to describe well-structured model objects that can be compiled into native code for a wide variety of programming languages. The primary implementation provided by Google supports Objective-C, but not Swift. However, thanks to extensible capabilities, Apple has been able to release a Swift plug-in that enables the protocol declarations to be compiled into Swift 3 code.

The companion to this is a framework (distributed along with the plug-in) that handles the transformation of the model objects to and from JSON or a compressed binary format. It is this later capability that we are interested for the purposes of communicating with Bluetooth Low Energy (BLE) devices.

The primary selling point of protobufs is their ability to describe the data contract between devices running different programming languages, such as an iOS app and a .Net API server. There are dozens of excellent blog posts scattered about the web on protobufs, so that is all I will say about them here.

Here is the protobuf declaration for the message I will be sending between devices via BLE:

[gist https://gist.github.com/JoshuaSullivan/3b5ee005775842eb49ef3197b5673a58 file=”Packet.proto”]

A Quick BLE Primer

There are two primary actors in a BLE network: peripherals and centrals. Peripherals are devices which exist to provide data; they advertise their presence for all nearby devices to see. When connected to, they deliver periodic data updates (usually on the order of 1-2 times per second or less). The second type of device is known as a “central”, it can connect to multiple peripherals in order to read data from and write data to them.

A peripheral’s data is arranged into semantically-related groups called “services”. Within each service exists one or more data points, known as characteristics. Centrals can subscribe to the peripheral’s characteristics and will be notified when the value changes. The BLE standard favors brevity and low power consumption, so the default data payload of a characteristic is only 20 bytes (not kilobytes).

Data from a characteristic is received as just that, a plain Data object containing the bytes of the value. Thus, it is often incumbent upon the iOS developer to parse this data into native types like Int, Float, String, etc. This process is complex and error-prone, as working with individual bytes is not a common use case for Swift.

Enter Protobufs

As I mentioned above, protocol buffers can encode themselves in a compressed binary format. This makes them ideal for data transport over BLE where space is at a premium. In the example project I link to below, I am transmitting a timestamp in the form of an NSTimeInterval (double) cast to a float and three Int32 values representing the spacial orientation of the host device. I converted the rotational units from floating-point radians to integer- based degrees because integers compress much better than floating-point numbers in protobufs. After I set the properties the model object, I request its Data representation, which I save as the value of the characteristic. The data payload ranges from 5 to 12 bytes, based largely on the magnitude of the orientation angles (larger magnitude angles compress less). This is well below the 20 byte goal size.

In action:
[gist https://gist.github.com/JoshuaSullivan/3b5ee005775842eb49ef3197b5673a58 file=”ProtobufEncoding.swift”]

On the central (receiving) end, the app is notified via a delegate callback whenever the subscribed characteristic’s value changes. I take the Data value from the characteristic argument and pass it to the initializer of the protobuf-backed model object. Voila! Instant model object with populated properties that I can do with what I please.

In action:
[gist https://gist.github.com/JoshuaSullivan/3b5ee005775842eb49ef3197b5673a58 file=”ProtobufDecoding.swift”]

I have a pair of example projects available. The sending app is designed to be run on an iOS device and the receiving app is a simple OS X command line app built using Swift Package Manager (because frameworks + Swift CLI apps = hell). I’ve written the core of both apps using only Foundation and CoreBluetooth, so the sending and receiving roles should be easy to swap between different platforms.

Peripheral (sender) app

Central (receiver) app