Alex Moiseenko
techpro.studio
Published in
4 min readFeb 15, 2020

--

I received a task to run Azure IoT C SDK on an independent watchOS device. SDK has a CocoaPod for iOS / OSX. It sounded like a simple task. In fact, it was not. So let’s go.

Attempt #1

The first idea was to get source code for iOS/OSX and append one more target. I got the source code but found out that the code relies on this header <CFNetwork/CFSocketStream.h> which is unavailable for watchOS. It’s odd because if you open https://developer.apple.com/documentation/cfnetwork, you can find that this framework is available from watchOS 2+. If you open the CFNetwork framework in watchOS SDK, you will find out that it’s empty. An interesting thing is that AzureSDK uses only one const string from CFNetwork. And it looked like a win to hardcode it for watchOS target and move on. But in fact, it was a bad idea because even in watchOS Simulator it caused a crash with EXC_BAD_ACCESS or EXC_BAD_INSTRUCTION. So hiding CFNetwork in SDK makes a sense. But Apple you have declared…

Attempt #2

Source code for iOS/OSX was not working on watchOS. But AzureIOT C SDK is a “Lego” for any device. To port this SDK to new platform, you need to provide TLS and I/O layers and the other small things. But the main task in porting is TLSIO. SDK contains a lot of ready to use TLS layers for open-source libs like OpenSSL, BearSSL, WolfSSL, etc. Also, SDK contains I/O layer based on BSD sockets. So since I did not want to write a custom TLSIO layer for watchOS, I decided to mix BSD sockets + BearSSL. I thought it was a victory when I launched this code on the watchOS simulator. I was too happy when it worked, but the reality was different. When I tried to run that code on watchOS, it did not work. “gethostbyname” and “connect” functions dropped errors. Well, I thought “Ok, Apple, watchOS started to be an independent since 6.0, so maybe it makes sense to block BSD sockets API”.

Attempt #3

Since BSD socket API + BearSSL did not work on the real watch, I decided to write custom TLSIO using Network.framework https://developer.apple.com/documentation/network.

Objective-C API was, in fact, C API, so I could use it in azure C SDK.

Apple declares that this framework is available from watchOS 6+. I thought “WatchOS was not an independent, it became so from version 6, so it is a little bit logical that Network framework should work”. Before starting to write a custom TLSIO layer, I decided to test the Network.framework. TLS and TCP socket doesn’t work on watchOS! According to comments on the internet, UDP doesn’t work neither. Hmmm… Apple, why do you do so?

Attempt #4 (Part 1)

Since I couldn’t adapt C SDK for watchOS, I decided to use Microsoft’s Azure Rest API. URLSession 100% works on real watches. Everything worked fine on watches but I found out that Microsoft doesn’t expose part of the necessary API. Microsoft, why? That API is available over MQTT or AMQP protocol only. Thus, working in the HTTP direction was suspended.

Attempt #5

Microsoft provides MQTT over TLS or over Websockets. Since the TLS socket doesn’t work on watchOS, my last chance was MQTT over Websockets. Also, I found out that Apple appended native Websocket API in URLSession. Since URLSession HTTP API works well, I thought that WebSocket API would work well too. I used the CocoaMQTT library to connect to Azure. However, due to lack of documentation, my half a day attempts to connect to azure failed. Well, it was ridiculous. I looked attentively at C SDK and found out that it uses this parameter “?iothub-no-client-cert=true” in URL. I tried and my app finally connected to Azure. Also, later I found the requirements of AzureIoT SDK for Java code. We should use this param while connecting to MQTT over WebSocket. Microsoft, why don’t you say about it in main docs? Ok, the app connects to MQTT, it works fine on watchOS. We can use all the API that we need using MQTT. And it seems like a victory. But unfortunately, if you totally disconnect your watch from iPhone, the watch becomes independent and starts using its own connection, so everything stops working. Apple, why?

Attempt #4 (Part 2)

Since WebSockets failed, HTTP direction was resumed. HTTP API works fine on the independent watches. I mentioned above that Microsoft didn’t provide all the necessary API via HTTP. So the solution was to create an HTTP proxy server that connects to azure using MQTT.

Apple’s answer regarding situations with socket API:

Well, we asked Apple about all of this, and a guy from the team responded to us that low-level socket API works on watchOS only for audio streaming. More details you can find here: https://forums.developer.apple.com/message/398837#398837

Conclusions

  1. If you have an audio streaming app, you can use the low-level network API.
  2. If you are developing not an audio streaming app and you need a network connection on “Independent” watchOS devices, you can rely on URLSession HTTP API only.
  3. If you can use your app with a Phone, you can also use URLSession WebSocket API.
  4. In most cases, “Independent” watchOS has fewer I/O possibilities than even browsers.
  5. Apple and Microsoft are two IT monsters and they still don’t provide good documentation. If they do, much less time would be spent on this.

--

--