Zum Hauptinhalt springen

Cwtch Stable API Design

· 18 Minuten Lesezeit
Sarah Jamie Lewis

Cwtch grew out of a prototype and has been allowed to evolve over time as we discovered better ways of implementing safe and secure metadata resistant communications.

As we grew, we inserted experimental functionality where it was most accessible to place - not, necessarily, where it was ultimately best to place it - this has led to some degree of overlapping, and inconsistent, responsibilities across Cwtch software packages.

As we move out of Beta and towards Cwtch Stable it is time to revisit these previous decisions with both the benefit of hindsight, and years of real-world testing.

In this post we will outline our plans for the Cwtch API that realign responsibilities, and explicitly enable new functionality to be built in a modular, controlled, and secure way. In preparation for Cwtch Stable, and beyond.

Clarifying Terminology

Over the years we have evolved how we talk about the various parts of the Cwtch ecosystem. To make this document clear we have revised and clarified some terms:

  • Cwtch refers to the overall ecosystem including all the component libraries, bindings, and the flagship Cwtch application.
  • Cwtchlib refers to the reference implementation of the Cwtch Protocol / Application framework, currently written in Go.
  • Bindings refers to C/Java/Kotlin/Rust bindings (primarily libcwtch-go) that act as an interface between Cwtchlib and downstream applications.
  • CwtchPeer is where the reference Cwtch API is defined. It is responsible for managing the state of a single Cwtch Profile, persistence (e.g. storing messages), and automatically reacting to certain messages like message acknowledgements and providing public profile attributes (e.g. profile display name).
  • ProtocolEngine is responsible for maintaining networking resources like listening threads, peer connections, ephemeral server connections. At present, ProtocolEngine is also responsible for automatically responding to certain kinds of messages like providing file chunks for shared files.

Tenets of the Cwtch API Design

Based on the tenets we have laid out for the Path to Cwtch Stable, we have adopted the following guiding principles for a new API design:

  • Robustness - new features and functionality can be implemented in Cwtch without adding new functions or dependencies to existing Cwtch interfaces.
  • Completeness - all behaviour is either defined in the official library, or explicitly deferred to applications, no special behaviour is implemented by intermediate wrappers.
  • Security – experiments should not compromise existing Cwtch functionality - and should be able to be turned on/off at any time without issue.

The Cwtch Experiment Landscape

A summary of the experiments that are currently implements or in design, and the changes to the code that were required to support them.

  • Groups – the very first prototypes of Cwtch were designed around group messaging and, as such, multi-party chats are the most integrated experiment within Cwtch sharing interfaces with P2P chat and requiring specialized ProtocolEngine functionality to manage ephemeral connections and antispam tokens, including the introduction of new peer events like NewMessageFromGroup.
    • Hybrid Groups - we have plans to upgrade the Groups experience to a more flexible “hybrid-groups” protocol which requires additional custom hook-response that needs to be tightly controlled and isolated from other parts of the system.
  • Filesharing – like Groups, Filesharing is a cross-cutting feature that required new APIs, new Hooks into Peer Events, and additional capability in ProtocolEngine.
  • Profile Images – based on Filesharing and the core get/val functionality, there are only a few small parts of the codebase that are explicitly dedicated to profile images, and these are all event-based reactions that currently reside in the event-decoration module of licwtch-go, but could easily be moved to a standalone module if a hook-based API was available.
  • Server Hosting – the only example of an Application-level experiment in Cwch at present. This functionality requires no changes to the cwtchlib module, but is mainly implemented in the libcwtch-go bindings themselves. Ideally this functionality would be moved into a standalone package.
  • Message Formatting – notable as the the main example of a former experimental-functionality that was promoted to an optional feature, but because it is entirely UI based in implementation there are few insights that can be gained from its history
  • Search / Microblogging – proposed features that would require database access/changes in order to implement fully and efficiently, any proposed changes to the Cwtch API should allow for the possibility of new functionality at all layers of the Cwtch stack, including storage.
  • Status / Profile Metadata – proposed features that only require specific APIs / hooks for saving requested information for the purposes of caching.

The Problem with Experiments

We have done some work in past to limit the impact an experimental feature can have on the rest of Cwtch, mainly through providing restricted sets of public Cwtch APIs e.g. the SendMessages interface that only allows callers to send messages.

We have also worked to package experimental functionality into so-called Gated Functionalities that are only available if a given experiment is turned on.

Together, these form the current basis for implementing to Cwtch features in the official libraries, but they are not without problems:

  • The scope of a functionality is rather broad, and can only be passed a complete Cwtch profile or a denoted subset of functionality e.g. SendMessages – there is no current way to scope a function to a specific conversation, or to a given zone (e.g. filesharing code is technically able to update attributes unrelated to filesharing).
  • The implementation of experiments has mostly been delegated to bindings and, as such, the gating inside CwtchLib is limited, often relying on state to be passed into it by the bindings, or relying on the bindings explicitly disable the functionality.
  • This lack of ownership over experiments by the official CwtchLib means that libraries based on CwtchLib instead of bindings do not have access to the safeguards provided by the bindings.

Restricting Powerful Cwtch APIs

To carefully expand Cwtch out using additional experimental APIs we must work to limit the impact further e.g. restricting actions to a given type of conversation, or only executing actions at registered times. To do this we require three separate but related strands of work:

  • Assume responsibility for experiments and features in Cwtch itself so that Cwtchlib has direct access to which experiments are enabled at any given time. Doing this allows changes to settings to always flow through Application and, (as currently happens with Anonymous Communication Network (ACN) state), provides a natural point at which to interface those changes into a Cwtch Profile.
  • Finer-grained Interfaces that allow restricting actions to preregistered conversation types e.g. a RestrictedCwtchConversationInterface which decorates a Cwtch Profile interface such that it can only interact with a single conversation – these can then be passed into hooks and interface functions to limit their impact.
  • Registered Hooks at pre-specified points with restricted capabilities – to allow experimental functionality to register interest in certain events, and act on them at the correct time, and to allow CwtchPeer to control which experiments get access to which events at a given time.

Pre-Registered Hooks

In order to implement certain functionality actions need to take place in-between events handled by CwtchPeer. As a motivating example consider a new group membership protocol overlayed above the existing messages. Such a protocol may require checking against group permission settings after receiving a new message, but before inserting it into into the database (e.g. the message author needs to be confirmed against the list of current members authorized to post to the group).

This is currently only possible with invasive changes to the CwtchPeer interface, explicitly inserting a hook point and acting on it. In an ideal design we would be able to register such hooks for most likely events without additional development effort.

We are introducing a new set of Cwtch APIs designed for this purpose:

  • OnNewPeerMessage - hooked prior to inserting the message into the database.
  • OnPeerMessageConfirmed – hooked after a peer message has been inserted into the database.
  • OnEncryptedGroupMessage – hooked after receiving an encrypted message from a group server.
  • OnGroupMessageReceived – hooked after a successful decryption of a group message, but before inserting it into the database.
  • OnContactRequestValue – hooked on request of a scoped (the permission level of the attribute e.g. public or conversation level attributes), zoned ( relating to a specific feature e.g. filesharing or chat), and keyed (the name of the attribute e.g. name or manifest) value from a contact.
  • OnContactReceiveValue – hooked on receipt of a requested scoped,zoned, and keyed value from a contact.

Including the following APIs for managing hooked functionality:

  • RegisterEvents - returns a set of events that the extension is interested processing.
  • RegisterExperiments - returns a set of experiments that the extension is interested in being notified about
  • OnEvent - to be called by CwtchPeer whenever an event registered with RegisterEvents is called (assuming all experiments registered through RegisterExperiments is active)

ProtocolEngine Subsystems

As mentioned in our experiment summary, some functionality needs to be implemented directly in the ProtocolEngine. The ProtocolEngine is responsible for managing networking clients, and sending/receiving packets from those clients to/from a CwtchPeer (via the event bus).

Some types of data are too costly to send over the event bus e.g. requested chunks from shared files, and as such we need to delegate the handling of such data to a ProtocolEngine.

At the moment is this done through the concept of informal “subsystems”, modular add-ons to ProtocolEngine that process certain events. The current informal nature of this design means that there are not hard-and-fast rules regarding what functionality lives in a subsystem, and how subsystems interact with the wider ProtocolEngine ecosystem.

We are formalizing this subsystem into an interface, similar to the hooked functionality in CwtchPeer:

  • RegisterEvents - returns a set of events that the subsystem needs to consume to operate.
  • OnEvent – to be called by ProtocolEngine whenever an event registered with RegisterEvents is called (when all the experiments registered through RegisterExperiments are active)
  • RegisterContexts - returns the set of contexts that the subsystem implements e.g. im.cwtch.filesharing

This also requires a formalization of two engine specific events (for use on the event bus):

  • SendCwtchMessage – encapsulating the existing CwtchPeerMessage that is used internally in ProtocolEngine for messages between subsystems.
  • CwtchMessageReceived – encapsulating the existing handlePeerMessage function which effectively already serves this purpose, but instead of using an Observer pattern, is implemented as an increasingly unwieldy set of if/else blocks.

And the introduction of three additional ProtocolEnine specific events:

  • StartEngineSubsystem – replaces subsystem specific start event, can be driven by functionalities to (re)start protocol specific handling.
  • StopEngineSubsystem – replaces subsystem specific stop event mechanisms, can be driven by functionalities to stop all protocol specific handling.
  • SubsystemStatus – a generic event that can be published by subsystems with a collection of fields useful for debugging

This will allow us to move the following functionality, currently part of ProtocolEngine itself, into generic subsystems:

  • Attribute Lookup Handling - this functionality is currently part of the overloaded handlePeerMessage function, filtered using the Context parameter of the CwtchPeerMessage. As such it can be entirely delegated to a subsystem.
  • Filesharing Chunk Request Handling – this is also part of handlePeerMessage, also filtered using the Context parameter, and is already almost entirely implementing in a standalone subsystem (only routing is handled by handlePeerMessage)
  • Filesharing Start File Share/Stop File Share – this is currently part of the handleEvent behaviour of ProtocolEngine and can be moved into an OnEvent handler of the file sharing subsystem (where such events are already processed).

The introduction of pre-registered hooks in combination with the formalizations of ProtocolEngine subsystems will allow the follow functionality, currently implemented in CwtchPeer or libcwtch-go to be moved to standalone packages:

  • Filesharing makes heavy use of the getval/retval functionality, we can move all of this into a hooked-based functionality extension.
    • Filesharing also depends on the file sharing subsystem to be enabled in a ProtocolEngine. This subsystem is responsible for processing chunk requests.
  • Profile Images – we treat profile images as a specialization of the file sharing function, as such the experiment can operate entirely over apis provided by the filesharing experiment. (Right now this specialization lives in libcwtch-go as hooks into the relevant functions)
  • Legacy Groups – while groups themselves are a first-class consideration for Cwtch, the actual process of constructing and receiving group messages relies heavily on processing of events, or interpreting generic conversation attributes, and as such this functionality can be moved entirely to hooked-based functionality. By doing this we also open the path towards introducing new group protocols based on the same interface.
  • Status/Profile Metadata – status depends entirely on OnPeerRequestValue / OnPeerReceiveValue and requires little Cwtch Peer interaction other than saving the result.

Impact on Enabling (Powerful) New Functionality

None of the above restricts our ability to introduce new functionality in to Cwtch that is dependent on more invasive changes (e.g. direct database access / updates), but they do allow us to structure such changes into discrete modules:

  • Search – a fulltext search feature requires new indexes to be created in Cwtch Storage (likely using the sqlite FT5 module). As an experiment SearchFunctionality would need access to a hook after database setup in order to create and populate those indexes. This is a far more powerful feature than most as it requires direct database access.
  • Non Chat Conversation Contexts - the storage backend work we implemented last year had a long-term goal of enabling non-chat contexts like microblogging. Like search, these kinds of experiments will require deeply integrated access to the Cwtch database.

Application Experiments

One kind of experiment we haven’t touched on yet is additional application functionality, at present we have one main example: Embedded Server Hosting – this allows a Cwtch desktop client to setup and manage Cwtch Servers.

This kind of functionality doesn’t belong in Cwtchlib – as it would necessarily introduce unrelated dependencies into the core library.

This functionality also doesn’t belong in the bindings either. They should be as minimal as possible. To that end, we will be moving this functionality out of the bindings and into dedicated repositories which can be managed via an Application Experiment interface.

Bindings

The last problem to be solved is how to interface experiments with the bindings (libcwtch-go) and ultimately downstream applications.

We can split the bindings into four core areas:

  • Application Management - functionality necessary to manage the core Cwtch application e.g. StartCwtch, ReconnectCwtchForeground, Shutdown, CreateProfile etc. This category also include FreePointer which is necessary for safe memory management.
  • Application Experiments - auxiliary functionality that augments the Cwtch application with new features e.g. Server Hosting etc.
  • Core Profile Management - core non-experimental functionality that requires a profile e.g. ImportBundle, SendMessage etc. These apis take a handle in addition to the parameters needed to call the underlying function.
  • Experimental Profile Features – auxiliary functionality that augments profiles with additional features e.g. ShareFile, SetProfileImage etc. These apis also take a handle.

The flip side of the bindings is the event bus handing which is responsible for maintaining a queue for the downstream application. This queue provides some filtering and enhancement of events to improve performance. This queue can be moved entirely into Application with only GetAppBusEvent defined and exposed in the bindings.

In an ideal future, all of these bindings could be generated automatically from the Cwtchlib interface definitions i.e. there should be no special functionality in the bindings themselves. The generation would need to include C bindings (untyped with automatic checks) and the Dart library calling convention (type safe)

We can define three types of C/Java/Kotlin interface function templates:

  • ProfileMethodName(profilehandle String, args...) – which directly resolves the Cwtch Profile and calls the function.
  • ProfileExperimentalMethodName(profilehandle String, args...) – which checks the current application settings to see if the experiment is enabled, and then resolves the CwtchProfile and calls the function - else errors.
  • ApplicationExperimentalMethodName(args...) – which checks the current application settings to see if the experiment is enabled, and if so, calls the experimental application functionality.

All we need to know from CwtchLib is what methods to export to C bindings, and what template they should use. This can be automatically derived from the context ProfileInterface for the first, exported methods of the various Functionalities for the second, and ApplicationExperiment definitions for the third.

Timelines and Next Actions

  • Freeze any changes to the bindings interface - we have made minimal changes to the bindings in the Cwtch 1.9 and 1.10 – until we have implemented the proposed changes into cwtchlib.
  • As part of Cwtch 1.11 and 1.12 Release Cycles
    • Implement the ProtocolEngine Subsystem Design as outlined above.
    • Implement the Hooks API.
    • Move all special behaviour / extra functionalities in the libcwtch-go bindings into cwtchlib – with the exception of behaviour related to Application Experiments (i.e. Server Hosting).
    • Move event handling from the bindings into Application.
    • Move Application Experiments defined in bindings into their own libraries (or integrate them into existing libraries like cwtch-server) – keeping the existing interface definitions.
  • Once Automated UI Tests have been integrated into the Cwtch UI Repository:
    • Write a generate-cwtch-bindings tool that auto generates the libcwtch-go C/Android bindings and a dart calling convention library from cwtchlib and any configured application experiments libraries
    • Port the existing UI app to use the newly generated dart Cwtch library (this must wait until we have automated UI testing as part of the build process to ensure that there are no regressions during this process).
    • At this point the bindings are based off of the generated library and libcwtch-go is deprecated / replaced with automatically generated and versioned bindings.

As these changes are made, and these goals met we will be posting about them here! Subscribe to our RSS feed, Atom feed, or JSON feed to stay up to date, and get the latest on, all Cwtch development.

Help us go further!

We couldn't do what we do without all the wonderful community support we get, from one-off donations to recurring support via Patreon.

If you want to see us move faster on some of these goals and are in a position to, please donate. If you happen to be at a company that wants to do more for the community and this aligns, please consider donating or sponsoring a developer.

Donations of $5 or more can opt to receive stickers as a thank-you gift!

For more information about donating to Open Privacy and claiming a thank you gift please visit the Open Privacy Donate page.

A Photo of Cwtch Stickers

Appendix A: Special Behaviour Defined by libcwtch-go

The following is an exhaustive list of functionality currently provided by libcwtch-go bindings instead of the cwtchlib:

  • Application Settings
    • Including Enabling / Disabling Experiment
  • ACN Process Management - starting/stopping/restarting/configuring Tor.
  • Notification Handling - augmenting/suppressing/augmenting interesting event notifications (primarily for Android)
  • Logging Levels - configuring appropriate logging levels (e.g. INFO or DEBUG)
  • Profile Images Helper Functions - handling default profile images for contacts and groups, in addition to looking up custom profile images if the experiment is enabled.
  • UI Contact Structures - aggregating contact information for the main Cwtch UI.
  • Group Experiment Functionality
    • Experiment Gating
    • GetServerInfoList
    • GetServerInfo
    • UI Server Struct Definition
  • Server Hosting Experiment Functionality - creating/deleting/managing the server hosting experiment for desktop Cwtch clients.
  • "Unencrypted" Profile Handling - replacing a blank password with a default password where the underlying API expects a password but the profile has been designated "unencrypted".
  • Image Previews Experiment Handling - automatically starting the downloading of certain file types (when the experiment is enabled).
  • Cwtch UI Reconnection Handling (for Android) - restarting various Cwtch subsystems when the UI attempts to reconnect in circumstances where the Android kernel has killed the underlying process.
  • Cwtch Profile Engine Activation - starting/stopping a ProtocolEngine when requested by the UI, or in response to changes in ACN state.
  • UI Profile Aggregation - aggregating information related to Profiles for the UI (e.g. network connection status / unread messages) into a single event.
  • File sharing restarts
  • UI Event Augmentation - augmenting various internal Cwtch events with information that the UI needs but that isn't directly embedded within the event (e.g. converting handle to a conversation id). Much of this augmentation is legacy, implemented before recent changes to internal Cwtch structs, and likely can either be removed entirely, or delegated into Cwtch itself.
  • Debug Information - special information available to Cwtch debug builds (memory use / active goroutines etc.)