Cross platform native mobile development with Rust

San Digital have extensive experience of mobile development and the use of Android as an embedded operating system. We treated android as a deployment target target for Rust firmware as well as writing our intricate real time communications component for both iOS and Android. This approach has advantages, you can maintain a single code base for a complicated communications layer, while also taking advantage of the full native capabilities of each platform

While building our Smart Home / Smart Energy platform for Presciense, we wanted to differentiate ourselves from the majority of products on the market that suffered from high latencies. Even while connected to the same network as many IoT gateways at the time, a simple operation like turning a light on and off could take multiple seconds to both perform and display on the application. The biggest contributor to these latencies (and also availability problems at scale) was the use of centralised, rather than peer to peer communications between the mobile handset and gateway. Along with this, most competitors used synchronous http or polling tricks to act on a device and receive data on its state. We came up with design utilising multiple factors to achieve low latency and reduce overall operational costs at high scales.

  1. Secure, zero password p2p communications while on the same network as the gateway
  2. Everything asynchronous, from invocations to state updates
  3. Aggressive approach to packet sizes and overall data use

Achieving these features and NFRs set over 2 mobile platforms (along with our linux based command line tools for testing) is a challenge for a small team, as to do so we needed:

  1. Gateway discovery, LAN and internet connectivity
  2. TLS 1.3, as it solved security issues that made our local PKI much simpler, but was not yet available on mobile platforms
  3. Highly efficient binary protocol using Captain Protocol and shared dictionaries
  4. Real time messaging utilising operational transformations within 50ms time windows to combine outgoing commands and state changes into their simplest possible representations
  5. A memory efficient DOM, updated asynchronously as devices connected to the gateway changed state

A component this intricate would be difficult to both develop, test and maintain over the 3 required platforms. Having this client functionality as a cross platform component that could also be tested on a non mobile platform independently of a user interface seemed to have a number of advantages, as did developing a client in the same language and ecosystem as our firmware - Rust.

Rust's pros and cons as a general purpose or systems programming language are discussed at great length in other places, but any language that has a bidirectional, transparent C ABI is highly amenable to integration into other language ecosystems. In the end, almost everything in modern software will be able to call into a Rust library.

An architecture for native cross platform development

Logical structure

Cross compilation

Rust, as a system programming language has supported native targets for a bewildering number of chip sets and platforms. Usefully, this includes iOS and 64 bit android - 32 bit android can be a little more difficult due to some interesting design choices in its LibC design but it only a common target in embedded systems rather than handsets or tablets nowadays.

For Android, you will require a .so, which needs no additional tooling. For iOS, you will need a universal static library generated by the cargo-lipo tool.

C ABI / Swift interface

Rust has a built in way of declaring functions to be accessible via a C ABI, but it is convenient to use additional tooling to generate and maintain a header file automatically for use by swift and other potential consumers. The sn-bingen crate is at the time of writing the most well maintained tool for this.

Android / JNI interface

As Android uses Java, you cannot directly call standard C ABI functions and must implement a JNI interface. The painful way to do this was via traditional tooling like SWIG, on top of the C interface. There is however now mature support for exporting a JNI interface directly from a Rust library using the reliably named JNI crate.

Rust interface

The two binding interfaces, C and JNI can be conditionally compiled depending on target architecture and share dispatch to a Rust API shared between them. Both binding interfaces require unsafe code due to raw pointer use, but this can be contained within the interface functions. Once you reach this point in the abstraction, you have the full power of standard rust and its ecosystem.

Jumping into the FHIR - type systems and objects

We have been doing a deep-dive on FHIR implementations and tooling following our initial FHIR investigation. A critical area of investigation for any system, particularly a large distributed system with many clients and peers that need longevity and guided evolution is its type system. Use of a strict type system can have many benefits - interoperability, governance, data quality, security, performance, developer experience, reduced component complexity and the ability to evolve services with confidence

Integrating with Events

The San Digital team has worked with numerous organisations in both the public and private sectors to transform their applications architecture into a flexible and business-focused model. Working with events at scale is key to maintaining individual teams' agility.

The process of building a mobile app

The team at San Digital has extensive experience developing apps for mobile devices, smartwatches, and smart TVs; using native and hybrid technologies (and everything in-between!) including using Rust for complex comms.

Low friction development environments

While setting up a sample project from an unnamed large vendor the other week I was disappointed by having to read large amounts of documentation and run various bits of script to install dependencies and set up infrastructure. We live in a world that has tools old (Make) and new (Docker) that can be combined to make onboarding engineers low or zero friction.

Cloud-native FHIR platforms

Continuing our series of posts on web protocols, we have been investigating more specialist protocols, in this case, "FHIR". We have produced a document based on our research, investigations and experience.

Team Structures

Multiple team structures can work to deliver software projects. There is no real one size fits all, however, there are common components that can be seen across different structures. At San Digital we believe that Engineer-led teams deliver great results for short duration high-impact projects.

Rules of the Road

This is called rules of the road but they aren't rules they're more guidelines, so they're rules until there is a good reason to ignore them.

Estimating and delivering defined outcomes

Recently there has been a shift away from time and materials projects towards defined outcomes, driven by various legislative changes, specifically IR35, but also cost control in the procurement function of larger organisations.

The San Digital Stack

San Digital has been designed as a remote first business from inception, on the assumption that it's easier to add offices later if they are necessary in an agile way. To work in collaborative way completely remotely takes a carefully thought out set of tools. Some of the ones that we use are really standard and some are a little more interesting.

Test driven design, or planning driven development

Design processes in most business software development resemble peer review or crowd-sourcing. A putative design is presented to peers, who will do their best to find problems while the originator of the design defends it against these challenges. Ideally, where they are demonstrated incorrect or incomplete the process will iterate and an updated design produced and defended.

A human view of computer vision at scale

Computers analysing and acting on what they see is not science fiction or even a new concept, it has been a reality of humankind's drive towards hyper-efficiency since around the time I was born.

Building scalable frontends

Scaling frontends is hard, actually scaling all codebases is hard, frontends just happen to be particularly visible and have a tighter feedback loop and a higher rate of change. As with all codebases, it is in principle possible to scale development through standards and integration processes, but these are a poor substitute for communication. Once development moves beyond the scope of a single team, either progress slows to take into account of different processes or implementations drift away from each other over time. Teams need to find a way to operate independently towards a goal.

Cross platform native mobile development with Rust

San Digital have extensive experience of mobile development and the use of Android as an embedded operating system. We treated android as a deployment target target for Rust firmware as well as writing our intricate real time communications component for both iOS and Android. This approach has advantages, you can maintain a single code base for a complicated communications layer, while also taking advantage of the full native capabilities of each platform

The evolution of web service protocols pt 2

At San Digital, some of us have been building things for people since the dawn of the web. Our historical perspective helps inform us about technological culture and trends today, almost compensating for the creaking knees.

The evolution of web service protocols pt 1

At San Digital, some of us have been building things for people since the dawn of the web. Our historical perspective helps inform us about technological culture and trends today, almost compensating for the creaking knees.