Skip to content
This repository has been archived by the owner on Sep 21, 2024. It is now read-only.

feat: Add noosphere crate-based Swift package #131

Merged
merged 6 commits into from
Nov 9, 2022
Merged

Conversation

cdata
Copy link
Collaborator

@cdata cdata commented Nov 9, 2022

This change introduces a counterpart Swift package for the noosphere crate, in service of imminent integration between our iOS app and the Noosphere body of libraries. The noosphere crate compiles for both web and native targets. On native targets, it has an auxiliary FFI interface making it suitable for use from any language that can talk C ABI (such as Swift). The change also includes an IndexedDB + WebCrypto key storage backend, to ensure parity in the API on web.

At this time, it's capabilities are limited, but the intention is for us to relocate most of the substantive implementation of noosphere-cli's commands under this crate, and re-implement noosphere-cli commands in terms of noosphere functionality.

Currently supported capabilities include: creating a key, and initializing a sphere.

Note that the Package.swift currently refers to a hand-uploaded artifact in a temporary Github repo that I intend to delete. This is necessary until we get a release of this library that uploads an artifact to an official Github release on the Noosphere repo, which can happen about as soon as this PR can be merged. At that time, we will update the Package.swift by hand to point to the new release.

I considered automating the Package.swift changes, but we can figure out if that would be useful down the road as we collectively hammer out our XCode workflow.

A word on FFI...

In Rust, it is very common practice to leverage macros to automatically generate bindings to other languages. A common approach for C ABI-speaking languages is to use a wonderful crate called cbindgen. The advantage of this approach is that it reduces a lot of code duplication. Instead of maintaining a C header file that matches, you can lean on codegen to produce the header for you.

However, one inescapable problem with FFI in Rust is that when you share access to memory with another language, you lose most of Rust's memory safety guarantees. So, FFI boundaries are almost always written in terms of unsafe Rust.

To partially alleviate this condition, we are using an alternative to cbindgen called safer-ffi. safer-ffi enables us to express our FFI interface in terms of safe Rust (with some constraints on what can be expressed), and moves all of the unsafe parts into codegen. This strikes an appealing balance from a safety perspective: code that is maintained by humans is able to be checked for memory safety, and code that is unsafe can (in theory) be patched when there is a bug. It may turn out that safer-ffi is burdensome, but for now I would like us to give it a shot.

Here is the C header that is produced by safer-ffi as of this change:

/*! \file */
/*******************************************
 *                                         *
 *  File auto-generated by `::safer_ffi`.  *
 *                                         *
 *  Do not manually edit this file.        *
 *                                         *
 *******************************************/

#ifndef __RUST_NOOSPHERE__
#define __RUST_NOOSPHERE__

#ifdef __cplusplus
extern "C" {
#endif

typedef struct NsSphereReceipt NsSphereReceipt_t;

/** \brief
 *  Read the sphere identity (a DID encoded as a UTF-8 string) from a
 *  [SphereReceipt]
 */
char const * ns_sphere_receipt_identity (
    NsSphereReceipt_t * const * sphere_receipt);

/** \brief
 *  Read the mnemonic from a [SphereReceipt]
 */
char const * ns_sphere_receipt_mnemonic (
    NsSphereReceipt_t * const * sphere_receipt);

/** \brief
 *  De-allocate a [SphereReceipt]
 */
void ns_sphere_receipt_free (
    NsSphereReceipt_t * sphere_receipt);

typedef struct NsNoosphereContext NsNoosphereContext_t;

/** \brief
 *  Initialize a brand new sphere, authorizing the given key to administer it.
 *  The returned value is a [SphereReceipt], containing the DID of the sphere
 *  and a human-readable mnemonic that can be used to rotate the key authorized
 *  to administer the sphere.
 */
NsSphereReceipt_t * ns_sphere_create (
    NsNoosphereContext_t * * noosphere,
    char const * owner_key_name);

/** \brief
 *  Join a sphere by initializing it and configuring it to use the specified
 *  key and authorization. The authorization should be provided in the form of
 *  a base64-encoded CID v1 string.
 */
void ns_sphere_join (
    NsNoosphereContext_t * * noosphere,
    char const * sphere_identity,
    char const * local_key_name,
    char const * authorization);

/** \brief
 *  Create a key with the given name in the current platform's support key
 *  storage mechanism.
 */
void ns_key_create (
    NsNoosphereContext_t const * noosphere,
    char const * name);

/** \brief
 *  Initialize a [NoosphereContext] and return a boxed pointer to it. This is
 *  the entrypoint to the Noosphere API, and the returned pointer is used to
 *  invoke almost all other API functions.
 * 
 *  In order to initialize the [NoosphereContext], you must provide two
 *  namespace strings: one for "global" Noosphere configuration, and another
 *  for sphere storage. Note that at this time "global" configuration is only
 *  used for insecure, on-disk key storage and we will probably deprecate such
 *  configuration at a future date.
 * 
 *  You can also initialize the [NoosphereContext] with an optional third
 *  argument: a URL string that refers to a Noosphere Gateway API somewhere
 *  on the network that one or more local spheres may have access to.
 */
NsNoosphereContext_t * ns_initialize (
    char const * global_storage_path,
    char const * sphere_storage_path,
    char const * gateway_url);

/** \brief
 *  De-allocate a [NoosphereContext]. Note that this will also drop every
 *  [SphereContext] that remains active within the [NoosphereContext].
 */
void ns_free (
    NsNoosphereContext_t * noosphere);


#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* __RUST_NOOSPHERE__ */

BREAKING CHANGE: The noosphere-api Client now holds an owned key instead of a reference.

BREAKING CHANGE: The `noosphere-api` Client now holds an owned key
instead of a reference.
jsantell
jsantell previously approved these changes Nov 9, 2022
Copy link
Contributor

@jsantell jsantell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on wrangling FFIs in noosphere!

rust/noosphere/src/ffi/sphere.rs Outdated Show resolved Hide resolved
rust/noosphere/src/key/insecure.rs Outdated Show resolved Hide resolved
rust/noosphere/src/key/interface.rs Outdated Show resolved Hide resolved
rust/noosphere/src/key/web.rs Outdated Show resolved Hide resolved
rust/noosphere/src/noosphere.rs Outdated Show resolved Hide resolved
rust/noosphere/src/ffi/mod.rs Show resolved Hide resolved
rust/noosphere/src/ffi/noosphere.rs Outdated Show resolved Hide resolved
rust/noosphere/src/ffi/noosphere.rs Show resolved Hide resolved
Package.swift Show resolved Hide resolved
rust/noosphere/src/lib.rs Show resolved Hide resolved
@jsantell
Copy link
Contributor

jsantell commented Nov 9, 2022

Note: the FFI'd opaque structs are not namespaced:

 SphereReceipt_t * noosphere_create_sphere (
    NoosphereContext_t * * noosphere,
    char const * owner_key_name);

While functions are (noosphere_*) the structs share global namespace in C, and for more generic structs, a collision could occur (not likely for NoosphereContext, but down the road a Version or Storage struct may). I'd say consider NS* prefix, though that's a rather overloaded prefix, and the C-style ns_* prefix (ns_NoosphereContext) looks tragic (maybe C-case for structs as well e.g. ns_noosphere_context?)

Also, for the C++ export, consider a namespace e.g. namespace noosphere {} when compiling as C++. Could also map from C-case ns_noosphere_context to C++ noosphere::NoosphereContext for context-appropriate casing. I know cbindgen has several flags for controlling these outputs, not sure about safer_ffi

@jsantell jsantell self-requested a review November 9, 2022 20:06
@github-actions github-actions bot mentioned this pull request Jul 1, 2023
@github-actions github-actions bot mentioned this pull request Aug 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
Status: 🌱 Done
Development

Successfully merging this pull request may close these issues.

3 participants