// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef IPCZ_INCLUDE_IPCZ_IPCZ_H_ #define IPCZ_INCLUDE_IPCZ_IPCZ_H_ // ipcz is a cross-platform C library for interprocess communication (IPC) which // supports efficient routing and data transfer over a large number of // dynamically relocatable messaging endpoints. // // ipcz operates in terms of a small handful of abstractions encapsulated in // this header: nodes, portals, parcels, drivers, boxes, and traps. // // NOTE: This header is intended to compile under C++11 or newer, and C99 or // newer. The ABI defined here can be considered stable. // // Glossary // -------- // *Nodes* are used by ipcz to model isolated units of an application. A typical // application will create one ipcz node within each OS process it controls. // // *Portals* are messaging endpoints which belong to a specific node. Portals // are created in entangled pairs: whatever goes into one portal comes out the // other (its "peer"). Pairs may be created local to a single node, or they may // be created to span two nodes. Portals may also be transferred freely through // other portals. // // *Parcels* are the unit of communication between portals. Parcels can contain // arbitrary application data as well as ipcz handles. Handles within a parcel // are used to transfer objects (namely other portals, or driver-defined // objects) from one portal to another, potentially on a different node. // // *Traps* provide a flexible mechanism to observe interesting portal state // changes such as new parcels arriving or a portal's peer being closed. // // *Drivers* are provided by applications to implement platform-specific IPC // details. They may also define new types of objects to be transmitted in // parcels via boxes. // // *Boxes* are opaque objects used to wrap driver- or application-defined // objects for seamless transmission across portals. Applications use the Box() // and Unbox() APIs to go between concrete objects and transferrable box // handles, and ipcz delegates to the driver or application to serialize boxed // objects as needed for transmission. // // Overview // -------- // To use ipcz effectively, an application must create multiple nodes to be // interconnected. One node must be designated as the "broker" by the // application (see CreateNode() flags). The broker is used by ipcz to // coordinate certain types of internal transactions which demand a heightened // level of trust and capability, so a broker node should always live in a more // trustworthy process. For example in Chrome, the browser process is // designated as the broker. // // In order for a node to communicate with other nodes in the system, the // application must explicitly connect it to ONE other node using the // ConnectNode() API. Once this is done, ipcz can automatically connect the node // to additional other nodes as needed for efficient portal operation. // // In the example below, assume node A is designated as the broker. Nodes A and // B have been connected directly by ConnectNode() calls in the application. // Nodes A and C have been similarly connected: // // ┌───────┐ // ConnectNode() │ │ ConnectNode() // ┌──────────>O A O<───────────┐ // │ │ │ │ // │ └───────┘ │ // │ │ // v ConnectNode() v ConnectNode() // ┌───O───┐ ┌───O───┐ // │ │ │ │ // │ B │ │ C │ // │ │ │ │ // └───────┘ └───────┘ // // ConnectNode() establishes initial portal pairs to link the two nodes // together, illustrated above as "O"s. Once ConnectNode() returns, the // application may immediately begin transmitting parcels through these portals. // // Now suppose node B creates a new local pair of portals (using OpenPortals()) // and sends one of those new portals in a parcel through its // already-established portal to node A. The sent portal is effectively // transferred to node A, and because its entangled peer still lives on node B // there are now TWO portal pairs between nodes A and B: // // ┌───────┐ // │ │ // ┌──────────>O A O<───────────┐ // │ ┌────────>O │ │ // │ │ └───────┘ │ // │ │ │ // v v v // ┌───O─O─┐ ┌───O───┐ // │ │ │ │ // │ B │ │ C │ // │ │ │ │ // └───────┘ └───────┘ // // Finally, suppose now the application takes this new portal on node A and // sends it further along, through node A's already-established portal to node // C. Because the transferred portal's peer still lives on node B, there is now // a portal pair spanning nodes B and C: // // ┌───────┐ // │ │ // ┌──────────>O A O<───────────┐ // │ │ │ │ // │ └───────┘ │ // │ │ // v v // ┌───O───┐ ┌───O───┐ // │ │ │ │ // │ B O────────────────────────O C │ // │ │ │ │ // └───────┘ └───────┘ // // These two nodes were never explicitly connected by the application, but ipcz // ensures that the portals will operate as expected. Behind the scenes, ipcz // achieves this by establishing a direct, secure, and efficient communication // channel between nodes B and C. #include <stddef.h> #include <stdint.h> #define IPCZ_NO_FLAGS … // Helper to clarify flag definitions. #define IPCZ_FLAG_BIT(bit) … // Opaque handle to an ipcz object. IpczHandle; // An IpczHandle value which is always invalid. Note that arbitrary non-zero // values are not necessarily valid either, but zero is never valid. #define IPCZ_INVALID_HANDLE … // Generic result code for all ipcz operations. See IPCZ_RESULT_* values below. IpczResult; // Specific meaning of each value depends on context, but IPCZ_RESULT_OK always // indicates success. These values are derived from common status code // definitions across Google software. #define IPCZ_RESULT_OK … #define IPCZ_RESULT_CANCELLED … #define IPCZ_RESULT_UNKNOWN … #define IPCZ_RESULT_INVALID_ARGUMENT … #define IPCZ_RESULT_DEADLINE_EXCEEDED … #define IPCZ_RESULT_NOT_FOUND … #define IPCZ_RESULT_ALREADY_EXISTS … #define IPCZ_RESULT_PERMISSION_DENIED … #define IPCZ_RESULT_RESOURCE_EXHAUSTED … #define IPCZ_RESULT_FAILED_PRECONDITION … #define IPCZ_RESULT_ABORTED … #define IPCZ_RESULT_OUT_OF_RANGE … #define IPCZ_RESULT_UNIMPLEMENTED … #define IPCZ_RESULT_INTERNAL … #define IPCZ_RESULT_UNAVAILABLE … #define IPCZ_RESULT_DATA_LOSS … // Helper to specify explicit struct alignment across C and C++ compilers. #if defined(__cplusplus) #define IPCZ_ALIGN(alignment) … #elif defined(__GNUC__) #define IPCZ_ALIGN … #elif defined(_MSC_VER) #define IPCZ_ALIGN … #else #error "IPCZ_ALIGN() is not defined for your compiler." #endif // Helper to generate the smallest constant value which is aligned with // `alignment` and at least as large as `value`. #define IPCZ_ALIGNED(value, alignment) … // Helper used to explicitly specify calling convention or other // compiler-specific annotations for each API function. #if defined(IPCZ_API_OVERRIDE) #define IPCZ_API … #elif defined(_WIN32) #define IPCZ_API … #else #define IPCZ_API #endif // An opaque handle value created by an IpczDriver implementation. ipcz uses // such handles to provide relevant context when calling back into the driver. IpczDriverHandle; #define IPCZ_INVALID_DRIVER_HANDLE … // Flags which may be passed by a driver to an IpczTransportActivityHandler when // notifying ipcz about transport activity. IpczTransportActivityFlags; // Indicates that the driver encountered an unrecoverable error while using the // transport. This generally results in ipcz deactivating the transport via the // driver's DeactivateTransport(). #define IPCZ_TRANSPORT_ACTIVITY_ERROR … // Informs ipcz that the driver will no longer invoke the activity handler for // a given listener, as the driver is no longer listening for activity on the // corresponding transport. #define IPCZ_TRANSPORT_ACTIVITY_DEACTIVATED … #if defined(__cplusplus) extern "C" { #endif // Notifies ipcz of activity on a transport. `listener` must be a handle to an // active transport's listener, as provided to the driver by ipcz via // ActivateTransport(). // // Drivers use this function to feed incoming data and driver handles from a // transport to ipcz, or to inform ipcz of any unrecoverable dysfunction of the // transport. In the latter case, drivers specify IPCZ_TRANSPORT_ACTIVITY_ERROR // in `flags` to instigate deactivation and disposal of the transport by ipcz. // // If IPCZ_TRANSPORT_ACTIVITY_DEACTIVATED is set in `flags`, this must be the // last call made by the driver for the given `listener`. See also // DeactivateTransport() defined on IpczDriver below. // // `options` is currently unused and must be null. // // IMPORTANT: Drivers must ensure that all calls to this handler for the same // `listener` are mutually exclusive. Overlapping calls are unsafe and will // result in undefined behavior. IpczTransportActivityHandler; // in // Structure to be filled in by a driver's GetSharedMemoryInfo(). struct IPCZ_ALIGN(8) IpczSharedMemoryInfo { … }; // IpczDriver // ========== // // IpczDriver is a function table to be populated by the application and // provided to ipcz when creating a new node. The driver implements concrete // I/O operations to facilitate communication between nodes, giving embedding // systems full control over choice of OS-specific transport mechanisms and I/O // scheduling decisions. struct IPCZ_ALIGN(8) IpczDriver { … }; #if defined(__cplusplus) } // extern "C" #endif // Flags which may be passed via the `memory_flags` field of // IpczCreateNodeOptions to configure features of ipcz internal memory // allocation and usage. IpczMemoryFlags; // If this flag is set, the node will not attempt to expand the shared memory // pools it uses to allocate parcel data between itself and other nodes. // // This means more application messages may fall back onto driver I/O for // transmission, but also that ipcz' memory footprint will remain largely // constant. Note that memory may still be expanded to facilitate new portal // links as needed. #define IPCZ_MEMORY_FIXED_PARCEL_CAPACITY … // Feature identifiers which may be passed through IpczCreateNodeOptions to // control dynamic runtime features. IpczFeature; // When this feature is enabled, ipcz will use alternative shared memory layout // and allocation behavior intended to be more efficient than the v1 scheme. #define IPCZ_FEATURE_MEM_V2 … // Options given to CreateNode() to configure the new node's behavior. struct IPCZ_ALIGN(8) IpczCreateNodeOptions { … }; // See CreateNode() and the IPCZ_CREATE_NODE_* flag descriptions below. IpczCreateNodeFlags; // Indicates that the created node will serve as the broker in its cluster. // // Brokers are expected to live in relatively trusted processes -- not // necessarily elevated in privilege but also generally not restricted by // sandbox constraints and not prone to processing risky, untrusted data -- as // they're responsible for helping other nodes establish direct lines of // communication as well as in some cases relaying data and driver handles // between lesser-privileged nodes. // // Broker nodes do not expose any additional ipcz APIs or require much other // special care on the part of the application**, but every cluster of connected // nodes must have a node designated as the broker. Typically this is the first // node created by an application's main process or a system-wide service // coordinator, and all other nodes are created in processes spawned by that one // or in processes which otherwise trust it. // // ** See notes on Close() regarding destruction of broker nodes. #define IPCZ_CREATE_NODE_AS_BROKER … // See ConnectNode() and the IPCZ_CONNECT_NODE_* flag descriptions below. IpczConnectNodeFlags; // Indicates that the remote node for this connection is expected to be a broker // node, and it will be treated as such. Do not use this flag when connecting to // any untrusted process. #define IPCZ_CONNECT_NODE_TO_BROKER … // Indicates that the remote node for this connection is expected not to be a // broker, but to already have a link to a broker; and that the calling node // wishes to inherit the remote node's broker as well. This flag must only be // used when connecting to a node the caller trusts. The calling node must not // already have an established broker from a previous ConnectNode() call. The // remote node must specify IPCZ_CONNECT_NODE_SHARE_BROKER as well. #define IPCZ_CONNECT_NODE_INHERIT_BROKER … // Indicates that the calling node already has a broker, and that this broker // will be inherited by the remote node. The remote node must also specify // IPCZ_CONNECT_NODE_INHERIT_BROKER in its corresponding ConnectNode() call. #define IPCZ_CONNECT_NODE_SHARE_BROKER … // ipcz may periodically allocate shared memory regions to facilitate // communication between two nodes. In many runtime environments, even within // a security sandbox, the driver can do this safely and directly by interfacing // with the OS. In some environments however, direct allocation is not possible. // In such cases a node must delegate this responsibility to some other trusted // node in the system, typically the broker node. // // Specifying this flag ensures that all shared memory allocation elicited by // the connecting node will be delegated to the connectee. #define IPCZ_CONNECT_NODE_TO_ALLOCATION_DELEGATE … // An opaque handle to a transaction returned by BeginGet() or BeginPut(). IpczTransaction; // See BeginPut() and the IPCZ_BEGIN_PUT_* flags described below. IpczBeginPutFlags; // Indicates that the caller is willing to produce less data than originally // requested by their `*num_bytes` argument to BeginPut(). If the implementation // would prefer a smaller chunk of data, passing this flag may allow the call to // succeed while returning a smaller acceptable value in `*num_bytes`, rather // than simply failing the call with IPCZ_RESULT_RESOURCE_EXHAUSTED. #define IPCZ_BEGIN_PUT_ALLOW_PARTIAL … // See EndPut() and the IPCZ_END_PUT_* flags described below. IpczEndPutFlags; // If this flag is given to EndPut(), the referenced transaction is aborted // without committing its parcel to the portal. #define IPCZ_END_PUT_ABORT … // See Get() and the IPCZ_GET_* flag descriptions below. IpczGetFlags; // When given to Get(), this flag indicates that the caller is willing to accept // a partial retrieval of the next available parcel. This means that in // situations where Get() would normally return IPCZ_RESULT_RESOURCE_EXHAUSTED, // it will instead return IPCZ_RESULT_OK with as much data and handles as the // caller indicated they could accept. #define IPCZ_GET_PARTIAL … // See BeginGet() and the IPCZ_BEGIN_GET_* flag descriptions below. IpczBeginGetFlags; // Indicates that the caller will accept partial retrieval of a parcel's // attached handles. When this flag is given handles are only transferred to // the caller as output capacity allows, and it is not an error for the caller // to provide insufficient output capacity. See notes on BeginGet(). #define IPCZ_BEGIN_GET_PARTIAL … // Indicates that BeginGet() should begin an "overlapped" get-transaction on its // source, meaning that additional overlapped get-transactions may begin on the // same source before this one is terminated. Only valid when the source is a // portal. #define IPCZ_BEGIN_GET_OVERLAPPED … // See EndGet() and the IPCZ_END_GET_* flag descriptions below. IpczEndGetFlags; // If this flag is given to EndGet() for a non-overlapped transaction on a // portal, the transaction's parcel is left intact in the portal's queue instead // of being dequeued. Note that if handles were transferred to the caller via // BeginGet(), they still remain property of the caller and will no longer be // attached to the parcel even if the transaction is aborted. #define IPCZ_END_GET_ABORT … // Enumerates the type of contents within a box. IpczBoxType; // A box which contains an opaque driver object. #define IPCZ_BOX_TYPE_DRIVER_OBJECT … // A box which contains an opaque application-defined object with a custom // serializer. #define IPCZ_BOX_TYPE_APPLICATION_OBJECT … // A box which contains a collection of bytes and ipcz handles. #define IPCZ_BOX_TYPE_SUBPARCEL … // A function passed to Box() when boxing application objects. This function // implements serialization for the object identified by `object` in a manner // similar to IpczDriver's Serialize() function. If the object is not // serializable this must return IPCZ_RESULT_FAILED_PRECONDITION and ignore // other arguments. // // This may be called with insufficient capacity (`num_bytes` and `num_handles`) // in which case it should update those values with capacity requirements and // return IPCZ_RESULT_RESOURCE_EXHAUSTED. // // Otherwise the function must fill in `bytes` and `handles` with values that // effectively represent the opaque object identified by `object`, and return // IPCZ_RESULT_OK to indicate success. IpczApplicationObjectSerializer; // A function passed to Box() when boxing application objects. This function // must clean up any resources associated with the opaque object identified by // `object`. IpczApplicationObjectDestructor; // Describes the contents of a box. Boxes may contain driver objects, arbitrary // application-defined objects, or collections of bytes and ipcz handles // (portals or other boxes). struct IPCZ_ALIGN(8) IpczBoxContents { … }; // See Unbox() and the IPCZ_UNBOX_* flags described below. IpczUnboxFlags; // If set, the box is not consumed and the driver handle returned is not removed // from the box. #define IPCZ_UNBOX_PEEK … // Flags given by the `flags` field in IpczPortalStatus. IpczPortalStatusFlags; // Indicates that the opposite portal is closed. Subsequent put-transactions on // this portal will always fail with IPCZ_RESULT_NOT_FOUND. If there are not // currently any unretrieved parcels in the portal either, subsequent // get-transactions will also fail with the same error. #define IPCZ_PORTAL_STATUS_PEER_CLOSED … // Indicates that the opposite portal is closed AND no more parcels can be // expected to arrive from it. If this bit is set on a portal's status, the // portal is essentially useless. Such portals no longer support Put() or // Get() operations, and those operations will subsequently always return // IPCZ_RESULT_NOT_FOUND. #define IPCZ_PORTAL_STATUS_DEAD … // Information returned by QueryPortalStatus() or provided to // IpczTrapEventHandlers when a trap's conditions are met on a portal. struct IPCZ_ALIGN(8) IpczPortalStatus { … }; // Flags given to IpczTrapConditions to indicate which types of conditions a // trap should observe. // // Note that each type of condition may be considered edge-triggered or // level-triggered. An edge-triggered condition is one which is only // observable momentarily in response to a state change, while a level-triggered // condition is continuously observable as long as some constraint about a // portal's state is met. // // Level-triggered conditions can cause a Trap() attempt to fail if they're // already satisfied when attempting to install a trap to monitor them. IpczTrapConditionFlags; // Triggers a trap event when the trap's portal is itself closed. This condition // is always observed even if not explicitly set in the IpczTrapConditions given // to the Trap() call. If a portal is closed while a trap is installed on it, // an event will fire for the trap with this condition flag set. This condition // is effectively edge-triggered, because as soon as it becomes true, any // observing trap as well as its observed subject cease to exist. #define IPCZ_TRAP_REMOVED … // Triggers a trap event whenever the opposite portal is closed. Typically // applications are interested in the more specific IPCZ_TRAP_DEAD. // Level-triggered. #define IPCZ_TRAP_PEER_CLOSED … // Triggers a trap event whenever there are no more parcels available to // retrieve from this portal AND the opposite portal is closed. This means the // portal will never again have parcels to retrieve and is effectively useless. // Level-triggered. #define IPCZ_TRAP_DEAD … // Triggers a trap event whenever the number of parcels queued for retrieval by // this portal exceeds the threshold given by `min_local_parcels` in // IpczTrapConditions. Level-triggered. #define IPCZ_TRAP_ABOVE_MIN_LOCAL_PARCELS … // Triggers a trap event whenever the number of bytes queued for retrieval by // this portal exceeds the threshold given by `min_local_bytes` in // IpczTrapConditions. Level-triggered. #define IPCZ_TRAP_ABOVE_MIN_LOCAL_BYTES … // Triggers a trap event whenever the number of locally available parcels // increases by any amount. Edge-triggered. #define IPCZ_TRAP_NEW_LOCAL_PARCEL … // Indicates that the trap event is being fired from within the extent of an // ipcz API call (i.e., as opposed to being fired from within the extent of an // incoming driver transport notification.) For example if a trap is monitoring // a portal for incoming parcels, and the application puts a parcel into the // portal's peer on the same node, the trap event will be fired within the // extent of the corresponding Put() call, and this flag will be set on the // event. // // This flag is ignored when specifying conditions to watch for Trap(), and it // may be set on any event dispatched to an IpczTrapEventHandler. #define IPCZ_TRAP_WITHIN_API_CALL … // A structure describing portal conditions necessary to trigger a trap and // invoke its event handler. struct IPCZ_ALIGN(8) IpczTrapConditions { … }; // Structure passed to each IpczTrapEventHandler invocation with details about // the event. struct IPCZ_ALIGN(8) IpczTrapEvent { … }; // An application-defined function to be invoked by a trap when its observed // conditions are satisfied on the monitored portal. IpczTrapEventHandler; #if defined(__cplusplus) extern "C" { #endif // IpczAPI // ======= // // Table of API functions defined by ipcz. Instances of this structure may be // populated by passing them to an implementation of IpczGetAPIFn. // // Note that all functions follow a consistent parameter ordering: // // 1. Object handle (node or portal) if applicable // 2. Function-specific strict input values // 3. Flags - possibly untyped and unused // 4. Options struct - possibly untyped and unused // 5. Function-specific in/out values // 6. Function-specific strict output values // // The rationale behind this convention is generally to have order flow from // input to output. Flags are inputs, and options provide an extension point for // future versions of these APIs; as such they skirt the boundary between strict // input values and in/out values. // // The order and signature (ABI) of functions defined here must never change, // but new functions may be added to the end. struct IPCZ_ALIGN(8) IpczAPI { … }; // A function which populates `api` with a table of ipcz API functions. The // `size` field must be set by the caller to the size of the structure before // issuing this call. // // In practice ipcz defines IpczGetAPI() as an implementation of this function // type. How applications acquire a reference to that function depends on how // the application builds and links against ipcz. // // Upon return, `api->size` indicates the size of the function table actually // populated and therefore which version of the ipcz implementation is in use. // Note that this size will never exceed the input value of `api->size`: if the // caller is built against an older version than what is available, the // available implementation will only populate the functions appropriate for // that older version. Conversely if the caller is built against a newer version // than what is available, `api->size` on output may be smaller than its value // was on input. // // Returns: // // IPCZ_RESULT_OK if `api` was successfully populated. In this case // `api->size` effectively indicates the API version provided, and the // appropriate function pointers within `api` are filled in. // // IPCZ_RESULT_INVALID_ARGUMENT if `api` is null or the caller's provided // `api->size` is less than the size of the function table required to // host API version 0. IpczGetAPIFn; #if defined(__cplusplus) } // extern "C" #endif #endif // IPCZ_INCLUDE_IPCZ_IPCZ_H_