An open API service indexing awesome lists of open source software.

https://github.com/71/capnp-swift

(Unofficial, WIP) Cap'n Proto support for Swift; serialization only.
https://github.com/71/capnp-swift

Last synced: 10 months ago
JSON representation

(Unofficial, WIP) Cap'n Proto support for Swift; serialization only.

Awesome Lists containing this project

README

          

# Cap'n Proto Swift

[Cap'n Proto](https://capnproto.org) runtime and code generation for Swift. Not
an official Cap'n Proto project.

## Features

- Encoding and decoding of Cap'n Proto messages.

- Swift-friendly APIs with `enum`s and typed errors.

- Zero-copy messages which operate directly on byte buffers.

- [Serialization over streams](https://capnproto.org/encoding.html#serialization-over-a-stream),
including zero-copy (when segments fully fit in input buffers).

The following are not supported:

- RPC and interfaces.

- Packing.

- Generics.

- Orphans.

- Reflection.

## Example

The `addressbook.capnp` example is available in
[`Tests/CapnProtoTests/AddressBook.swift`](Tests/CapnProtoTests/AddressBook.swift).

## Safety

The library and generated code is designed to handle malicious inputs, but it is
not 100% there:

1. Possible decoding errors (invalid pointers, overflows) are surfaced as Swift
errors. However some parts of the implementation do not perform checked
arithmetic yet, and need to be updated to safely decode untrusted messages.

1. The
[traversal limit which prevents amplification attacks](https://capnproto.org/encoding.html#amplification-attack)
is not implemented. Only the
[pointer depth limit which prevents stack overflows](https://capnproto.org/encoding.html#stack-overflow-dos-attack)
is implemented.

1. There are few tests, and the code has not undergone any thorough review.

## Usage

### Using plugins

If `capnp` is in your `PATH`, Cap'n Proto Swift can be used as a plugin which
will automatically convert `.capnp` files in your source directory.

```swift
// Package.swift

let package = Package(
dependencies: [
.package(url: "https://github.com/71/capnp-swift", branch: "main"),
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "CapnProto", package: "capnp-swift"),
],
plugins: [
.plugin(name: "CapnProtoPlugin", package: "capnp-swift"),
]
),
]
)
```

### Manually

If you do not want the build to happen automatically, you can instead use
`capnpc-swift` as a [Cap'n Proto plugin](https://capnproto.org/capnp-tool.html).

First, add a dependency to `capnp-swift`:

```swift
// Package.swift

let package = Package(
dependencies: [
.package(url: "https://github.com/71/capnp-swift", branch: "main"),
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "CapnProto", package: "capnp-swift"),
]
),
]
)
```

Then, use `capnp compile` to generate your code:

```sh
capnp compile $(swift package print-capnp-compile) Sources/MyTarget/schema.capnp
```

> [!NOTE]
> `swift package print-capnp-compile [output-directory]` automatically resolves
> paths needed to compile `.capnp` files and generates arguments given to
> `capnp compile`:
>
> ```sh
> $ swift package print-capnp-compile
> --output=/path/to/project/.build/arm64-apple-macosx/debug/capnpc-swift-tool --import-path=/path/to/capnp-swift/
> ```
>
> An even more manual way to do this is to build `capnpc-swift` and use its
> path:
>
> ```sh
> $ swift build --product capnpc-swift --show-bin-path
> /path/to/project/.build/arm64-apple-macosx/debug
> ```

## Design

Unlike the [C++](https://capnproto.org/cxx.html) and
[Rust](https://github.com/capnproto/capnproto-rust) APIs (but like the
[Go](https://github.com/capnproto/go-capnp) and
[ECMAScript](https://github.com/unjs/capnp-es) APIs), there are no distinctive
types for reading and writing messages. Instead, the same types are used when
reading and writing, and writing can surface "not written" errors. This was
deemed okay as writing can _always_ fail, since the message you're working with
in memory may not have enough space for the field you're trying to write (if the
message was generated by a previous version of the code or
[canonicalized](https://capnproto.org/encoding.html#canonicalization)).

### APIs

- Pointer fields are exposed as `throw`ing methods as decoding can fail, whereas
non-pointer fields are exposed as properties.

- Enums are represented as a generated enum type wrapped in an `EnumValue`,
as their value may be unknown to the program reading them. `EnumValue`
provides access to the underlying value as `E?` or `UInt16`.

- Unions are represented as `struct`s where all union fields are `Optional`, and
with additional methods and types:

- `var whichDiscriminant: EnumValue` returns the raw
discriminant of the union.

- `func which() -> Which?` returns a Swift `enum` wrapping the union data, or
`nil` if the discriminant is for an unknown field. It can `throw` if one of
the fields is a pointer.

### Multithreading

Synchronizing access to messages when reading or writing would be expensive, so
most Cap'n Proto types are not
[`Sendable`](https://developer.apple.com/documentation/swift/sendable) and
instead work on a shared `Message` instance within a single thread.

`Message`s, `List`s and `Struct`s can be frozen into a `Frozen` object which
prevents further mutations and is `Sendable`.

Tracking whether a `Message` or `Struct` is mutable could be needlessly
expensive for those who do not care about multithreading, but the cost was
deemed okay for the following reasons:

1. Both the `Message` and all pointer types (`List`, `Struct`) store a "mutable
bit", so determining if a `Struct` is mutable does not require dereferencing
its `Message`.

This mutable bit takes no space at all; it is stored in the traversal limit
counter.

1. In order to support default values, it is necessary for parts of a message to
be immutable anyway.

Note that freezing a type has, like Swift arrays and strings, copy-on-write
semantics: if no other object refers to the underlying data, the data is
directly frozen. Otherwise, it is first copied.

## Development

After modifying a `.capnp` file in this repository, run:

```sh
Tools/compile-proto.sh
```