Fix vendors

This commit is contained in:
kjake 2025-01-19 17:23:59 -05:00
parent abce3d76f8
commit 018cba60e5
74 changed files with 3 additions and 17760 deletions

3
vendor/go.opentelemetry.io/otel/.gitmodules generated vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "opentelemetry-proto"]
path = exporters/otlp/internal/opentelemetry-proto
url = https://github.com/open-telemetry/opentelemetry-proto

View File

@ -1,12 +0,0 @@
/capnpc-go/capnpc-go
/internal/cmd/mktemplates/mktemplates
TAGS
*~
*.swp
# Bazel
/bazel-bin
/bazel-capnproto2
/bazel-genfiles
/bazel-out
/bazel-testlogs

View File

@ -1,9 +0,0 @@
language: go
go_import_path: zombiezen.com/go/capnproto2
go:
- 1.x
install: _travis/install.bash
script: _travis/build.bash
env:
- USE_BAZEL=0
- USE_BAZEL=1

View File

@ -1,32 +0,0 @@
# This is the official list of go-capnproto authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as one of
# Organization's name
# Individual's name <submission email address>
# Individual's name <submission email address> <email2> <emailN>
# See CONTRIBUTORS for the meaning of multiple email addresses.
# Please keep the list sorted.
Anapaya Systems AG
CloudFlare Inc.
Daniel Darabos <darabos.daniel@gmail.com>
Dominik Roos <domi.roos@gmail.com>
Eran Duchan <pavius@gmail.com>
Evan Shaw <edsrzf@gmail.com>
Google Inc.
Ian Denhardt <ian@zenhack.net>
James McKaskill <james@foobar.co.nz>
Jason E. Aten <j.e.aten@gmail.com>
Johan Hernandez <im@bithavoc.io>
Joonsung Lee <joonsung@devsisters.com>
Kiwi.com s.r.o.
Lev Radomislensky <lev.radomislensky@gmail.com>
Peter Waldschmidt <peterw@gnoso.com>
Tiit Pikma <pikma@hot.ee>
Tom Thorogood <me+github@tomthorogood.co.uk>
TJ Holowaychuk <tj@apex.sh>
William Laffin <william.laffin@gmail.com>
Colin Arnott <colin@urandom.co.uk>

View File

@ -1,62 +0,0 @@
load("@bazel_gazelle//:def.bzl", "gazelle")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
# gazelle:prefix zombiezen.com/go/capnproto2
gazelle(
name = "gazelle",
command = "fix",
)
go_library(
name = "go_default_library",
srcs = [
"address.go",
"canonical.go",
"capability.go",
"capn.go",
"doc.go",
"go.capnp.go",
"list.go",
"mem.go",
"mem_18.go",
"mem_other.go",
"pointer.go",
"rawpointer.go",
"readlimit.go",
"strings.go",
"struct.go",
],
importpath = "zombiezen.com/go/capnproto2",
visibility = ["//visibility:public"],
deps = [
"//internal/packed:go_default_library",
"//internal/strquote:go_default_library",
"//schemas:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"address_test.go",
"canonical_test.go",
"capability_test.go",
"capn_test.go",
"example_test.go",
"integration_test.go",
"integrationutil_test.go",
"list_test.go",
"mem_test.go",
"rawpointer_test.go",
"readlimit_test.go",
],
data = [
"//internal/aircraftlib:schema",
],
embed = [":go_default_library"],
deps = [
"//internal/aircraftlib:go_default_library",
"//internal/capnptool:go_default_library",
],
)

View File

@ -1,199 +0,0 @@
# Go Cap'n Proto Release Notes
## 2.17.3
- Clear read limits for `const` messages in schemas.
([#131](https://github.com/capnproto/go-capnproto2/pull/131))
## 2.17.0
- Add `capnp.Canonicalize` function that implements the
[canonicalization](https://capnproto.org/encoding.html#canonicalization)
algorithm. ([#92](https://github.com/capnproto/go-capnproto2/issues/92))
- Zero-sized struct pointers are now written with an offset of
-1 to distinguish them from a null pointer.
([#92](https://github.com/capnproto/go-capnproto2/issues/92))
- Better support for alternate `Arena` implementations
- [Document `Arena` contract](https://godoc.org/zombiezen.com/go/capnproto2#Arena)
in more detail
- Permit an `Arena` to have a single empty segment in `NewMessage`
- `Arena` allocation optimizations: both `SingleSegment` and
`MultiSegment` now gradually ramp up the amount of space allocated in
a single allocation as the message grows. This is similar to how
built-in Go `append` function works. Workloads with medium to large
messages should expect a decrease in number of allocations, while
small message workloads should remain about the same. Please file an
issue if you encounter any performance regressions.
([#96](https://github.com/capnproto/go-capnproto2/issues/96))
- Fix double-far pointer logic. ([#97](https://github.com/capnproto/go-capnproto2/issues/97))
This is a long-standing bug with reading and writing multi-segment
messages. I've added broader test coverage for multi-segment messages
and far pointers, so it's unlikely that such a failure will persist in
the future.
- Accessing a field in a union when that field is not the one set now
results in a panic. ([#56](https://github.com/capnproto/go-capnproto2/issues/56))
This is intended to help uncover programming mistakes where a union
field is accessed without checking `Which()`. Prior to this change,
unset union field accessors would silently return garbage.
- `Struct.Address()` and `List.Address()` are now deprecated.
Especially for `List`, where the address is at the beginning of the
data, not the composite literal, the return value is not well-defined
and its not clear how to use it. Use `capnp.SamePtr` if you need to
check for pointer reference equality. File an issue if you're using
`Address()` for something else.
## 2.16.0
- Add BUILD.bazel files ([#88](https://github.com/capnproto/go-capnproto2/issues/88))
## 2.15.0
- capnpc-go now fails when a file does not include an import annotation.
([#41](https://github.com/capnproto/go-capnproto2/issues/41))
- Remove rbtree dependency ([#80](https://github.com/capnproto/go-capnproto2/issues/80))
- Add option to reduce allocations in `capnp.Decoder`
([#79](https://github.com/capnproto/go-capnproto2/issues/79))
- Add `String()` methods for lists
([#85](https://github.com/capnproto/go-capnproto2/issues/85))
- Add `String()` methods to schema.capnp.go
([#83](https://github.com/capnproto/go-capnproto2/issues/83))
## 2.14.1
- Use [new Go generated code convention](https://golang.org/s/generatedcode) in
capnpc-go output ([#78](https://github.com/capnproto/go-capnproto2/issues/78))
## Retroactive Releases
go-capnproto2 was originally a "build from HEAD" sort of library, as was
convention for most Go libraries at the time. Before 2.14.1, Semantic
Versioning tags were retroactively added so that it would be clear what the
differences were since original release, since marking it as "2.0.0" would seem
awkward.
The general process was: any significant new feature was given a minor release,
and then any bugfixes before the next minor release were given a "2.X.1"
release.
<table>
<thead>
<tr>
<th scope="col">Version</th>
<th scope="col">Description</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.14.0">2.14.0</a></th>
<td>Add support to <code>pogs</code> for interface types (<a href="https://github.com/capnproto/go-capnproto2/issues/66">#66</a> and <a href="https://github.com/capnproto/go-capnproto2/issues/74">#74</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.13.1">2.13.1</a></th>
<td>Fix bug with far far pointers (<a href="https://github.com/capnproto/go-capnproto2/issues/71">#71</a>), use <code>writev</code> system call to encode multi-segment messages efficiently in Go 1.8+ (<a href="https://github.com/capnproto/go-capnproto2/issues/70">#70</a>), and add GitHub-Linguist-compatible code generation comment</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.13.0">2.13.0</a></th>
<td>Add <code>Conn.Done</code> and <code>Conn.Err</code> methods</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.12.4">2.12.4</a></th>
<td>Fix size of created <code>List(Float32)</code></td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.12.3">2.12.3</a></th>
<td>Fix bugs from fuzz tests: mismatched element size on list access causing crashes (<a href="https://github.com/capnproto/go-capnproto2/issues/59">#59</a>) and miscellaneous packed reader issues</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.12.2">2.12.2</a></th>
<td>Fix another shutdown race condition (<a href="https://github.com/capnproto/go-capnproto2/issues/54">#54</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.12.1">2.12.1</a></th>
<td>Fix ownership bug with receiver-hosted capabilities, add discriminant check to <code>HasField</code> (<a href="https://github.com/capnproto/go-capnproto2/issues/55">#55</a>), fix multi-segment bug for data/text lists, and use nulls for setting empty data/text</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.12.0">2.12.0</a></th>
<td>Add <code>rpc.ConnLog</code> option and fix race conditions and edge cases in RPC implementation</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.11.1">2.11.1</a></th>
<td>Fix packed reader behavior on certain readers (<a href="https://github.com/capnproto/go-capnproto2/issues/49">#49</a>), add <code>capnp.UnmarshalPacked</code> function that performs faster, and reduce locking overhead of segment maps</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.11.0">2.11.0</a></th>
<td>Fix shutdown deadlock in RPC shutdown (<a href="https://github.com/capnproto/go-capnproto2/issues/45">#45</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.10.1">2.10.1</a></th>
<td>Work around lack of support for RPC-level promise capabilities (<a href="https://github.com/capnproto/go-capnproto2/issues/2">#2</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.10.0">2.10.0</a></th>
<td>Add <code>pogs</code> package (<a href="https://github.com/capnproto/go-capnproto2/issues/33">#33</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.9.1">2.9.1</a></th>
<td>Fix not-found behavior in schemas and add missing group IDs in generated embedded schemas</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.9.0">2.9.0</a></th>
<td>Add <code>encoding/text</code> package (<a href="https://github.com/capnproto/go-capnproto2/issues/20">#20</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.8.0">2.8.0</a></th>
<td>Reduce generated code size for text fields and correct NUL check</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.7.0">2.7.0</a></th>
<td>Insert compressed schema data into generated code</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.6.1">2.6.1</a></th>
<td>Strip NUL byte from <code>TextList.BytesAt</code> and fix capnpc-go output for struct groups</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.6.0">2.6.0</a></th>
<td>Add packages for predefined Cap'n Proto schemas</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.5.1">2.5.1</a></th>
<td>Fix capnpc-go regression (<a href="https://github.com/capnproto/go-capnproto2/issues/29">#29</a>) and strip trailing NUL byte in TextBytes accessor</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.5.0">2.5.0</a></th>
<td>Add <code>NewFoo</code> method for list fields in generated structs (<a href="https://github.com/capnproto/go-capnproto2/issues/7">#7</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.4.0">2.4.0</a></th>
<td>Add maximum segment limit (<a href="https://github.com/capnproto/go-capnproto2/issues/25">#25</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.3.0">2.3.0</a></th>
<td>Add depth and traversal limit security checks</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.2.1">2.2.1</a></th>
<td>Fix data race in reading <code>Message</code> from multiple goroutines</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.2.0">2.2.0</a></th>
<td>Add <code>HasFoo</code> pointer field methods to generated code (<a href="https://github.com/capnproto/go-capnproto2/issues/24">#24</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.1.0">2.1.0</a></th>
<td><a href="https://github.com/capnproto/go-capnproto2/wiki/New-Ptr-Type">Introduce <code>Ptr</code> type</a> and reduce allocations in single-segment cases</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.0.2">2.0.2</a></th>
<td>Allow allocation-less string field access via <code>TextList.BytesAt()</code> and <code>StringBytes()</code> (<a href="https://github.com/capnproto/go-capnproto2/issues/17">#17</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.0.1">2.0.1</a></th>
<td>Allow nil params in client wrappers (<a href="https://github.com/capnproto/go-capnproto2/issues/9">#9</a>) and fix integer underflow on compare function (<a href="https://github.com/capnproto/go-capnproto2/issues/12">#12</a>)</td>
</tr>
<tr>
<th scope="row"><a href="https://github.com/capnproto/go-capnproto2/releases/tag/v2.0.0">2.0.0</a></th>
<td>First release under <code>zombiezen.com/go/capnproto2</code></td>
</tr>
</tbody>
</table>

View File

@ -1,22 +0,0 @@
# How to contribute
We'd love to accept your patches and contributions to this project. There are a
just a few small guidelines you need to follow.
## Code contributions
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult [GitHub Help][] for more
information on using pull requests.
When you make your first submission to this repository, please add your name to
the AUTHORS and CONTRIBUTORS file as part of your first pull request.
As a policy, go-capnproto2 should always build cleanly and pass tests on every
commit. We run a [Travis build][] that checks before and after merges to
enforce this policy. However, as a courtesy to other contributors, please run
`go test ./...` before sending a pull request (this is what the Travis
build does).
[GitHub Help]: https://help.github.com/articles/about-pull-requests/
[Travis build]: https://travis-ci.org/capnproto/go-capnproto2

View File

@ -1,39 +0,0 @@
# This is the official list of people who can contribute
# (and typically have contributed) code to the go-capnproto repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# copyright belongs to the individual or the corporation.
# Names should be added to this file like so:
# Individual's name <submission email address>
# Individual's name <submission email address> <email2> <emailN>
# Please keep the list sorted.
Alan Braithwaite <alan@cloudflare.com>
Albert Strasheim <albert@cloudflare.com>
Daniel Darabos <darabos.daniel@gmail.com>
Dominik Roos <domi.roos@gmail.com>
Eran Duchan <pavius@gmail.com>
Evan Shaw <edsrzf@gmail.com>
Ian Denhardt <ian@zenhack.net>
James McKaskill <james@foobar.co.nz>
Jason E. Aten <j.e.aten@gmail.com>
Johan Hernandez <im@bithavoc.io>
Joonsung Lee <joonsung@devsisters.com>
Lev Radomislensky <lev.radomislensky@gmail.com>
Lukas Vogel <vogel@anapaya.net> <lukedirtwalker@gmail.com>
Martin Sucha <martin.sucha@kiwi.com>
Peter Waldschmidt <peterw@gnoso.com>
Ross Light <light@google.com> <ross@zombiezen.com>
Stephen Shirley <kormat@anapaya.net> <kormat@gmail.com>
Tiit Pikma <pikma@hot.ee>
Tom Thorogood <me+github@tomthorogood.co.uk>
TJ Holowaychuk <tj@apex.sh>
William Laffin <william.laffin@gmail.com>
Colin Arnott <colin@urandom.co.uk>

View File

@ -1,25 +0,0 @@
go-capnproto is licensed under the terms of the MIT license reproduced below.
===============================================================================
Copyright (C) 2014 the go-capnproto authors and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================================================

View File

@ -1,68 +0,0 @@
# Cap'n Proto bindings for Go
[![GoDoc](https://godoc.org/zombiezen.com/go/capnproto2?status.svg)][godoc]
[![Build Status](https://travis-ci.org/capnproto/go-capnproto2.svg?branch=master)][travis]
go-capnproto consists of:
- a Go code generator for [Cap'n Proto](https://capnproto.org/)
- a Go package that provides runtime support
- a Go package that implements Level 1 of the RPC protocol
[godoc]: https://godoc.org/zombiezen.com/go/capnproto2
[travis]: https://travis-ci.org/capnproto/go-capnproto2
## Getting started
You will need the `capnp` tool to compile schemas into Go.
This package has been tested with Cap'n Proto 0.5.0.
```
$ go get -u -t zombiezen.com/go/capnproto2/...
$ go test -v zombiezen.com/go/capnproto2/...
```
This library uses [SemVer tags][] to indicate stable releases.
While the goal is that master should always be passing all known tests, tagged releases are vetted more.
When possible, use the [latest release tag](https://github.com/capnproto/go-capnproto2/releases).
```
$ cd $GOPATH/src/zombiezen.com/go/capnproto2
$ git fetch
$ git checkout v2.16.0 # check the releases page for the latest
```
Then read the [Getting Started guide][].
[SemVer tags]: http://semver.org/
[Getting Started guide]: https://github.com/capnproto/go-capnproto2/wiki/Getting-Started
## API Compatibility
Consider this package's API as beta software, since the Cap'n Proto spec is not final.
In the spirit of the [Go 1 compatibility guarantee][gocompat], I will make every effort to avoid making breaking API changes.
The major cases where I reserve the right to make breaking changes are:
- Security.
- Changes in the Cap'n Proto specification.
- Bugs.
The `pogs` package is relatively new and may change over time.
However, its functionality has been well-tested and will probably only relax restrictions.
[gocompat]: https://golang.org/doc/go1compat
## Documentation
See the docs on [godoc.org][godoc].
## What is Cap'n Proto?
The best cerealization...
https://capnproto.org/
## License
MIT - see [LICENSE][] file
[LICENSE]: https://github.com/capnproto/go-capnproto2/blob/master/LICENSE

View File

@ -1,45 +0,0 @@
workspace(name = "com_zombiezen_go_capnproto2")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "io_bazel_rules_go",
sha256 = "86ae934bd4c43b99893fc64be9d9fc684b81461581df7ea8fc291c816f5ee8c5",
urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.18.3/rules_go-0.18.3.tar.gz"],
)
http_archive(
name = "bazel_gazelle",
sha256 = "3c681998538231a2d24d0c07ed5a7658cb72bfb5fd4bf9911157c0e9ac6a2687",
urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.17.0/bazel-gazelle-0.17.0.tar.gz"],
)
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
go_rules_dependencies()
go_register_toolchains()
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
gazelle_dependencies()
go_repository(
name = "com_github_kylelemons_godebug",
importpath = "github.com/kylelemons/godebug",
sha256 = "4415b09bae90e41695bc17e4d00d0708e1f6bbb6e21cc22ce0146a26ddc243a7",
strip_prefix = "godebug-a616ab194758ae0a11290d87ca46ee8c440117b0",
urls = [
"https://github.com/kylelemons/godebug/archive/a616ab194758ae0a11290d87ca46ee8c440117b0.zip",
],
)
go_repository(
name = "org_golang_x_net",
importpath = "golang.org/x/net",
sha256 = "880dc04d0af397dce6875ee2349bbb4295fe5a47352f7a4da4270456f726edd4",
strip_prefix = "net-f5079bd7f6f74e23c4d65efa0f4ce14cbd6a3c0f",
urls = [
"https://github.com/golang/net/archive/f5079bd7f6f74e23c4d65efa0f4ce14cbd6a3c0f.zip",
],
)

View File

@ -1,116 +0,0 @@
package capnp
// An Address is an index inside a segment's data (in bytes).
type Address uint32
// addSize returns the address a+sz.
func (a Address) addSize(sz Size) (b Address, ok bool) {
x := int64(a) + int64(sz)
if x > int64(maxSize) {
return 0, false
}
return Address(x), true
}
// element returns the address a+i*sz.
func (a Address) element(i int32, sz Size) (b Address, ok bool) {
x := int64(i) * int64(sz)
if x > int64(maxSize) {
return 0, false
}
x += int64(a)
if x > int64(maxSize) {
return 0, false
}
return Address(x), true
}
// addOffset returns the address a+o.
func (a Address) addOffset(o DataOffset) Address {
return a + Address(o)
}
// A Size is a size (in bytes).
type Size uint32
// wordSize is the number of bytes in a Cap'n Proto word.
const wordSize Size = 8
// maxSize is the maximum representable size.
const maxSize Size = 1<<32 - 1
// times returns the size sz*n.
func (sz Size) times(n int32) (ns Size, ok bool) {
x := int64(sz) * int64(n)
if x > int64(maxSize) {
return 0, false
}
return Size(x), true
}
// padToWord adds padding to sz to make it divisible by wordSize.
func (sz Size) padToWord() Size {
n := Size(wordSize - 1)
return (sz + n) &^ n
}
// DataOffset is an offset in bytes from the beginning of a struct's data section.
type DataOffset uint32
// ObjectSize records section sizes for a struct or list.
type ObjectSize struct {
DataSize Size
PointerCount uint16
}
// isZero reports whether sz is the zero size.
func (sz ObjectSize) isZero() bool {
return sz.DataSize == 0 && sz.PointerCount == 0
}
// isOneByte reports whether the object size is one byte (for Text/Data element sizes).
func (sz ObjectSize) isOneByte() bool {
return sz.DataSize == 1 && sz.PointerCount == 0
}
// isValid reports whether sz's fields are in range.
func (sz ObjectSize) isValid() bool {
return sz.DataSize <= 0xffff*wordSize
}
// pointerSize returns the number of bytes the pointer section occupies.
func (sz ObjectSize) pointerSize() Size {
// Guaranteed not to overflow
return wordSize * Size(sz.PointerCount)
}
// totalSize returns the number of bytes that the object occupies.
func (sz ObjectSize) totalSize() Size {
return sz.DataSize + sz.pointerSize()
}
// dataWordCount returns the number of words in the data section.
func (sz ObjectSize) dataWordCount() int32 {
if sz.DataSize%wordSize != 0 {
panic("data size not aligned by word")
}
return int32(sz.DataSize / wordSize)
}
// totalWordCount returns the number of words that the object occupies.
func (sz ObjectSize) totalWordCount() int32 {
return sz.dataWordCount() + int32(sz.PointerCount)
}
// BitOffset is an offset in bits from the beginning of a struct's data section.
type BitOffset uint32
// offset returns the equivalent byte offset.
func (bit BitOffset) offset() DataOffset {
return DataOffset(bit / 8)
}
// mask returns the bitmask for the bit.
func (bit BitOffset) mask() byte {
return byte(1 << (bit % 8))
}

View File

@ -1,161 +0,0 @@
package capnp
import (
"errors"
"fmt"
)
// Canonicalize encodes a struct into its canonical form: a single-
// segment blob without a segment table. The result will be identical
// for equivalent structs, even as the schema evolves. The blob is
// suitable for hashing or signing.
func Canonicalize(s Struct) ([]byte, error) {
msg, seg, _ := NewMessage(SingleSegment(nil))
if !s.IsValid() {
return seg.Data(), nil
}
root, err := NewRootStruct(seg, canonicalStructSize(s))
if err != nil {
return nil, fmt.Errorf("canonicalize: %v", err)
}
if err := msg.SetRootPtr(root.ToPtr()); err != nil {
return nil, fmt.Errorf("canonicalize: %v", err)
}
if err := fillCanonicalStruct(root, s); err != nil {
return nil, fmt.Errorf("canonicalize: %v", err)
}
return seg.Data(), nil
}
func canonicalPtr(dst *Segment, p Ptr) (Ptr, error) {
if !p.IsValid() {
return Ptr{}, nil
}
switch p.flags.ptrType() {
case structPtrType:
ss, err := NewStruct(dst, canonicalStructSize(p.Struct()))
if err != nil {
return Ptr{}, err
}
if err := fillCanonicalStruct(ss, p.Struct()); err != nil {
return Ptr{}, err
}
return ss.ToPtr(), nil
case listPtrType:
ll, err := canonicalList(dst, p.List())
if err != nil {
return Ptr{}, err
}
return ll.ToPtr(), nil
case interfacePtrType:
return Ptr{}, errors.New("cannot canonicalize interface")
default:
panic("unreachable")
}
}
func fillCanonicalStruct(dst, s Struct) error {
copy(dst.seg.slice(dst.off, dst.size.DataSize), s.seg.slice(s.off, s.size.DataSize))
for i := uint16(0); i < dst.size.PointerCount; i++ {
p, err := s.Ptr(i)
if err != nil {
return fmt.Errorf("pointer %d: %v", i, err)
}
cp, err := canonicalPtr(dst.seg, p)
if err != nil {
return fmt.Errorf("pointer %d: %v", i, err)
}
if err := dst.SetPtr(i, cp); err != nil {
return fmt.Errorf("pointer %d: %v", i, err)
}
}
return nil
}
func canonicalStructSize(s Struct) ObjectSize {
if !s.IsValid() {
return ObjectSize{}
}
var sz ObjectSize
// int32 will not overflow because max struct data size is 2^16 words.
for off := int32(s.size.DataSize &^ (wordSize - 1)); off >= 0; off -= int32(wordSize) {
if s.Uint64(DataOffset(off)) != 0 {
sz.DataSize = Size(off) + wordSize
break
}
}
for i := int32(s.size.PointerCount) - 1; i >= 0; i-- {
if s.seg.readRawPointer(s.pointerAddress(uint16(i))) != 0 {
sz.PointerCount = uint16(i + 1)
break
}
}
return sz
}
func canonicalList(dst *Segment, l List) (List, error) {
if !l.IsValid() {
return List{}, nil
}
if l.size.PointerCount == 0 {
// Data only, just copy over.
sz := l.allocSize()
_, newAddr, err := alloc(dst, sz)
if err != nil {
return List{}, err
}
cl := List{
seg: dst,
off: newAddr,
length: l.length,
size: l.size,
flags: l.flags,
depthLimit: maxDepth,
}
end, _ := l.off.addSize(sz) // list was already validated
copy(dst.data[newAddr:], l.seg.data[l.off:end])
return cl, nil
}
if l.flags&isCompositeList == 0 {
cl, err := NewPointerList(dst, l.length)
if err != nil {
return List{}, err
}
for i := 0; i < l.Len(); i++ {
p, err := PointerList{l}.PtrAt(i)
if err != nil {
return List{}, fmt.Errorf("element %d: %v", i, err)
}
cp, err := canonicalPtr(dst, p)
if err != nil {
return List{}, fmt.Errorf("element %d: %v", i, err)
}
if err := cl.SetPtr(i, cp); err != nil {
return List{}, fmt.Errorf("element %d: %v", i, err)
}
}
return cl.List, nil
}
// Struct/composite list
var elemSize ObjectSize
for i := 0; i < l.Len(); i++ {
sz := canonicalStructSize(l.Struct(i))
if sz.DataSize > elemSize.DataSize {
elemSize.DataSize = sz.DataSize
}
if sz.PointerCount > elemSize.PointerCount {
elemSize.PointerCount = sz.PointerCount
}
}
cl, err := NewCompositeList(dst, elemSize, l.length)
if err != nil {
return List{}, err
}
for i := 0; i < cl.Len(); i++ {
if err := fillCanonicalStruct(cl.Struct(i), l.Struct(i)); err != nil {
return List{}, fmt.Errorf("element %d: %v", i, err)
}
}
return cl, nil
}

View File

@ -1,541 +0,0 @@
package capnp
import (
"errors"
"strconv"
"golang.org/x/net/context"
)
// An Interface is a reference to a client in a message's capability table.
type Interface struct {
seg *Segment
cap CapabilityID
}
// NewInterface creates a new interface pointer. No allocation is
// performed; s is only used for Segment()'s return value.
func NewInterface(s *Segment, cap CapabilityID) Interface {
return Interface{
seg: s,
cap: cap,
}
}
// ToInterface converts p to an Interface.
//
// Deprecated: Use Ptr.Interface.
func ToInterface(p Pointer) Interface {
if !IsValid(p) {
return Interface{}
}
i, ok := p.underlying().(Interface)
if !ok {
return Interface{}
}
return i
}
// ToPtr converts the interface to a generic pointer.
func (p Interface) ToPtr() Ptr {
return Ptr{
seg: p.seg,
lenOrCap: uint32(p.cap),
flags: interfacePtrFlag,
}
}
// Segment returns the segment this pointer came from.
func (i Interface) Segment() *Segment {
return i.seg
}
// IsValid returns whether the interface is valid.
func (i Interface) IsValid() bool {
return i.seg != nil
}
// HasData is always true.
func (i Interface) HasData() bool {
return true
}
// Capability returns the capability ID of the interface.
func (i Interface) Capability() CapabilityID {
return i.cap
}
// value returns a raw interface pointer with the capability ID.
func (i Interface) value(paddr Address) rawPointer {
if i.seg == nil {
return 0
}
return rawInterfacePointer(i.cap)
}
func (i Interface) underlying() Pointer {
return i
}
// Client returns the client stored in the message's capability table
// or nil if the pointer is invalid.
func (i Interface) Client() Client {
if i.seg == nil {
return nil
}
tab := i.seg.msg.CapTable
if int64(i.cap) >= int64(len(tab)) {
return nil
}
return tab[i.cap]
}
// ErrNullClient is returned from a call made on a null client pointer.
var ErrNullClient = errors.New("capnp: call on null client")
// A CapabilityID is an index into a message's capability table.
type CapabilityID uint32
// A Client represents an Cap'n Proto interface type. It is safe to use
// from multiple goroutines.
//
// Generally, only RPC protocol implementers should provide types that
// implement Client: call ordering guarantees, promises, and
// synchronization are tricky to get right. Prefer creating a server
// that wraps another interface than trying to implement Client.
type Client interface {
// Call starts executing a method and returns an answer that will hold
// the resulting struct. The call's parameters must be placed before
// Call() returns.
//
// Calls are delivered to the capability in the order they are made.
// This guarantee is based on the concept of a capability
// acknowledging delivery of a call: this is specific to an
// implementation of Client. A type that implements Client must
// guarantee that if foo() then bar() is called on a client, that
// acknowledging foo() happens before acknowledging bar().
Call(call *Call) Answer
// Close releases any resources associated with this client.
// No further calls to the client should be made after calling Close.
Close() error
}
// The Call type holds the record for an outgoing interface call.
type Call struct {
// Ctx is the context of the call.
Ctx context.Context
// Method is the interface ID and method ID, along with the optional name,
// of the method to call.
Method Method
// Params is a struct containing parameters for the call.
// This should be set when the RPC system receives a call for an
// exported interface. It is mutually exclusive with ParamsFunc
// and ParamsSize.
Params Struct
// ParamsFunc is a function that populates an allocated struct with
// the parameters for the call. ParamsSize determines the size of the
// struct to allocate. This is used when application code is using a
// client. These settings should be set together; they are mutually
// exclusive with Params.
ParamsFunc func(Struct) error
ParamsSize ObjectSize
// Options passes RPC-specific options for the call.
Options CallOptions
}
// Copy clones a call, ensuring that its Params are placed.
// If Call.ParamsFunc is nil, then the same Call will be returned.
func (call *Call) Copy(s *Segment) (*Call, error) {
if call.ParamsFunc == nil {
return call, nil
}
p, err := call.PlaceParams(s)
if err != nil {
return nil, err
}
return &Call{
Ctx: call.Ctx,
Method: call.Method,
Params: p,
Options: call.Options,
}, nil
}
// PlaceParams returns the parameters struct, allocating it inside
// segment s as necessary. If s is nil, a new single-segment message
// is allocated.
func (call *Call) PlaceParams(s *Segment) (Struct, error) {
if call.ParamsFunc == nil {
return call.Params, nil
}
if s == nil {
var err error
_, s, err = NewMessage(SingleSegment(nil))
if err != nil {
return Struct{}, err
}
}
p, err := NewStruct(s, call.ParamsSize)
if err != nil {
return Struct{}, nil
}
err = call.ParamsFunc(p)
return p, err
}
// CallOptions holds RPC-specific options for an interface call.
// Its usage is similar to the values in context.Context, but is only
// used for a single call: its values are not intended to propagate to
// other callees. An example of an option would be the
// Call.sendResultsTo field in rpc.capnp.
type CallOptions struct {
m map[interface{}]interface{}
}
// NewCallOptions builds a CallOptions value from a list of individual options.
func NewCallOptions(opts []CallOption) CallOptions {
co := CallOptions{make(map[interface{}]interface{})}
for _, o := range opts {
o.f(co)
}
return co
}
// Value retrieves the value associated with the options for this key,
// or nil if no value is associated with this key.
func (co CallOptions) Value(key interface{}) interface{} {
return co.m[key]
}
// With creates a copy of the CallOptions value with other options applied.
func (co CallOptions) With(opts []CallOption) CallOptions {
newopts := CallOptions{make(map[interface{}]interface{})}
for k, v := range co.m {
newopts.m[k] = v
}
for _, o := range opts {
o.f(newopts)
}
return newopts
}
// A CallOption is a function that modifies options on an interface call.
type CallOption struct {
f func(CallOptions)
}
// SetOptionValue returns a call option that associates a value to an
// option key. This can be retrieved later with CallOptions.Value.
func SetOptionValue(key, value interface{}) CallOption {
return CallOption{func(co CallOptions) {
co.m[key] = value
}}
}
// An Answer is the deferred result of a client call, which is usually wrapped by a Pipeline.
type Answer interface {
// Struct waits until the call is finished and returns the result.
Struct() (Struct, error)
// The following methods are the same as in Client except with
// an added transform parameter -- a path to the interface to use.
PipelineCall(transform []PipelineOp, call *Call) Answer
PipelineClose(transform []PipelineOp) error
}
// A Pipeline is a generic wrapper for an answer.
type Pipeline struct {
answer Answer
parent *Pipeline
op PipelineOp
}
// NewPipeline returns a new pipeline based on an answer.
func NewPipeline(ans Answer) *Pipeline {
return &Pipeline{answer: ans}
}
// Answer returns the answer the pipeline is derived from.
func (p *Pipeline) Answer() Answer {
return p.answer
}
// Transform returns the operations needed to transform the root answer
// into the value p represents.
func (p *Pipeline) Transform() []PipelineOp {
n := 0
for q := p; q.parent != nil; q = q.parent {
n++
}
xform := make([]PipelineOp, n)
for i, q := n-1, p; q.parent != nil; i, q = i-1, q.parent {
xform[i] = q.op
}
return xform
}
// Struct waits until the answer is resolved and returns the struct
// this pipeline represents.
func (p *Pipeline) Struct() (Struct, error) {
s, err := p.answer.Struct()
if err != nil {
return Struct{}, err
}
ptr, err := TransformPtr(s.ToPtr(), p.Transform())
if err != nil {
return Struct{}, err
}
return ptr.Struct(), nil
}
// Client returns the client version of p.
func (p *Pipeline) Client() *PipelineClient {
return (*PipelineClient)(p)
}
// GetPipeline returns a derived pipeline which yields the pointer field given.
func (p *Pipeline) GetPipeline(off uint16) *Pipeline {
return p.GetPipelineDefault(off, nil)
}
// GetPipelineDefault returns a derived pipeline which yields the pointer field given,
// defaulting to the value given.
func (p *Pipeline) GetPipelineDefault(off uint16, def []byte) *Pipeline {
return &Pipeline{
answer: p.answer,
parent: p,
op: PipelineOp{
Field: off,
DefaultValue: def,
},
}
}
// PipelineClient implements Client by calling to the pipeline's answer.
type PipelineClient Pipeline
func (pc *PipelineClient) transform() []PipelineOp {
return (*Pipeline)(pc).Transform()
}
// Call calls Answer.PipelineCall with the pipeline's transform.
func (pc *PipelineClient) Call(call *Call) Answer {
return pc.answer.PipelineCall(pc.transform(), call)
}
// Close calls Answer.PipelineClose with the pipeline's transform.
func (pc *PipelineClient) Close() error {
return pc.answer.PipelineClose(pc.transform())
}
// A PipelineOp describes a step in transforming a pipeline.
// It maps closely with the PromisedAnswer.Op struct in rpc.capnp.
type PipelineOp struct {
Field uint16
DefaultValue []byte
}
// String returns a human-readable description of op.
func (op PipelineOp) String() string {
s := make([]byte, 0, 32)
s = append(s, "get field "...)
s = strconv.AppendInt(s, int64(op.Field), 10)
if op.DefaultValue == nil {
return string(s)
}
s = append(s, " with default"...)
return string(s)
}
// A Method identifies a method along with an optional human-readable
// description of the method.
type Method struct {
InterfaceID uint64
MethodID uint16
// Canonical name of the interface. May be empty.
InterfaceName string
// Method name as it appears in the schema. May be empty.
MethodName string
}
// String returns a formatted string containing the interface name or
// the method name if present, otherwise it uses the raw IDs.
// This is suitable for use in error messages and logs.
func (m *Method) String() string {
buf := make([]byte, 0, 128)
if m.InterfaceName == "" {
buf = append(buf, '@', '0', 'x')
buf = strconv.AppendUint(buf, m.InterfaceID, 16)
} else {
buf = append(buf, m.InterfaceName...)
}
buf = append(buf, '.')
if m.MethodName == "" {
buf = append(buf, '@')
buf = strconv.AppendUint(buf, uint64(m.MethodID), 10)
} else {
buf = append(buf, m.MethodName...)
}
return string(buf)
}
// Transform applies a sequence of pipeline operations to a pointer
// and returns the result.
//
// Deprecated: Use TransformPtr.
func Transform(p Pointer, transform []PipelineOp) (Pointer, error) {
pp, err := TransformPtr(toPtr(p), transform)
return pp.toPointer(), err
}
// TransformPtr applies a sequence of pipeline operations to a pointer
// and returns the result.
func TransformPtr(p Ptr, transform []PipelineOp) (Ptr, error) {
n := len(transform)
if n == 0 {
return p, nil
}
s := p.Struct()
for _, op := range transform[:n-1] {
field, err := s.Ptr(op.Field)
if err != nil {
return Ptr{}, err
}
s, err = field.StructDefault(op.DefaultValue)
if err != nil {
return Ptr{}, err
}
}
op := transform[n-1]
p, err := s.Ptr(op.Field)
if err != nil {
return Ptr{}, err
}
if op.DefaultValue != nil {
p, err = p.Default(op.DefaultValue)
}
return p, err
}
type immediateAnswer struct {
s Struct
}
// ImmediateAnswer returns an Answer that accesses s.
func ImmediateAnswer(s Struct) Answer {
return immediateAnswer{s}
}
func (ans immediateAnswer) Struct() (Struct, error) {
return ans.s, nil
}
func (ans immediateAnswer) findClient(transform []PipelineOp) Client {
p, err := TransformPtr(ans.s.ToPtr(), transform)
if err != nil {
return ErrorClient(err)
}
return p.Interface().Client()
}
func (ans immediateAnswer) PipelineCall(transform []PipelineOp, call *Call) Answer {
c := ans.findClient(transform)
if c == nil {
return ErrorAnswer(ErrNullClient)
}
return c.Call(call)
}
func (ans immediateAnswer) PipelineClose(transform []PipelineOp) error {
c := ans.findClient(transform)
if c == nil {
return ErrNullClient
}
return c.Close()
}
type errorAnswer struct {
e error
}
// ErrorAnswer returns a Answer that always returns error e.
func ErrorAnswer(e error) Answer {
return errorAnswer{e}
}
func (ans errorAnswer) Struct() (Struct, error) {
return Struct{}, ans.e
}
func (ans errorAnswer) PipelineCall([]PipelineOp, *Call) Answer {
return ans
}
func (ans errorAnswer) PipelineClose([]PipelineOp) error {
return ans.e
}
// IsFixedAnswer reports whether an answer was created by
// ImmediateAnswer or ErrorAnswer.
func IsFixedAnswer(ans Answer) bool {
switch ans.(type) {
case immediateAnswer:
return true
case errorAnswer:
return true
default:
return false
}
}
type errorClient struct {
e error
}
// ErrorClient returns a Client that always returns error e.
func ErrorClient(e error) Client {
return errorClient{e}
}
func (ec errorClient) Call(*Call) Answer {
return ErrorAnswer(ec.e)
}
func (ec errorClient) Close() error {
return nil
}
// IsErrorClient reports whether c was created with ErrorClient.
func IsErrorClient(c Client) bool {
_, ok := c.(errorClient)
return ok
}
// MethodError is an error on an associated method.
type MethodError struct {
Method *Method
Err error
}
// Error returns the method name concatenated with the error string.
func (e *MethodError) Error() string {
return e.Method.String() + ": " + e.Err.Error()
}
// ErrUnimplemented is the error returned when a method is called on
// a server that does not implement the method.
var ErrUnimplemented = errors.New("capnp: method not implemented")
// IsUnimplemented reports whether e indicates an unimplemented method error.
func IsUnimplemented(e error) bool {
if me, ok := e.(*MethodError); ok {
e = me.Err
}
return e == ErrUnimplemented
}

View File

@ -1,434 +0,0 @@
package capnp
import (
"encoding/binary"
"errors"
)
// A SegmentID is a numeric identifier for a Segment.
type SegmentID uint32
// A Segment is an allocation arena for Cap'n Proto objects.
// It is part of a Message, which can contain other segments that
// reference each other.
type Segment struct {
msg *Message
id SegmentID
data []byte
}
// Message returns the message that contains s.
func (s *Segment) Message() *Message {
return s.msg
}
// ID returns the segment's ID.
func (s *Segment) ID() SegmentID {
return s.id
}
// Data returns the raw byte slice for the segment.
func (s *Segment) Data() []byte {
return s.data
}
func (s *Segment) inBounds(addr Address) bool {
return addr < Address(len(s.data))
}
func (s *Segment) regionInBounds(base Address, sz Size) bool {
end, ok := base.addSize(sz)
if !ok {
return false
}
return end <= Address(len(s.data))
}
// slice returns the segment of data from base to base+sz.
func (s *Segment) slice(base Address, sz Size) []byte {
// Bounds check should have happened before calling slice.
return s.data[base : base+Address(sz)]
}
func (s *Segment) readUint8(addr Address) uint8 {
return s.slice(addr, 1)[0]
}
func (s *Segment) readUint16(addr Address) uint16 {
return binary.LittleEndian.Uint16(s.slice(addr, 2))
}
func (s *Segment) readUint32(addr Address) uint32 {
return binary.LittleEndian.Uint32(s.slice(addr, 4))
}
func (s *Segment) readUint64(addr Address) uint64 {
return binary.LittleEndian.Uint64(s.slice(addr, 8))
}
func (s *Segment) readRawPointer(addr Address) rawPointer {
return rawPointer(s.readUint64(addr))
}
func (s *Segment) writeUint8(addr Address, val uint8) {
s.slice(addr, 1)[0] = val
}
func (s *Segment) writeUint16(addr Address, val uint16) {
binary.LittleEndian.PutUint16(s.slice(addr, 2), val)
}
func (s *Segment) writeUint32(addr Address, val uint32) {
binary.LittleEndian.PutUint32(s.slice(addr, 4), val)
}
func (s *Segment) writeUint64(addr Address, val uint64) {
binary.LittleEndian.PutUint64(s.slice(addr, 8), val)
}
func (s *Segment) writeRawPointer(addr Address, val rawPointer) {
s.writeUint64(addr, uint64(val))
}
// root returns a 1-element pointer list that references the first word
// in the segment. This only makes sense to call on the first segment
// in a message.
func (s *Segment) root() PointerList {
sz := ObjectSize{PointerCount: 1}
if !s.regionInBounds(0, sz.totalSize()) {
return PointerList{}
}
return PointerList{List{
seg: s,
length: 1,
size: sz,
depthLimit: s.msg.depthLimit(),
}}
}
func (s *Segment) lookupSegment(id SegmentID) (*Segment, error) {
if s.id == id {
return s, nil
}
return s.msg.Segment(id)
}
func (s *Segment) readPtr(paddr Address, depthLimit uint) (ptr Ptr, err error) {
s, base, val, err := s.resolveFarPointer(paddr)
if err != nil {
return Ptr{}, err
}
if val == 0 {
return Ptr{}, nil
}
if depthLimit == 0 {
return Ptr{}, errDepthLimit
}
switch val.pointerType() {
case structPointer:
sp, err := s.readStructPtr(base, val)
if err != nil {
return Ptr{}, err
}
if !s.msg.ReadLimiter().canRead(sp.readSize()) {
return Ptr{}, errReadLimit
}
sp.depthLimit = depthLimit - 1
return sp.ToPtr(), nil
case listPointer:
lp, err := s.readListPtr(base, val)
if err != nil {
return Ptr{}, err
}
if !s.msg.ReadLimiter().canRead(lp.readSize()) {
return Ptr{}, errReadLimit
}
lp.depthLimit = depthLimit - 1
return lp.ToPtr(), nil
case otherPointer:
if val.otherPointerType() != 0 {
return Ptr{}, errOtherPointer
}
return Interface{
seg: s,
cap: val.capabilityIndex(),
}.ToPtr(), nil
default:
// Only other types are far pointers.
return Ptr{}, errBadLandingPad
}
}
func (s *Segment) readStructPtr(base Address, val rawPointer) (Struct, error) {
addr, ok := val.offset().resolve(base)
if !ok {
return Struct{}, errPointerAddress
}
sz := val.structSize()
if !s.regionInBounds(addr, sz.totalSize()) {
return Struct{}, errPointerAddress
}
return Struct{
seg: s,
off: addr,
size: sz,
}, nil
}
func (s *Segment) readListPtr(base Address, val rawPointer) (List, error) {
addr, ok := val.offset().resolve(base)
if !ok {
return List{}, errPointerAddress
}
lsize, ok := val.totalListSize()
if !ok {
return List{}, errOverflow
}
if !s.regionInBounds(addr, lsize) {
return List{}, errPointerAddress
}
lt := val.listType()
if lt == compositeList {
hdr := s.readRawPointer(addr)
var ok bool
addr, ok = addr.addSize(wordSize)
if !ok {
return List{}, errOverflow
}
if hdr.pointerType() != structPointer {
return List{}, errBadTag
}
sz := hdr.structSize()
n := int32(hdr.offset())
if n < 0 {
return List{}, errListSize
}
// TODO(light): check that this has the same end address
if tsize, ok := sz.totalSize().times(n); !ok {
return List{}, errOverflow
} else if !s.regionInBounds(addr, tsize) {
return List{}, errPointerAddress
}
return List{
seg: s,
size: sz,
off: addr,
length: n,
flags: isCompositeList,
}, nil
}
n := val.numListElements()
if n < 0 {
return List{}, errListSize
}
if lt == bit1List {
return List{
seg: s,
off: addr,
length: n,
flags: isBitList,
}, nil
}
return List{
seg: s,
size: val.elementSize(),
off: addr,
length: n,
}, nil
}
func (s *Segment) resolveFarPointer(paddr Address) (dst *Segment, base Address, resolved rawPointer, err error) {
// Encoding details at https://capnproto.org/encoding.html#inter-segment-pointers
val := s.readRawPointer(paddr)
switch val.pointerType() {
case doubleFarPointer:
padSeg, err := s.lookupSegment(val.farSegment())
if err != nil {
return nil, 0, 0, err
}
padAddr := val.farAddress()
if !padSeg.regionInBounds(padAddr, wordSize*2) {
return nil, 0, 0, errPointerAddress
}
far := padSeg.readRawPointer(padAddr)
if far.pointerType() != farPointer {
return nil, 0, 0, errBadLandingPad
}
tagAddr, ok := padAddr.addSize(wordSize)
if !ok {
return nil, 0, 0, errOverflow
}
tag := padSeg.readRawPointer(tagAddr)
if pt := tag.pointerType(); (pt != structPointer && pt != listPointer) || tag.offset() != 0 {
return nil, 0, 0, errBadLandingPad
}
if dst, err = s.lookupSegment(far.farSegment()); err != nil {
return nil, 0, 0, err
}
return dst, 0, landingPadNearPointer(far, tag), nil
case farPointer:
var err error
dst, err = s.lookupSegment(val.farSegment())
if err != nil {
return nil, 0, 0, err
}
padAddr := val.farAddress()
if !dst.regionInBounds(padAddr, wordSize) {
return nil, 0, 0, errPointerAddress
}
var ok bool
base, ok = padAddr.addSize(wordSize)
if !ok {
return nil, 0, 0, errOverflow
}
return dst, base, dst.readRawPointer(padAddr), nil
default:
var ok bool
base, ok = paddr.addSize(wordSize)
if !ok {
return nil, 0, 0, errOverflow
}
return s, base, val, nil
}
}
func (s *Segment) writePtr(off Address, src Ptr, forceCopy bool) error {
if !src.IsValid() {
s.writeRawPointer(off, 0)
return nil
}
// Copy src, if needed, and process pointers where placement is
// irrelevant (capabilities and zero-sized structs).
var srcAddr Address
var srcRaw rawPointer
switch src.flags.ptrType() {
case structPtrType:
st := src.Struct()
if st.size.isZero() {
// Zero-sized structs should always be encoded with offset -1 in
// order to avoid conflating with null. No allocation needed.
s.writeRawPointer(off, rawStructPointer(-1, ObjectSize{}))
return nil
}
if forceCopy || src.seg.msg != s.msg || st.flags&isListMember != 0 {
newSeg, newAddr, err := alloc(s, st.size.totalSize())
if err != nil {
return err
}
dst := Struct{
seg: newSeg,
off: newAddr,
size: st.size,
depthLimit: maxDepth,
// clear flags
}
if err := copyStruct(dst, st); err != nil {
return err
}
st = dst
src = dst.ToPtr()
}
srcAddr = st.off
srcRaw = rawStructPointer(0, st.size)
case listPtrType:
l := src.List()
if forceCopy || src.seg.msg != s.msg {
sz := l.allocSize()
newSeg, newAddr, err := alloc(s, sz)
if err != nil {
return err
}
dst := List{
seg: newSeg,
off: newAddr,
length: l.length,
size: l.size,
flags: l.flags,
depthLimit: maxDepth,
}
if dst.flags&isCompositeList != 0 {
// Copy tag word
newSeg.writeRawPointer(newAddr, l.seg.readRawPointer(l.off-Address(wordSize)))
var ok bool
dst.off, ok = dst.off.addSize(wordSize)
if !ok {
return errOverflow
}
sz -= wordSize
}
if dst.flags&isBitList != 0 || dst.size.PointerCount == 0 {
end, _ := l.off.addSize(sz) // list was already validated
copy(newSeg.data[dst.off:], l.seg.data[l.off:end])
} else {
for i := 0; i < l.Len(); i++ {
err := copyStruct(dst.Struct(i), l.Struct(i))
if err != nil {
return err
}
}
}
l = dst
src = dst.ToPtr()
}
srcAddr = l.off
if l.flags&isCompositeList != 0 {
srcAddr -= Address(wordSize)
}
srcRaw = l.raw()
case interfacePtrType:
i := src.Interface()
if src.seg.msg != s.msg {
c := s.msg.AddCap(i.Client())
i = NewInterface(s, c)
}
s.writeRawPointer(off, i.value(off))
return nil
default:
panic("unreachable")
}
switch {
case src.seg == s:
// Common case: src is in same segment as pointer.
// Use a near pointer.
s.writeRawPointer(off, srcRaw.withOffset(nearPointerOffset(off, srcAddr)))
return nil
case hasCapacity(src.seg.data, wordSize):
// Enough room adjacent to src to write a far pointer landing pad.
_, padAddr, _ := alloc(src.seg, wordSize)
src.seg.writeRawPointer(padAddr, srcRaw.withOffset(nearPointerOffset(padAddr, srcAddr)))
s.writeRawPointer(off, rawFarPointer(src.seg.id, padAddr))
return nil
default:
// Not enough room for a landing pad, need to use a double-far pointer.
padSeg, padAddr, err := alloc(s, wordSize*2)
if err != nil {
return err
}
padSeg.writeRawPointer(padAddr, rawFarPointer(src.seg.id, srcAddr))
padSeg.writeRawPointer(padAddr+Address(wordSize), srcRaw)
s.writeRawPointer(off, rawDoubleFarPointer(padSeg.id, padAddr))
return nil
}
}
var (
errPointerAddress = errors.New("capnp: invalid pointer address")
errBadLandingPad = errors.New("capnp: invalid far pointer landing pad")
errBadTag = errors.New("capnp: invalid tag word")
errOtherPointer = errors.New("capnp: unknown pointer type")
errObjectSize = errors.New("capnp: invalid object size")
errElementSize = errors.New("capnp: mismatched list element size")
errReadLimit = errors.New("capnp: read traversal limit reached")
errDepthLimit = errors.New("capnp: depth limit reached")
)
var (
errOverflow = errors.New("capnp: address or size overflow")
errOutOfBounds = errors.New("capnp: address out of bounds")
errCopyDepth = errors.New("capnp: copy depth too large")
errOverlap = errors.New("capnp: overlapping data on copy")
errListSize = errors.New("capnp: invalid list size")
)

View File

@ -1,384 +0,0 @@
/*
Package capnp is a Cap'n Proto library for Go.
https://capnproto.org/
Read the Getting Started guide for a tutorial on how to use this
package. https://github.com/capnproto/go-capnproto2/wiki/Getting-Started
Generating code
capnpc-go provides the compiler backend for capnp.
# First, install capnpc-go to $PATH.
go install zombiezen.com/go/capnproto2/capnpc-go
# Then, generate Go files.
capnp compile -I$GOPATH/src/zombiezen.com/go/capnproto2/std -ogo *.capnp
capnpc-go requires two annotations for all files: package and import.
package is needed to know what package to place at the head of the
generated file and what identifier to use when referring to the type
from another package. import should be the fully qualified import path
and is used to generate import statement from other packages and to
detect when two types are in the same package. For example:
using Go = import "/go.capnp";
$Go.package("main");
$Go.import("zombiezen.com/go/capnproto2/example");
For adding documentation comments to the generated code, there's the doc
annotation. This annotation adds the comment to a struct, enum or field so
that godoc will pick it up. For example:
struct Zdate $Go.doc("Zdate represents a calendar date") {
year @0 :Int16;
month @1 :UInt8;
day @2 :UInt8 ;
}
Messages and Segments
In Cap'n Proto, the unit of communication is a message. A message
consists of one or more segments -- contiguous blocks of memory. This
allows large messages to be split up and loaded independently or lazily.
Typically you will use one segment per message. Logically, a message is
organized in a tree of objects, with the root always being a struct (as
opposed to a list or primitive). Messages can be read from and written
to a stream.
The Message and Segment types are the main types that application code
will use from this package. The Message type has methods for marshaling
and unmarshaling its segments to the wire format. If the application
needs to read or write from a stream, it should use the Encoder and
Decoder types.
Pointers
The type for a generic reference to a Cap'n Proto object is Ptr. A Ptr
can refer to a struct, a list, or an interface. Ptr, Struct, List, and
Interface (the pointer types) have value semantics and refer to data in
a single segment. All of the pointer types have a notion of "valid".
An invalid pointer will return the default value from any accessor and
panic when any setter is called.
In previous versions of this package, the Pointer interface was used
instead of the Ptr struct. This interface and functions that use it are
now deprecated. See https://github.com/capnproto/go-capnproto2/wiki/New-Ptr-Type
for details about this API change.
Data accessors and setters (i.e. struct primitive fields and list
elements) do not return errors, but pointer accessors and setters do.
There are a few reasons that a read or write of a pointer can fail, but
the most common are bad pointers or allocation failures. For accessors,
an invalid object will be returned in case of an error.
Since Go doesn't have generics, wrapper types provide type safety on
lists. This package provides lists of basic types, and capnpc-go
generates list wrappers for named types. However, if you need to use
deeper nesting of lists (e.g. List(List(UInt8))), you will need to use a
PointerList and wrap the elements.
Structs
For the following schema:
struct Foo @0x8423424e9b01c0af {
num @0 :UInt32;
bar @1 :Foo;
}
capnpc-go will generate:
// Foo is a pointer to a Foo struct in a segment.
// Member functions are provided to get/set members in the
// struct.
type Foo struct{ capnp.Struct }
// Foo_TypeID is the unique identifier for the type Foo.
// It remains the same across languages and schema changes.
const Foo_TypeID = 0x8423424e9b01c0af
// NewFoo creates a new orphaned Foo struct, preferring placement in
// s. If there isn't enough space, then another segment in the
// message will be used or allocated. You can set a field of type Foo
// to this new message, but usually you will want to use the
// NewBar()-style method shown below.
func NewFoo(s *capnp.Segment) (Foo, error)
// NewRootFoo creates a new Foo struct and sets the message's root to
// it.
func NewRootFoo(s *capnp.Segment) (Foo, error)
// ReadRootFoo reads the message's root pointer and converts it to a
// Foo struct.
func ReadRootFoo(msg *capnp.Message) (Foo, error)
// Num returns the value of the num field.
func (s Foo) Num() uint32
// SetNum sets the value of the num field to v.
func (s Foo) SetNum(v uint32)
// Bar returns the value of the bar field. This can return an error
// if the pointer goes beyond the segment's range, the segment fails
// to load, or the pointer recursion limit has been reached.
func (s Foo) Bar() (Foo, error)
// HasBar reports whether the bar field was initialized (non-null).
func (s Foo) HasBar() bool
// SetBar sets the value of the bar field to v.
func (s Foo) SetBar(v Foo) error
// NewBar sets the bar field to a newly allocated Foo struct,
// preferring placement in s's segment.
func (s Foo) NewBar() (Foo, error)
// Foo_List is a value with pointer semantics. It is created for all
// structs, and is used for List(Foo) in the capnp file.
type Foo_List struct{ capnp.List }
// NewFoo_List creates a new orphaned List(Foo), preferring placement
// in s. This can then be added to a message by using a Set function
// which takes a Foo_List. sz specifies the number of elements in the
// list. The list's size cannot be changed after creation.
func NewFoo_List(s *capnp.Segment, sz int32) Foo_List
// Len returns the number of elements in the list.
func (s Foo_List) Len() int
// At returns a pointer to the i'th element. If i is an invalid index,
// this will return an invalid Foo (all getters will return default
// values, setters will fail).
func (s Foo_List) At(i int) Foo
// Foo_Promise is a promise for a Foo. Methods are provided to get
// promises of struct and interface fields.
type Foo_Promise struct{ *capnp.Pipeline }
// Get waits until the promise is resolved and returns the result.
func (p Foo_Promise) Get() (Foo, error)
// Bar returns a promise for that bar field.
func (p Foo_Promise) Bar() Foo_Promise
Groups
For each group a typedef is created with a different method set for just the
groups fields:
struct Foo {
group :Group {
field @0 :Bool;
}
}
generates the following:
type Foo struct{ capnp.Struct }
type Foo_group Foo
func (s Foo) Group() Foo_group
func (s Foo_group) Field() bool
That way the following may be used to access a field in a group:
var f Foo
value := f.Group().Field()
Note that group accessors just convert the type and so have no overhead.
Unions
Named unions are treated as a group with an inner unnamed union. Unnamed
unions generate an enum Type_Which and a corresponding Which() function:
struct Foo {
union {
a @0 :Bool;
b @1 :Bool;
}
}
generates the following:
type Foo_Which uint16
const (
Foo_Which_a Foo_Which = 0
Foo_Which_b Foo_Which = 1
)
func (s Foo) A() bool
func (s Foo) B() bool
func (s Foo) SetA(v bool)
func (s Foo) SetB(v bool)
func (s Foo) Which() Foo_Which
Which() should be checked before using the getters, and the default case must
always be handled.
Setters for single values will set the union discriminator as well as set the
value.
For voids in unions, there is a void setter that just sets the discriminator.
For example:
struct Foo {
union {
a @0 :Void;
b @1 :Void;
}
}
generates the following:
func (s Foo) SetA() // Set that we are using A
func (s Foo) SetB() // Set that we are using B
Similarly, for groups in unions, there is a group setter that just sets
the discriminator. This must be called before the group getter can be
used to set values. For example:
struct Foo {
union {
a :group {
v :Bool
}
b :group {
v :Bool
}
}
}
and in usage:
f.SetA() // Set that we are using group A
f.A().SetV(true) // then we can use the group A getter to set the inner values
Enums
capnpc-go generates enum values as constants. For example in the capnp file:
enum ElementSize {
empty @0;
bit @1;
byte @2;
twoBytes @3;
fourBytes @4;
eightBytes @5;
pointer @6;
inlineComposite @7;
}
In the generated capnp.go file:
type ElementSize uint16
const (
ElementSize_empty ElementSize = 0
ElementSize_bit ElementSize = 1
ElementSize_byte ElementSize = 2
ElementSize_twoBytes ElementSize = 3
ElementSize_fourBytes ElementSize = 4
ElementSize_eightBytes ElementSize = 5
ElementSize_pointer ElementSize = 6
ElementSize_inlineComposite ElementSize = 7
)
In addition an enum.String() function is generated that will convert the constants to a string
for debugging or logging purposes. By default, the enum name is used as the tag value,
but the tags can be customized with a $Go.tag or $Go.notag annotation.
For example:
enum ElementSize {
empty @0 $Go.tag("void");
bit @1 $Go.tag("1 bit");
byte @2 $Go.tag("8 bits");
inlineComposite @7 $Go.notag;
}
In the generated go file:
func (c ElementSize) String() string {
switch c {
case ElementSize_empty:
return "void"
case ElementSize_bit:
return "1 bit"
case ElementSize_byte:
return "8 bits"
default:
return ""
}
}
Interfaces
capnpc-go generates type-safe Client wrappers for interfaces. For parameter
lists and result lists, structs are generated as described above with the names
Interface_method_Params and Interface_method_Results, unless a single struct
type is used. For example, for this interface:
interface Calculator {
evaluate @0 (expression :Expression) -> (value :Value);
}
capnpc-go generates the following Go code (along with the structs
Calculator_evaluate_Params and Calculator_evaluate_Results):
// Calculator is a client to a Calculator interface.
type Calculator struct{ Client capnp.Client }
// Evaluate calls `evaluate` on the client. params is called on a newly
// allocated Calculator_evaluate_Params struct to fill in the parameters.
func (c Calculator) Evaluate(
ctx context.Context,
params func(Calculator_evaluate_Params) error,
opts ...capnp.CallOption) *Calculator_evaluate_Results_Promise
capnpc-go also generates code to implement the interface:
// A Calculator_Server implements the Calculator interface.
type Calculator_Server interface {
Evaluate(Calculator_evaluate_Call) error
}
// Calculator_evaluate_Call holds the arguments for a Calculator.evaluate server call.
type Calculator_evaluate_Call struct {
Ctx context.Context
Options capnp.CallOptions
Params Calculator_evaluate_Params
Results Calculator_evaluate_Results
}
// Calculator_ServerToClient is equivalent to calling:
// NewCalculator(capnp.NewServer(Calculator_Methods(nil, s), s))
// If s does not implement the Close method, then nil is used.
func Calculator_ServerToClient(s Calculator_Server) Calculator
// Calculator_Methods appends methods from Calculator that call to server and
// returns the methods. If methods is nil or the capacity of the underlying
// slice is too small, a new slice is returned.
func Calculator_Methods(methods []server.Method, s Calculator_Server) []server.Method
Since a single capability may want to implement many interfaces, you can
use multiple *_Methods functions to build a single slice to send to
NewServer.
An example of combining the client/server code to communicate with a locally
implemented Calculator:
var srv Calculator_Server
calc := Calculator_ServerToClient(srv)
result := calc.Evaluate(ctx, func(params Calculator_evaluate_Params) {
params.SetExpression(expr)
})
val := result.Value().Get()
A note about message ordering: when implementing a server method, you
are responsible for acknowledging delivery of a method call. Failure to
do so can cause deadlocks. See the server.Ack function for more details.
*/
package capnp // import "zombiezen.com/go/capnproto2"

View File

@ -1,27 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["marshal.go"],
importpath = "zombiezen.com/go/capnproto2/encoding/text",
visibility = ["//visibility:public"],
deps = [
"//:go_default_library",
"//internal/nodemap:go_default_library",
"//internal/schema:go_default_library",
"//internal/strquote:go_default_library",
"//schemas:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["marshal_test.go"],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//:go_default_library",
"//internal/schema:go_default_library",
"//schemas:go_default_library",
],
)

View File

@ -1,582 +0,0 @@
// Package text supports marshaling Cap'n Proto messages as text based on a schema.
package text
import (
"bytes"
"fmt"
"io"
"math"
"strconv"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/nodemap"
"zombiezen.com/go/capnproto2/internal/schema"
"zombiezen.com/go/capnproto2/internal/strquote"
"zombiezen.com/go/capnproto2/schemas"
)
// Marker strings.
const (
voidMarker = "void"
interfaceMarker = "<external capability>"
anyPointerMarker = "<opaque pointer>"
)
// Marshal returns the text representation of a struct.
func Marshal(typeID uint64, s capnp.Struct) (string, error) {
buf := new(bytes.Buffer)
if err := NewEncoder(buf).Encode(typeID, s); err != nil {
return "", err
}
return buf.String(), nil
}
// MarshalList returns the text representation of a struct list.
func MarshalList(typeID uint64, l capnp.List) (string, error) {
buf := new(bytes.Buffer)
if err := NewEncoder(buf).EncodeList(typeID, l); err != nil {
return "", err
}
return buf.String(), nil
}
// An Encoder writes the text format of Cap'n Proto messages to an output stream.
type Encoder struct {
w indentWriter
tmp []byte
nodes nodemap.Map
}
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: indentWriter{w: w}}
}
// UseRegistry changes the registry that the encoder consults for
// schemas from the default registry.
func (enc *Encoder) UseRegistry(reg *schemas.Registry) {
enc.nodes.UseRegistry(reg)
}
// SetIndent sets string to indent each level with.
// An empty string disables indentation.
func (enc *Encoder) SetIndent(indent string) {
enc.w.indentPerLevel = indent
}
// Encode writes the text representation of s to the stream.
func (enc *Encoder) Encode(typeID uint64, s capnp.Struct) error {
if enc.w.err != nil {
return enc.w.err
}
err := enc.marshalStruct(typeID, s)
if err != nil {
return err
}
return enc.w.err
}
// EncodeList writes the text representation of struct list l to the stream.
func (enc *Encoder) EncodeList(typeID uint64, l capnp.List) error {
_, seg, _ := capnp.NewMessage(capnp.SingleSegment(nil))
typ, _ := schema.NewRootType(seg)
typ.SetStructType()
typ.StructType().SetTypeId(typeID)
return enc.marshalList(typ, l)
}
func (enc *Encoder) marshalBool(v bool) {
if v {
enc.w.WriteString("true")
} else {
enc.w.WriteString("false")
}
}
func (enc *Encoder) marshalInt(i int64) {
enc.tmp = strconv.AppendInt(enc.tmp[:0], i, 10)
enc.w.Write(enc.tmp)
}
func (enc *Encoder) marshalUint(i uint64) {
enc.tmp = strconv.AppendUint(enc.tmp[:0], i, 10)
enc.w.Write(enc.tmp)
}
func (enc *Encoder) marshalFloat32(f float32) {
enc.tmp = strconv.AppendFloat(enc.tmp[:0], float64(f), 'g', -1, 32)
enc.w.Write(enc.tmp)
}
func (enc *Encoder) marshalFloat64(f float64) {
enc.tmp = strconv.AppendFloat(enc.tmp[:0], f, 'g', -1, 64)
enc.w.Write(enc.tmp)
}
func (enc *Encoder) marshalText(t []byte) {
enc.tmp = strquote.Append(enc.tmp[:0], t)
enc.w.Write(enc.tmp)
}
func needsEscape(b byte) bool {
return b < 0x20 || b >= 0x7f
}
func hexDigit(b byte) byte {
const digits = "0123456789abcdef"
return digits[b]
}
func (enc *Encoder) marshalStruct(typeID uint64, s capnp.Struct) error {
n, err := enc.nodes.Find(typeID)
if err != nil {
return err
}
if !n.IsValid() || n.Which() != schema.Node_Which_structNode {
return fmt.Errorf("cannot find struct type %#x", typeID)
}
var discriminant uint16
if n.StructNode().DiscriminantCount() > 0 {
discriminant = s.Uint16(capnp.DataOffset(n.StructNode().DiscriminantOffset() * 2))
}
fields := codeOrderFields(n.StructNode())
if len(fields) == 0 {
enc.w.WriteString("()")
return nil
}
enc.w.WriteByte('(')
enc.w.Indent()
enc.w.NewLine()
first := true
for _, f := range fields {
if !(f.Which() == schema.Field_Which_slot || f.Which() == schema.Field_Which_group) {
continue
}
if dv := f.DiscriminantValue(); !(dv == schema.Field_noDiscriminant || dv == discriminant) {
continue
}
if !first {
enc.w.WriteByte(',')
enc.w.NewLineOrSpace()
}
first = false
name, err := f.NameBytes()
if err != nil {
return err
}
enc.w.Write(name)
enc.w.WriteString(" = ")
switch f.Which() {
case schema.Field_Which_slot:
if err := enc.marshalFieldValue(s, f); err != nil {
return err
}
case schema.Field_Which_group:
if err := enc.marshalStruct(f.Group().TypeId(), s); err != nil {
return err
}
}
}
enc.w.NewLine()
enc.w.Unindent()
enc.w.WriteByte(')')
return nil
}
func (enc *Encoder) marshalFieldValue(s capnp.Struct, f schema.Field) error {
typ, err := f.Slot().Type()
if err != nil {
return err
}
dv, err := f.Slot().DefaultValue()
if err != nil {
return err
}
if dv.IsValid() && int(typ.Which()) != int(dv.Which()) {
name, _ := f.Name()
return fmt.Errorf("marshal field %s: default value is a %v, want %v", name, dv.Which(), typ.Which())
}
switch typ.Which() {
case schema.Type_Which_void:
enc.w.WriteString(voidMarker)
case schema.Type_Which_bool:
v := s.Bit(capnp.BitOffset(f.Slot().Offset()))
d := dv.Bool()
enc.marshalBool(!d && v || d && !v)
case schema.Type_Which_int8:
v := s.Uint8(capnp.DataOffset(f.Slot().Offset()))
d := uint8(dv.Int8())
enc.marshalInt(int64(int8(v ^ d)))
case schema.Type_Which_int16:
v := s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2))
d := uint16(dv.Int16())
enc.marshalInt(int64(int16(v ^ d)))
case schema.Type_Which_int32:
v := s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4))
d := uint32(dv.Int32())
enc.marshalInt(int64(int32(v ^ d)))
case schema.Type_Which_int64:
v := s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8))
d := uint64(dv.Int64())
enc.marshalInt(int64(v ^ d))
case schema.Type_Which_uint8:
v := s.Uint8(capnp.DataOffset(f.Slot().Offset()))
d := dv.Uint8()
enc.marshalUint(uint64(v ^ d))
case schema.Type_Which_uint16:
v := s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2))
d := dv.Uint16()
enc.marshalUint(uint64(v ^ d))
case schema.Type_Which_uint32:
v := s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4))
d := dv.Uint32()
enc.marshalUint(uint64(v ^ d))
case schema.Type_Which_uint64:
v := s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8))
d := dv.Uint64()
enc.marshalUint(v ^ d)
case schema.Type_Which_float32:
v := s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4))
d := math.Float32bits(dv.Float32())
enc.marshalFloat32(math.Float32frombits(v ^ d))
case schema.Type_Which_float64:
v := s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8))
d := math.Float64bits(dv.Float64())
enc.marshalFloat64(math.Float64frombits(v ^ d))
case schema.Type_Which_structType:
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
if !p.IsValid() {
p, _ = dv.StructValuePtr()
}
return enc.marshalStruct(typ.StructType().TypeId(), p.Struct())
case schema.Type_Which_data:
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
if !p.IsValid() {
b, _ := dv.Data()
enc.marshalText(b)
return nil
}
enc.marshalText(p.Data())
case schema.Type_Which_text:
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
if !p.IsValid() {
b, _ := dv.TextBytes()
enc.marshalText(b)
return nil
}
enc.marshalText(p.TextBytes())
case schema.Type_Which_list:
elem, err := typ.List().ElementType()
if err != nil {
return err
}
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
if !p.IsValid() {
p, _ = dv.ListPtr()
}
return enc.marshalList(elem, p.List())
case schema.Type_Which_enum:
v := s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2))
d := dv.Uint16()
return enc.marshalEnum(typ.Enum().TypeId(), v^d)
case schema.Type_Which_interface:
enc.w.WriteString(interfaceMarker)
case schema.Type_Which_anyPointer:
enc.w.WriteString(anyPointerMarker)
default:
return fmt.Errorf("unknown field type %v", typ.Which())
}
return nil
}
func codeOrderFields(s schema.Node_structNode) []schema.Field {
list, _ := s.Fields()
n := list.Len()
fields := make([]schema.Field, n)
for i := 0; i < n; i++ {
f := list.At(i)
fields[f.CodeOrder()] = f
}
return fields
}
func (enc *Encoder) marshalList(elem schema.Type, l capnp.List) error {
writeListItems := func(writeItem func(i int) error) error {
if l.Len() == 0 {
_, err := enc.w.WriteString("[]")
return err
}
enc.w.WriteByte('[')
enc.w.Indent()
enc.w.NewLine()
for i := 0; i < l.Len(); i++ {
err := writeItem(i)
if err != nil {
return err
}
if i == l.Len()-1 {
enc.w.NewLine()
} else {
enc.w.WriteByte(',')
enc.w.NewLineOrSpace()
}
}
enc.w.Unindent()
enc.w.WriteByte(']')
return nil
}
writeListItemsN := func(writeItem func(i int) (int, error)) error {
return writeListItems(func(i int) error {
_, err := writeItem(i)
return err
})
}
switch elem.Which() {
case schema.Type_Which_void:
return writeListItemsN(func(_ int) (int, error) {
return enc.w.WriteString("void")
})
case schema.Type_Which_bool:
p := capnp.BitList{List: l}
return writeListItemsN(func(i int) (int, error) {
if p.At(i) {
return enc.w.WriteString("true")
} else {
return enc.w.WriteString("false")
}
})
case schema.Type_Which_int8:
p := capnp.Int8List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
})
case schema.Type_Which_int16:
p := capnp.Int16List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
})
case schema.Type_Which_int32:
p := capnp.Int32List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
})
case schema.Type_Which_int64:
p := capnp.Int64List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatInt(p.At(i), 10))
})
case schema.Type_Which_uint8:
p := capnp.UInt8List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
})
case schema.Type_Which_uint16:
p := capnp.UInt16List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
})
case schema.Type_Which_uint32:
p := capnp.UInt32List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
})
case schema.Type_Which_uint64:
p := capnp.UInt64List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatUint(p.At(i), 10))
})
case schema.Type_Which_float32:
p := capnp.Float32List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatFloat(float64(p.At(i)), 'g', -1, 32))
})
case schema.Type_Which_float64:
p := capnp.Float64List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatFloat(p.At(i), 'g', -1, 64))
})
case schema.Type_Which_data:
p := capnp.DataList{List: l}
return writeListItemsN(func(i int) (int, error) {
s, err := p.At(i)
if err != nil {
return enc.w.WriteString("<error>")
}
buf := strquote.Append(nil, s)
return enc.w.Write(buf)
})
case schema.Type_Which_text:
p := capnp.TextList{List: l}
return writeListItemsN(func(i int) (int, error) {
s, err := p.BytesAt(i)
if err != nil {
return enc.w.WriteString("<error>")
}
buf := strquote.Append(nil, s)
return enc.w.Write(buf)
})
case schema.Type_Which_structType:
return writeListItems(func(i int) error {
return enc.marshalStruct(elem.StructType().TypeId(), l.Struct(i))
})
case schema.Type_Which_list:
ee, err := elem.List().ElementType()
if err != nil {
return err
}
return writeListItems(func(i int) error {
p, err := capnp.PointerList{List: l}.PtrAt(i)
if err != nil {
return err
}
return enc.marshalList(ee, p.List())
})
case schema.Type_Which_enum:
il := capnp.UInt16List{List: l}
typ := elem.Enum().TypeId()
// TODO(light): only search for node once
return writeListItems(func(i int) error {
return enc.marshalEnum(typ, il.At(i))
})
case schema.Type_Which_interface:
return writeListItemsN(func(_ int) (int, error) {
return enc.w.WriteString(interfaceMarker)
})
case schema.Type_Which_anyPointer:
return writeListItemsN(func(_ int) (int, error) {
return enc.w.WriteString(anyPointerMarker)
})
default:
return fmt.Errorf("unknown list type %v", elem.Which())
}
}
func (enc *Encoder) marshalEnum(typ uint64, val uint16) error {
n, err := enc.nodes.Find(typ)
if err != nil {
return err
}
if n.Which() != schema.Node_Which_enum {
return fmt.Errorf("marshaling enum of type @%#x: type is not an enum", typ)
}
enums, err := n.Enum().Enumerants()
if err != nil {
return err
}
if int(val) >= enums.Len() {
enc.marshalUint(uint64(val))
return nil
}
name, err := enums.At(int(val)).NameBytes()
if err != nil {
return err
}
enc.w.Write(name)
return nil
}
// indentWriter is helper for writing indented text
type indentWriter struct {
w io.Writer
err error
// indentPerLevel is a string to prepend to a line for every level of indentation.
indentPerLevel string
// current indent level
currentIndent int
// hasLineContent is true when we have written something on the current line.
hasLineContent bool
}
func (iw *indentWriter) beforeWrite() {
if iw.err != nil {
return
}
if len(iw.indentPerLevel) > 0 && !iw.hasLineContent {
iw.hasLineContent = true
for i := 0; i < iw.currentIndent; i++ {
_, err := iw.w.Write([]byte(iw.indentPerLevel))
if err != nil {
iw.err = err
return
}
}
}
}
func (iw *indentWriter) Write(p []byte) (int, error) {
iw.beforeWrite()
if iw.err != nil {
return 0, iw.err
}
var n int
n, iw.err = iw.w.Write(p)
return n, iw.err
}
func (iw *indentWriter) WriteString(s string) (int, error) {
iw.beforeWrite()
if iw.err != nil {
return 0, iw.err
}
var n int
n, iw.err = io.WriteString(iw.w, s)
return n, iw.err
}
func (iw *indentWriter) WriteByte(b byte) error {
iw.beforeWrite()
if iw.err != nil {
return iw.err
}
if bw, ok := iw.w.(io.ByteWriter); ok {
iw.err = bw.WriteByte(b)
} else {
_, iw.err = iw.w.Write([]byte{b})
}
return iw.err
}
func (iw *indentWriter) Indent() {
iw.currentIndent++
}
func (iw *indentWriter) Unindent() {
iw.currentIndent--
}
func (iw *indentWriter) NewLine() {
if len(iw.indentPerLevel) > 0 && iw.hasLineContent {
if iw.err != nil {
return
}
if bw, ok := iw.w.(io.ByteWriter); ok {
iw.err = bw.WriteByte('\n')
} else {
_, iw.err = iw.w.Write([]byte{'\n'})
}
iw.hasLineContent = false
}
}
func (iw *indentWriter) NewLineOrSpace() {
if len(iw.indentPerLevel) > 0 {
iw.NewLine()
} else {
iw.WriteByte(' ')
}
}

View File

@ -1,45 +0,0 @@
// Code generated by capnpc-go. DO NOT EDIT.
package capnp
import (
schemas "zombiezen.com/go/capnproto2/schemas"
)
const Package = uint64(0xbea97f1023792be0)
const Import = uint64(0xe130b601260e44b5)
const Doc = uint64(0xc58ad6bd519f935e)
const Tag = uint64(0xa574b41924caefc7)
const Notag = uint64(0xc8768679ec52e012)
const Customtype = uint64(0xfa10659ae02f2093)
const Name = uint64(0xc2b96012172f8df1)
const schema_d12a1c51fedd6c88 = "x\xda\x12\x98\xe2\xc0d\xc8z\x9c\x89\x81!P\x81\x95" +
"\xed\xff\xf1\xf7\xa7T$\xb7\x94,e\x08\xe4e\xe5\xf8" +
"\xdf\x91s\xf7_\xa0\x8c\xd6E\x06\x06FaO\xc6." +
"\xe1@Fv\x06\x86`\x1fFfF\x06\xc6\xff\x0f\xb4" +
"+\x95\x05\xeaW\xee\x03)eDQj\xcb\xb8J\xd8" +
"\x15\xac\xd4\x01\xa2\xf4c\xaf\xbe\xb8P\xc2\xceC\x0c\x17" +
"yY\xff\xf1\xa3\xa85d\x9c$l\x09Vk\x02Q" +
"\x1b7y~\xe0\xdek]GA\xc6\x9a\xa0(Ue" +
"\xec\x12\xd6\x05+\xd5\x80(\x15z\x10\xf4\xa6\xb2\xad\xec" +
"\x04\xa6c%\x19g\x09+\x82\x95\xca@\x94nu\xe1" +
"Sc\xdcf\xf0\x10\xd3\xb1\xbc\x8c\x8b\x84E\xc1J\x05" +
" J'+\xe8?\x98\x95*\xf0\x0b\xa4T\x01E)" +
"#\xe3!aN\xb0R\x16\x90R\x9e\xff\xc5%)\xfa" +
"\xe9\xf9z\xc9\x8c\x89\x05y\x05V%\x89\xe9\x0c\x0c\x01" +
"\x8c\x8c\x8c<\x0cLhR\x05\x89\xc9\xfc\xd9\x89\xe9\xa9" +
"\xd8e\xf3\x12s\x19qH\xa5\xe4'\xe323/\xbf" +
"\x8491=\x80\x91\x91\x81\x19M&3\xb7\x80=\xbf" +
"\xa8\x04]\x1b\x13X2\xb9\xb4\xb8$?\xb7\xa4\xb2 " +
"\x15f. \x00\x00\xff\xff\x89\xff\x94\xdf"
func init() {
schemas.Register(schema_d12a1c51fedd6c88,
0xa574b41924caefc7,
0xbea97f1023792be0,
0xc2b96012172f8df1,
0xc58ad6bd519f935e,
0xc8768679ec52e012,
0xe130b601260e44b5,
0xfa10659ae02f2093)
}

View File

@ -1,19 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["fulfiller.go"],
importpath = "zombiezen.com/go/capnproto2/internal/fulfiller",
visibility = ["//:__subpackages__"],
deps = [
"//:go_default_library",
"//internal/queue:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["fulfiller_test.go"],
embed = [":go_default_library"],
deps = ["//:go_default_library"],
)

View File

@ -1,329 +0,0 @@
// Package fulfiller provides a type that implements capnp.Answer that
// resolves by calling setter methods.
package fulfiller
import (
"errors"
"sync"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/queue"
)
// callQueueSize is the maximum number of pending calls.
const callQueueSize = 64
// Fulfiller is a promise for a Struct. The zero value is an unresolved
// answer. A Fulfiller is considered to be resolved once Fulfill or
// Reject is called. Calls to the Fulfiller will queue up until it is
// resolved. A Fulfiller is safe to use from multiple goroutines.
type Fulfiller struct {
once sync.Once
resolved chan struct{} // initialized by init()
// Protected by mu
mu sync.RWMutex
answer capnp.Answer
queue []pcall // initialized by init()
}
// init initializes the Fulfiller. It is idempotent.
// Should be called for each method on Fulfiller.
func (f *Fulfiller) init() {
f.once.Do(func() {
f.resolved = make(chan struct{})
f.queue = make([]pcall, 0, callQueueSize)
})
}
// Fulfill sets the fulfiller's answer to s. If there are queued
// pipeline calls, the capabilities on the struct will be embargoed
// until the queued calls finish. Fulfill will panic if the fulfiller
// has already been resolved.
func (f *Fulfiller) Fulfill(s capnp.Struct) {
f.init()
f.mu.Lock()
if f.answer != nil {
f.mu.Unlock()
panic("Fulfiller.Fulfill called more than once")
}
f.answer = capnp.ImmediateAnswer(s)
queues := f.emptyQueue(s)
ctab := s.Segment().Message().CapTable
for capIdx, q := range queues {
ctab[capIdx] = newEmbargoClient(ctab[capIdx], q)
}
close(f.resolved)
f.mu.Unlock()
}
// emptyQueue splits the queue by which capability it targets and
// drops any invalid calls. Once this function returns, f.queue will
// be nil.
func (f *Fulfiller) emptyQueue(s capnp.Struct) map[capnp.CapabilityID][]ecall {
qs := make(map[capnp.CapabilityID][]ecall, len(f.queue))
for i, pc := range f.queue {
c, err := capnp.TransformPtr(s.ToPtr(), pc.transform)
if err != nil {
pc.f.Reject(err)
continue
}
in := c.Interface()
if !in.IsValid() {
pc.f.Reject(capnp.ErrNullClient)
continue
}
cn := in.Capability()
if qs[cn] == nil {
qs[cn] = make([]ecall, 0, len(f.queue)-i)
}
qs[cn] = append(qs[cn], pc.ecall)
}
f.queue = nil
return qs
}
// Reject sets the fulfiller's answer to err. If there are queued
// pipeline calls, they will all return errors. Reject will panic if
// the error is nil or the fulfiller has already been resolved.
func (f *Fulfiller) Reject(err error) {
if err == nil {
panic("Fulfiller.Reject called with nil")
}
f.init()
f.mu.Lock()
if f.answer != nil {
f.mu.Unlock()
panic("Fulfiller.Reject called more than once")
}
f.answer = capnp.ErrorAnswer(err)
for i := range f.queue {
f.queue[i].f.Reject(err)
f.queue[i] = pcall{}
}
close(f.resolved)
f.mu.Unlock()
}
// Done returns a channel that is closed once f is resolved.
func (f *Fulfiller) Done() <-chan struct{} {
f.init()
return f.resolved
}
// Peek returns f's resolved answer or nil if f has not been resolved.
// The Struct method of an answer returned from Peek returns immediately.
func (f *Fulfiller) Peek() capnp.Answer {
f.init()
f.mu.RLock()
a := f.answer
f.mu.RUnlock()
return a
}
// Struct waits until f is resolved and returns its struct if fulfilled
// or an error if rejected.
func (f *Fulfiller) Struct() (capnp.Struct, error) {
<-f.Done()
return f.Peek().Struct()
}
// PipelineCall calls PipelineCall on the fulfilled answer or queues the
// call if f has not been fulfilled.
func (f *Fulfiller) PipelineCall(transform []capnp.PipelineOp, call *capnp.Call) capnp.Answer {
f.init()
// Fast path: pass-through after fulfilled.
if a := f.Peek(); a != nil {
return a.PipelineCall(transform, call)
}
f.mu.Lock()
// Make sure that f wasn't fulfilled.
if a := f.answer; a != nil {
f.mu.Unlock()
return a.PipelineCall(transform, call)
}
if len(f.queue) == cap(f.queue) {
f.mu.Unlock()
return capnp.ErrorAnswer(errCallQueueFull)
}
cc, err := call.Copy(nil)
if err != nil {
f.mu.Unlock()
return capnp.ErrorAnswer(err)
}
g := new(Fulfiller)
f.queue = append(f.queue, pcall{
transform: transform,
ecall: ecall{
call: cc,
f: g,
},
})
f.mu.Unlock()
return g
}
// PipelineClose waits until f is resolved and then calls PipelineClose
// on the fulfilled answer.
func (f *Fulfiller) PipelineClose(transform []capnp.PipelineOp) error {
<-f.Done()
return f.Peek().PipelineClose(transform)
}
// pcall is a queued pipeline call.
type pcall struct {
transform []capnp.PipelineOp
ecall
}
// EmbargoClient is a client that flushes a queue of calls.
// Fulfiller will create these automatically when pipelined calls are
// made on unresolved answers. EmbargoClient is exported so that rpc
// can avoid making calls on its own Conn.
type EmbargoClient struct {
client capnp.Client
mu sync.RWMutex
q queue.Queue
calls ecallList
}
func newEmbargoClient(client capnp.Client, queue []ecall) capnp.Client {
ec := &EmbargoClient{
client: client,
calls: make(ecallList, callQueueSize),
}
ec.q.Init(ec.calls, copy(ec.calls, queue))
go ec.flushQueue()
return ec
}
func (ec *EmbargoClient) push(cl *capnp.Call) capnp.Answer {
f := new(Fulfiller)
cl, err := cl.Copy(nil)
if err != nil {
return capnp.ErrorAnswer(err)
}
i := ec.q.Push()
if i == -1 {
return capnp.ErrorAnswer(errCallQueueFull)
}
ec.calls[i] = ecall{cl, f}
return f
}
// flushQueue is run in its own goroutine.
func (ec *EmbargoClient) flushQueue() {
var c ecall
ec.mu.Lock()
if i := ec.q.Front(); i != -1 {
c = ec.calls[i]
}
ec.mu.Unlock()
for c.call != nil {
ans := ec.client.Call(c.call)
go func(f *Fulfiller, ans capnp.Answer) {
s, err := ans.Struct()
if err == nil {
f.Fulfill(s)
} else {
f.Reject(err)
}
}(c.f, ans)
ec.mu.Lock()
ec.q.Pop()
if i := ec.q.Front(); i != -1 {
c = ec.calls[i]
} else {
c = ecall{}
}
ec.mu.Unlock()
}
}
// Client returns the underlying client if the embargo has been lifted
// and nil otherwise.
func (ec *EmbargoClient) Client() capnp.Client {
ec.mu.RLock()
ok := ec.isPassthrough()
ec.mu.RUnlock()
if !ok {
return nil
}
return ec.client
}
func (ec *EmbargoClient) isPassthrough() bool {
return ec.q.Len() == 0
}
// Call either queues a call to the underlying client or starts a call
// if the embargo has been lifted.
func (ec *EmbargoClient) Call(cl *capnp.Call) capnp.Answer {
// Fast path: queue is flushed.
ec.mu.RLock()
ok := ec.isPassthrough()
ec.mu.RUnlock()
if ok {
return ec.client.Call(cl)
}
// Add to queue.
ec.mu.Lock()
// Since we released the lock, check that the queue hasn't been flushed.
if ec.isPassthrough() {
ec.mu.Unlock()
return ec.client.Call(cl)
}
ans := ec.push(cl)
ec.mu.Unlock()
return ans
}
// TryQueue will attempt to queue a call or return nil if the embargo
// has been lifted.
func (ec *EmbargoClient) TryQueue(cl *capnp.Call) capnp.Answer {
ec.mu.Lock()
if ec.isPassthrough() {
ec.mu.Unlock()
return nil
}
ans := ec.push(cl)
ec.mu.Unlock()
return ans
}
// Close closes the underlying client, rejecting any queued calls.
func (ec *EmbargoClient) Close() error {
ec.mu.Lock()
// reject all queued calls
for ec.q.Len() > 0 {
ec.calls[ec.q.Front()].f.Reject(errQueueCallCancel)
ec.q.Pop()
}
ec.mu.Unlock()
return ec.client.Close()
}
// ecall is an queued embargoed call.
type ecall struct {
call *capnp.Call
f *Fulfiller
}
type ecallList []ecall
func (el ecallList) Len() int {
return len(el)
}
func (el ecallList) Clear(i int) {
el[i] = ecall{}
}
var (
errCallQueueFull = errors.New("capnp: promised answer call queue full")
errQueueCallCancel = errors.New("capnp: queued call canceled")
)

View File

@ -1,13 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["nodemap.go"],
importpath = "zombiezen.com/go/capnproto2/internal/nodemap",
visibility = ["//:__subpackages__"],
deps = [
"//:go_default_library",
"//internal/schema:go_default_library",
"//schemas:go_default_library",
],
)

View File

@ -1,58 +0,0 @@
// Package nodemap provides a schema registry index type.
package nodemap
import (
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/schema"
"zombiezen.com/go/capnproto2/schemas"
)
// Map is a lazy index of a registry.
// The zero value is an index of the default registry.
type Map struct {
reg *schemas.Registry
nodes map[uint64]schema.Node
}
func (m *Map) registry() *schemas.Registry {
if m.reg != nil {
return m.reg
}
return &schemas.DefaultRegistry
}
func (m *Map) UseRegistry(reg *schemas.Registry) {
m.reg = reg
m.nodes = make(map[uint64]schema.Node)
}
// Find returns the node for the given ID.
func (m *Map) Find(id uint64) (schema.Node, error) {
if n := m.nodes[id]; n.IsValid() {
return n, nil
}
data, err := m.registry().Find(id)
if err != nil {
return schema.Node{}, err
}
msg, err := capnp.Unmarshal(data)
if err != nil {
return schema.Node{}, err
}
req, err := schema.ReadRootCodeGeneratorRequest(msg)
if err != nil {
return schema.Node{}, err
}
nodes, err := req.Nodes()
if err != nil {
return schema.Node{}, err
}
if m.nodes == nil {
m.nodes = make(map[uint64]schema.Node)
}
for i := 0; i < nodes.Len(); i++ {
n := nodes.At(i)
m.nodes[n.Id()] = n
}
return m.nodes[id], nil
}

View File

@ -1,19 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"discard.go",
"discard_go14.go",
"packed.go",
],
importpath = "zombiezen.com/go/capnproto2/internal/packed",
visibility = ["//:__subpackages__"],
)
go_test(
name = "go_default_test",
srcs = ["packed_test.go"],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
)

View File

@ -1,11 +0,0 @@
// +build go1.5
package packed
import (
"bufio"
)
func discard(r *bufio.Reader, n int) {
r.Discard(n)
}

View File

@ -1,13 +0,0 @@
// +build !go1.5
package packed
import (
"bufio"
"io"
"io/ioutil"
)
func discard(r *bufio.Reader, n int) {
io.CopyN(ioutil.Discard, r, int64(n))
}

View File

@ -1,65 +0,0 @@
// +build gofuzz
// Fuzz test harness. To run:
// go-fuzz-build zombiezen.com/go/capnproto2/internal/packed
// go-fuzz -bin=packed-fuzz.zip -workdir=internal/packed/testdata
package packed
import (
"bufio"
"bytes"
"io"
"io/ioutil"
)
func Fuzz(data []byte) int {
result := 0
// Unpacked
if unpacked, err := Unpack(nil, data); err == nil {
checkRepack(unpacked)
result = 1
}
// Read
{
r := NewReader(bufio.NewReader(bytes.NewReader(data)))
if unpacked, err := ioutil.ReadAll(r); err == nil {
checkRepack(unpacked)
result = 1
}
}
// ReadWord
{
r := NewReader(bufio.NewReader(bytes.NewReader(data)))
var unpacked []byte
var err error
for {
n := len(unpacked)
unpacked = append(unpacked, 0, 0, 0, 0, 0, 0, 0, 0)
if err = r.ReadWord(unpacked[n:]); err != nil {
unpacked = unpacked[:n]
break
}
}
if err == io.EOF {
checkRepack(unpacked)
result = 1
}
}
return result
}
func checkRepack(unpacked []byte) {
packed := Pack(nil, unpacked)
unpacked2, err := Unpack(nil, packed)
if err != nil {
panic("correctness: unpack, pack, unpack gives error: " + err.Error())
}
if !bytes.Equal(unpacked, unpacked2) {
panic("correctness: unpack, pack, unpack gives different results")
}
}

View File

@ -1,337 +0,0 @@
// Package packed provides functions to read and write the "packed"
// compression scheme described at https://capnproto.org/encoding.html#packing.
package packed
import (
"bufio"
"errors"
"io"
)
const wordSize = 8
// Special case tags.
const (
zeroTag byte = 0x00
unpackedTag byte = 0xff
)
// Pack appends the packed version of src to dst and returns the
// resulting slice. len(src) must be a multiple of 8 or Pack panics.
func Pack(dst, src []byte) []byte {
if len(src)%wordSize != 0 {
panic("packed.Pack len(src) must be a multiple of 8")
}
var buf [wordSize]byte
for len(src) > 0 {
var hdr byte
n := 0
for i := uint(0); i < wordSize; i++ {
if src[i] != 0 {
hdr |= 1 << i
buf[n] = src[i]
n++
}
}
dst = append(dst, hdr)
dst = append(dst, buf[:n]...)
src = src[wordSize:]
switch hdr {
case zeroTag:
z := min(numZeroWords(src), 0xff)
dst = append(dst, byte(z))
src = src[z*wordSize:]
case unpackedTag:
i := 0
end := min(len(src), 0xff*wordSize)
for i < end {
zeros := 0
for _, b := range src[i : i+wordSize] {
if b == 0 {
zeros++
}
}
if zeros > 1 {
break
}
i += wordSize
}
rawWords := byte(i / wordSize)
dst = append(dst, rawWords)
dst = append(dst, src[:i]...)
src = src[i:]
}
}
return dst
}
// numZeroWords returns the number of leading zero words in b.
func numZeroWords(b []byte) int {
for i, bb := range b {
if bb != 0 {
return i / wordSize
}
}
return len(b) / wordSize
}
// Unpack appends the unpacked version of src to dst and returns the
// resulting slice.
func Unpack(dst, src []byte) ([]byte, error) {
for len(src) > 0 {
tag := src[0]
src = src[1:]
pstart := len(dst)
dst = allocWords(dst, 1)
p := dst[pstart : pstart+wordSize]
if len(src) >= wordSize {
i := 0
nz := tag & 1
p[0] = src[i] & -nz
i += int(nz)
nz = tag >> 1 & 1
p[1] = src[i] & -nz
i += int(nz)
nz = tag >> 2 & 1
p[2] = src[i] & -nz
i += int(nz)
nz = tag >> 3 & 1
p[3] = src[i] & -nz
i += int(nz)
nz = tag >> 4 & 1
p[4] = src[i] & -nz
i += int(nz)
nz = tag >> 5 & 1
p[5] = src[i] & -nz
i += int(nz)
nz = tag >> 6 & 1
p[6] = src[i] & -nz
i += int(nz)
nz = tag >> 7 & 1
p[7] = src[i] & -nz
i += int(nz)
src = src[i:]
} else {
for i := uint(0); i < wordSize; i++ {
if tag&(1<<i) == 0 {
continue
}
if len(src) == 0 {
return dst, io.ErrUnexpectedEOF
}
p[i] = src[0]
src = src[1:]
}
}
switch tag {
case zeroTag:
if len(src) == 0 {
return dst, io.ErrUnexpectedEOF
}
dst = allocWords(dst, int(src[0]))
src = src[1:]
case unpackedTag:
if len(src) == 0 {
return dst, io.ErrUnexpectedEOF
}
start := len(dst)
dst = allocWords(dst, int(src[0]))
src = src[1:]
n := copy(dst[start:], src)
if n < len(dst)-start {
return dst, io.ErrUnexpectedEOF
}
src = src[n:]
}
}
return dst, nil
}
func allocWords(p []byte, n int) []byte {
target := len(p) + n*wordSize
if cap(p) >= target {
pp := p[len(p):target]
for i := range pp {
pp[i] = 0
}
return p[:target]
}
newcap := cap(p)
doublecap := newcap + newcap
if target > doublecap {
newcap = target
} else {
if len(p) < 1024 {
newcap = doublecap
} else {
for newcap < target {
newcap += newcap / 4
}
}
}
pp := make([]byte, target, newcap)
copy(pp, p)
return pp
}
// A Reader decompresses a packed byte stream.
type Reader struct {
// ReadWord state
rd *bufio.Reader
err error
zeroes int
literal int
// Read state
word [wordSize]byte
wordIdx int
}
// NewReader returns a reader that decompresses a packed stream from r.
func NewReader(r *bufio.Reader) *Reader {
return &Reader{rd: r, wordIdx: wordSize}
}
func min(a, b int) int {
if b < a {
return b
}
return a
}
// ReadWord decompresses the next word from the underlying stream.
func (r *Reader) ReadWord(p []byte) error {
if len(p) < wordSize {
return errors.New("packed: read word buffer too small")
}
r.wordIdx = wordSize // if the caller tries to call ReadWord and Read, don't give them partial words.
if r.err != nil {
err := r.err
r.err = nil
return err
}
p = p[:wordSize]
switch {
case r.zeroes > 0:
r.zeroes--
for i := range p {
p[i] = 0
}
return nil
case r.literal > 0:
r.literal--
_, err := io.ReadFull(r.rd, p)
return err
}
var tag byte
if r.rd.Buffered() < wordSize+1 {
var err error
tag, err = r.rd.ReadByte()
if err != nil {
return err
}
for i := range p {
p[i] = 0
}
for i := uint(0); i < wordSize; i++ {
if tag&(1<<i) != 0 {
p[i], err = r.rd.ReadByte()
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
}
} else {
p[i] = 0
}
}
} else {
b, _ := r.rd.Peek(wordSize + 1)
tag = b[0]
i := 1
nz := tag & 1
p[0] = b[i] & -nz
i += int(nz)
nz = tag >> 1 & 1
p[1] = b[i] & -nz
i += int(nz)
nz = tag >> 2 & 1
p[2] = b[i] & -nz
i += int(nz)
nz = tag >> 3 & 1
p[3] = b[i] & -nz
i += int(nz)
nz = tag >> 4 & 1
p[4] = b[i] & -nz
i += int(nz)
nz = tag >> 5 & 1
p[5] = b[i] & -nz
i += int(nz)
nz = tag >> 6 & 1
p[6] = b[i] & -nz
i += int(nz)
nz = tag >> 7 & 1
p[7] = b[i] & -nz
i += int(nz)
discard(r.rd, i)
}
switch tag {
case zeroTag:
z, err := r.rd.ReadByte()
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
r.err = err
return err
}
r.zeroes = int(z)
case unpackedTag:
l, err := r.rd.ReadByte()
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
r.err = err
return err
}
r.literal = int(l)
}
return nil
}
// Read reads up to len(p) bytes into p. This will decompress whole
// words at a time, so mixing calls to Read and ReadWord may lead to
// bytes missing.
func (r *Reader) Read(p []byte) (n int, err error) {
if r.wordIdx < wordSize {
n = copy(p, r.word[r.wordIdx:])
r.wordIdx += n
}
for n < len(p) {
if r.rd.Buffered() < wordSize+1 && n > 0 {
return n, nil
}
if len(p)-n >= wordSize {
err := r.ReadWord(p[n:])
if err != nil {
return n, err
}
n += wordSize
} else {
err := r.ReadWord(r.word[:])
if err != nil {
return n, err
}
r.wordIdx = copy(p[n:], r.word[:])
n += r.wordIdx
}
}
return n, nil
}

View File

@ -1,14 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["queue.go"],
importpath = "zombiezen.com/go/capnproto2/internal/queue",
visibility = ["//:__subpackages__"],
)
go_test(
name = "go_default_test",
srcs = ["queue_test.go"],
embed = [":go_default_library"],
)

View File

@ -1,70 +0,0 @@
// Package queue implements a generic queue using a ring buffer.
package queue
// A Queue wraps an Interface to provide queue operations.
type Queue struct {
q Interface
start int
n int
cap int
}
// New creates a new queue that starts with n elements. The interface's
// length must not change over the course of the queue's usage.
func New(q Interface, n int) *Queue {
qq := new(Queue)
qq.Init(q, n)
return qq
}
// Init initializes a queue. The old queue is untouched.
func (q *Queue) Init(r Interface, n int) {
q.q = r
q.start = 0
q.n = n
q.cap = r.Len()
}
// Len returns the length of the queue. This is different from the
// underlying interface's length, which is the queue's capacity.
func (q *Queue) Len() int {
return q.n
}
// Push reserves space for an element on the queue, returning its index.
// If the queue is full, Push returns -1.
func (q *Queue) Push() int {
if q.n >= q.cap {
return -1
}
i := (q.start + q.n) % q.cap
q.n++
return i
}
// Front returns the index of the front of the queue, or -1 if the queue is empty.
func (q *Queue) Front() int {
if q.n == 0 {
return -1
}
return q.start
}
// Pop pops an element from the queue, returning whether it succeeded.
func (q *Queue) Pop() bool {
if q.n == 0 {
return false
}
q.q.Clear(q.start)
q.start = (q.start + 1) % q.cap
q.n--
return true
}
// A type implementing Interface can be used to store elements in a Queue.
type Interface interface {
// Len returns the number of elements available.
Len() int
// Clear removes the element at i.
Clear(i int)
}

View File

@ -1,9 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["schema.capnp.go"],
importpath = "zombiezen.com/go/capnproto2/internal/schema",
visibility = ["//:__subpackages__"],
deps = ["//:go_default_library"],
)

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["strquote.go"],
importpath = "zombiezen.com/go/capnproto2/internal/strquote",
visibility = ["//:__subpackages__"],
)

View File

@ -1,52 +0,0 @@
// Package strquote provides a function for formatting a string as a
// Cap'n Proto string literal.
package strquote
// Append appends a Cap'n Proto string literal of s to buf.
func Append(buf []byte, s []byte) []byte {
buf = append(buf, '"')
last := 0
for i, b := range s {
if !needsEscape(b) {
continue
}
buf = append(buf, s[last:i]...)
switch b {
case '\a':
buf = append(buf, '\\', 'a')
case '\b':
buf = append(buf, '\\', 'b')
case '\f':
buf = append(buf, '\\', 'f')
case '\n':
buf = append(buf, '\\', 'n')
case '\r':
buf = append(buf, '\\', 'r')
case '\t':
buf = append(buf, '\\', 't')
case '\v':
buf = append(buf, '\\', 'v')
case '\'':
buf = append(buf, '\\', '\'')
case '"':
buf = append(buf, '\\', '"')
case '\\':
buf = append(buf, '\\', '\\')
default:
buf = append(buf, '\\', 'x', hexDigit(b/16), hexDigit(b%16))
}
last = i + 1
}
buf = append(buf, s[last:]...)
buf = append(buf, '"')
return buf
}
func needsEscape(b byte) bool {
return b < 0x20 || b >= 0x7f
}
func hexDigit(b byte) byte {
const digits = "0123456789abcdef"
return digits[b]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,913 +0,0 @@
package capnp
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
"zombiezen.com/go/capnproto2/internal/packed"
)
// Security limits. Matches C++ implementation.
const (
defaultTraverseLimit = 64 << 20 // 64 MiB
defaultDepthLimit = 64
maxStreamSegments = 512
defaultDecodeLimit = 64 << 20 // 64 MiB
)
const maxDepth = ^uint(0)
// A Message is a tree of Cap'n Proto objects, split into one or more
// segments of contiguous memory. The only required field is Arena.
// A Message is safe to read from multiple goroutines.
type Message struct {
// rlimit must be first so that it is 64-bit aligned.
// See sync/atomic docs.
rlimit ReadLimiter
rlimitInit sync.Once
Arena Arena
// CapTable is the indexed list of the clients referenced in the
// message. Capability pointers inside the message will use this table
// to map pointers to Clients. The table is usually populated by the
// RPC system.
//
// See https://capnproto.org/encoding.html#capabilities-interfaces for
// more details on the capability table.
CapTable []Client
// TraverseLimit limits how many total bytes of data are allowed to be
// traversed while reading. Traversal is counted when a Struct or
// List is obtained. This means that calling a getter for the same
// sub-struct multiple times will cause it to be double-counted. Once
// the traversal limit is reached, pointer accessors will report
// errors. See https://capnproto.org/encoding.html#amplification-attack
// for more details on this security measure.
//
// If not set, this defaults to 64 MiB.
TraverseLimit uint64
// DepthLimit limits how deeply-nested a message structure can be.
// If not set, this defaults to 64.
DepthLimit uint
// mu protects the following fields:
mu sync.Mutex
segs map[SegmentID]*Segment
firstSeg Segment // Preallocated first segment. msg is non-nil once initialized.
}
// NewMessage creates a message with a new root and returns the first
// segment. It is an error to call NewMessage on an arena with data in it.
func NewMessage(arena Arena) (msg *Message, first *Segment, err error) {
msg = &Message{Arena: arena}
switch arena.NumSegments() {
case 0:
first, err = msg.allocSegment(wordSize)
if err != nil {
return nil, nil, err
}
case 1:
first, err = msg.Segment(0)
if err != nil {
return nil, nil, err
}
if len(first.data) > 0 {
return nil, nil, errHasData
}
default:
return nil, nil, errHasData
}
if first.ID() != 0 {
return nil, nil, errors.New("capnp: arena allocated first segment with non-zero ID")
}
seg, _, err := alloc(first, wordSize) // allocate root
if err != nil {
return nil, nil, err
}
if seg != first {
return nil, nil, errors.New("capnp: arena didn't allocate first word in first segment")
}
return msg, first, nil
}
// Reset resets a message to use a different arena, allowing a single
// Message to be reused for reading multiple messages. This invalidates
// any existing pointers in the Message, so use with caution.
func (m *Message) Reset(arena Arena) {
m.mu.Lock()
m.Arena = arena
m.CapTable = nil
m.segs = nil
m.firstSeg = Segment{}
m.mu.Unlock()
if m.TraverseLimit == 0 {
m.ReadLimiter().Reset(defaultTraverseLimit)
} else {
m.ReadLimiter().Reset(m.TraverseLimit)
}
}
// Root returns the pointer to the message's root object.
//
// Deprecated: Use RootPtr.
func (m *Message) Root() (Pointer, error) {
p, err := m.RootPtr()
return p.toPointer(), err
}
// RootPtr returns the pointer to the message's root object.
func (m *Message) RootPtr() (Ptr, error) {
s, err := m.Segment(0)
if err != nil {
return Ptr{}, err
}
return s.root().PtrAt(0)
}
// SetRoot sets the message's root object to p.
//
// Deprecated: Use SetRootPtr.
func (m *Message) SetRoot(p Pointer) error {
return m.SetRootPtr(toPtr(p))
}
// SetRootPtr sets the message's root object to p.
func (m *Message) SetRootPtr(p Ptr) error {
s, err := m.Segment(0)
if err != nil {
return err
}
return s.root().SetPtr(0, p)
}
// AddCap appends a capability to the message's capability table and
// returns its ID.
func (m *Message) AddCap(c Client) CapabilityID {
n := CapabilityID(len(m.CapTable))
m.CapTable = append(m.CapTable, c)
return n
}
// ReadLimiter returns the message's read limiter. Useful if you want
// to reset the traversal limit while reading.
func (m *Message) ReadLimiter() *ReadLimiter {
m.rlimitInit.Do(func() {
if m.TraverseLimit == 0 {
m.rlimit.limit = defaultTraverseLimit
} else {
m.rlimit.limit = m.TraverseLimit
}
})
return &m.rlimit
}
func (m *Message) depthLimit() uint {
if m.DepthLimit != 0 {
return m.DepthLimit
}
return defaultDepthLimit
}
// NumSegments returns the number of segments in the message.
func (m *Message) NumSegments() int64 {
return int64(m.Arena.NumSegments())
}
// Segment returns the segment with the given ID.
func (m *Message) Segment(id SegmentID) (*Segment, error) {
if isInt32Bit && id > maxInt32 {
return nil, errSegment32Bit
}
if int64(id) >= m.Arena.NumSegments() {
return nil, errSegmentOutOfBounds
}
m.mu.Lock()
if seg := m.segment(id); seg != nil {
m.mu.Unlock()
return seg, nil
}
data, err := m.Arena.Data(id)
if err != nil {
m.mu.Unlock()
return nil, err
}
seg := m.setSegment(id, data)
m.mu.Unlock()
return seg, nil
}
// segment returns the segment with the given ID.
// The caller must be holding m.mu.
func (m *Message) segment(id SegmentID) *Segment {
if m.segs == nil {
if id == 0 && m.firstSeg.msg != nil {
return &m.firstSeg
}
return nil
}
return m.segs[id]
}
// setSegment creates or updates the Segment with the given ID.
// The caller must be holding m.mu.
func (m *Message) setSegment(id SegmentID, data []byte) *Segment {
if m.segs == nil {
if id == 0 {
m.firstSeg = Segment{
id: id,
msg: m,
data: data,
}
return &m.firstSeg
}
m.segs = make(map[SegmentID]*Segment)
if m.firstSeg.msg != nil {
m.segs[0] = &m.firstSeg
}
} else if seg := m.segs[id]; seg != nil {
seg.data = data
return seg
}
seg := &Segment{
id: id,
msg: m,
data: data,
}
m.segs[id] = seg
return seg
}
// allocSegment creates or resizes an existing segment such that
// cap(seg.Data) - len(seg.Data) >= sz.
func (m *Message) allocSegment(sz Size) (*Segment, error) {
m.mu.Lock()
if m.segs == nil && m.firstSeg.msg != nil {
m.segs = make(map[SegmentID]*Segment)
m.segs[0] = &m.firstSeg
}
id, data, err := m.Arena.Allocate(sz, m.segs)
if err != nil {
m.mu.Unlock()
return nil, err
}
if isInt32Bit && id > maxInt32 {
m.mu.Unlock()
return nil, errSegment32Bit
}
seg := m.setSegment(id, data)
m.mu.Unlock()
return seg, nil
}
// alloc allocates sz zero-filled bytes. It prefers using s, but may
// use a different segment in the same message if there's not sufficient
// capacity.
func alloc(s *Segment, sz Size) (*Segment, Address, error) {
sz = sz.padToWord()
if sz > maxSize-wordSize {
return nil, 0, errOverflow
}
if !hasCapacity(s.data, sz) {
var err error
s, err = s.msg.allocSegment(sz)
if err != nil {
return nil, 0, err
}
}
addr := Address(len(s.data))
end, ok := addr.addSize(sz)
if !ok {
return nil, 0, errOverflow
}
space := s.data[len(s.data):end]
s.data = s.data[:end]
for i := range space {
space[i] = 0
}
return s, addr, nil
}
// An Arena loads and allocates segments for a Message.
type Arena interface {
// NumSegments returns the number of segments in the arena.
// This must not be larger than 1<<32.
NumSegments() int64
// Data loads the data for the segment with the given ID. IDs are in
// the range [0, NumSegments()).
// must be tightly packed in the range [0, NumSegments()).
Data(id SegmentID) ([]byte, error)
// Allocate selects a segment to place a new object in, creating a
// segment or growing the capacity of a previously loaded segment if
// necessary. If Allocate does not return an error, then the
// difference of the capacity and the length of the returned slice
// must be at least minsz. segs is a map of segment slices returned
// by the Data method keyed by ID (although the length of these slices
// may have changed by previous allocations). Allocate must not
// modify segs.
//
// If Allocate creates a new segment, the ID must be one larger than
// the last segment's ID or zero if it is the first segment.
//
// If Allocate returns an previously loaded segment's ID, then the
// arena is responsible for preserving the existing data in the
// returned byte slice.
Allocate(minsz Size, segs map[SegmentID]*Segment) (SegmentID, []byte, error)
}
type singleSegmentArena []byte
// SingleSegment returns a new arena with an expanding single-segment
// buffer. b can be used to populate the segment for reading or to
// reserve memory of a specific size. A SingleSegment arena does not
// return errors unless you attempt to access another segment.
func SingleSegment(b []byte) Arena {
ssa := new(singleSegmentArena)
*ssa = b
return ssa
}
func (ssa *singleSegmentArena) NumSegments() int64 {
return 1
}
func (ssa *singleSegmentArena) Data(id SegmentID) ([]byte, error) {
if id != 0 {
return nil, errSegmentOutOfBounds
}
return *ssa, nil
}
func (ssa *singleSegmentArena) Allocate(sz Size, segs map[SegmentID]*Segment) (SegmentID, []byte, error) {
data := []byte(*ssa)
if segs[0] != nil {
data = segs[0].data
}
if len(data)%int(wordSize) != 0 {
return 0, nil, errors.New("capnp: segment size is not a multiple of word size")
}
if hasCapacity(data, sz) {
return 0, data, nil
}
inc, err := nextAlloc(int64(cap(data)), int64(maxSegmentSize()), sz)
if err != nil {
return 0, nil, fmt.Errorf("capnp: alloc %d bytes: %v", sz, err)
}
buf := make([]byte, len(data), cap(data)+inc)
copy(buf, data)
*ssa = buf
return 0, *ssa, nil
}
type roSingleSegment []byte
func (ss roSingleSegment) NumSegments() int64 {
return 1
}
func (ss roSingleSegment) Data(id SegmentID) ([]byte, error) {
if id != 0 {
return nil, errSegmentOutOfBounds
}
return ss, nil
}
func (ss roSingleSegment) Allocate(sz Size, segs map[SegmentID]*Segment) (SegmentID, []byte, error) {
return 0, nil, errors.New("capnp: segment is read-only")
}
type multiSegmentArena [][]byte
// MultiSegment returns a new arena that allocates new segments when
// they are full. b can be used to populate the buffer for reading or
// to reserve memory of a specific size.
func MultiSegment(b [][]byte) Arena {
msa := new(multiSegmentArena)
*msa = b
return msa
}
// demuxArena slices b into a multi-segment arena.
func demuxArena(hdr streamHeader, data []byte) (Arena, error) {
segs := make([][]byte, int(hdr.maxSegment())+1)
for i := range segs {
sz, err := hdr.segmentSize(uint32(i))
if err != nil {
return nil, err
}
segs[i], data = data[:sz:sz], data[sz:]
}
return MultiSegment(segs), nil
}
func (msa *multiSegmentArena) NumSegments() int64 {
return int64(len(*msa))
}
func (msa *multiSegmentArena) Data(id SegmentID) ([]byte, error) {
if int64(id) >= int64(len(*msa)) {
return nil, errSegmentOutOfBounds
}
return (*msa)[id], nil
}
func (msa *multiSegmentArena) Allocate(sz Size, segs map[SegmentID]*Segment) (SegmentID, []byte, error) {
var total int64
for i, data := range *msa {
id := SegmentID(i)
if s := segs[id]; s != nil {
data = s.data
}
if hasCapacity(data, sz) {
return id, data, nil
}
total += int64(cap(data))
if total < 0 {
// Overflow.
return 0, nil, fmt.Errorf("capnp: alloc %d bytes: message too large", sz)
}
}
n, err := nextAlloc(total, 1<<63-1, sz)
if err != nil {
return 0, nil, fmt.Errorf("capnp: alloc %d bytes: %v", sz, err)
}
buf := make([]byte, 0, n)
id := SegmentID(len(*msa))
*msa = append(*msa, buf)
return id, buf, nil
}
// nextAlloc computes how much more space to allocate given the number
// of bytes allocated in the entire message and the requested number of
// bytes. It will always return a multiple of wordSize. max must be a
// multiple of wordSize. The sum of curr and the returned size will
// always be less than max.
func nextAlloc(curr, max int64, req Size) (int, error) {
if req == 0 {
return 0, nil
}
maxinc := int64(1<<32 - 8) // largest word-aligned Size
if isInt32Bit {
maxinc = 1<<31 - 8 // largest word-aligned int
}
if int64(req) > maxinc {
return 0, errors.New("allocation too large")
}
req = req.padToWord()
want := curr + int64(req)
if want <= curr || want > max {
return 0, errors.New("allocation overflows message size")
}
new := curr
double := new + new
switch {
case want < 1024:
next := (1024 - curr + 7) &^ 7
if next < curr {
return int((curr + 7) &^ 7), nil
}
return int(next), nil
case want > double:
return int(req), nil
default:
for 0 < new && new < want {
new += new / 4
}
if new <= 0 {
return int(req), nil
}
delta := new - curr
if delta > maxinc {
return int(maxinc), nil
}
return int((delta + 7) &^ 7), nil
}
}
// A Decoder represents a framer that deserializes a particular Cap'n
// Proto input stream.
type Decoder struct {
r io.Reader
segbuf [msgHeaderSize]byte
hdrbuf []byte
reuse bool
buf []byte
msg Message
arena roSingleSegment
// Maximum number of bytes that can be read per call to Decode.
// If not set, a reasonable default is used.
MaxMessageSize uint64
}
// NewDecoder creates a new Cap'n Proto framer that reads from r.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
// NewPackedDecoder creates a new Cap'n Proto framer that reads from a
// packed stream r.
func NewPackedDecoder(r io.Reader) *Decoder {
return NewDecoder(packed.NewReader(bufio.NewReader(r)))
}
// Decode reads a message from the decoder stream.
func (d *Decoder) Decode() (*Message, error) {
maxSize := d.MaxMessageSize
if maxSize == 0 {
maxSize = defaultDecodeLimit
}
if _, err := io.ReadFull(d.r, d.segbuf[:]); err != nil {
return nil, err
}
maxSeg := binary.LittleEndian.Uint32(d.segbuf[:])
if maxSeg > maxStreamSegments {
return nil, errTooManySegments
}
hdrSize := streamHeaderSize(maxSeg)
if hdrSize > maxSize || hdrSize > (1<<31-1) {
return nil, errDecodeLimit
}
d.hdrbuf = resizeSlice(d.hdrbuf, int(hdrSize))
copy(d.hdrbuf, d.segbuf[:])
if _, err := io.ReadFull(d.r, d.hdrbuf[msgHeaderSize:]); err != nil {
return nil, err
}
hdr, _, err := parseStreamHeader(d.hdrbuf)
if err != nil {
return nil, err
}
total, err := hdr.totalSize()
if err != nil {
return nil, err
}
// TODO(someday): if total size is greater than can fit in one buffer,
// attempt to allocate buffer per segment.
if total > maxSize-hdrSize || total > (1<<31-1) {
return nil, errDecodeLimit
}
if !d.reuse {
buf := make([]byte, int(total))
if _, err := io.ReadFull(d.r, buf); err != nil {
return nil, err
}
arena, err := demuxArena(hdr, buf)
if err != nil {
return nil, err
}
return &Message{Arena: arena}, nil
}
d.buf = resizeSlice(d.buf, int(total))
if _, err := io.ReadFull(d.r, d.buf); err != nil {
return nil, err
}
var arena Arena
if hdr.maxSegment() == 0 {
d.arena = d.buf[:len(d.buf):len(d.buf)]
arena = &d.arena
} else {
var err error
arena, err = demuxArena(hdr, d.buf)
if err != nil {
return nil, err
}
}
d.msg.Reset(arena)
return &d.msg, nil
}
func resizeSlice(b []byte, size int) []byte {
if cap(b) < size {
return make([]byte, size)
}
return b[:size]
}
// ReuseBuffer causes the decoder to reuse its buffer on subsequent decodes.
// The decoder may return messages that cannot handle allocations.
func (d *Decoder) ReuseBuffer() {
d.reuse = true
}
// Unmarshal reads an unpacked serialized stream into a message. No
// copying is performed, so the objects in the returned message read
// directly from data.
func Unmarshal(data []byte) (*Message, error) {
if len(data) == 0 {
return nil, io.EOF
}
hdr, data, err := parseStreamHeader(data)
if err != nil {
return nil, err
}
if tot, err := hdr.totalSize(); err != nil {
return nil, err
} else if tot > uint64(len(data)) {
return nil, io.ErrUnexpectedEOF
}
arena, err := demuxArena(hdr, data)
if err != nil {
return nil, err
}
return &Message{Arena: arena}, nil
}
// UnmarshalPacked reads a packed serialized stream into a message.
func UnmarshalPacked(data []byte) (*Message, error) {
if len(data) == 0 {
return nil, io.EOF
}
data, err := packed.Unpack(nil, data)
if err != nil {
return nil, err
}
return Unmarshal(data)
}
// MustUnmarshalRoot reads an unpacked serialized stream and returns
// its root pointer. If there is any error, it panics.
//
// Deprecated: Use MustUnmarshalRootPtr.
func MustUnmarshalRoot(data []byte) Pointer {
msg, err := Unmarshal(data)
if err != nil {
panic(err)
}
p, err := msg.Root()
if err != nil {
panic(err)
}
return p
}
// MustUnmarshalRootPtr reads an unpacked serialized stream and returns
// its root pointer. If there is any error, it panics.
func MustUnmarshalRootPtr(data []byte) Ptr {
msg, err := Unmarshal(data)
if err != nil {
panic(err)
}
p, err := msg.RootPtr()
if err != nil {
panic(err)
}
return p
}
// An Encoder represents a framer for serializing a particular Cap'n
// Proto stream.
type Encoder struct {
w io.Writer
hdrbuf []byte
bufs [][]byte
packed bool
packbuf []byte
}
// NewEncoder creates a new Cap'n Proto framer that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w}
}
// NewPackedEncoder creates a new Cap'n Proto framer that writes to a
// packed stream w.
func NewPackedEncoder(w io.Writer) *Encoder {
return &Encoder{w: w, packed: true}
}
// Encode writes a message to the encoder stream.
func (e *Encoder) Encode(m *Message) error {
nsegs := m.NumSegments()
if nsegs == 0 {
return errMessageEmpty
}
e.bufs = append(e.bufs[:0], nil) // first element is placeholder for header
maxSeg := uint32(nsegs - 1)
hdrSize := streamHeaderSize(maxSeg)
if uint64(cap(e.hdrbuf)) < hdrSize {
e.hdrbuf = make([]byte, 0, hdrSize)
}
e.hdrbuf = appendUint32(e.hdrbuf[:0], maxSeg)
for i := int64(0); i < nsegs; i++ {
s, err := m.Segment(SegmentID(i))
if err != nil {
return err
}
n := len(s.data)
if int64(n) > int64(maxSize) {
return errSegmentTooLarge
}
e.hdrbuf = appendUint32(e.hdrbuf, uint32(Size(n)/wordSize))
e.bufs = append(e.bufs, s.data)
}
if len(e.hdrbuf)%int(wordSize) != 0 {
e.hdrbuf = appendUint32(e.hdrbuf, 0)
}
e.bufs[0] = e.hdrbuf
if e.packed {
return e.writePacked(e.bufs)
}
return e.write(e.bufs)
}
func (e *Encoder) writePacked(bufs [][]byte) error {
for _, b := range bufs {
e.packbuf = packed.Pack(e.packbuf[:0], b)
if _, err := e.w.Write(e.packbuf); err != nil {
return err
}
}
return nil
}
func (m *Message) segmentSizes() ([]Size, error) {
nsegs := m.NumSegments()
sizes := make([]Size, nsegs)
for i := int64(0); i < nsegs; i++ {
s, err := m.Segment(SegmentID(i))
if err != nil {
return sizes[:i], err
}
n := len(s.data)
if int64(n) > int64(maxSize) {
return sizes[:i], errSegmentTooLarge
}
sizes[i] = Size(n)
}
return sizes, nil
}
// Marshal concatenates the segments in the message into a single byte
// slice including framing.
func (m *Message) Marshal() ([]byte, error) {
// Compute buffer size.
// TODO(light): error out if too many segments
nsegs := m.NumSegments()
if nsegs == 0 {
return nil, errMessageEmpty
}
maxSeg := uint32(nsegs - 1)
hdrSize := streamHeaderSize(maxSeg)
sizes, err := m.segmentSizes()
if err != nil {
return nil, err
}
// TODO(light): error out if too large
total := uint64(hdrSize) + totalSize(sizes)
// Fill in buffer.
buf := make([]byte, hdrSize, total)
// TODO: remove marshalStreamHeader and inline.
marshalStreamHeader(buf, sizes)
for i := int64(0); i < nsegs; i++ {
s, err := m.Segment(SegmentID(i))
if err != nil {
return nil, err
}
buf = append(buf, s.data...)
}
return buf, nil
}
// MarshalPacked marshals the message in packed form.
func (m *Message) MarshalPacked() ([]byte, error) {
data, err := m.Marshal()
if err != nil {
return nil, err
}
buf := make([]byte, 0, len(data))
buf = packed.Pack(buf, data)
return buf, nil
}
// Stream header sizes.
const (
msgHeaderSize = 4
segHeaderSize = 4
)
// streamHeaderSize returns the size of the header, given the
// first 32-bit number.
func streamHeaderSize(n uint32) uint64 {
return (msgHeaderSize + segHeaderSize*(uint64(n)+1) + 7) &^ 7
}
// marshalStreamHeader marshals the sizes into the byte slice, which
// must be of size streamHeaderSize(len(sizes) - 1).
//
// TODO: remove marshalStreamHeader and inline.
func marshalStreamHeader(b []byte, sizes []Size) {
binary.LittleEndian.PutUint32(b, uint32(len(sizes)-1))
for i, sz := range sizes {
loc := msgHeaderSize + i*segHeaderSize
binary.LittleEndian.PutUint32(b[loc:], uint32(sz/Size(wordSize)))
}
}
// appendUint32 appends a uint32 to a byte slice and returns the
// new slice.
func appendUint32(b []byte, v uint32) []byte {
b = append(b, 0, 0, 0, 0)
binary.LittleEndian.PutUint32(b[len(b)-4:], v)
return b
}
type streamHeader struct {
b []byte
}
// parseStreamHeader parses the header of the stream framing format.
func parseStreamHeader(data []byte) (h streamHeader, tail []byte, err error) {
if uint64(len(data)) < streamHeaderSize(0) {
return streamHeader{}, nil, io.ErrUnexpectedEOF
}
maxSeg := binary.LittleEndian.Uint32(data)
// TODO(light): check int
hdrSize := streamHeaderSize(maxSeg)
if uint64(len(data)) < hdrSize {
return streamHeader{}, nil, io.ErrUnexpectedEOF
}
return streamHeader{b: data}, data[hdrSize:], nil
}
func (h streamHeader) maxSegment() uint32 {
return binary.LittleEndian.Uint32(h.b)
}
func (h streamHeader) segmentSize(i uint32) (Size, error) {
s := binary.LittleEndian.Uint32(h.b[msgHeaderSize+i*segHeaderSize:])
sz, ok := wordSize.times(int32(s))
if !ok {
return 0, errSegmentTooLarge
}
return sz, nil
}
func (h streamHeader) totalSize() (uint64, error) {
var sum uint64
for i := uint64(0); i <= uint64(h.maxSegment()); i++ {
x, err := h.segmentSize(uint32(i))
if err != nil {
return sum, err
}
sum += uint64(x)
}
return sum, nil
}
func hasCapacity(b []byte, sz Size) bool {
return sz <= Size(cap(b)-len(b))
}
func totalSize(s []Size) uint64 {
var sum uint64
for _, sz := range s {
sum += uint64(sz)
}
return sum
}
const (
maxInt32 = 0x7fffffff
maxInt = int(^uint(0) >> 1)
isInt32Bit = maxInt == maxInt32
)
// maxSegmentSize returns the maximum permitted size of a single segment
// on this platform.
//
// This is effectively a compile-time constant, but can't be represented
// as a constant because it requires a conditional. It is trivially
// inlinable and optimizable, so should act like one.
func maxSegmentSize() Size {
if isInt32Bit {
return Size(maxInt32 - 7)
} else {
return maxSize - 7
}
}
var (
errSegmentOutOfBounds = errors.New("capnp: segment ID out of bounds")
errSegment32Bit = errors.New("capnp: segment ID larger than 31 bits")
errMessageEmpty = errors.New("capnp: marshalling an empty message")
errHasData = errors.New("capnp: NewMessage called on arena with data")
errSegmentTooLarge = errors.New("capnp: segment too large")
errTooManySegments = errors.New("capnp: too many segments to decode")
errDecodeLimit = errors.New("capnp: message too large")
)

View File

@ -1,10 +0,0 @@
// +build go1.8
package capnp
import "net"
func (e *Encoder) write(bufs [][]byte) error {
_, err := (*net.Buffers)(&bufs).WriteTo(e.w)
return err
}

View File

@ -1,12 +0,0 @@
// +build !go1.8
package capnp
func (e *Encoder) write(bufs [][]byte) error {
for _, b := range bufs {
if _, err := e.w.Write(b); err != nil {
return err
}
}
return nil
}

View File

@ -1,37 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"extract.go",
"fields.go",
"insert.go",
],
importpath = "zombiezen.com/go/capnproto2/pogs",
visibility = ["//visibility:public"],
deps = [
"//:go_default_library",
"//internal/nodemap:go_default_library",
"//internal/schema:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"bench_test.go",
"embed_test.go",
"example_test.go",
"interface_test.go",
"pogs_test.go",
],
embed = [":go_default_library"],
deps = [
"//:go_default_library",
"//internal/aircraftlib:go_default_library",
"//internal/demo/books:go_default_library",
"@com_github_kylelemons_godebug//pretty:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)

View File

@ -1,164 +0,0 @@
/*
Package pogs provides functions to convert Cap'n Proto messages to and
from Go structs. pogs operates similarly to encoding/json: define a
struct that is optionally marked up with tags, then Insert and Extract
will copy the fields to and from the corresponding Cap'n Proto struct.
Inserting
To copy data into a Cap'n Proto struct, we use the Insert function.
Consider the following schema:
struct Message {
name @0 :Text;
body @1 :Text;
time @2 :Int64;
}
and the Go struct:
type Message struct {
Name string
Body string
Time int64
}
We can copy the Go struct into a Cap'n Proto struct like this:
_, arena, _ := capnp.NewMessage(capnp.SingleSegment(nil))
root, _ := myschema.NewRootMessage(arena)
m := &Message{"Alice", "Hello", 1294706395881547000}
err := pogs.Insert(myschema.Message_TypeID, root.Struct, m)
Note that if any field names in our Go struct don't match to a field in
the Cap'n Proto struct, Insert returns an error. We'll see how to fix
that in a moment.
Extracting
Copying data back out from a Cap'n Proto struct is quite similar: we
pass a pointer to our Go struct to Extract.
m := new(Message)
err := pogs.Extract(m, myschema.Message_TypeID, root.Struct)
Types
The mapping between Cap'n Proto types and underlying Go types is as
follows:
Bool -> bool
Int8, Int16, Int32, Int64 -> int8, int16, int32, int64
UInt8, UInt16, UInt32, UInt64 -> uint8, uint16, uint32, uint64
Float32, Float64 -> float32, float64
Text -> either []byte or string
Data -> []byte
List -> slice
enum -> uint16
struct -> a struct or pointer to struct
interface -> a capnp.Client or struct with
exactly one field, named
"Client", of type capnp.Client
Note that the unsized int and uint type can't be used: int and float
types must match in size. For Data and Text fields using []byte, the
filled-in byte slice will point to original segment.
Renaming and Omitting Fields
By default, the Go field name is the same as the Cap'n Proto schema
field name with the first letter capitalized. If we want to change this
mapping, we use the capnp field tag.
type MessageRenamed struct {
Subject string `capnp:"name"`
Body string
SentMillis int64 `capnp:"time"`
}
Using a "-" will cause the field to be ignored by the Insert and
Extract functions.
type ExtraFieldsMessage struct {
ID uint64 `capnp:"-"`
Name string
Body string
Time int64
}
Unions
Since Go does not have support for variant types, Go structs that want
to use fields inside a Cap'n Proto union must have an explicit
discriminant field called Which. The Extract function will populate the
Which field and the Insert function will read the Which field to
determine which field to set. Given this schema:
struct Shape {
area @0 :Float64;
union {
circle @1 :Float64;
square @2 :Float64;
}
}
the Go struct should look like this:
type Shape struct {
Area float64
Which myschema.Shape_Which // or any other uint16 type
Circle float64
Square float64
}
Attempting to use fields in a union without a uint16 Which field will
result in an error. There is one exception: we can declare our Which
field to be fixed to one particular union value by using a field tag.
type Square struct {
Which struct{} `capnp:",which=square"`
Area float64
Width float64 `capnp:"square"`
}
This can be useful if we want to use a different Go type depending on
which field in the union is set.
shape, err := myschema.ReadRootShape(msg)
if err != nil {
return nil, err
}
switch shape.Which() {
case myschema.Shape_Which_square:
sq := new(Square)
err = pogs.Extract(sq, myschema.Square_TypeID, shape.Struct)
return sq, err
case myschema.Shape_Which_circle:
// ...
}
Embedding
Anonymous struct fields are usually extracted or inserted as if their
inner exported fields were fields in the outer struct, subject to the
rules in the next paragraph. An anonymous struct field with a name
given in its capnp tag is treated as having that name, rather than being
anonymous. An anonymous struct field with a capnp tag of "-" will be
ignored.
The visibility rules for struct fields are amended for pogs in the same
way they are amended in encoding/json: if there are multiple fields at
the same level, and that level is the least nested, the following extra
rules apply:
1) Of those fields, if any are capnp-tagged, only tagged fields are
considered, even if there are multiple untagged fields that would
otherwise conflict.
2) If there is exactly one field (tagged or not according to the first
rule), that is selected.
3) Otherwise, there are multiple fields, and all are ignored; no error
occurs.
*/
package pogs // import "zombiezen.com/go/capnproto2/pogs"

View File

@ -1,418 +0,0 @@
package pogs
import (
"errors"
"fmt"
"math"
"reflect"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/nodemap"
"zombiezen.com/go/capnproto2/internal/schema"
)
// Extract copies s into val, a pointer to a Go struct.
func Extract(val interface{}, typeID uint64, s capnp.Struct) error {
e := new(extracter)
err := e.extractStruct(reflect.ValueOf(val), typeID, s)
if err != nil {
return fmt.Errorf("pogs: extract @%#x: %v", typeID, err)
}
return nil
}
type extracter struct {
nodes nodemap.Map
}
var clientType = reflect.TypeOf((*capnp.Client)(nil)).Elem()
func (e *extracter) extractStruct(val reflect.Value, typeID uint64, s capnp.Struct) error {
if val.Kind() == reflect.Ptr {
if val.Type().Elem().Kind() != reflect.Struct {
return fmt.Errorf("can't extract struct into %v", val.Type())
}
switch {
case !val.CanSet() && val.IsNil():
// Even if the Cap'n Proto pointer isn't valid, this is probably
// the caller's fault and will be a bug at some point.
return errors.New("can't extract struct into nil")
case !s.IsValid() && val.CanSet():
val.Set(reflect.Zero(val.Type()))
return nil
case s.IsValid() && val.CanSet() && val.IsNil():
val.Set(reflect.New(val.Type().Elem()))
}
val = val.Elem()
} else if val.Kind() != reflect.Struct {
return fmt.Errorf("can't extract struct into %v", val.Type())
}
if !val.CanSet() {
return errors.New("can't modify struct, did you pass in a pointer to your struct?")
}
n, err := e.nodes.Find(typeID)
if err != nil {
return err
}
if !n.IsValid() || n.Which() != schema.Node_Which_structNode {
return fmt.Errorf("cannot find struct type %#x", typeID)
}
props, err := mapStruct(val.Type(), n)
if err != nil {
return fmt.Errorf("can't extract %s: %v", val.Type(), err)
}
var discriminant uint16
hasWhich := false
if hasDiscriminant(n) {
discriminant = s.Uint16(capnp.DataOffset(n.StructNode().DiscriminantOffset() * 2))
if err := props.setWhich(val, discriminant); err == nil {
hasWhich = true
} else if !isNoWhichError(err) {
return err
}
}
fields, err := n.StructNode().Fields()
if err != nil {
return err
}
for i := 0; i < fields.Len(); i++ {
f := fields.At(i)
vf := props.makeFieldByOrdinal(val, i)
if !vf.IsValid() {
// Don't have a field for this.
continue
}
if dv := f.DiscriminantValue(); dv != schema.Field_noDiscriminant {
if !hasWhich {
return fmt.Errorf("can't extract %s into %v: has union field but no Which field", shortDisplayName(n), val.Type())
}
if dv != discriminant {
continue
}
}
switch f.Which() {
case schema.Field_Which_slot:
if err := e.extractField(vf, s, f); err != nil {
return err
}
case schema.Field_Which_group:
if err := e.extractStruct(vf, f.Group().TypeId(), s); err != nil {
return err
}
}
}
return nil
}
func (e *extracter) extractField(val reflect.Value, s capnp.Struct, f schema.Field) error {
typ, err := f.Slot().Type()
if err != nil {
return err
}
dv, err := f.Slot().DefaultValue()
if err != nil {
return err
}
if dv.IsValid() && int(typ.Which()) != int(dv.Which()) {
name, _ := f.NameBytes()
return fmt.Errorf("extract field %s: default value is a %v, want %v", name, dv.Which(), typ.Which())
}
if !isTypeMatch(val.Type(), typ) {
name, _ := f.NameBytes()
return fmt.Errorf("can't extract field %s of type %v into a Go %v", name, typ.Which(), val.Type())
}
switch typ.Which() {
case schema.Type_Which_bool:
v := s.Bit(capnp.BitOffset(f.Slot().Offset()))
d := dv.Bool()
val.SetBool(v != d) // != acts as XOR
case schema.Type_Which_int8:
v := int8(s.Uint8(capnp.DataOffset(f.Slot().Offset())))
d := dv.Int8()
val.SetInt(int64(v ^ d))
case schema.Type_Which_int16:
v := int16(s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2)))
d := dv.Int16()
val.SetInt(int64(v ^ d))
case schema.Type_Which_int32:
v := int32(s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4)))
d := dv.Int32()
val.SetInt(int64(v ^ d))
case schema.Type_Which_int64:
v := int64(s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8)))
d := dv.Int64()
val.SetInt(v ^ d)
case schema.Type_Which_uint8:
v := s.Uint8(capnp.DataOffset(f.Slot().Offset()))
d := dv.Uint8()
val.SetUint(uint64(v ^ d))
case schema.Type_Which_uint16:
v := s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2))
d := dv.Uint16()
val.SetUint(uint64(v ^ d))
case schema.Type_Which_enum:
v := s.Uint16(capnp.DataOffset(f.Slot().Offset() * 2))
d := dv.Enum()
val.SetUint(uint64(v ^ d))
case schema.Type_Which_uint32:
v := s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4))
d := dv.Uint32()
val.SetUint(uint64(v ^ d))
case schema.Type_Which_uint64:
v := s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8))
d := dv.Uint64()
val.SetUint(v ^ d)
case schema.Type_Which_float32:
v := s.Uint32(capnp.DataOffset(f.Slot().Offset() * 4))
d := math.Float32bits(dv.Float32())
val.SetFloat(float64(math.Float32frombits(v ^ d)))
case schema.Type_Which_float64:
v := s.Uint64(capnp.DataOffset(f.Slot().Offset() * 8))
d := math.Float64bits(dv.Float64())
val.SetFloat(math.Float64frombits(v ^ d))
case schema.Type_Which_text:
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
var b []byte
if p.IsValid() {
b = p.TextBytes()
} else {
b, _ = dv.TextBytes()
}
if val.Kind() == reflect.String {
val.SetString(string(b))
} else {
// byte slice, as guaranteed by isTypeMatch
val.SetBytes(b)
}
case schema.Type_Which_data:
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
var b []byte
if p.IsValid() {
b = p.Data()
} else {
b, _ = dv.Data()
}
val.SetBytes(b)
case schema.Type_Which_structType:
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
ss := p.Struct()
if !ss.IsValid() {
p, _ = dv.StructValuePtr()
ss = p.Struct()
}
return e.extractStruct(val, typ.StructType().TypeId(), ss)
case schema.Type_Which_list:
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
l := p.List()
if !l.IsValid() {
p, _ = dv.ListPtr()
l = p.List()
}
return e.extractList(val, typ, l)
case schema.Type_Which_interface:
p, err := s.Ptr(uint16(f.Slot().Offset()))
if err != nil {
return err
}
if val.Type() != clientType {
// Must be a struct wrapper.
val = val.FieldByName("Client")
}
client := p.Interface().Client()
if client == nil {
val.Set(reflect.Zero(val.Type()))
} else {
val.Set(reflect.ValueOf(client))
}
default:
return fmt.Errorf("unknown field type %v", typ.Which())
}
return nil
}
func (e *extracter) extractList(val reflect.Value, typ schema.Type, l capnp.List) error {
vt := val.Type()
elem, err := typ.List().ElementType()
if err != nil {
return err
}
if !isTypeMatch(vt, typ) {
// TODO(light): the error won't be that useful for nested lists.
return fmt.Errorf("can't extract %v list into a Go %v", elem.Which(), vt)
}
if !l.IsValid() {
val.Set(reflect.Zero(vt))
return nil
}
n := l.Len()
val.Set(reflect.MakeSlice(vt, n, n))
switch elem.Which() {
case schema.Type_Which_bool:
for i := 0; i < n; i++ {
val.Index(i).SetBool(capnp.BitList{List: l}.At(i))
}
case schema.Type_Which_int8:
for i := 0; i < n; i++ {
val.Index(i).SetInt(int64(capnp.Int8List{List: l}.At(i)))
}
case schema.Type_Which_int16:
for i := 0; i < n; i++ {
val.Index(i).SetInt(int64(capnp.Int16List{List: l}.At(i)))
}
case schema.Type_Which_int32:
for i := 0; i < n; i++ {
val.Index(i).SetInt(int64(capnp.Int32List{List: l}.At(i)))
}
case schema.Type_Which_int64:
for i := 0; i < n; i++ {
val.Index(i).SetInt(capnp.Int64List{List: l}.At(i))
}
case schema.Type_Which_uint8:
for i := 0; i < n; i++ {
val.Index(i).SetUint(uint64(capnp.UInt8List{List: l}.At(i)))
}
case schema.Type_Which_uint16, schema.Type_Which_enum:
for i := 0; i < n; i++ {
val.Index(i).SetUint(uint64(capnp.UInt16List{List: l}.At(i)))
}
case schema.Type_Which_uint32:
for i := 0; i < n; i++ {
val.Index(i).SetUint(uint64(capnp.UInt32List{List: l}.At(i)))
}
case schema.Type_Which_uint64:
for i := 0; i < n; i++ {
val.Index(i).SetUint(capnp.UInt64List{List: l}.At(i))
}
case schema.Type_Which_float32:
for i := 0; i < n; i++ {
val.Index(i).SetFloat(float64(capnp.Float32List{List: l}.At(i)))
}
case schema.Type_Which_float64:
for i := 0; i < n; i++ {
val.Index(i).SetFloat(capnp.Float64List{List: l}.At(i))
}
case schema.Type_Which_text:
if val.Type().Elem().Kind() == reflect.String {
for i := 0; i < n; i++ {
s, err := capnp.TextList{List: l}.At(i)
if err != nil {
// TODO(light): collect errors and finish
return err
}
val.Index(i).SetString(s)
}
} else {
for i := 0; i < n; i++ {
b, err := capnp.TextList{List: l}.BytesAt(i)
if err != nil {
// TODO(light): collect errors and finish
return err
}
val.Index(i).SetBytes(b)
}
}
case schema.Type_Which_data:
for i := 0; i < n; i++ {
b, err := capnp.DataList{List: l}.At(i)
if err != nil {
// TODO(light): collect errors and finish
return err
}
val.Index(i).SetBytes(b)
}
case schema.Type_Which_list:
for i := 0; i < n; i++ {
p, err := capnp.PointerList{List: l}.PtrAt(i)
// TODO(light): collect errors and finish
if err != nil {
return err
}
if err := e.extractList(val.Index(i), elem, p.List()); err != nil {
return err
}
}
case schema.Type_Which_structType:
if val.Type().Elem().Kind() == reflect.Struct {
for i := 0; i < n; i++ {
err := e.extractStruct(val.Index(i), elem.StructType().TypeId(), l.Struct(i))
if err != nil {
return err
}
}
} else {
for i := 0; i < n; i++ {
newval := reflect.New(val.Type().Elem().Elem())
val.Index(i).Set(newval)
err := e.extractStruct(newval, elem.StructType().TypeId(), l.Struct(i))
if err != nil {
return err
}
}
}
default:
return fmt.Errorf("unknown list type %v", elem.Which())
}
return nil
}
var typeMap = map[schema.Type_Which]reflect.Kind{
schema.Type_Which_bool: reflect.Bool,
schema.Type_Which_int8: reflect.Int8,
schema.Type_Which_int16: reflect.Int16,
schema.Type_Which_int32: reflect.Int32,
schema.Type_Which_int64: reflect.Int64,
schema.Type_Which_uint8: reflect.Uint8,
schema.Type_Which_uint16: reflect.Uint16,
schema.Type_Which_uint32: reflect.Uint32,
schema.Type_Which_uint64: reflect.Uint64,
schema.Type_Which_float32: reflect.Float32,
schema.Type_Which_float64: reflect.Float64,
schema.Type_Which_enum: reflect.Uint16,
}
func isTypeMatch(r reflect.Type, s schema.Type) bool {
switch s.Which() {
case schema.Type_Which_text:
return r.Kind() == reflect.String || r.Kind() == reflect.Slice && r.Elem().Kind() == reflect.Uint8
case schema.Type_Which_data:
return r.Kind() == reflect.Slice && r.Elem().Kind() == reflect.Uint8
case schema.Type_Which_structType:
return isStructOrStructPtr(r)
case schema.Type_Which_list:
e, _ := s.List().ElementType()
return r.Kind() == reflect.Slice && isTypeMatch(r.Elem(), e)
case schema.Type_Which_interface:
if r == clientType {
return true
}
// Otherwise, the type must be a struct with one element named
// "Client" of type capnp.Client.
if r.Kind() != reflect.Struct {
return false
}
if r.NumField() != 1 {
return false
}
field, ok := r.FieldByName("Client")
if !ok {
return false
}
return field.Type == clientType
}
k, ok := typeMap[s.Which()]
return ok && k == r.Kind()
}

View File

@ -1,350 +0,0 @@
package pogs
import (
"fmt"
"reflect"
"strings"
"zombiezen.com/go/capnproto2/internal/schema"
)
type fieldProps struct {
schemaName string // empty if doesn't map to schema
typ fieldType
fixedWhich string
tagged bool
}
type fieldType int
const (
mappedField fieldType = iota
whichField
embedField
)
func parseField(f reflect.StructField, hasDiscrim bool) fieldProps {
var p fieldProps
tag := f.Tag.Get("capnp")
p.tagged = tag != ""
tname, opts := nextOpt(tag)
switch tname {
case "-":
// omitted field
case "":
if f.Anonymous && isStructOrStructPtr(f.Type) {
p.typ = embedField
return p
}
if hasDiscrim && f.Name == "Which" {
p.typ = whichField
for len(opts) > 0 {
var curr string
curr, opts = nextOpt(opts)
if strings.HasPrefix(curr, "which=") {
p.fixedWhich = strings.TrimPrefix(curr, "which=")
break
}
}
return p
}
// TODO(light): check it's uppercase.
x := f.Name[0] - 'A' + 'a'
p.schemaName = string(x) + f.Name[1:]
default:
p.schemaName = tname
}
return p
}
func nextOpt(opts string) (head, tail string) {
i := strings.Index(opts, ",")
if i == -1 {
return opts, ""
}
return opts[:i], opts[i+1:]
}
type fieldLoc struct {
i int
path []int
}
func (loc fieldLoc) depth() int {
if len(loc.path) > 0 {
return len(loc.path)
}
return 1
}
func (loc fieldLoc) sub(i int) fieldLoc {
n := len(loc.path)
switch {
case !loc.isValid():
return fieldLoc{i: i}
case n > 0:
p := make([]int, n+1)
copy(p, loc.path)
p[n] = i
return fieldLoc{path: p}
default:
return fieldLoc{path: []int{loc.i, i}}
}
}
func (loc fieldLoc) isValid() bool {
return loc.i >= 0
}
type structProps struct {
fields []fieldLoc
whichLoc fieldLoc // i == -1: none; i == -2: fixed
fixedWhich uint16
}
func mapStruct(t reflect.Type, n schema.Node) (structProps, error) {
fields, err := n.StructNode().Fields()
if err != nil {
return structProps{}, err
}
sp := structProps{
fields: make([]fieldLoc, fields.Len()),
whichLoc: fieldLoc{i: -1},
}
for i := range sp.fields {
sp.fields[i] = fieldLoc{i: -1}
}
sm := structMapper{
sp: &sp,
t: t,
hasDiscrim: hasDiscriminant(n),
fields: fields,
}
if err := sm.visit(fieldLoc{i: -1}); err != nil {
return structProps{}, err
}
for len(sm.embedQueue) > 0 {
loc := sm.embedQueue[0]
copy(sm.embedQueue, sm.embedQueue[1:])
sm.embedQueue = sm.embedQueue[:len(sm.embedQueue)-1]
if err := sm.visit(loc); err != nil {
return structProps{}, err
}
}
return sp, nil
}
type structMapper struct {
sp *structProps
t reflect.Type
hasDiscrim bool
fields schema.Field_List
embedQueue []fieldLoc
}
func (sm *structMapper) visit(base fieldLoc) error {
t := sm.t
if base.isValid() {
t = typeFieldByLoc(t, base).Type
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.PkgPath != "" && !f.Anonymous {
// unexported field
continue
}
loc := base.sub(i)
p := parseField(f, sm.hasDiscrim)
if p.typ == embedField {
sm.embedQueue = append(sm.embedQueue, loc)
continue
}
if err := sm.visitField(loc, f, p); err != nil {
return err
}
}
return nil
}
func (sm *structMapper) visitField(loc fieldLoc, f reflect.StructField, p fieldProps) error {
switch p.typ {
case mappedField:
if p.schemaName == "" {
return nil
}
fi := fieldIndex(sm.fields, p.schemaName)
if fi < 0 {
return fmt.Errorf("%v has unknown field %s, maps to %s", sm.t, f.Name, p.schemaName)
}
switch oldloc := sm.sp.fields[fi]; {
case oldloc.i == -2:
// Prior tag collision, do nothing.
case oldloc.i == -3 && p.tagged && loc.depth() == len(oldloc.path):
// A tagged field wins over untagged fields.
sm.sp.fields[fi] = loc
case oldloc.isValid() && oldloc.depth() < loc.depth():
// This is deeper, don't override.
case oldloc.isValid() && oldloc.depth() == loc.depth():
oldp := parseField(typeFieldByLoc(sm.t, oldloc), sm.hasDiscrim)
if oldp.tagged && p.tagged {
// Tag collision
sm.sp.fields[fi] = fieldLoc{i: -2}
} else if p.tagged {
sm.sp.fields[fi] = loc
} else if !oldp.tagged {
// Multiple untagged fields. Keep path, because we need it for depth.
sm.sp.fields[fi].i = -3
}
default:
sm.sp.fields[fi] = loc
}
case whichField:
if sm.sp.whichLoc.i != -1 {
return fmt.Errorf("%v embeds multiple Which fields", sm.t)
}
switch {
case p.fixedWhich != "":
fi := fieldIndex(sm.fields, p.fixedWhich)
if fi < 0 {
return fmt.Errorf("%v.Which is tagged with unknown field %s", sm.t, p.fixedWhich)
}
dv := sm.fields.At(fi).DiscriminantValue()
if dv == schema.Field_noDiscriminant {
return fmt.Errorf("%v.Which is tagged with non-union field %s", sm.t, p.fixedWhich)
}
sm.sp.whichLoc = fieldLoc{i: -2}
sm.sp.fixedWhich = dv
case f.Type.Kind() != reflect.Uint16:
return fmt.Errorf("%v.Which is type %v, not uint16", sm.t, f.Type)
default:
sm.sp.whichLoc = loc
}
}
return nil
}
// fieldBySchemaName returns the field for the given name.
// Returns an invalid value if the field was not found or it is
// contained inside a nil anonymous struct pointer.
func (sp structProps) fieldByOrdinal(val reflect.Value, i int) reflect.Value {
return fieldByLoc(val, sp.fields[i], false)
}
// makeFieldBySchemaName returns the field for the given name, creating
// its parent anonymous structs if necessary. Returns an invalid value
// if the field was not found.
func (sp structProps) makeFieldByOrdinal(val reflect.Value, i int) reflect.Value {
return fieldByLoc(val, sp.fields[i], true)
}
// which returns the value of the discriminator field.
func (sp structProps) which(val reflect.Value) (discrim uint16, ok bool) {
if sp.whichLoc.i == -2 {
return sp.fixedWhich, true
}
f := fieldByLoc(val, sp.whichLoc, false)
if !f.IsValid() {
return 0, false
}
return uint16(f.Uint()), true
}
// setWhich sets the value of the discriminator field, creating its
// parent anonymous structs if necessary. Returns whether the struct
// had a field to set.
func (sp structProps) setWhich(val reflect.Value, discrim uint16) error {
if sp.whichLoc.i == -2 {
if discrim != sp.fixedWhich {
return fmt.Errorf("extract union field @%d into %v; expected @%d", discrim, val.Type(), sp.fixedWhich)
}
return nil
}
f := fieldByLoc(val, sp.whichLoc, true)
if !f.IsValid() {
return noWhichError{val.Type()}
}
f.SetUint(uint64(discrim))
return nil
}
type noWhichError struct {
t reflect.Type
}
func (e noWhichError) Error() string {
return fmt.Sprintf("%v has no field Which", e.t)
}
func isNoWhichError(e error) bool {
_, ok := e.(noWhichError)
return ok
}
func fieldByLoc(val reflect.Value, loc fieldLoc, mkparents bool) reflect.Value {
if !loc.isValid() {
return reflect.Value{}
}
if len(loc.path) > 0 {
for i, x := range loc.path {
if i > 0 {
if val.Kind() == reflect.Ptr {
if val.IsNil() {
if !mkparents {
return reflect.Value{}
}
val.Set(reflect.New(val.Type().Elem()))
}
val = val.Elem()
}
}
val = val.Field(x)
}
return val
}
return val.Field(loc.i)
}
func typeFieldByLoc(t reflect.Type, loc fieldLoc) reflect.StructField {
if len(loc.path) > 0 {
return t.FieldByIndex(loc.path)
}
return t.Field(loc.i)
}
func hasDiscriminant(n schema.Node) bool {
return n.Which() == schema.Node_Which_structNode && n.StructNode().DiscriminantCount() > 0
}
func shortDisplayName(n schema.Node) []byte {
dn, _ := n.DisplayNameBytes()
return dn[n.DisplayNamePrefixLength():]
}
func fieldIndex(fields schema.Field_List, name string) int {
for i := 0; i < fields.Len(); i++ {
b, _ := fields.At(i).NameBytes()
if bytesStrEqual(b, name) {
return i
}
}
return -1
}
func bytesStrEqual(b []byte, s string) bool {
if len(b) != len(s) {
return false
}
for i := range b {
if b[i] != s[i] {
return false
}
}
return true
}
func isStructOrStructPtr(t reflect.Type) bool {
return t.Kind() == reflect.Struct || t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}

View File

@ -1,485 +0,0 @@
package pogs
import (
"fmt"
"math"
"reflect"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/nodemap"
"zombiezen.com/go/capnproto2/internal/schema"
)
// Insert copies val, a pointer to a Go struct, into s.
func Insert(typeID uint64, s capnp.Struct, val interface{}) error {
ins := new(inserter)
err := ins.insertStruct(typeID, s, reflect.ValueOf(val))
if err != nil {
return fmt.Errorf("pogs: insert @%#x: %v", typeID, err)
}
return nil
}
type inserter struct {
nodes nodemap.Map
}
func (ins *inserter) insertStruct(typeID uint64, s capnp.Struct, val reflect.Value) error {
if val.Kind() == reflect.Ptr {
// TODO(light): ignore if nil?
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return fmt.Errorf("can't insert %v into a struct", val.Kind())
}
n, err := ins.nodes.Find(typeID)
if err != nil {
return err
}
if !n.IsValid() || n.Which() != schema.Node_Which_structNode {
return fmt.Errorf("cannot find struct type %#x", typeID)
}
props, err := mapStruct(val.Type(), n)
if err != nil {
return fmt.Errorf("can't insert into %v: %v", val.Type(), err)
}
var discriminant uint16
hasWhich := false
if hasDiscriminant(n) {
discriminant, hasWhich = props.which(val)
if hasWhich {
off := capnp.DataOffset(n.StructNode().DiscriminantOffset() * 2)
if s.Size().DataSize < capnp.Size(off+2) {
return fmt.Errorf("can't set discriminant for %s: allocated struct is too small", shortDisplayName(n))
}
s.SetUint16(off, discriminant)
}
}
fields, err := n.StructNode().Fields()
if err != nil {
return err
}
for i := 0; i < fields.Len(); i++ {
f := fields.At(i)
vf := props.fieldByOrdinal(val, i)
if !vf.IsValid() {
// Don't have a field for this.
continue
}
if dv := f.DiscriminantValue(); dv != schema.Field_noDiscriminant {
if !hasWhich {
sname, _ := f.NameBytes()
return fmt.Errorf("can't insert %s from %v: has union field %s but no Which field", shortDisplayName(n), val.Type(), sname)
}
if dv != discriminant {
continue
}
}
switch f.Which() {
case schema.Field_Which_slot:
if err := ins.insertField(s, f, vf); err != nil {
return err
}
case schema.Field_Which_group:
if err := ins.insertStruct(f.Group().TypeId(), s, vf); err != nil {
return err
}
}
}
return nil
}
func (ins *inserter) insertField(s capnp.Struct, f schema.Field, val reflect.Value) error {
typ, err := f.Slot().Type()
if err != nil {
return err
}
dv, err := f.Slot().DefaultValue()
if err != nil {
return err
}
if dv.IsValid() && int(typ.Which()) != int(dv.Which()) {
name, _ := f.NameBytes()
return fmt.Errorf("insert field %s: default value is a %v, want %v", name, dv.Which(), typ.Which())
}
if !isTypeMatch(val.Type(), typ) {
name, _ := f.NameBytes()
return fmt.Errorf("can't insert field %s of type Go %v into a %v", name, val.Type(), typ.Which())
}
if !isFieldInBounds(s.Size(), f.Slot().Offset(), typ) {
name, _ := f.NameBytes()
return fmt.Errorf("can't insert field %s: allocated struct is too small", name)
}
switch typ.Which() {
case schema.Type_Which_bool:
v := val.Bool()
d := dv.Bool()
s.SetBit(capnp.BitOffset(f.Slot().Offset()), v != d) // != acts as XOR
case schema.Type_Which_int8:
v := int8(val.Int())
d := dv.Int8()
s.SetUint8(capnp.DataOffset(f.Slot().Offset()), uint8(v^d))
case schema.Type_Which_int16:
v := int16(val.Int())
d := dv.Int16()
s.SetUint16(capnp.DataOffset(f.Slot().Offset()*2), uint16(v^d))
case schema.Type_Which_int32:
v := int32(val.Int())
d := dv.Int32()
s.SetUint32(capnp.DataOffset(f.Slot().Offset()*4), uint32(v^d))
case schema.Type_Which_int64:
v := val.Int()
d := dv.Int64()
s.SetUint64(capnp.DataOffset(f.Slot().Offset()*8), uint64(v^d))
case schema.Type_Which_uint8:
v := uint8(val.Uint())
d := dv.Uint8()
s.SetUint8(capnp.DataOffset(f.Slot().Offset()), v^d)
case schema.Type_Which_uint16:
v := uint16(val.Uint())
d := dv.Uint16()
s.SetUint16(capnp.DataOffset(f.Slot().Offset()*2), v^d)
case schema.Type_Which_enum:
v := uint16(val.Uint())
d := dv.Enum()
s.SetUint16(capnp.DataOffset(f.Slot().Offset()*2), v^d)
case schema.Type_Which_uint32:
v := uint32(val.Uint())
d := dv.Uint32()
s.SetUint32(capnp.DataOffset(f.Slot().Offset()*4), v^d)
case schema.Type_Which_uint64:
v := val.Uint()
d := dv.Uint64()
s.SetUint64(capnp.DataOffset(f.Slot().Offset()*8), v^d)
case schema.Type_Which_float32:
v := math.Float32bits(float32(val.Float()))
d := math.Float32bits(dv.Float32())
s.SetUint32(capnp.DataOffset(f.Slot().Offset()*4), v^d)
case schema.Type_Which_float64:
v := math.Float64bits(val.Float())
d := uint64(math.Float64bits(dv.Float64()))
s.SetUint64(capnp.DataOffset(f.Slot().Offset()*8), v^d)
case schema.Type_Which_text:
off := uint16(f.Slot().Offset())
if val.Len() == 0 {
if !isEmptyValue(dv) {
return s.SetNewText(off, "")
}
return s.SetText(off, "")
}
if val.Kind() == reflect.String {
return s.SetText(off, val.String())
} else {
return s.SetTextFromBytes(off, val.Bytes())
}
case schema.Type_Which_data:
b := val.Bytes()
if b == nil && !isEmptyValue(dv) {
b = []byte{}
}
off := uint16(f.Slot().Offset())
return s.SetData(off, b)
case schema.Type_Which_structType:
off := uint16(f.Slot().Offset())
sval := val
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return s.SetPtr(off, capnp.Ptr{})
}
sval = val.Elem()
}
id := typ.StructType().TypeId()
sz, err := ins.structSize(id)
if err != nil {
return err
}
ss, err := capnp.NewStruct(s.Segment(), sz)
if err != nil {
return err
}
if err := s.SetPtr(off, ss.ToPtr()); err != nil {
return err
}
return ins.insertStruct(id, ss, sval)
case schema.Type_Which_list:
off := uint16(f.Slot().Offset())
if val.IsNil() && isEmptyValue(dv) {
return s.SetPtr(off, capnp.Ptr{})
}
elem, err := typ.List().ElementType()
if err != nil {
return err
}
l, err := ins.newList(s.Segment(), elem, int32(val.Len()))
if err != nil {
return err
}
if err := s.SetPtr(off, l.ToPtr()); err != nil {
return err
}
return ins.insertList(l, typ, val)
case schema.Type_Which_interface:
off := uint16(f.Slot().Offset())
ptr := capPtr(s.Segment(), val)
if err := s.SetPtr(off, ptr); err != nil {
return err
}
default:
return fmt.Errorf("unknown field type %v", typ.Which())
}
return nil
}
func capPtr(seg *capnp.Segment, val reflect.Value) capnp.Ptr {
client, ok := val.Interface().(capnp.Client)
if !ok {
client, ok = val.FieldByName("Client").Interface().(capnp.Client)
if !ok {
// interface is nil.
return capnp.Ptr{}
}
}
cap := seg.Message().AddCap(client)
iface := capnp.NewInterface(seg, cap)
return iface.ToPtr()
}
func (ins *inserter) insertList(l capnp.List, typ schema.Type, val reflect.Value) error {
elem, err := typ.List().ElementType()
if err != nil {
return err
}
if !isTypeMatch(val.Type(), typ) {
// TODO(light): the error won't be that useful for nested lists.
return fmt.Errorf("can't insert Go %v into a %v list", val.Type(), elem.Which())
}
n := val.Len()
switch elem.Which() {
case schema.Type_Which_void:
case schema.Type_Which_bool:
for i := 0; i < n; i++ {
capnp.BitList{List: l}.Set(i, val.Index(i).Bool())
}
case schema.Type_Which_int8:
for i := 0; i < n; i++ {
capnp.Int8List{List: l}.Set(i, int8(val.Index(i).Int()))
}
case schema.Type_Which_int16:
for i := 0; i < n; i++ {
capnp.Int16List{List: l}.Set(i, int16(val.Index(i).Int()))
}
case schema.Type_Which_int32:
for i := 0; i < n; i++ {
capnp.Int32List{List: l}.Set(i, int32(val.Index(i).Int()))
}
case schema.Type_Which_int64:
for i := 0; i < n; i++ {
capnp.Int64List{List: l}.Set(i, val.Index(i).Int())
}
case schema.Type_Which_uint8:
for i := 0; i < n; i++ {
capnp.UInt8List{List: l}.Set(i, uint8(val.Index(i).Uint()))
}
case schema.Type_Which_uint16, schema.Type_Which_enum:
for i := 0; i < n; i++ {
capnp.UInt16List{List: l}.Set(i, uint16(val.Index(i).Uint()))
}
case schema.Type_Which_uint32:
for i := 0; i < n; i++ {
capnp.UInt32List{List: l}.Set(i, uint32(val.Index(i).Uint()))
}
case schema.Type_Which_uint64:
for i := 0; i < n; i++ {
capnp.UInt64List{List: l}.Set(i, val.Index(i).Uint())
}
case schema.Type_Which_float32:
for i := 0; i < n; i++ {
capnp.Float32List{List: l}.Set(i, float32(val.Index(i).Float()))
}
case schema.Type_Which_float64:
for i := 0; i < n; i++ {
capnp.Float64List{List: l}.Set(i, val.Index(i).Float())
}
case schema.Type_Which_text:
if val.Type().Elem().Kind() == reflect.String {
for i := 0; i < n; i++ {
err := capnp.TextList{List: l}.Set(i, val.Index(i).String())
if err != nil {
// TODO(light): collect errors and finish
return err
}
}
} else {
for i := 0; i < n; i++ {
b := val.Index(i).Bytes()
if len(b) == 0 {
err := capnp.PointerList{List: l}.SetPtr(i, capnp.Ptr{})
if err != nil {
// TODO(light): collect errors and finish
return err
}
}
t, err := capnp.NewTextFromBytes(l.Segment(), b)
if err != nil {
// TODO(light): collect errors and finish
return err
}
err = capnp.PointerList{List: l}.SetPtr(i, t.ToPtr())
if err != nil {
// TODO(light): collect errors and finish
return err
}
}
}
case schema.Type_Which_data:
for i := 0; i < n; i++ {
b := val.Index(i).Bytes()
if len(b) == 0 {
err := capnp.PointerList{List: l}.SetPtr(i, capnp.Ptr{})
if err != nil {
// TODO(light): collect errors and finish
return err
}
}
err := capnp.DataList{List: l}.Set(i, b)
if err != nil {
// TODO(light): collect errors and finish
return err
}
}
case schema.Type_Which_list:
pl := capnp.PointerList{List: l}
for i := 0; i < n; i++ {
vi := val.Index(i)
if vi.IsNil() {
if err := pl.SetPtr(i, capnp.Ptr{}); err != nil {
return err
}
continue
}
ee, err := elem.List().ElementType()
if err != nil {
return err
}
li, err := ins.newList(l.Segment(), ee, int32(vi.Len()))
if err != nil {
return err
}
if err := pl.SetPtr(i, li.ToPtr()); err != nil {
return err
}
if err := ins.insertList(li, elem, vi); err != nil {
return err
}
}
case schema.Type_Which_structType:
id := elem.StructType().TypeId()
for i := 0; i < n; i++ {
err := ins.insertStruct(id, l.Struct(i), val.Index(i))
if err != nil {
// TODO(light): collect errors and finish
return err
}
}
case schema.Type_Which_interface:
pl := capnp.PointerList{List: l}
for i := 0; i < n; i++ {
ptr := capPtr(l.Segment(), val.Index(i))
if err := pl.SetPtr(i, ptr); err != nil {
// TODO(zenhack): collect errors and finish
return err
}
}
default:
return fmt.Errorf("unknown list type %v", elem.Which())
}
return nil
}
func (ins *inserter) newList(s *capnp.Segment, t schema.Type, len int32) (capnp.List, error) {
switch t.Which() {
case schema.Type_Which_void:
l := capnp.NewVoidList(s, len)
return l.List, nil
case schema.Type_Which_bool:
l, err := capnp.NewBitList(s, len)
return l.List, err
case schema.Type_Which_int8, schema.Type_Which_uint8:
l, err := capnp.NewUInt8List(s, len)
return l.List, err
case schema.Type_Which_int16, schema.Type_Which_uint16, schema.Type_Which_enum:
l, err := capnp.NewUInt16List(s, len)
return l.List, err
case schema.Type_Which_int32, schema.Type_Which_uint32, schema.Type_Which_float32:
l, err := capnp.NewUInt32List(s, len)
return l.List, err
case schema.Type_Which_int64, schema.Type_Which_uint64, schema.Type_Which_float64:
l, err := capnp.NewUInt64List(s, len)
return l.List, err
case schema.Type_Which_text, schema.Type_Which_data, schema.Type_Which_list, schema.Type_Which_interface, schema.Type_Which_anyPointer:
l, err := capnp.NewPointerList(s, len)
return l.List, err
case schema.Type_Which_structType:
sz, err := ins.structSize(t.StructType().TypeId())
if err != nil {
return capnp.List{}, err
}
return capnp.NewCompositeList(s, sz, len)
default:
return capnp.List{}, fmt.Errorf("new list: unknown element type: %v", t.Which())
}
}
func (ins *inserter) structSize(id uint64) (capnp.ObjectSize, error) {
n, err := ins.nodes.Find(id)
if err != nil {
return capnp.ObjectSize{}, err
}
if n.Which() != schema.Node_Which_structNode {
return capnp.ObjectSize{}, fmt.Errorf("insert struct: sizing: node @%#x is not a struct", id)
}
return capnp.ObjectSize{
DataSize: capnp.Size(n.StructNode().DataWordCount()) * 8,
PointerCount: n.StructNode().PointerCount(),
}, nil
}
func isFieldInBounds(sz capnp.ObjectSize, off uint32, t schema.Type) bool {
switch t.Which() {
case schema.Type_Which_void:
return true
case schema.Type_Which_bool:
return sz.DataSize >= capnp.Size(off/8+1)
case schema.Type_Which_int8, schema.Type_Which_uint8:
return sz.DataSize >= capnp.Size(off+1)
case schema.Type_Which_int16, schema.Type_Which_uint16, schema.Type_Which_enum:
return sz.DataSize >= capnp.Size(off+1)*2
case schema.Type_Which_int32, schema.Type_Which_uint32, schema.Type_Which_float32:
return sz.DataSize >= capnp.Size(off+1)*4
case schema.Type_Which_int64, schema.Type_Which_uint64, schema.Type_Which_float64:
return sz.DataSize >= capnp.Size(off+1)*8
case schema.Type_Which_text, schema.Type_Which_data, schema.Type_Which_list, schema.Type_Which_structType, schema.Type_Which_interface, schema.Type_Which_anyPointer:
return sz.PointerCount >= uint16(off+1)
default:
return false
}
}
func isEmptyValue(v schema.Value) bool {
if !v.IsValid() {
return false
}
switch v.Which() {
case schema.Value_Which_text:
b, _ := v.TextBytes()
return len(b) == 0
case schema.Value_Which_data:
b, _ := v.Data()
return len(b) == 0
case schema.Value_Which_list:
p, _ := v.ListPtr()
return p.List().Len() == 0
default:
return false
}
}

View File

@ -1,304 +0,0 @@
package capnp
// A Ptr is a reference to a Cap'n Proto struct, list, or interface.
// The zero value is a null pointer.
type Ptr struct {
seg *Segment
off Address
lenOrCap uint32
size ObjectSize
depthLimit uint
flags ptrFlags
}
func toPtr(p Pointer) Ptr {
if p == nil {
return Ptr{}
}
switch p := p.underlying().(type) {
case Struct:
return p.ToPtr()
case List:
return p.ToPtr()
case Interface:
return p.ToPtr()
}
return Ptr{}
}
// Struct converts p to a Struct. If p does not hold a Struct pointer,
// the zero value is returned.
func (p Ptr) Struct() Struct {
if p.flags.ptrType() != structPtrType {
return Struct{}
}
return Struct{
seg: p.seg,
off: p.off,
size: p.size,
flags: p.flags.structFlags(),
depthLimit: p.depthLimit,
}
}
// StructDefault attempts to convert p into a struct, reading the
// default value from def if p is not a struct.
func (p Ptr) StructDefault(def []byte) (Struct, error) {
s := p.Struct()
if s.seg == nil {
if def == nil {
return Struct{}, nil
}
defp, err := unmarshalDefault(def)
if err != nil {
return Struct{}, err
}
return defp.Struct(), nil
}
return s, nil
}
// List converts p to a List. If p does not hold a List pointer,
// the zero value is returned.
func (p Ptr) List() List {
if p.flags.ptrType() != listPtrType {
return List{}
}
return List{
seg: p.seg,
off: p.off,
length: int32(p.lenOrCap),
size: p.size,
flags: p.flags.listFlags(),
depthLimit: p.depthLimit,
}
}
// ListDefault attempts to convert p into a list, reading the default
// value from def if p is not a list.
func (p Ptr) ListDefault(def []byte) (List, error) {
l := p.List()
if l.seg == nil {
if def == nil {
return List{}, nil
}
defp, err := unmarshalDefault(def)
if err != nil {
return List{}, err
}
return defp.List(), nil
}
return l, nil
}
// Interface converts p to an Interface. If p does not hold a List
// pointer, the zero value is returned.
func (p Ptr) Interface() Interface {
if p.flags.ptrType() != interfacePtrType {
return Interface{}
}
return Interface{
seg: p.seg,
cap: CapabilityID(p.lenOrCap),
}
}
// Text attempts to convert p into Text, returning an empty string if
// p is not a valid 1-byte list pointer.
func (p Ptr) Text() string {
b, ok := p.text()
if !ok {
return ""
}
return string(b)
}
// TextDefault attempts to convert p into Text, returning def if p is
// not a valid 1-byte list pointer.
func (p Ptr) TextDefault(def string) string {
b, ok := p.text()
if !ok {
return def
}
return string(b)
}
// TextBytes attempts to convert p into Text, returning nil if p is not
// a valid 1-byte list pointer. It returns a slice directly into the
// segment.
func (p Ptr) TextBytes() []byte {
b, ok := p.text()
if !ok {
return nil
}
return b
}
// TextBytesDefault attempts to convert p into Text, returning def if p
// is not a valid 1-byte list pointer. It returns a slice directly into
// the segment.
func (p Ptr) TextBytesDefault(def string) []byte {
b, ok := p.text()
if !ok {
return []byte(def)
}
return b
}
func (p Ptr) text() (b []byte, ok bool) {
if !isOneByteList(p) {
return nil, false
}
l := p.List()
b = l.seg.slice(l.off, Size(l.length))
if len(b) == 0 || b[len(b)-1] != 0 {
// Text must be null-terminated.
return nil, false
}
return b[: len(b)-1 : len(b)], true
}
// Data attempts to convert p into Data, returning nil if p is not a
// valid 1-byte list pointer.
func (p Ptr) Data() []byte {
return p.DataDefault(nil)
}
// DataDefault attempts to convert p into Data, returning def if p is
// not a valid 1-byte list pointer.
func (p Ptr) DataDefault(def []byte) []byte {
if !isOneByteList(p) {
return def
}
l := p.List()
b := l.seg.slice(l.off, Size(l.length))
if b == nil {
return def
}
return b
}
func (p Ptr) toPointer() Pointer {
if p.seg == nil {
return nil
}
switch p.flags.ptrType() {
case structPtrType:
return p.Struct()
case listPtrType:
return p.List()
case interfacePtrType:
return p.Interface()
}
return nil
}
// IsValid reports whether p is valid.
func (p Ptr) IsValid() bool {
return p.seg != nil
}
// Segment returns the segment this pointer points into.
// If nil, then this is an invalid pointer.
func (p Ptr) Segment() *Segment {
return p.seg
}
// Default returns p if it is valid, otherwise it unmarshals def.
func (p Ptr) Default(def []byte) (Ptr, error) {
if !p.IsValid() {
return unmarshalDefault(def)
}
return p, nil
}
// SamePtr reports whether p and q refer to the same object.
func SamePtr(p, q Ptr) bool {
return p.seg == q.seg && p.off == q.off
}
// A value that implements Pointer is a reference to a Cap'n Proto object.
//
// Deprecated: Using this type introduces an unnecessary allocation.
// Use Ptr instead.
type Pointer interface {
// Segment returns the segment this pointer points into.
// If nil, then this is an invalid pointer.
Segment() *Segment
// HasData reports whether the object referenced by the pointer has
// non-zero size.
HasData() bool
// underlying returns a Pointer that is one of a Struct, a List, or an
// Interface.
underlying() Pointer
}
// IsValid reports whether p is valid.
//
// Deprecated: Use Ptr.IsValid instead.
func IsValid(p Pointer) bool {
return p != nil && p.Segment() != nil
}
// HasData reports whether p has non-zero size.
//
// Deprecated: There are usually better ways to determine this
// information: length of a list, checking fields, or using HasFoo
// accessors.
func HasData(p Pointer) bool {
return IsValid(p) && p.HasData()
}
// PointerDefault returns p if it is valid, otherwise it unmarshals def.
//
// Deprecated: Use Ptr.Default.
func PointerDefault(p Pointer, def []byte) (Pointer, error) {
pp, err := toPtr(p).Default(def)
return pp.toPointer(), err
}
func unmarshalDefault(def []byte) (Ptr, error) {
msg, err := Unmarshal(def)
if err != nil {
return Ptr{}, err
}
p, err := msg.RootPtr()
if err != nil {
return Ptr{}, err
}
return p, nil
}
type ptrFlags uint8
const interfacePtrFlag ptrFlags = interfacePtrType << 6
func structPtrFlag(f structFlags) ptrFlags {
return structPtrType<<6 | ptrFlags(f)&ptrLowerMask
}
func listPtrFlag(f listFlags) ptrFlags {
return listPtrType<<6 | ptrFlags(f)&ptrLowerMask
}
const (
structPtrType = iota
listPtrType
interfacePtrType
)
func (f ptrFlags) ptrType() int {
return int(f >> 6)
}
const ptrLowerMask ptrFlags = 0x3f
func (f ptrFlags) listFlags() listFlags {
return listFlags(f & ptrLowerMask)
}
func (f ptrFlags) structFlags() structFlags {
return structFlags(f & ptrLowerMask)
}

View File

@ -1,189 +0,0 @@
package capnp
// pointerOffset is an address offset in multiples of word size.
type pointerOffset int32
// resolve returns an absolute address relative to a base address.
// For near pointers, the base is the end of the near pointer.
// For far pointers, the base is zero (the beginning of the segment).
func (off pointerOffset) resolve(base Address) (_ Address, ok bool) {
if off == 0 {
return base, true
}
addr := base + Address(off*pointerOffset(wordSize))
return addr, (addr > base || off < 0) && (addr < base || off > 0)
}
// nearPointerOffset computes the offset for a pointer at paddr to point to addr.
func nearPointerOffset(paddr, addr Address) pointerOffset {
return pointerOffset(addr/Address(wordSize) - paddr/Address(wordSize) - 1)
}
// rawPointer is an encoded pointer.
type rawPointer uint64
// rawStructPointer returns a struct pointer. The offset is from the
// end of the pointer to the start of the struct.
func rawStructPointer(off pointerOffset, sz ObjectSize) rawPointer {
return rawPointer(structPointer) | rawPointer(uint32(off)<<2) | rawPointer(sz.dataWordCount())<<32 | rawPointer(sz.PointerCount)<<48
}
// rawListPointer returns a list pointer. The offset is the number of
// words relative to the end of the pointer that the list starts. If
// listType is compositeList, then length is the number of words
// that the list occupies, otherwise it is the number of elements in
// the list.
func rawListPointer(off pointerOffset, listType listType, length int32) rawPointer {
return rawPointer(listPointer) | rawPointer(uint32(off)<<2) | rawPointer(listType)<<32 | rawPointer(length)<<35
}
// rawInterfacePointer returns an interface pointer that references
// a capability number.
func rawInterfacePointer(capability CapabilityID) rawPointer {
return rawPointer(otherPointer) | rawPointer(capability)<<32
}
// rawFarPointer returns a pointer to a pointer in another segment.
func rawFarPointer(segID SegmentID, off Address) rawPointer {
return rawPointer(farPointer) | rawPointer(off&^7) | (rawPointer(segID) << 32)
}
// rawDoubleFarPointer returns a pointer to a pointer in another segment.
func rawDoubleFarPointer(segID SegmentID, off Address) rawPointer {
return rawPointer(doubleFarPointer) | rawPointer(off&^7) | (rawPointer(segID) << 32)
}
// landingPadNearPointer converts a double-far pointer landing pad into
// a near pointer in the destination segment. Its offset will be
// relative to the beginning of the segment. tag must be either a
// struct or a list pointer.
func landingPadNearPointer(far, tag rawPointer) rawPointer {
// Replace tag's offset with far's offset.
// far's offset (29-bit unsigned) just needs to be shifted down to
// make it into a signed 30-bit value.
return tag&^0xfffffffc | rawPointer(uint32(far&^3)>>1)
}
type pointerType int
// Raw pointer types.
const (
structPointer pointerType = 0
listPointer pointerType = 1
farPointer pointerType = 2
doubleFarPointer pointerType = 6
otherPointer pointerType = 3
)
func (p rawPointer) pointerType() pointerType {
t := pointerType(p & 3)
if t == farPointer {
return pointerType(p & 7)
}
return t
}
func (p rawPointer) structSize() ObjectSize {
c := uint16(p >> 32)
d := uint16(p >> 48)
return ObjectSize{
DataSize: Size(c) * wordSize,
PointerCount: d,
}
}
type listType int
// Raw list pointer types.
const (
voidList listType = 0
bit1List listType = 1
byte1List listType = 2
byte2List listType = 3
byte4List listType = 4
byte8List listType = 5
pointerList listType = 6
compositeList listType = 7
)
func (p rawPointer) listType() listType {
return listType((p >> 32) & 7)
}
func (p rawPointer) numListElements() int32 {
return int32(p >> 35)
}
// elementSize returns the size of an individual element in the list referenced by p.
func (p rawPointer) elementSize() ObjectSize {
switch p.listType() {
case voidList:
return ObjectSize{}
case bit1List:
// Size is ignored on bit lists.
return ObjectSize{}
case byte1List:
return ObjectSize{DataSize: 1}
case byte2List:
return ObjectSize{DataSize: 2}
case byte4List:
return ObjectSize{DataSize: 4}
case byte8List:
return ObjectSize{DataSize: 8}
case pointerList:
return ObjectSize{PointerCount: 1}
default:
panic("elementSize not supposed to be called on composite or unknown list type")
}
}
// totalListSize returns the total size of the list referenced by p.
func (p rawPointer) totalListSize() (sz Size, ok bool) {
n := p.numListElements()
switch p.listType() {
case voidList:
return 0, true
case bit1List:
return Size((n + 7) / 8), true
case compositeList:
// For a composite list, n represents the number of words (excluding the tag word).
return wordSize.times(n + 1)
default:
return p.elementSize().totalSize().times(n)
}
}
// offset returns a pointer's offset. Only valid for struct or list
// pointers.
func (p rawPointer) offset() pointerOffset {
return pointerOffset(int32(p) >> 2)
}
// withOffset replaces a pointer's offset. Only valid for struct or
// list pointers.
func (p rawPointer) withOffset(off pointerOffset) rawPointer {
return p&^0xfffffffc | rawPointer(uint32(off<<2))
}
// farAddress returns the address of the landing pad pointer.
func (p rawPointer) farAddress() Address {
// Far pointer offset is 29 bits, starting after the low 3 bits.
// It's an unsigned word offset, which would be equivalent to a
// logical left shift by 3.
return Address(p) &^ 7
}
// farSegment returns the segment ID that the far pointer references.
func (p rawPointer) farSegment() SegmentID {
return SegmentID(p >> 32)
}
// otherPointerType returns the type of "other pointer" from p.
func (p rawPointer) otherPointerType() uint32 {
return uint32(p) >> 2
}
// capabilityIndex returns the index of the capability in the message's capability table.
func (p rawPointer) capabilityIndex() CapabilityID {
return CapabilityID(p >> 32)
}

View File

@ -1,38 +0,0 @@
package capnp
import "sync/atomic"
// A ReadLimiter tracks the number of bytes read from a message in order
// to avoid amplification attacks as detailed in
// https://capnproto.org/encoding.html#amplification-attack.
// It is safe to use from multiple goroutines.
type ReadLimiter struct {
limit uint64
}
// canRead reports whether the amount of bytes can be stored safely.
func (rl *ReadLimiter) canRead(sz Size) bool {
for {
curr := atomic.LoadUint64(&rl.limit)
ok := curr >= uint64(sz)
var new uint64
if ok {
new = curr - uint64(sz)
} else {
new = 0
}
if atomic.CompareAndSwapUint64(&rl.limit, curr, new) {
return ok
}
}
}
// Reset sets the number of bytes allowed to be read.
func (rl *ReadLimiter) Reset(limit uint64) {
atomic.StoreUint64(&rl.limit, limit)
}
// Unread increases the limit by sz.
func (rl *ReadLimiter) Unread(sz Size) {
atomic.AddUint64(&rl.limit, uint64(sz))
}

View File

@ -1,17 +0,0 @@
#!/bin/bash
# regen.sh - update capnpc-go and regenerate schemas
set -euo pipefail
cd "$(dirname "$0")"
echo "** mktemplates"
(cd internal/cmd/mktemplates && go build -tags=mktemplates)
echo "** capnpc-go"
# Run tests so that we don't install a broken capnpc-go.
(cd capnpc-go && go generate && go test && go install)
echo "** schemas"
(cd std/capnp; ./gen.sh compile)
capnp compile -ogo std/go.capnp && mv std/go.capnp.go ./
go generate ./...

View File

@ -1,49 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"answer.go",
"errors.go",
"introspect.go",
"log.go",
"question.go",
"rpc.go",
"tables.go",
"transport.go",
],
importpath = "zombiezen.com/go/capnproto2/rpc",
visibility = ["//visibility:public"],
deps = [
"//:go_default_library",
"//internal/fulfiller:go_default_library",
"//internal/queue:go_default_library",
"//rpc/internal/refcount:go_default_library",
"//std/capnp/rpc:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"bench_test.go",
"cancel_test.go",
"embargo_test.go",
"example_test.go",
"issue3_test.go",
"promise_test.go",
"release_test.go",
"rpc_test.go",
],
embed = [":go_default_library"],
deps = [
"//:go_default_library",
"//rpc/internal/logtransport:go_default_library",
"//rpc/internal/pipetransport:go_default_library",
"//rpc/internal/testcapnp:go_default_library",
"//server:go_default_library",
"//std/capnp/rpc:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)

View File

@ -1,498 +0,0 @@
package rpc
import (
"errors"
"sync"
"golang.org/x/net/context"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/fulfiller"
"zombiezen.com/go/capnproto2/internal/queue"
rpccapnp "zombiezen.com/go/capnproto2/std/capnp/rpc"
)
// callQueueSize is the maximum number of calls that can be queued per answer or client.
// TODO(light): make this a ConnOption
const callQueueSize = 64
// insertAnswer creates a new answer with the given ID, returning nil
// if the ID is already in use.
func (c *Conn) insertAnswer(id answerID, cancel context.CancelFunc) *answer {
if c.answers == nil {
c.answers = make(map[answerID]*answer)
} else if _, exists := c.answers[id]; exists {
return nil
}
a := &answer{
id: id,
cancel: cancel,
conn: c,
resolved: make(chan struct{}),
queue: make([]pcall, 0, callQueueSize),
}
c.answers[id] = a
return a
}
func (c *Conn) popAnswer(id answerID) *answer {
if c.answers == nil {
return nil
}
a := c.answers[id]
delete(c.answers, id)
return a
}
type answer struct {
id answerID
cancel context.CancelFunc
resultCaps []exportID
conn *Conn
resolved chan struct{}
mu sync.RWMutex
obj capnp.Ptr
err error
done bool
queue []pcall
}
// fulfill is called to resolve an answer successfully. It returns an
// error if its connection is shut down while sending messages. The
// caller must be holding onto a.conn.mu.
func (a *answer) fulfill(obj capnp.Ptr) error {
a.mu.Lock()
if a.done {
panic("answer.fulfill called more than once")
}
a.obj, a.done = obj, true
// TODO(light): populate resultCaps
var firstErr error
if err := a.conn.startWork(); err != nil {
firstErr = err
for i := range a.queue {
a.queue[i].a.reject(err)
}
a.queue = nil
} else {
retmsg := newReturnMessage(nil, a.id)
ret, _ := retmsg.Return()
payload, _ := ret.NewResults()
payload.SetContentPtr(obj)
if payloadTab, err := a.conn.makeCapTable(ret.Segment()); err != nil {
firstErr = err
} else {
payload.SetCapTable(payloadTab)
if err := a.conn.sendMessage(retmsg); err != nil {
firstErr = err
}
}
queues, err := a.emptyQueue(obj)
if err != nil && firstErr == nil {
firstErr = err
}
ctab := obj.Segment().Message().CapTable
for capIdx, q := range queues {
ctab[capIdx] = newQueueClient(a.conn, ctab[capIdx], q)
}
a.conn.workers.Done()
}
close(a.resolved)
a.mu.Unlock()
return firstErr
}
// reject is called to resolve an answer with failure. It returns an
// error if its connection is shut down while sending messages. The
// caller must be holding onto a.conn.mu.
func (a *answer) reject(err error) error {
if err == nil {
panic("answer.reject called with nil")
}
a.mu.Lock()
if a.done {
panic("answer.reject called more than once")
}
a.err, a.done = err, true
m := newReturnMessage(nil, a.id)
mret, _ := m.Return()
setReturnException(mret, err)
var firstErr error
if err := a.conn.sendMessage(m); err != nil {
firstErr = err
}
for i := range a.queue {
if err := a.queue[i].a.reject(err); err != nil && firstErr == nil {
firstErr = err
}
}
a.queue = nil
close(a.resolved)
a.mu.Unlock()
return firstErr
}
// emptyQueue splits the queue by which capability it targets
// and drops any invalid calls. Once this function returns, a.queue
// will be nil.
func (a *answer) emptyQueue(obj capnp.Ptr) (map[capnp.CapabilityID][]qcall, error) {
var firstErr error
qs := make(map[capnp.CapabilityID][]qcall, len(a.queue))
for i, pc := range a.queue {
c, err := capnp.TransformPtr(obj, pc.transform)
if err != nil {
if err := pc.a.reject(err); err != nil && firstErr == nil {
firstErr = err
}
continue
}
ci := c.Interface()
if !ci.IsValid() {
if err := pc.a.reject(capnp.ErrNullClient); err != nil && firstErr == nil {
firstErr = err
}
continue
}
cn := ci.Capability()
if qs[cn] == nil {
qs[cn] = make([]qcall, 0, len(a.queue)-i)
}
qs[cn] = append(qs[cn], pc.qcall)
}
a.queue = nil
return qs, firstErr
}
// queueCallLocked enqueues a call to be made after the answer has been
// resolved. The answer must not be resolved yet. pc should have
// transform and one of pc.a or pc.f to be set. The caller must be
// holding onto a.mu.
func (a *answer) queueCallLocked(call *capnp.Call, pc pcall) error {
if len(a.queue) == cap(a.queue) {
return errQueueFull
}
var err error
pc.call, err = call.Copy(nil)
if err != nil {
return err
}
a.queue = append(a.queue, pc)
return nil
}
// queueDisembargo enqueues a disembargo message.
func (a *answer) queueDisembargo(transform []capnp.PipelineOp, id embargoID, target rpccapnp.MessageTarget) (queued bool, err error) {
a.mu.Lock()
defer a.mu.Unlock()
if !a.done {
return false, errDisembargoOngoingAnswer
}
if a.err != nil {
return false, errDisembargoNonImport
}
targetPtr, err := capnp.TransformPtr(a.obj, transform)
if err != nil {
return false, err
}
client := targetPtr.Interface().Client()
qc, ok := client.(*queueClient)
if !ok {
// No need to embargo, disembargo immediately.
return false, nil
}
if ic := isImport(qc.client); ic == nil || a.conn != ic.conn {
return false, errDisembargoNonImport
}
qc.mu.Lock()
if !qc.isPassthrough() {
err = qc.pushEmbargoLocked(id, target)
if err == nil {
queued = true
}
}
qc.mu.Unlock()
return queued, err
}
func (a *answer) pipelineClient(transform []capnp.PipelineOp) capnp.Client {
return &localAnswerClient{a: a, transform: transform}
}
// joinAnswer resolves an RPC answer by waiting on a generic answer.
// The caller must not be holding onto a.conn.mu.
func joinAnswer(a *answer, ca capnp.Answer) {
s, err := ca.Struct()
a.conn.mu.Lock()
if err == nil {
a.fulfill(s.ToPtr())
} else {
a.reject(err)
}
a.conn.mu.Unlock()
}
// joinFulfiller resolves a fulfiller by waiting on a generic answer.
func joinFulfiller(f *fulfiller.Fulfiller, ca capnp.Answer) {
s, err := ca.Struct()
if err != nil {
f.Reject(err)
} else {
f.Fulfill(s)
}
}
type queueClient struct {
client capnp.Client
conn *Conn
mu sync.RWMutex
q queue.Queue
calls qcallList
}
func newQueueClient(c *Conn, client capnp.Client, queue []qcall) *queueClient {
qc := &queueClient{
client: client,
conn: c,
calls: make(qcallList, callQueueSize),
}
qc.q.Init(qc.calls, copy(qc.calls, queue))
go qc.flushQueue()
return qc
}
func (qc *queueClient) pushCallLocked(cl *capnp.Call) capnp.Answer {
f := new(fulfiller.Fulfiller)
cl, err := cl.Copy(nil)
if err != nil {
return capnp.ErrorAnswer(err)
}
i := qc.q.Push()
if i == -1 {
return capnp.ErrorAnswer(errQueueFull)
}
qc.calls[i] = qcall{call: cl, f: f}
return f
}
func (qc *queueClient) pushEmbargoLocked(id embargoID, tgt rpccapnp.MessageTarget) error {
i := qc.q.Push()
if i == -1 {
return errQueueFull
}
qc.calls[i] = qcall{embargoID: id, embargoTarget: tgt}
return nil
}
// flushQueue is run in its own goroutine.
func (qc *queueClient) flushQueue() {
var c qcall
qc.mu.RLock()
if i := qc.q.Front(); i != -1 {
c = qc.calls[i]
}
qc.mu.RUnlock()
for c.which() != qcallInvalid {
qc.handle(&c)
qc.mu.Lock()
qc.q.Pop()
if i := qc.q.Front(); i != -1 {
c = qc.calls[i]
} else {
c = qcall{}
}
qc.mu.Unlock()
}
}
func (qc *queueClient) handle(c *qcall) {
switch c.which() {
case qcallRemoteCall:
answer := qc.client.Call(c.call)
go joinAnswer(c.a, answer)
case qcallLocalCall:
answer := qc.client.Call(c.call)
go joinFulfiller(c.f, answer)
case qcallDisembargo:
msg := newDisembargoMessage(nil, rpccapnp.Disembargo_context_Which_receiverLoopback, c.embargoID)
d, _ := msg.Disembargo()
d.SetTarget(c.embargoTarget)
qc.conn.sendMessage(msg)
}
}
func (qc *queueClient) isPassthrough() bool {
return qc.q.Len() == 0
}
func (qc *queueClient) Call(cl *capnp.Call) capnp.Answer {
// Fast path: queue is flushed.
qc.mu.RLock()
ok := qc.isPassthrough()
qc.mu.RUnlock()
if ok {
return qc.client.Call(cl)
}
// Add to queue.
qc.mu.Lock()
// Since we released the lock, check that the queue hasn't been flushed.
if qc.isPassthrough() {
qc.mu.Unlock()
return qc.client.Call(cl)
}
ans := qc.pushCallLocked(cl)
qc.mu.Unlock()
return ans
}
func (qc *queueClient) tryQueue(cl *capnp.Call) capnp.Answer {
qc.mu.Lock()
if qc.isPassthrough() {
qc.mu.Unlock()
return nil
}
ans := qc.pushCallLocked(cl)
qc.mu.Unlock()
return ans
}
func (qc *queueClient) Close() error {
qc.conn.mu.Lock()
if err := qc.conn.startWork(); err != nil {
qc.conn.mu.Unlock()
return err
}
rejErr := qc.rejectQueue()
qc.conn.workers.Done()
qc.conn.mu.Unlock()
if err := qc.client.Close(); err != nil {
return err
}
return rejErr
}
// rejectQueue drains the client's queue. It returns an error if the
// connection was shut down while messages are sent. The caller must be
// holding onto qc.conn.mu.
func (qc *queueClient) rejectQueue() error {
var firstErr error
qc.mu.Lock()
for ; qc.q.Len() > 0; qc.q.Pop() {
c := qc.calls[qc.q.Front()]
switch c.which() {
case qcallRemoteCall:
if err := c.a.reject(errQueueCallCancel); err != nil && firstErr == nil {
firstErr = err
}
case qcallLocalCall:
c.f.Reject(errQueueCallCancel)
case qcallDisembargo:
m := newDisembargoMessage(nil, rpccapnp.Disembargo_context_Which_receiverLoopback, c.embargoID)
d, _ := m.Disembargo()
d.SetTarget(c.embargoTarget)
if err := qc.conn.sendMessage(m); err != nil && firstErr == nil {
firstErr = err
}
}
}
qc.mu.Unlock()
return firstErr
}
// pcall is a queued pipeline call.
type pcall struct {
transform []capnp.PipelineOp
qcall
}
// qcall is a queued call.
type qcall struct {
// Calls
a *answer // non-nil if remote call
f *fulfiller.Fulfiller // non-nil if local call
call *capnp.Call
// Disembargo
embargoID embargoID
embargoTarget rpccapnp.MessageTarget
}
// Queued call types.
const (
qcallInvalid = iota
qcallRemoteCall
qcallLocalCall
qcallDisembargo
)
func (c *qcall) which() int {
switch {
case c.a != nil:
return qcallRemoteCall
case c.f != nil:
return qcallLocalCall
case c.embargoTarget.IsValid():
return qcallDisembargo
default:
return qcallInvalid
}
}
type qcallList []qcall
func (ql qcallList) Len() int {
return len(ql)
}
func (ql qcallList) Clear(i int) {
ql[i] = qcall{}
}
// A localAnswerClient is used to provide a pipelined client of an answer.
type localAnswerClient struct {
a *answer
transform []capnp.PipelineOp
}
func (lac *localAnswerClient) Call(call *capnp.Call) capnp.Answer {
lac.a.mu.Lock()
if lac.a.done {
obj, err := lac.a.obj, lac.a.err
lac.a.mu.Unlock()
return clientFromResolution(lac.transform, obj, err).Call(call)
}
f := new(fulfiller.Fulfiller)
err := lac.a.queueCallLocked(call, pcall{
transform: lac.transform,
qcall: qcall{f: f},
})
lac.a.mu.Unlock()
if err != nil {
return capnp.ErrorAnswer(errQueueFull)
}
return f
}
func (lac *localAnswerClient) Close() error {
lac.a.mu.RLock()
obj, err, done := lac.a.obj, lac.a.err, lac.a.done
lac.a.mu.RUnlock()
if !done {
return nil
}
client := clientFromResolution(lac.transform, obj, err)
return client.Close()
}
var (
errQueueFull = errors.New("rpc: pipeline queue full")
errQueueCallCancel = errors.New("rpc: queued call canceled")
errDisembargoOngoingAnswer = errors.New("rpc: disembargo attempted on in-progress answer")
errDisembargoNonImport = errors.New("rpc: disembargo attempted on non-import capability")
errDisembargoMissingAnswer = errors.New("rpc: disembargo attempted on missing answer (finished too early?)")
)

View File

@ -1,102 +0,0 @@
package rpc
import (
"errors"
"fmt"
"zombiezen.com/go/capnproto2"
rpccapnp "zombiezen.com/go/capnproto2/std/capnp/rpc"
)
// An Exception is a Cap'n Proto RPC error.
type Exception struct {
rpccapnp.Exception
}
// Error returns the exception's reason.
func (e Exception) Error() string {
r, err := e.Reason()
if err != nil {
return "rpc exception"
}
return "rpc exception: " + r
}
// An Abort is a hang-up by a remote vat.
type Abort Exception
func copyAbort(m rpccapnp.Message) (Abort, error) {
ma, err := m.Abort()
if err != nil {
return Abort{}, err
}
msg, _, _ := capnp.NewMessage(capnp.SingleSegment(nil))
if err := msg.SetRootPtr(ma.ToPtr()); err != nil {
return Abort{}, err
}
p, err := msg.RootPtr()
if err != nil {
return Abort{}, err
}
return Abort{rpccapnp.Exception{Struct: p.Struct()}}, nil
}
// Error returns the exception's reason.
func (a Abort) Error() string {
r, err := a.Reason()
if err != nil {
return "rpc: aborted by remote"
}
return "rpc: aborted by remote: " + r
}
// toException sets fields on exc to match err.
func toException(exc rpccapnp.Exception, err error) {
if ee, ok := err.(Exception); ok {
// TODO(light): copy struct
r, err := ee.Reason()
if err == nil {
exc.SetReason(r)
}
exc.SetType(ee.Type())
return
}
exc.SetReason(err.Error())
exc.SetType(rpccapnp.Exception_Type_failed)
}
// Errors
var (
ErrConnClosed = errors.New("rpc: connection closed")
)
// Internal errors
var (
errQuestionReused = errors.New("rpc: question ID reused")
errNoMainInterface = errors.New("rpc: no bootstrap interface")
errBadTarget = errors.New("rpc: target not found")
errShutdown = errors.New("rpc: shutdown")
errUnimplemented = errors.New("rpc: remote used unimplemented protocol feature")
)
type bootstrapError struct {
err error
}
func (e bootstrapError) Error() string {
return "rpc bootstrap:" + e.err.Error()
}
type questionError struct {
id questionID
method *capnp.Method // nil if this is bootstrap
err error
}
func (qe *questionError) Error() string {
if qe.method == nil {
return fmt.Sprintf("bootstrap call id=%d: %v", qe.id, qe.err)
}
return fmt.Sprintf("%v call id=%d: %v", qe.method, qe.id, qe.err)
}

View File

@ -1,16 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["refcount.go"],
importpath = "zombiezen.com/go/capnproto2/rpc/internal/refcount",
visibility = ["//rpc:__subpackages__"],
deps = ["//:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["refcount_test.go"],
embed = [":go_default_library"],
deps = ["//:go_default_library"],
)

View File

@ -1,116 +0,0 @@
// Package refcount implements a reference-counting client.
package refcount
import (
"errors"
"runtime"
"sync"
"zombiezen.com/go/capnproto2"
)
// A RefCount will close its underlying client once all its references are closed.
type RefCount struct {
Client capnp.Client
mu sync.Mutex
refs int
}
// New creates a reference counter and the first client reference.
func New(c capnp.Client) (rc *RefCount, ref1 capnp.Client) {
if rr, ok := c.(*Ref); ok {
return rr.rc, rr.rc.Ref()
}
rc = &RefCount{Client: c, refs: 1}
ref1 = rc.newRef()
return
}
// Ref makes a new client reference.
func (rc *RefCount) Ref() capnp.Client {
rc.mu.Lock()
if rc.refs <= 0 {
rc.mu.Unlock()
return capnp.ErrorClient(errZeroRef)
}
rc.refs++
rc.mu.Unlock()
return rc.newRef()
}
func (rc *RefCount) newRef() *Ref {
r := &Ref{rc: rc}
runtime.SetFinalizer(r, (*Ref).Close)
return r
}
func (rc *RefCount) call(cl *capnp.Call) capnp.Answer {
// We lock here so that we can prevent the client from being closed
// while we start the call.
rc.mu.Lock()
if rc.refs <= 0 {
rc.mu.Unlock()
return capnp.ErrorAnswer(errClosed)
}
ans := rc.Client.Call(cl)
rc.mu.Unlock()
return ans
}
// decref decreases the reference count by one, closing the Client if it reaches zero.
func (rc *RefCount) decref() error {
shouldClose := false
rc.mu.Lock()
if rc.refs <= 0 {
rc.mu.Unlock()
return errClosed
}
rc.refs--
if rc.refs == 0 {
shouldClose = true
}
rc.mu.Unlock()
if shouldClose {
return rc.Client.Close()
}
return nil
}
var (
errZeroRef = errors.New("rpc: Ref() called on zeroed refcount")
errClosed = errors.New("rpc: Close() called on closed client")
)
// A Ref is a single reference to a client wrapped by RefCount.
type Ref struct {
rc *RefCount
once sync.Once
}
// Call makes a call on the underlying client.
func (r *Ref) Call(cl *capnp.Call) capnp.Answer {
return r.rc.call(cl)
}
// Client returns the underlying client.
func (r *Ref) Client() capnp.Client {
return r.rc.Client
}
// Close decrements the reference count. Close will be called on
// finalization (i.e. garbage collection).
func (r *Ref) Close() error {
var err error
closed := false
r.once.Do(func() {
err = r.rc.decref()
closed = true
})
if !closed {
return errClosed
}
return err
}

View File

@ -1,347 +0,0 @@
package rpc
import (
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/fulfiller"
"zombiezen.com/go/capnproto2/rpc/internal/refcount"
rpccapnp "zombiezen.com/go/capnproto2/std/capnp/rpc"
)
// While the code below looks repetitive, resist the urge to refactor.
// Each operation is distinct in assumptions it can make about
// particular cases, and there isn't a convenient type signature that
// fits all cases.
// lockedCall is used to make a call to an arbitrary client while
// holding onto c.mu. Since the client could point back to c, naively
// calling c.Call could deadlock.
func (c *Conn) lockedCall(client capnp.Client, cl *capnp.Call) capnp.Answer {
dig:
for client := client; ; {
switch curr := client.(type) {
case *importClient:
if curr.conn != c {
// This doesn't use our conn's lock, so it is safe to call.
return curr.Call(cl)
}
return curr.lockedCall(cl)
case *fulfiller.EmbargoClient:
if ans := curr.TryQueue(cl); ans != nil {
return ans
}
client = curr.Client()
case *refcount.Ref:
client = curr.Client()
case *embargoClient:
if ans := curr.tryQueue(cl); ans != nil {
return ans
}
client = curr.client
case *queueClient:
if ans := curr.tryQueue(cl); ans != nil {
return ans
}
client = curr.client
case *localAnswerClient:
curr.a.mu.Lock()
if curr.a.done {
obj, err := curr.a.obj, curr.a.err
curr.a.mu.Unlock()
client = clientFromResolution(curr.transform, obj, err)
} else {
f := new(fulfiller.Fulfiller)
err := curr.a.queueCallLocked(cl, pcall{
transform: curr.transform,
qcall: qcall{f: f},
})
curr.a.mu.Unlock()
if err != nil {
return capnp.ErrorAnswer(err)
}
return f
}
case *capnp.PipelineClient:
p := (*capnp.Pipeline)(curr)
ans := p.Answer()
transform := p.Transform()
if capnp.IsFixedAnswer(ans) {
s, err := ans.Struct()
client = clientFromResolution(transform, s.ToPtr(), err)
continue
}
switch ans := ans.(type) {
case *fulfiller.Fulfiller:
ap := ans.Peek()
if ap == nil {
break dig
}
s, err := ap.Struct()
client = clientFromResolution(transform, s.ToPtr(), err)
case *question:
if ans.conn != c {
// This doesn't use our conn's lock, so it is safe to call.
return ans.PipelineCall(transform, cl)
}
return ans.lockedPipelineCall(transform, cl)
default:
break dig
}
default:
break dig
}
}
// TODO(light): Add a CallOption that signals to bypass sync.
// The above hack works in *most* cases.
//
// If your code is deadlocking here, you've hit the edge of the
// compromise between these three goals:
// 1) Package capnp is loosely coupled with package rpc
// 2) Arbitrary implementations of Client may exist
// 3) Local E-order must be preserved
//
// #3 is the one that creates a deadlock, since application code must
// acquire the connection mutex to preserve order of delivery. You
// can't really overcome this without breaking one of the first two
// constraints.
//
// To avoid #2 as much as possible, implementing Client is discouraged
// by several docs.
return client.Call(cl)
}
// descriptorForClient fills desc for client, adding it to the export
// table if necessary. The caller must be holding onto c.mu.
func (c *Conn) descriptorForClient(desc rpccapnp.CapDescriptor, client capnp.Client) error {
dig:
for client := client; ; {
switch ct := client.(type) {
case *importClient:
if ct.conn != c {
break dig
}
desc.SetReceiverHosted(uint32(ct.id))
return nil
case *fulfiller.EmbargoClient:
client = ct.Client()
if client == nil {
break dig
}
case *refcount.Ref:
client = ct.Client()
case *embargoClient:
ct.mu.RLock()
ok := ct.isPassthrough()
ct.mu.RUnlock()
if !ok {
break dig
}
client = ct.client
case *queueClient:
ct.mu.RLock()
ok := ct.isPassthrough()
ct.mu.RUnlock()
if !ok {
break dig
}
client = ct.client
case *localAnswerClient:
ct.a.mu.RLock()
obj, err, done := ct.a.obj, ct.a.err, ct.a.done
ct.a.mu.RUnlock()
if !done {
break dig
}
client = clientFromResolution(ct.transform, obj, err)
case *capnp.PipelineClient:
p := (*capnp.Pipeline)(ct)
ans := p.Answer()
transform := p.Transform()
if capnp.IsFixedAnswer(ans) {
s, err := ans.Struct()
client = clientFromResolution(transform, s.ToPtr(), err)
continue
}
switch ans := ans.(type) {
case *fulfiller.Fulfiller:
ap := ans.Peek()
if ap == nil {
break dig
}
s, err := ap.Struct()
client = clientFromResolution(transform, s.ToPtr(), err)
case *question:
ans.mu.RLock()
obj, err, state := ans.obj, ans.err, ans.state
ans.mu.RUnlock()
if state != questionInProgress {
client = clientFromResolution(transform, obj, err)
continue
}
if ans.conn != c {
break dig
}
a, err := desc.NewReceiverAnswer()
if err != nil {
return err
}
a.SetQuestionId(uint32(ans.id))
err = transformToPromisedAnswer(desc.Segment(), a, p.Transform())
if err != nil {
return err
}
return nil
default:
break dig
}
default:
break dig
}
}
id := c.addExport(client)
desc.SetSenderHosted(uint32(id))
return nil
}
// isSameClient reports whether c and d refer to the same capability.
func isSameClient(c, d capnp.Client) bool {
norm := func(client capnp.Client) capnp.Client {
for {
switch curr := client.(type) {
case *fulfiller.EmbargoClient:
client = curr.Client()
if client == nil {
return curr
}
case *refcount.Ref:
client = curr.Client()
case *embargoClient:
curr.mu.RLock()
ok := curr.isPassthrough()
curr.mu.RUnlock()
if !ok {
return curr
}
client = curr.client
case *queueClient:
curr.mu.RLock()
ok := curr.isPassthrough()
curr.mu.RUnlock()
if !ok {
return curr
}
client = curr.client
case *localAnswerClient:
curr.a.mu.RLock()
obj, err, done := curr.a.obj, curr.a.err, curr.a.done
curr.a.mu.RUnlock()
if !done {
return curr
}
client = clientFromResolution(curr.transform, obj, err)
case *capnp.PipelineClient:
p := (*capnp.Pipeline)(curr)
ans := p.Answer()
if capnp.IsFixedAnswer(ans) {
s, err := ans.Struct()
client = clientFromResolution(p.Transform(), s.ToPtr(), err)
continue
}
switch ans := ans.(type) {
case *fulfiller.Fulfiller:
ap := ans.Peek()
if ap == nil {
return curr
}
s, err := ap.Struct()
client = clientFromResolution(p.Transform(), s.ToPtr(), err)
case *question:
ans.mu.RLock()
obj, err, state := ans.obj, ans.err, ans.state
ans.mu.RUnlock()
if state != questionResolved {
return curr
}
client = clientFromResolution(p.Transform(), obj, err)
default:
return curr
}
default:
return curr
}
}
}
return norm(c) == norm(d)
}
// isImport returns the underlying import if client represents an import
// or nil otherwise.
func isImport(client capnp.Client) *importClient {
for {
switch curr := client.(type) {
case *importClient:
return curr
case *fulfiller.EmbargoClient:
client = curr.Client()
if client == nil {
return nil
}
case *refcount.Ref:
client = curr.Client()
case *embargoClient:
curr.mu.RLock()
ok := curr.isPassthrough()
curr.mu.RUnlock()
if !ok {
return nil
}
client = curr.client
case *queueClient:
curr.mu.RLock()
ok := curr.isPassthrough()
curr.mu.RUnlock()
if !ok {
return nil
}
client = curr.client
case *localAnswerClient:
curr.a.mu.RLock()
obj, err, done := curr.a.obj, curr.a.err, curr.a.done
curr.a.mu.RUnlock()
if !done {
return nil
}
client = clientFromResolution(curr.transform, obj, err)
case *capnp.PipelineClient:
p := (*capnp.Pipeline)(curr)
ans := p.Answer()
if capnp.IsFixedAnswer(ans) {
s, err := ans.Struct()
client = clientFromResolution(p.Transform(), s.ToPtr(), err)
continue
}
switch ans := ans.(type) {
case *fulfiller.Fulfiller:
ap := ans.Peek()
if ap == nil {
return nil
}
s, err := ap.Struct()
client = clientFromResolution(p.Transform(), s.ToPtr(), err)
case *question:
ans.mu.RLock()
obj, err, state := ans.obj, ans.err, ans.state
ans.mu.RUnlock()
if state != questionResolved {
return nil
}
client = clientFromResolution(p.Transform(), obj, err)
default:
return nil
}
default:
return nil
}
}
}

View File

@ -1,49 +0,0 @@
package rpc
import (
"log"
"golang.org/x/net/context"
)
// A Logger records diagnostic information and errors that are not
// associated with a call. The arguments passed into a log call are
// interpreted like fmt.Printf. They should not be held onto past the
// call's return.
type Logger interface {
Infof(ctx context.Context, format string, args ...interface{})
Errorf(ctx context.Context, format string, args ...interface{})
}
type defaultLogger struct{}
func (defaultLogger) Infof(ctx context.Context, format string, args ...interface{}) {
log.Printf("rpc: "+format, args...)
}
func (defaultLogger) Errorf(ctx context.Context, format string, args ...interface{}) {
log.Printf("rpc: "+format, args...)
}
func (c *Conn) infof(format string, args ...interface{}) {
if c.log == nil {
return
}
c.log.Infof(c.bg, format, args...)
}
func (c *Conn) errorf(format string, args ...interface{}) {
if c.log == nil {
return
}
c.log.Errorf(c.bg, format, args...)
}
// ConnLog sets the connection's log to the given Logger, which may be
// nil to disable logging. By default, logs are sent to the standard
// log package.
func ConnLog(log Logger) ConnOption {
return ConnOption{func(c *connParams) {
c.log = log
}}
}

View File

@ -1,442 +0,0 @@
package rpc
import (
"sync"
"golang.org/x/net/context"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/fulfiller"
"zombiezen.com/go/capnproto2/internal/queue"
rpccapnp "zombiezen.com/go/capnproto2/std/capnp/rpc"
)
// newQuestion creates a new question with an unassigned ID.
func (c *Conn) newQuestion(ctx context.Context, method *capnp.Method) *question {
id := questionID(c.questionID.next())
q := &question{
ctx: ctx,
conn: c,
method: method,
resolved: make(chan struct{}),
id: id,
}
// TODO(light): populate paramCaps
if int(id) == len(c.questions) {
c.questions = append(c.questions, q)
} else {
c.questions[id] = q
}
return q
}
func (c *Conn) findQuestion(id questionID) *question {
if int(id) >= len(c.questions) {
return nil
}
return c.questions[id]
}
func (c *Conn) popQuestion(id questionID) *question {
q := c.findQuestion(id)
if q == nil {
return nil
}
c.questions[id] = nil
c.questionID.remove(uint32(id))
return q
}
type question struct {
id questionID
ctx context.Context
conn *Conn
method *capnp.Method // nil if this is bootstrap
paramCaps []exportID
resolved chan struct{}
// Protected by conn.mu
derived [][]capnp.PipelineOp
// Fields below are protected by mu.
mu sync.RWMutex
obj capnp.Ptr
err error
state questionState
}
type questionState uint8
// Question states
const (
questionInProgress questionState = iota
questionResolved
questionCanceled
)
// start signals that the question has been sent.
func (q *question) start() {
go func() {
select {
case <-q.resolved:
// Resolved naturally, nothing to do.
case <-q.conn.bg.Done():
case <-q.ctx.Done():
select {
case <-q.resolved:
case <-q.conn.bg.Done():
case <-q.conn.mu:
if err := q.conn.startWork(); err != nil {
// teardown calls cancel.
q.conn.mu.Unlock()
return
}
if q.cancel(q.ctx.Err()) {
q.conn.sendMessage(newFinishMessage(nil, q.id, true /* release */))
}
q.conn.workers.Done()
q.conn.mu.Unlock()
}
}
}()
}
// fulfill is called to resolve a question successfully.
// The caller must be holding onto q.conn.mu.
func (q *question) fulfill(obj capnp.Ptr) {
var ctab []capnp.Client
if obj.IsValid() {
ctab = obj.Segment().Message().CapTable
}
visited := make([]bool, len(ctab))
for _, d := range q.derived {
tgt, err := capnp.TransformPtr(obj, d)
if err != nil {
continue
}
in := tgt.Interface()
if !in.IsValid() {
continue
}
if ic := isImport(in.Client()); ic != nil && ic.conn == q.conn {
// Imported from remote vat. Don't need to disembargo.
continue
}
cn := in.Capability()
if visited[cn] {
continue
}
visited[cn] = true
id, e := q.conn.newEmbargo()
ctab[cn] = newEmbargoClient(ctab[cn], e, q.conn.bg.Done())
m := newDisembargoMessage(nil, rpccapnp.Disembargo_context_Which_senderLoopback, id)
dis, _ := m.Disembargo()
mt, _ := dis.NewTarget()
pa, _ := mt.NewPromisedAnswer()
pa.SetQuestionId(uint32(q.id))
transformToPromisedAnswer(m.Segment(), pa, d)
mt.SetPromisedAnswer(pa)
select {
case q.conn.out <- m:
case <-q.conn.bg.Done():
// TODO(soon): perhaps just drop all embargoes in this case?
}
}
q.mu.Lock()
if q.state != questionInProgress {
panic("question.fulfill called more than once")
}
q.obj, q.state = obj, questionResolved
close(q.resolved)
q.mu.Unlock()
}
// reject is called to resolve a question with failure.
// The caller must be holding onto q.conn.mu.
func (q *question) reject(err error) {
if err == nil {
panic("question.reject called with nil")
}
q.mu.Lock()
if q.state != questionInProgress {
panic("question.reject called more than once")
}
q.err = err
q.state = questionResolved
close(q.resolved)
q.mu.Unlock()
}
// cancel is called to resolve a question with cancellation.
// The caller must be holding onto q.conn.mu.
func (q *question) cancel(err error) bool {
if err == nil {
panic("question.cancel called with nil")
}
q.mu.Lock()
canceled := q.state == questionInProgress
if canceled {
q.err = err
q.state = questionCanceled
close(q.resolved)
}
q.mu.Unlock()
return canceled
}
// addPromise records a returned capability as being used for a call.
// This is needed for determining embargoes upon resolution. The
// caller must be holding onto q.conn.mu.
func (q *question) addPromise(transform []capnp.PipelineOp) {
for _, d := range q.derived {
if transformsEqual(transform, d) {
return
}
}
q.derived = append(q.derived, transform)
}
func transformsEqual(t, u []capnp.PipelineOp) bool {
if len(t) != len(u) {
return false
}
for i := range t {
if t[i].Field != u[i].Field {
return false
}
}
return true
}
func (q *question) Struct() (capnp.Struct, error) {
select {
case <-q.resolved:
case <-q.conn.bg.Done():
return capnp.Struct{}, ErrConnClosed
}
q.mu.RLock()
s, err := q.obj.Struct(), q.err
q.mu.RUnlock()
return s, err
}
func (q *question) PipelineCall(transform []capnp.PipelineOp, ccall *capnp.Call) capnp.Answer {
select {
case <-q.conn.mu:
if err := q.conn.startWork(); err != nil {
q.conn.mu.Unlock()
return capnp.ErrorAnswer(err)
}
case <-ccall.Ctx.Done():
return capnp.ErrorAnswer(ccall.Ctx.Err())
}
ans := q.lockedPipelineCall(transform, ccall)
q.conn.workers.Done()
q.conn.mu.Unlock()
return ans
}
// lockedPipelineCall is equivalent to PipelineCall but assumes that the
// caller is already holding onto q.conn.mu.
func (q *question) lockedPipelineCall(transform []capnp.PipelineOp, ccall *capnp.Call) capnp.Answer {
if q.conn.findQuestion(q.id) != q {
// Question has been finished. The call should happen as if it is
// back in application code.
q.mu.RLock()
obj, err, state := q.obj, q.err, q.state
q.mu.RUnlock()
if state == questionInProgress {
panic("question popped but not done")
}
client := clientFromResolution(transform, obj, err)
return q.conn.lockedCall(client, ccall)
}
pipeq := q.conn.newQuestion(ccall.Ctx, &ccall.Method)
msg := newMessage(nil)
msgCall, _ := msg.NewCall()
msgCall.SetQuestionId(uint32(pipeq.id))
msgCall.SetInterfaceId(ccall.Method.InterfaceID)
msgCall.SetMethodId(ccall.Method.MethodID)
target, _ := msgCall.NewTarget()
a, _ := target.NewPromisedAnswer()
a.SetQuestionId(uint32(q.id))
err := transformToPromisedAnswer(a.Segment(), a, transform)
if err != nil {
q.conn.popQuestion(pipeq.id)
return capnp.ErrorAnswer(err)
}
payload, _ := msgCall.NewParams()
if err := q.conn.fillParams(payload, ccall); err != nil {
q.conn.popQuestion(q.id)
return capnp.ErrorAnswer(err)
}
select {
case q.conn.out <- msg:
case <-ccall.Ctx.Done():
q.conn.popQuestion(pipeq.id)
return capnp.ErrorAnswer(ccall.Ctx.Err())
case <-q.conn.bg.Done():
q.conn.popQuestion(pipeq.id)
return capnp.ErrorAnswer(ErrConnClosed)
}
q.addPromise(transform)
pipeq.start()
return pipeq
}
func (q *question) PipelineClose(transform []capnp.PipelineOp) error {
<-q.resolved
q.mu.RLock()
obj, err := q.obj, q.err
q.mu.RUnlock()
if err != nil {
return err
}
x, err := capnp.TransformPtr(obj, transform)
if err != nil {
return err
}
c := x.Interface().Client()
if c == nil {
return capnp.ErrNullClient
}
return c.Close()
}
// embargoClient is a client that waits until an embargo signal is
// received to deliver calls.
type embargoClient struct {
cancel <-chan struct{}
client capnp.Client
embargo embargo
mu sync.RWMutex
q queue.Queue
calls ecallList
}
func newEmbargoClient(client capnp.Client, e embargo, cancel <-chan struct{}) *embargoClient {
ec := &embargoClient{
client: client,
embargo: e,
cancel: cancel,
calls: make(ecallList, callQueueSize),
}
ec.q.Init(ec.calls, 0)
go ec.flushQueue()
return ec
}
func (ec *embargoClient) push(cl *capnp.Call) capnp.Answer {
f := new(fulfiller.Fulfiller)
cl, err := cl.Copy(nil)
if err != nil {
return capnp.ErrorAnswer(err)
}
i := ec.q.Push()
if i == -1 {
return capnp.ErrorAnswer(errQueueFull)
}
ec.calls[i] = ecall{cl, f}
return f
}
func (ec *embargoClient) Call(cl *capnp.Call) capnp.Answer {
// Fast path: queue is flushed.
ec.mu.RLock()
ok := ec.isPassthrough()
ec.mu.RUnlock()
if ok {
return ec.client.Call(cl)
}
ec.mu.Lock()
if ec.isPassthrough() {
ec.mu.Unlock()
return ec.client.Call(cl)
}
ans := ec.push(cl)
ec.mu.Unlock()
return ans
}
func (ec *embargoClient) tryQueue(cl *capnp.Call) capnp.Answer {
ec.mu.Lock()
if ec.isPassthrough() {
ec.mu.Unlock()
return nil
}
ans := ec.push(cl)
ec.mu.Unlock()
return ans
}
func (ec *embargoClient) isPassthrough() bool {
select {
case <-ec.embargo:
default:
return false
}
return ec.q.Len() == 0
}
func (ec *embargoClient) Close() error {
ec.mu.Lock()
for ; ec.q.Len() > 0; ec.q.Pop() {
c := ec.calls[ec.q.Front()]
c.f.Reject(errQueueCallCancel)
}
ec.mu.Unlock()
return ec.client.Close()
}
// flushQueue is run in its own goroutine.
func (ec *embargoClient) flushQueue() {
select {
case <-ec.embargo:
case <-ec.cancel:
ec.mu.Lock()
for ec.q.Len() > 0 {
ec.q.Pop()
}
ec.mu.Unlock()
return
}
var c ecall
ec.mu.RLock()
if i := ec.q.Front(); i != -1 {
c = ec.calls[i]
}
ec.mu.RUnlock()
for c.call != nil {
ans := ec.client.Call(c.call)
go joinFulfiller(c.f, ans)
ec.mu.Lock()
ec.q.Pop()
if i := ec.q.Front(); i != -1 {
c = ec.calls[i]
} else {
c = ecall{}
}
ec.mu.Unlock()
}
}
type ecall struct {
call *capnp.Call
f *fulfiller.Fulfiller
}
type ecallList []ecall
func (el ecallList) Len() int {
return len(el)
}
func (el ecallList) Clear(i int) {
el[i] = ecall{}
}

View File

@ -1,913 +0,0 @@
// Package rpc implements the Cap'n Proto RPC protocol.
package rpc // import "zombiezen.com/go/capnproto2/rpc"
import (
"fmt"
"io"
"sync"
"golang.org/x/net/context"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/rpc/internal/refcount"
rpccapnp "zombiezen.com/go/capnproto2/std/capnp/rpc"
)
// A Conn is a connection to another Cap'n Proto vat.
// It is safe to use from multiple goroutines.
type Conn struct {
transport Transport
log Logger
mainFunc func(context.Context) (capnp.Client, error)
mainCloser io.Closer
death chan struct{} // closed after state is connDead
out chan rpccapnp.Message
bg context.Context
bgCancel context.CancelFunc
workers sync.WaitGroup
// Mutable state protected by stateMu
// If you need to acquire both mu and stateMu, acquire mu first.
stateMu sync.RWMutex
state connState
closeErr error
// Mutable state protected by mu
mu chanMutex
questions []*question
questionID idgen
exports []*export
exportID idgen
embargoes []chan<- struct{}
embargoID idgen
answers map[answerID]*answer
imports map[importID]*impent
}
type connParams struct {
log Logger
mainFunc func(context.Context) (capnp.Client, error)
mainCloser io.Closer
sendBufferSize int
}
// A ConnOption is an option for opening a connection.
type ConnOption struct {
f func(*connParams)
}
// MainInterface specifies that the connection should use client when
// receiving bootstrap messages. By default, all bootstrap messages will
// fail. The client will be closed when the connection is closed.
func MainInterface(client capnp.Client) ConnOption {
rc, ref1 := refcount.New(client)
ref2 := rc.Ref()
return ConnOption{func(c *connParams) {
c.mainFunc = func(ctx context.Context) (capnp.Client, error) {
return ref1, nil
}
c.mainCloser = ref2
}}
}
// BootstrapFunc specifies the function to call to create a capability
// for handling bootstrap messages. This function should not make any
// RPCs or block.
func BootstrapFunc(f func(context.Context) (capnp.Client, error)) ConnOption {
return ConnOption{func(c *connParams) {
c.mainFunc = f
}}
}
// SendBufferSize sets the number of outgoing messages to buffer on the
// connection. This is in addition to whatever buffering the connection's
// transport performs.
func SendBufferSize(numMsgs int) ConnOption {
return ConnOption{func(c *connParams) {
c.sendBufferSize = numMsgs
}}
}
// NewConn creates a new connection that communicates on c.
// Closing the connection will cause c to be closed.
func NewConn(t Transport, options ...ConnOption) *Conn {
p := &connParams{
log: defaultLogger{},
sendBufferSize: 4,
}
for _, o := range options {
o.f(p)
}
conn := &Conn{
transport: t,
out: make(chan rpccapnp.Message, p.sendBufferSize),
mainFunc: p.mainFunc,
mainCloser: p.mainCloser,
log: p.log,
death: make(chan struct{}),
mu: newChanMutex(),
}
conn.bg, conn.bgCancel = context.WithCancel(context.Background())
conn.workers.Add(2)
go conn.dispatchRecv()
go conn.dispatchSend()
return conn
}
// Wait waits until the connection is closed or aborted by the remote vat.
// Wait will always return an error, usually ErrConnClosed or of type Abort.
func (c *Conn) Wait() error {
<-c.Done()
return c.Err()
}
// Done is a channel that is closed once the connection is fully shut down.
func (c *Conn) Done() <-chan struct{} {
return c.death
}
// Err returns the error that caused the connection to close.
// Err returns nil before Done is closed.
func (c *Conn) Err() error {
c.stateMu.RLock()
var err error
if c.state != connDead {
err = c.closeErr
}
c.stateMu.RUnlock()
return err
}
// Close closes the connection and the underlying transport.
func (c *Conn) Close() error {
c.stateMu.Lock()
alive := c.state == connAlive
if alive {
c.bgCancel()
c.closeErr = ErrConnClosed
c.state = connDying
}
c.stateMu.Unlock()
if !alive {
return ErrConnClosed
}
c.teardown(newAbortMessage(nil, errShutdown))
c.stateMu.RLock()
err := c.closeErr
c.stateMu.RUnlock()
if err != ErrConnClosed {
return err
}
return nil
}
// shutdown cancels the background context and sets closeErr to e.
// No abort message will be sent on the transport. After shutdown
// returns, the Conn will be in the dying or dead state. Calling
// shutdown on a dying or dead Conn is a no-op.
func (c *Conn) shutdown(e error) {
c.stateMu.Lock()
if c.state == connAlive {
c.bgCancel()
c.closeErr = e
c.state = connDying
go c.teardown(rpccapnp.Message{})
}
c.stateMu.Unlock()
}
// abort cancels the background context, sets closeErr to e, and queues
// an abort message to be sent on the transport before the Conn goes
// into the dead state. After abort returns, the Conn will be in the
// dying or dead state. Calling abort on a dying or dead Conn is a
// no-op.
func (c *Conn) abort(e error) {
c.stateMu.Lock()
if c.state == connAlive {
c.bgCancel()
c.closeErr = e
c.state = connDying
go c.teardown(newAbortMessage(nil, e))
}
c.stateMu.Unlock()
}
// startWork adds a new worker if c is not dying or dead.
// Otherwise, it returns the close error.
// The caller is responsible for calling c.workers.Done().
// The caller must not be holding onto c.stateMu.
func (c *Conn) startWork() error {
var err error
c.stateMu.RLock()
if c.state == connAlive {
c.workers.Add(1)
} else {
err = c.closeErr
}
c.stateMu.RUnlock()
return err
}
// teardown moves the connection from the dying to the dead state.
func (c *Conn) teardown(abort rpccapnp.Message) {
c.workers.Wait()
c.mu.Lock()
for _, q := range c.questions {
if q != nil {
q.cancel(ErrConnClosed)
}
}
c.questions = nil
exps := c.exports
c.exports = nil
c.embargoes = nil
for _, a := range c.answers {
a.cancel()
}
c.answers = nil
c.imports = nil
c.mainFunc = nil
c.mu.Unlock()
if c.mainCloser != nil {
if err := c.mainCloser.Close(); err != nil {
c.errorf("closing main interface: %v", err)
}
c.mainCloser = nil
}
// Closing an export may try to lock the Conn, so run it outside
// critical section.
for id, e := range exps {
if e == nil {
continue
}
if err := e.client.Close(); err != nil {
c.errorf("export %v close: %v", id, err)
}
}
exps = nil
var werr error
if abort.IsValid() {
werr = c.transport.SendMessage(context.Background(), abort)
}
cerr := c.transport.Close()
c.stateMu.Lock()
if c.closeErr == ErrConnClosed {
if cerr != nil {
c.closeErr = cerr
} else if werr != nil {
c.closeErr = werr
}
}
c.state = connDead
close(c.death)
c.stateMu.Unlock()
}
// Bootstrap returns the receiver's main interface.
func (c *Conn) Bootstrap(ctx context.Context) capnp.Client {
// TODO(light): Create a client that returns immediately.
select {
case <-c.mu:
// Locked.
defer c.mu.Unlock()
if err := c.startWork(); err != nil {
return capnp.ErrorClient(err)
}
defer c.workers.Done()
case <-ctx.Done():
return capnp.ErrorClient(ctx.Err())
case <-c.bg.Done():
return capnp.ErrorClient(ErrConnClosed)
}
q := c.newQuestion(ctx, nil /* method */)
msg := newMessage(nil)
boot, _ := msg.NewBootstrap()
boot.SetQuestionId(uint32(q.id))
// The mutex must be held while sending so that call order is preserved.
// Worst case, this blocks until a message is sent on the transport.
// Common case, this just adds to the channel queue.
select {
case c.out <- msg:
q.start()
return capnp.NewPipeline(q).Client()
case <-ctx.Done():
c.popQuestion(q.id)
return capnp.ErrorClient(ctx.Err())
case <-c.bg.Done():
c.popQuestion(q.id)
return capnp.ErrorClient(ErrConnClosed)
}
}
// handleMessage is run from the receive goroutine to process a single
// message. m cannot be held onto past the return of handleMessage, and
// c.mu is not held at the start of handleMessage.
func (c *Conn) handleMessage(m rpccapnp.Message) {
switch m.Which() {
case rpccapnp.Message_Which_unimplemented:
// no-op for now to avoid feedback loop
case rpccapnp.Message_Which_abort:
a, err := copyAbort(m)
if err != nil {
c.errorf("decode abort: %v", err)
// Keep going, since we're trying to abort anyway.
}
c.infof("abort: %v", a)
c.shutdown(a)
case rpccapnp.Message_Which_return:
m = copyRPCMessage(m)
c.mu.Lock()
err := c.handleReturnMessage(m)
c.mu.Unlock()
if err != nil {
c.errorf("handle return: %v", err)
}
case rpccapnp.Message_Which_finish:
mfin, err := m.Finish()
if err != nil {
c.errorf("decode finish: %v", err)
return
}
id := answerID(mfin.QuestionId())
c.mu.Lock()
a := c.popAnswer(id)
if a == nil {
c.mu.Unlock()
c.errorf("finish called for unknown answer %d", id)
return
}
a.cancel()
if mfin.ReleaseResultCaps() {
for _, id := range a.resultCaps {
c.releaseExport(id, 1)
}
}
c.mu.Unlock()
case rpccapnp.Message_Which_bootstrap:
boot, err := m.Bootstrap()
if err != nil {
c.errorf("decode bootstrap: %v", err)
return
}
id := answerID(boot.QuestionId())
c.mu.Lock()
err = c.handleBootstrapMessage(id)
c.mu.Unlock()
if err != nil {
c.errorf("handle bootstrap: %v", err)
}
case rpccapnp.Message_Which_call:
m = copyRPCMessage(m)
c.mu.Lock()
err := c.handleCallMessage(m)
c.mu.Unlock()
if err != nil {
c.errorf("handle call: %v", err)
}
case rpccapnp.Message_Which_release:
rel, err := m.Release()
if err != nil {
c.errorf("decode release: %v", err)
return
}
id := exportID(rel.Id())
refs := int(rel.ReferenceCount())
c.mu.Lock()
c.releaseExport(id, refs)
c.mu.Unlock()
case rpccapnp.Message_Which_disembargo:
m = copyRPCMessage(m)
c.mu.Lock()
err := c.handleDisembargoMessage(m)
c.mu.Unlock()
if err != nil {
// Any failure in a disembargo is a protocol violation.
c.abort(err)
}
default:
c.infof("received unimplemented message, which = %v", m.Which())
um := newUnimplementedMessage(nil, m)
c.sendMessage(um)
}
}
func newUnimplementedMessage(buf []byte, m rpccapnp.Message) rpccapnp.Message {
n := newMessage(buf)
n.SetUnimplemented(m)
return n
}
func (c *Conn) fillParams(payload rpccapnp.Payload, cl *capnp.Call) error {
params, err := cl.PlaceParams(payload.Segment())
if err != nil {
return err
}
if err := payload.SetContent(params); err != nil {
return err
}
ctab, err := c.makeCapTable(payload.Segment())
if err != nil {
return err
}
if err := payload.SetCapTable(ctab); err != nil {
return err
}
return nil
}
func transformToPromisedAnswer(s *capnp.Segment, answer rpccapnp.PromisedAnswer, transform []capnp.PipelineOp) error {
opList, err := rpccapnp.NewPromisedAnswer_Op_List(s, int32(len(transform)))
if err != nil {
return err
}
for i, op := range transform {
opList.At(i).SetGetPointerField(uint16(op.Field))
}
err = answer.SetTransform(opList)
return err
}
// handleReturnMessage is to handle a received return message.
// The caller is holding onto c.mu.
func (c *Conn) handleReturnMessage(m rpccapnp.Message) error {
ret, err := m.Return()
if err != nil {
return err
}
id := questionID(ret.AnswerId())
q := c.popQuestion(id)
if q == nil {
return fmt.Errorf("received return for unknown question id=%d", id)
}
if ret.ReleaseParamCaps() {
for _, id := range q.paramCaps {
c.releaseExport(id, 1)
}
}
q.mu.RLock()
qstate := q.state
q.mu.RUnlock()
if qstate == questionCanceled {
// We already sent the finish message.
return nil
}
releaseResultCaps := true
switch ret.Which() {
case rpccapnp.Return_Which_results:
releaseResultCaps = false
results, err := ret.Results()
if err != nil {
return err
}
if err := c.populateMessageCapTable(results); err == errUnimplemented {
um := newUnimplementedMessage(nil, m)
c.sendMessage(um)
return errUnimplemented
} else if err != nil {
c.abort(err)
return err
}
content, err := results.ContentPtr()
if err != nil {
return err
}
q.fulfill(content)
case rpccapnp.Return_Which_exception:
exc, err := ret.Exception()
if err != nil {
return err
}
e := error(Exception{exc})
if q.method != nil {
e = &capnp.MethodError{
Method: q.method,
Err: e,
}
} else {
e = bootstrapError{e}
}
q.reject(e)
case rpccapnp.Return_Which_canceled:
err := &questionError{
id: id,
method: q.method,
err: fmt.Errorf("receiver reported canceled"),
}
c.errorf("%v", err)
q.reject(err)
return nil
default:
um := newUnimplementedMessage(nil, m)
c.sendMessage(um)
return errUnimplemented
}
fin := newFinishMessage(nil, id, releaseResultCaps)
c.sendMessage(fin)
return nil
}
func newFinishMessage(buf []byte, questionID questionID, release bool) rpccapnp.Message {
m := newMessage(buf)
f, _ := m.NewFinish()
f.SetQuestionId(uint32(questionID))
f.SetReleaseResultCaps(release)
return m
}
// populateMessageCapTable converts the descriptors in the payload into
// clients and sets it on the message the payload is a part of.
func (c *Conn) populateMessageCapTable(payload rpccapnp.Payload) error {
msg := payload.Segment().Message()
ctab, err := payload.CapTable()
if err != nil {
return err
}
for i, n := 0, ctab.Len(); i < n; i++ {
desc := ctab.At(i)
switch desc.Which() {
case rpccapnp.CapDescriptor_Which_none:
msg.AddCap(nil)
case rpccapnp.CapDescriptor_Which_senderHosted:
id := importID(desc.SenderHosted())
client := c.addImport(id)
msg.AddCap(client)
case rpccapnp.CapDescriptor_Which_senderPromise:
// We do the same thing as senderHosted, above. @kentonv suggested this on
// issue #2; this let's messages be delivered properly, although it's a bit
// of a hack, and as Kenton describes, it has some disadvantages:
//
// > * Apps sometimes want to wait for promise resolution, and to find out if
// > it resolved to an exception. You won't be able to provide that API. But,
// > usually, it isn't needed.
// > * If the promise resolves to a capability hosted on the receiver,
// > messages sent to it will uselessly round-trip over the network
// > rather than being delivered locally.
id := importID(desc.SenderPromise())
client := c.addImport(id)
msg.AddCap(client)
case rpccapnp.CapDescriptor_Which_receiverHosted:
id := exportID(desc.ReceiverHosted())
e := c.findExport(id)
if e == nil {
return fmt.Errorf("rpc: capability table references unknown export ID %d", id)
}
msg.AddCap(e.rc.Ref())
case rpccapnp.CapDescriptor_Which_receiverAnswer:
recvAns, err := desc.ReceiverAnswer()
if err != nil {
return err
}
id := answerID(recvAns.QuestionId())
a := c.answers[id]
if a == nil {
return fmt.Errorf("rpc: capability table references unknown answer ID %d", id)
}
recvTransform, err := recvAns.Transform()
if err != nil {
return err
}
transform := promisedAnswerOpsToTransform(recvTransform)
msg.AddCap(a.pipelineClient(transform))
default:
c.errorf("unknown capability type %v", desc.Which())
return errUnimplemented
}
}
return nil
}
// makeCapTable converts the clients in the segment's message into capability descriptors.
func (c *Conn) makeCapTable(s *capnp.Segment) (rpccapnp.CapDescriptor_List, error) {
msgtab := s.Message().CapTable
t, err := rpccapnp.NewCapDescriptor_List(s, int32(len(msgtab)))
if err != nil {
return rpccapnp.CapDescriptor_List{}, nil
}
for i, client := range msgtab {
desc := t.At(i)
if client == nil {
desc.SetNone()
continue
}
c.descriptorForClient(desc, client)
}
return t, nil
}
// handleBootstrapMessage handles a received bootstrap message.
// The caller holds onto c.mu.
func (c *Conn) handleBootstrapMessage(id answerID) error {
ctx, cancel := c.newContext()
defer cancel()
a := c.insertAnswer(id, cancel)
if a == nil {
// Question ID reused, error out.
retmsg := newReturnMessage(nil, id)
r, _ := retmsg.Return()
setReturnException(r, errQuestionReused)
return c.sendMessage(retmsg)
}
if c.mainFunc == nil {
return a.reject(errNoMainInterface)
}
main, err := c.mainFunc(ctx)
if err != nil {
return a.reject(errNoMainInterface)
}
m := &capnp.Message{
Arena: capnp.SingleSegment(make([]byte, 0)),
CapTable: []capnp.Client{main},
}
s, _ := m.Segment(0)
in := capnp.NewInterface(s, 0)
return a.fulfill(in.ToPtr())
}
// handleCallMessage handles a received call message. It mutates the
// capability table of its parameter. The caller holds onto c.mu.
func (c *Conn) handleCallMessage(m rpccapnp.Message) error {
mcall, err := m.Call()
if err != nil {
return err
}
mt, err := mcall.Target()
if err != nil {
return err
}
if mt.Which() != rpccapnp.MessageTarget_Which_importedCap && mt.Which() != rpccapnp.MessageTarget_Which_promisedAnswer {
um := newUnimplementedMessage(nil, m)
return c.sendMessage(um)
}
mparams, err := mcall.Params()
if err != nil {
return err
}
if err := c.populateMessageCapTable(mparams); err == errUnimplemented {
um := newUnimplementedMessage(nil, m)
return c.sendMessage(um)
} else if err != nil {
c.abort(err)
return err
}
ctx, cancel := c.newContext()
id := answerID(mcall.QuestionId())
a := c.insertAnswer(id, cancel)
if a == nil {
// Question ID reused, error out.
c.abort(errQuestionReused)
return errQuestionReused
}
meth := capnp.Method{
InterfaceID: mcall.InterfaceId(),
MethodID: mcall.MethodId(),
}
paramContent, err := mparams.ContentPtr()
if err != nil {
return err
}
cl := &capnp.Call{
Ctx: ctx,
Method: meth,
Params: paramContent.Struct(),
}
if err := c.routeCallMessage(a, mt, cl); err != nil {
return a.reject(err)
}
return nil
}
func (c *Conn) routeCallMessage(result *answer, mt rpccapnp.MessageTarget, cl *capnp.Call) error {
switch mt.Which() {
case rpccapnp.MessageTarget_Which_importedCap:
id := exportID(mt.ImportedCap())
e := c.findExport(id)
if e == nil {
return errBadTarget
}
answer := c.lockedCall(e.client, cl)
go joinAnswer(result, answer)
case rpccapnp.MessageTarget_Which_promisedAnswer:
mpromise, err := mt.PromisedAnswer()
if err != nil {
return err
}
id := answerID(mpromise.QuestionId())
if id == result.id {
// Grandfather paradox.
return errBadTarget
}
pa := c.answers[id]
if pa == nil {
return errBadTarget
}
mtrans, err := mpromise.Transform()
if err != nil {
return err
}
transform := promisedAnswerOpsToTransform(mtrans)
pa.mu.Lock()
if pa.done {
obj, err := pa.obj, pa.err
pa.mu.Unlock()
client := clientFromResolution(transform, obj, err)
answer := c.lockedCall(client, cl)
go joinAnswer(result, answer)
} else {
err = pa.queueCallLocked(cl, pcall{transform: transform, qcall: qcall{a: result}})
pa.mu.Unlock()
}
return err
default:
panic("unreachable")
}
return nil
}
func (c *Conn) handleDisembargoMessage(msg rpccapnp.Message) error {
d, err := msg.Disembargo()
if err != nil {
return err
}
dtarget, err := d.Target()
if err != nil {
return err
}
switch d.Context().Which() {
case rpccapnp.Disembargo_context_Which_senderLoopback:
id := embargoID(d.Context().SenderLoopback())
if dtarget.Which() != rpccapnp.MessageTarget_Which_promisedAnswer {
return errDisembargoNonImport
}
dpa, err := dtarget.PromisedAnswer()
if err != nil {
return err
}
aid := answerID(dpa.QuestionId())
a := c.answers[aid]
if a == nil {
return errDisembargoMissingAnswer
}
dtrans, err := dpa.Transform()
if err != nil {
return err
}
transform := promisedAnswerOpsToTransform(dtrans)
queued, err := a.queueDisembargo(transform, id, dtarget)
if err != nil {
return err
}
if !queued {
// There's nothing to embargo; everything's been delivered.
resp := newDisembargoMessage(nil, rpccapnp.Disembargo_context_Which_receiverLoopback, id)
rd, _ := resp.Disembargo()
if err := rd.SetTarget(dtarget); err != nil {
return err
}
c.sendMessage(resp)
}
case rpccapnp.Disembargo_context_Which_receiverLoopback:
id := embargoID(d.Context().ReceiverLoopback())
c.disembargo(id)
default:
um := newUnimplementedMessage(nil, msg)
c.sendMessage(um)
}
return nil
}
// newDisembargoMessage creates a disembargo message. Its target will be left blank.
func newDisembargoMessage(buf []byte, which rpccapnp.Disembargo_context_Which, id embargoID) rpccapnp.Message {
msg := newMessage(buf)
d, _ := msg.NewDisembargo()
switch which {
case rpccapnp.Disembargo_context_Which_senderLoopback:
d.Context().SetSenderLoopback(uint32(id))
case rpccapnp.Disembargo_context_Which_receiverLoopback:
d.Context().SetReceiverLoopback(uint32(id))
default:
panic("unreachable")
}
return msg
}
// newContext creates a new context for a local call.
func (c *Conn) newContext() (context.Context, context.CancelFunc) {
return context.WithCancel(c.bg)
}
func promisedAnswerOpsToTransform(list rpccapnp.PromisedAnswer_Op_List) []capnp.PipelineOp {
n := list.Len()
transform := make([]capnp.PipelineOp, 0, n)
for i := 0; i < n; i++ {
op := list.At(i)
switch op.Which() {
case rpccapnp.PromisedAnswer_Op_Which_getPointerField:
transform = append(transform, capnp.PipelineOp{
Field: op.GetPointerField(),
})
case rpccapnp.PromisedAnswer_Op_Which_noop:
// no-op
}
}
return transform
}
func newAbortMessage(buf []byte, err error) rpccapnp.Message {
n := newMessage(buf)
e, _ := n.NewAbort()
toException(e, err)
return n
}
func newReturnMessage(buf []byte, id answerID) rpccapnp.Message {
retmsg := newMessage(buf)
ret, _ := retmsg.NewReturn()
ret.SetAnswerId(uint32(id))
ret.SetReleaseParamCaps(false)
return retmsg
}
func setReturnException(ret rpccapnp.Return, err error) rpccapnp.Exception {
e, _ := rpccapnp.NewException(ret.Segment())
toException(e, err)
ret.SetException(e)
return e
}
// clientFromResolution retrieves a client from a resolved question or
// answer by applying a transform.
func clientFromResolution(transform []capnp.PipelineOp, obj capnp.Ptr, err error) capnp.Client {
if err != nil {
return capnp.ErrorClient(err)
}
out, err := capnp.TransformPtr(obj, transform)
if err != nil {
return capnp.ErrorClient(err)
}
c := out.Interface().Client()
if c == nil {
return capnp.ErrorClient(capnp.ErrNullClient)
}
return c
}
func newMessage(buf []byte) rpccapnp.Message {
_, s, err := capnp.NewMessage(capnp.SingleSegment(buf))
if err != nil {
panic(err)
}
m, err := rpccapnp.NewRootMessage(s)
if err != nil {
panic(err)
}
return m
}
// chanMutex is a mutex backed by a channel so that it can be used in a select.
// A receive is a lock and a send is an unlock.
type chanMutex chan struct{}
type connState int
const (
connAlive connState = iota
connDying
connDead
)
func newChanMutex() chanMutex {
mu := make(chanMutex, 1)
mu <- struct{}{}
return mu
}
func (mu chanMutex) Lock() {
<-mu
}
func (mu chanMutex) TryLock(ctx context.Context) error {
select {
case <-mu:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (mu chanMutex) Unlock() {
mu <- struct{}{}
}

View File

@ -1,255 +0,0 @@
package rpc
import (
"errors"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/rpc/internal/refcount"
)
// Table IDs
type (
questionID uint32
answerID uint32
exportID uint32
importID uint32
embargoID uint32
)
// impent is an entry in the import table.
type impent struct {
rc *refcount.RefCount
refs int
}
// addImport increases the counter of the times the import ID was sent to this vat.
func (c *Conn) addImport(id importID) capnp.Client {
if c.imports == nil {
c.imports = make(map[importID]*impent)
} else if ent := c.imports[id]; ent != nil {
ent.refs++
return ent.rc.Ref()
}
client := &importClient{
id: id,
conn: c,
}
rc, ref := refcount.New(client)
c.imports[id] = &impent{rc: rc, refs: 1}
return ref
}
// popImport removes the import ID and returns the number of times the import ID was sent to this vat.
func (c *Conn) popImport(id importID) (refs int) {
if c.imports == nil {
return 0
}
ent := c.imports[id]
if ent == nil {
return 0
}
refs = ent.refs
delete(c.imports, id)
return refs
}
// An importClient implements capnp.Client for a remote capability.
type importClient struct {
id importID
conn *Conn
closed bool // protected by conn.mu
}
func (ic *importClient) Call(cl *capnp.Call) capnp.Answer {
select {
case <-ic.conn.mu:
if err := ic.conn.startWork(); err != nil {
return capnp.ErrorAnswer(err)
}
case <-cl.Ctx.Done():
return capnp.ErrorAnswer(cl.Ctx.Err())
}
ans := ic.lockedCall(cl)
ic.conn.workers.Done()
ic.conn.mu.Unlock()
return ans
}
// lockedCall is equivalent to Call but assumes that the caller is
// already holding onto ic.conn.mu.
func (ic *importClient) lockedCall(cl *capnp.Call) capnp.Answer {
if ic.closed {
return capnp.ErrorAnswer(errImportClosed)
}
q := ic.conn.newQuestion(cl.Ctx, &cl.Method)
msg := newMessage(nil)
msgCall, _ := msg.NewCall()
msgCall.SetQuestionId(uint32(q.id))
msgCall.SetInterfaceId(cl.Method.InterfaceID)
msgCall.SetMethodId(cl.Method.MethodID)
target, _ := msgCall.NewTarget()
target.SetImportedCap(uint32(ic.id))
payload, _ := msgCall.NewParams()
if err := ic.conn.fillParams(payload, cl); err != nil {
ic.conn.popQuestion(q.id)
return capnp.ErrorAnswer(err)
}
select {
case ic.conn.out <- msg:
case <-cl.Ctx.Done():
ic.conn.popQuestion(q.id)
return capnp.ErrorAnswer(cl.Ctx.Err())
case <-ic.conn.bg.Done():
ic.conn.popQuestion(q.id)
return capnp.ErrorAnswer(ErrConnClosed)
}
q.start()
return q
}
func (ic *importClient) Close() error {
ic.conn.mu.Lock()
if err := ic.conn.startWork(); err != nil {
ic.conn.mu.Unlock()
return err
}
closed := ic.closed
var i int
if !closed {
i = ic.conn.popImport(ic.id)
ic.closed = true
}
ic.conn.workers.Done()
ic.conn.mu.Unlock()
if closed {
return errImportClosed
}
if i == 0 {
return nil
}
msg := newMessage(nil)
mr, err := msg.NewRelease()
if err != nil {
return err
}
mr.SetId(uint32(ic.id))
mr.SetReferenceCount(uint32(i))
select {
case ic.conn.out <- msg:
return nil
case <-ic.conn.bg.Done():
return ErrConnClosed
}
}
type export struct {
id exportID
rc *refcount.RefCount
client capnp.Client
wireRefs int
}
func (c *Conn) findExport(id exportID) *export {
if int(id) >= len(c.exports) {
return nil
}
return c.exports[id]
}
// addExport ensures that the client is present in the table, returning its ID.
// If the client is already in the table, the previous ID is returned.
func (c *Conn) addExport(client capnp.Client) exportID {
for i, e := range c.exports {
if e != nil && isSameClient(e.rc.Client, client) {
e.wireRefs++
return exportID(i)
}
}
id := exportID(c.exportID.next())
rc, client := refcount.New(client)
export := &export{
id: id,
rc: rc,
client: client,
wireRefs: 1,
}
if int(id) == len(c.exports) {
c.exports = append(c.exports, export)
} else {
c.exports[id] = export
}
return id
}
func (c *Conn) releaseExport(id exportID, refs int) {
e := c.findExport(id)
if e == nil {
return
}
e.wireRefs -= refs
if e.wireRefs > 0 {
return
}
if e.wireRefs < 0 {
c.errorf("warning: export %v has negative refcount (%d)", id, e.wireRefs)
}
if err := e.client.Close(); err != nil {
c.errorf("export %v close: %v", id, err)
}
c.exports[id] = nil
c.exportID.remove(uint32(id))
}
type embargo <-chan struct{}
func (c *Conn) newEmbargo() (embargoID, embargo) {
id := embargoID(c.embargoID.next())
e := make(chan struct{})
if int(id) == len(c.embargoes) {
c.embargoes = append(c.embargoes, e)
} else {
c.embargoes[id] = e
}
return id, e
}
func (c *Conn) disembargo(id embargoID) {
if int(id) >= len(c.embargoes) {
return
}
e := c.embargoes[id]
if e == nil {
return
}
close(e)
c.embargoes[id] = nil
c.embargoID.remove(uint32(id))
}
// idgen returns a sequence of monotonically increasing IDs with
// support for replacement. The zero value is a generator that
// starts at zero.
type idgen struct {
i uint32
free []uint32
}
func (gen *idgen) next() uint32 {
if n := len(gen.free); n > 0 {
i := gen.free[n-1]
gen.free = gen.free[:n-1]
return i
}
i := gen.i
gen.i++
return i
}
func (gen *idgen) remove(i uint32) {
gen.free = append(gen.free, i)
}
var errImportClosed = errors.New("rpc: call on closed import")

View File

@ -1,175 +0,0 @@
package rpc
import (
"bytes"
"io"
"time"
"golang.org/x/net/context"
"zombiezen.com/go/capnproto2"
rpccapnp "zombiezen.com/go/capnproto2/std/capnp/rpc"
)
// Transport is the interface that abstracts sending and receiving
// individual messages of the Cap'n Proto RPC protocol.
type Transport interface {
// SendMessage sends msg.
SendMessage(ctx context.Context, msg rpccapnp.Message) error
// RecvMessage waits to receive a message and returns it.
// Implementations may re-use buffers between calls, so the message is
// only valid until the next call to RecvMessage.
RecvMessage(ctx context.Context) (rpccapnp.Message, error)
// Close releases any resources associated with the transport.
Close() error
}
type streamTransport struct {
rwc io.ReadWriteCloser
deadline writeDeadlineSetter
enc *capnp.Encoder
dec *capnp.Decoder
wbuf bytes.Buffer
}
// StreamTransport creates a transport that sends and receives messages
// by serializing and deserializing unpacked Cap'n Proto messages.
// Closing the transport will close the underlying ReadWriteCloser.
func StreamTransport(rwc io.ReadWriteCloser) Transport {
d, _ := rwc.(writeDeadlineSetter)
s := &streamTransport{
rwc: rwc,
deadline: d,
dec: capnp.NewDecoder(rwc),
}
s.wbuf.Grow(4096)
s.enc = capnp.NewEncoder(&s.wbuf)
return s
}
func (s *streamTransport) SendMessage(ctx context.Context, msg rpccapnp.Message) error {
s.wbuf.Reset()
if err := s.enc.Encode(msg.Segment().Message()); err != nil {
return err
}
if s.deadline != nil {
// TODO(light): log errors
if d, ok := ctx.Deadline(); ok {
s.deadline.SetWriteDeadline(d)
} else {
s.deadline.SetWriteDeadline(time.Time{})
}
}
_, err := s.rwc.Write(s.wbuf.Bytes())
return err
}
func (s *streamTransport) RecvMessage(ctx context.Context) (rpccapnp.Message, error) {
var (
msg *capnp.Message
err error
)
read := make(chan struct{})
go func() {
msg, err = s.dec.Decode()
close(read)
}()
select {
case <-read:
case <-ctx.Done():
return rpccapnp.Message{}, ctx.Err()
}
if err != nil {
return rpccapnp.Message{}, err
}
return rpccapnp.ReadRootMessage(msg)
}
func (s *streamTransport) Close() error {
return s.rwc.Close()
}
type writeDeadlineSetter interface {
SetWriteDeadline(t time.Time) error
}
// dispatchSend runs in its own goroutine and sends messages on a transport.
func (c *Conn) dispatchSend() {
defer c.workers.Done()
for {
select {
case msg := <-c.out:
err := c.transport.SendMessage(c.bg, msg)
if err != nil {
c.errorf("writing %v: %v", msg.Which(), err)
}
case <-c.bg.Done():
return
}
}
}
// sendMessage enqueues a message to be sent or returns an error if the
// connection is shut down before the message is queued. It is safe to
// call from multiple goroutines and does not require holding c.mu.
func (c *Conn) sendMessage(msg rpccapnp.Message) error {
select {
case c.out <- msg:
return nil
case <-c.bg.Done():
return ErrConnClosed
}
}
// dispatchRecv runs in its own goroutine and receives messages from a transport.
func (c *Conn) dispatchRecv() {
defer c.workers.Done()
for {
msg, err := c.transport.RecvMessage(c.bg)
if err == nil {
c.handleMessage(msg)
} else if isTemporaryError(err) {
c.errorf("read temporary error: %v", err)
} else {
c.shutdown(err)
return
}
}
}
// copyMessage clones a Cap'n Proto buffer.
func copyMessage(msg *capnp.Message) *capnp.Message {
n := msg.NumSegments()
segments := make([][]byte, n)
for i := range segments {
s, err := msg.Segment(capnp.SegmentID(i))
if err != nil {
panic(err)
}
segments[i] = make([]byte, len(s.Data()))
copy(segments[i], s.Data())
}
return &capnp.Message{Arena: capnp.MultiSegment(segments)}
}
// copyRPCMessage clones an RPC packet.
func copyRPCMessage(m rpccapnp.Message) rpccapnp.Message {
mm := copyMessage(m.Segment().Message())
rpcMsg, err := rpccapnp.ReadRootMessage(mm)
if err != nil {
panic(err)
}
return rpcMsg
}
// isTemporaryError reports whether e has a Temporary() method that
// returns true.
func isTemporaryError(e error) bool {
type temp interface {
Temporary() bool
}
t, ok := e.(temp)
return ok && t.Temporary()
}

View File

@ -1,19 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["schemas.go"],
importpath = "zombiezen.com/go/capnproto2/schemas",
visibility = ["//visibility:public"],
deps = ["//internal/packed:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["schemas_test.go"],
embed = [":go_default_library"],
deps = [
"//:go_default_library",
"//internal/schema:go_default_library",
],
)

View File

@ -1,185 +0,0 @@
// Package schemas provides a container for Cap'n Proto reflection data.
// The code generated by capnpc-go will register its schema in the
// default registry (unless disabled at generation time).
//
// Most programs will use the default registry. However, a program
// could dynamically build up a registry, perhaps by invoking the capnp
// tool or querying a service.
package schemas
import (
"bufio"
"bytes"
"compress/zlib"
"errors"
"fmt"
"io"
"io/ioutil"
"strings"
"sync"
"zombiezen.com/go/capnproto2/internal/packed"
)
// A Schema is a collection of schema nodes parsed by the capnp tool.
type Schema struct {
// Either String or Bytes must be populated with a CodeGeneratorRequest
// message in the standard Cap'n Proto framing format.
String string
Bytes []byte
// If true, the input is assumed to be zlib-compressed and packed.
Compressed bool
// Node IDs that are contained in this schema.
Nodes []uint64
}
// A Registry is a mapping of IDs to schema blobs. It is safe to read
// from multiple goroutines. The zero value is an empty registry.
type Registry struct {
m map[uint64]*record
}
// Register indexes a schema in the registry. It is an error to
// register schemas with overlapping IDs.
func (reg *Registry) Register(s *Schema) error {
if len(s.String) > 0 && len(s.Bytes) > 0 {
return errors.New("schemas: schema should have only one of string or bytes")
}
r := &record{
s: s.String,
data: s.Bytes,
compressed: s.Compressed,
}
if reg.m == nil {
reg.m = make(map[uint64]*record)
}
for _, id := range s.Nodes {
if _, dup := reg.m[id]; dup {
return &dupeError{id: id}
}
reg.m[id] = r
}
return nil
}
// Find returns the CodeGeneratorRequest message for the given ID,
// suitable for capnp.Unmarshal. If the ID is not found, Find returns
// an error that can be identified with IsNotFound. The returned byte
// slice should not be modified.
func (reg *Registry) Find(id uint64) ([]byte, error) {
r := reg.m[id]
if r == nil {
return nil, &notFoundError{id: id}
}
b, err := r.read()
if err != nil {
return nil, &decompressError{id, err}
}
return b, nil
}
type record struct {
// All the fields are protected by once.
once sync.Once
s string // input
compressed bool
data []byte // input and result
err error // result
}
func (r *record) read() ([]byte, error) {
r.once.Do(func() {
if !r.compressed {
if r.s != "" {
r.data = []byte(r.s)
r.s = ""
}
return
}
var in io.Reader
if r.s != "" {
in = strings.NewReader(r.s)
r.s = ""
} else {
in = bytes.NewReader(r.data)
}
z, err := zlib.NewReader(in)
if err != nil {
r.data, r.err = nil, err
return
}
p := packed.NewReader(bufio.NewReader(z))
r.data, r.err = ioutil.ReadAll(p)
if r.err != nil {
r.data = nil
return
}
})
return r.data, r.err
}
// DefaultRegistry is the process-wide registry used by Register and Find.
var DefaultRegistry Registry
// Register is called by generated code to associate a blob of zlib-
// compressed, packed Cap'n Proto data for a CodeGeneratorRequest with
// the IDs it contains. It should only be called during init().
func Register(data string, ids ...uint64) {
err := DefaultRegistry.Register(&Schema{
String: data,
Nodes: ids,
Compressed: true,
})
if err != nil {
panic(err)
}
}
// Find returns the CodeGeneratorRequest message for the given ID,
// suitable for capnp.Unmarshal, or nil if the ID was not found.
// It is safe to call Find from multiple goroutines, so the returned
// byte slice should not be modified. However, it is not safe to
// call Find concurrently with Register.
func Find(id uint64) []byte {
b, err := DefaultRegistry.Find(id)
if IsNotFound(err) {
return nil
}
if err != nil {
panic(err)
}
return b
}
// IsNotFound reports whether e indicates a failure to find a schema.
func IsNotFound(e error) bool {
_, ok := e.(*notFoundError)
return ok
}
type dupeError struct {
id uint64
}
func (e *dupeError) Error() string {
return fmt.Sprintf("schemas: registered @%#x twice", e.id)
}
type notFoundError struct {
id uint64
}
func (e *notFoundError) Error() string {
return fmt.Sprintf("schemas: could not find @%#x", e.id)
}
type decompressError struct {
id uint64
err error
}
func (e *decompressError) Error() string {
return fmt.Sprintf("schemas: decompressing schema for @%#x: %v", e.id, e.err)
}

View File

@ -1,23 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["server.go"],
importpath = "zombiezen.com/go/capnproto2/server",
visibility = ["//visibility:public"],
deps = [
"//:go_default_library",
"//internal/fulfiller:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["server_test.go"],
embed = [":go_default_library"],
deps = [
"//internal/aircraftlib:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)

View File

@ -1,231 +0,0 @@
// Package server provides runtime support for implementing Cap'n Proto
// interfaces locally.
package server // import "zombiezen.com/go/capnproto2/server"
import (
"errors"
"sort"
"sync"
"golang.org/x/net/context"
"zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/internal/fulfiller"
)
// A Method describes a single method on a server object.
type Method struct {
capnp.Method
Impl Func
ResultsSize capnp.ObjectSize
}
// A Func is a function that implements a single method.
type Func func(ctx context.Context, options capnp.CallOptions, params, results capnp.Struct) error
// Closer is the interface that wraps the Close method.
type Closer interface {
Close() error
}
// A server is a locally implemented interface.
type server struct {
methods sortedMethods
closer Closer
queue chan *call
stop chan struct{}
done chan struct{}
}
// New returns a client that makes calls to a set of methods.
// If closer is nil then the client's Close is a no-op. The server
// guarantees message delivery order by blocking each call on the
// return or acknowledgment of the previous call. See the Ack function
// for more details.
func New(methods []Method, closer Closer) capnp.Client {
s := &server{
methods: make(sortedMethods, len(methods)),
closer: closer,
queue: make(chan *call),
stop: make(chan struct{}),
done: make(chan struct{}),
}
copy(s.methods, methods)
sort.Sort(s.methods)
go s.dispatch()
return s
}
// dispatch runs in its own goroutine.
func (s *server) dispatch() {
defer close(s.done)
for {
select {
case cl := <-s.queue:
err := s.startCall(cl)
if err != nil {
cl.ans.Reject(err)
}
case <-s.stop:
return
}
}
}
// startCall runs in the dispatch goroutine to start a call.
func (s *server) startCall(cl *call) error {
_, out, err := capnp.NewMessage(capnp.SingleSegment(nil))
if err != nil {
return err
}
results, err := capnp.NewRootStruct(out, cl.method.ResultsSize)
if err != nil {
return err
}
acksig := newAckSignal()
opts := cl.Options.With([]capnp.CallOption{capnp.SetOptionValue(ackSignalKey, acksig)})
go func() {
err := cl.method.Impl(cl.Ctx, opts, cl.Params, results)
if err == nil {
cl.ans.Fulfill(results)
} else {
cl.ans.Reject(err)
}
}()
select {
case <-acksig.c:
case <-cl.ans.Done():
// Implementation functions may not call Ack, which is fine for
// smaller functions.
case <-cl.Ctx.Done():
// Ideally, this would reject the answer immediately, but then you
// would race with the implementation function.
}
return nil
}
func (s *server) Call(cl *capnp.Call) capnp.Answer {
sm := s.methods.find(&cl.Method)
if sm == nil {
return capnp.ErrorAnswer(&capnp.MethodError{
Method: &cl.Method,
Err: capnp.ErrUnimplemented,
})
}
cl, err := cl.Copy(nil)
if err != nil {
return capnp.ErrorAnswer(err)
}
scall := newCall(cl, sm)
select {
case s.queue <- scall:
return &scall.ans
case <-s.stop:
return capnp.ErrorAnswer(errClosed)
case <-cl.Ctx.Done():
return capnp.ErrorAnswer(cl.Ctx.Err())
}
}
func (s *server) Close() error {
close(s.stop)
<-s.done
if s.closer == nil {
return nil
}
return s.closer.Close()
}
// Ack acknowledges delivery of a server call, allowing other methods
// to be called on the server. It is intended to be used inside the
// implementation of a server function. Calling Ack on options that
// aren't from a server method implementation is a no-op.
//
// Example:
//
// func (my *myServer) MyMethod(call schema.MyServer_myMethod) error {
// server.Ack(call.Options)
// // ... do long-running operation ...
// return nil
// }
//
// Ack need not be the first call in a function nor is it required.
// Since the function's return is also an acknowledgment of delivery,
// short functions can return without calling Ack. However, since
// clients will not return an Answer until the delivery is acknowledged,
// it is advisable to ack early.
func Ack(opts capnp.CallOptions) {
if ack, _ := opts.Value(ackSignalKey).(*ackSignal); ack != nil {
ack.signal()
}
}
type call struct {
*capnp.Call
ans fulfiller.Fulfiller
method *Method
}
func newCall(cl *capnp.Call, sm *Method) *call {
return &call{Call: cl, method: sm}
}
type sortedMethods []Method
// find returns the method with the given ID or nil.
func (sm sortedMethods) find(id *capnp.Method) *Method {
i := sort.Search(len(sm), func(i int) bool {
m := &sm[i]
if m.InterfaceID != id.InterfaceID {
return m.InterfaceID >= id.InterfaceID
}
return m.MethodID >= id.MethodID
})
if i == len(sm) {
return nil
}
m := &sm[i]
if m.InterfaceID != id.InterfaceID || m.MethodID != id.MethodID {
return nil
}
return m
}
func (sm sortedMethods) Len() int {
return len(sm)
}
func (sm sortedMethods) Less(i, j int) bool {
if id1, id2 := sm[i].InterfaceID, sm[j].InterfaceID; id1 != id2 {
return id1 < id2
}
return sm[i].MethodID < sm[j].MethodID
}
func (sm sortedMethods) Swap(i, j int) {
sm[i], sm[j] = sm[j], sm[i]
}
type ackSignal struct {
c chan struct{}
once sync.Once
}
func newAckSignal() *ackSignal {
return &ackSignal{c: make(chan struct{})}
}
func (ack *ackSignal) signal() {
ack.once.Do(func() {
close(ack.c)
})
}
// callOptionKey is the unexported key type for predefined options.
type callOptionKey int
// Predefined call options
const (
ackSignalKey callOptionKey = iota + 1
)
var errClosed = errors.New("capnp: server closed")

View File

@ -1,13 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["rpc.capnp.go"],
importpath = "zombiezen.com/go/capnproto2/std/capnp/rpc",
visibility = ["//visibility:public"],
deps = [
"//:go_default_library",
"//encoding/text:go_default_library",
"//schemas:go_default_library",
],
)

File diff suppressed because it is too large Load Diff

View File

@ -1,125 +0,0 @@
// +build !nocapnpstrings
package capnp
import (
"fmt"
)
// String returns the address in hex format.
func (addr Address) String() string {
return fmt.Sprintf("%#08x", uint64(addr))
}
// GoString returns the address in hex format.
func (addr Address) GoString() string {
return fmt.Sprintf("capnp.Address(%#08x)", uint64(addr))
}
// String returns the size in the format "X bytes".
func (sz Size) String() string {
if sz == 1 {
return "1 byte"
}
return fmt.Sprintf("%d bytes", sz)
}
// GoString returns the size as a Go expression.
func (sz Size) GoString() string {
return fmt.Sprintf("capnp.Size(%d)", sz)
}
// String returns the offset in the format "+X bytes".
func (off DataOffset) String() string {
if off == 1 {
return "+1 byte"
}
return fmt.Sprintf("+%d bytes", off)
}
// GoString returns the offset as a Go expression.
func (off DataOffset) GoString() string {
return fmt.Sprintf("capnp.DataOffset(%d)", off)
}
// String returns a short, human readable representation of the object
// size.
func (sz ObjectSize) String() string {
return fmt.Sprintf("{datasz=%d ptrs=%d}", sz.DataSize, sz.PointerCount)
}
// GoString formats the ObjectSize as a keyed struct literal.
func (sz ObjectSize) GoString() string {
return fmt.Sprintf("capnp.ObjectSize{DataSize: %d, PointerCount: %d}", sz.DataSize, sz.PointerCount)
}
// String returns the offset in the format "bit X".
func (bit BitOffset) String() string {
return fmt.Sprintf("bit %d", bit)
}
// GoString returns the offset as a Go expression.
func (bit BitOffset) GoString() string {
return fmt.Sprintf("capnp.BitOffset(%d)", bit)
}
// String returns the ID in the format "capability X".
func (id CapabilityID) String() string {
return fmt.Sprintf("capability %d", id)
}
// GoString returns the ID as a Go expression.
func (id CapabilityID) GoString() string {
return fmt.Sprintf("capnp.CapabilityID(%d)", id)
}
// GoString formats the pointer as a call to one of the rawPointer
// construction functions.
func (p rawPointer) GoString() string {
if p == 0 {
return "rawPointer(0)"
}
switch p.pointerType() {
case structPointer:
return fmt.Sprintf("rawStructPointer(%d, %#v)", p.offset(), p.structSize())
case listPointer:
var lt string
switch p.listType() {
case voidList:
lt = "voidList"
case bit1List:
lt = "bit1List"
case byte1List:
lt = "byte1List"
case byte2List:
lt = "byte2List"
case byte4List:
lt = "byte4List"
case byte8List:
lt = "byte8List"
case pointerList:
lt = "pointerList"
case compositeList:
lt = "compositeList"
}
return fmt.Sprintf("rawListPointer(%d, %s, %d)", p.offset(), lt, p.numListElements())
case farPointer:
return fmt.Sprintf("rawFarPointer(%d, %v)", p.farSegment(), p.farAddress())
case doubleFarPointer:
return fmt.Sprintf("rawDoubleFarPointer(%d, %v)", p.farSegment(), p.farAddress())
default:
// other pointer
if p.otherPointerType() != 0 {
return fmt.Sprintf("rawPointer(%#016x)", uint64(p))
}
return fmt.Sprintf("rawInterfacePointer(%d)", p.capabilityIndex())
}
}
func (ssa *singleSegmentArena) String() string {
return fmt.Sprintf("single-segment arena [len=%d cap=%d]", len(*ssa), cap(*ssa))
}
func (msa *multiSegmentArena) String() string {
return fmt.Sprintf("multi-segment arena [%d segments]", len(*msa))
}

View File

@ -1,368 +0,0 @@
package capnp
// Struct is a pointer to a struct.
type Struct struct {
seg *Segment
off Address
size ObjectSize
depthLimit uint
flags structFlags
}
// NewStruct creates a new struct, preferring placement in s.
func NewStruct(s *Segment, sz ObjectSize) (Struct, error) {
if !sz.isValid() {
return Struct{}, errObjectSize
}
sz.DataSize = sz.DataSize.padToWord()
seg, addr, err := alloc(s, sz.totalSize())
if err != nil {
return Struct{}, err
}
return Struct{
seg: seg,
off: addr,
size: sz,
depthLimit: maxDepth,
}, nil
}
// NewRootStruct creates a new struct, preferring placement in s, then sets the
// message's root to the new struct.
func NewRootStruct(s *Segment, sz ObjectSize) (Struct, error) {
st, err := NewStruct(s, sz)
if err != nil {
return st, err
}
if err := s.msg.SetRootPtr(st.ToPtr()); err != nil {
return st, err
}
return st, nil
}
// ToStruct converts p to a Struct.
//
// Deprecated: Use Ptr.Struct.
func ToStruct(p Pointer) Struct {
if !IsValid(p) {
return Struct{}
}
s, ok := p.underlying().(Struct)
if !ok {
return Struct{}
}
return s
}
// ToStructDefault attempts to convert p into a struct, reading the
// default value from def if p is not a struct.
//
// Deprecated: Use Ptr.StructDefault.
func ToStructDefault(p Pointer, def []byte) (Struct, error) {
return toPtr(p).StructDefault(def)
}
// ToPtr converts the struct to a generic pointer.
func (p Struct) ToPtr() Ptr {
return Ptr{
seg: p.seg,
off: p.off,
size: p.size,
depthLimit: p.depthLimit,
flags: structPtrFlag(p.flags),
}
}
// Segment returns the segment this pointer came from.
func (p Struct) Segment() *Segment {
return p.seg
}
// IsValid returns whether the struct is valid.
func (p Struct) IsValid() bool {
return p.seg != nil
}
// Address returns the address the pointer references.
//
// Deprecated: The return value is not well-defined. Use SamePtr if you
// need to check whether two pointers refer to the same object.
func (p Struct) Address() Address {
return p.off
}
// Size returns the size of the struct.
func (p Struct) Size() ObjectSize {
return p.size
}
// HasData reports whether the struct has a non-zero size.
func (p Struct) HasData() bool {
return !p.size.isZero()
}
// readSize returns the struct's size for the purposes of read limit
// accounting.
func (p Struct) readSize() Size {
if p.seg == nil {
return 0
}
return p.size.totalSize()
}
func (p Struct) underlying() Pointer {
return p
}
// Pointer returns the i'th pointer in the struct.
//
// Deprecated: Use Ptr.
func (p Struct) Pointer(i uint16) (Pointer, error) {
pp, err := p.Ptr(i)
return pp.toPointer(), err
}
// Ptr returns the i'th pointer in the struct.
func (p Struct) Ptr(i uint16) (Ptr, error) {
if p.seg == nil || i >= p.size.PointerCount {
return Ptr{}, nil
}
return p.seg.readPtr(p.pointerAddress(i), p.depthLimit)
}
// SetPointer sets the i'th pointer in the struct to src.
//
// Deprecated: Use SetPtr.
func (p Struct) SetPointer(i uint16, src Pointer) error {
return p.SetPtr(i, toPtr(src))
}
// SetPtr sets the i'th pointer in the struct to src.
func (p Struct) SetPtr(i uint16, src Ptr) error {
if p.seg == nil || i >= p.size.PointerCount {
panic(errOutOfBounds)
}
return p.seg.writePtr(p.pointerAddress(i), src, false)
}
// SetText sets the i'th pointer to a newly allocated text or null if v is empty.
func (p Struct) SetText(i uint16, v string) error {
if v == "" {
return p.SetPtr(i, Ptr{})
}
return p.SetNewText(i, v)
}
// SetNewText sets the i'th pointer to a newly allocated text.
func (p Struct) SetNewText(i uint16, v string) error {
t, err := NewText(p.seg, v)
if err != nil {
return err
}
return p.SetPtr(i, t.List.ToPtr())
}
// SetTextFromBytes sets the i'th pointer to a newly allocated text or null if v is nil.
func (p Struct) SetTextFromBytes(i uint16, v []byte) error {
if v == nil {
return p.SetPtr(i, Ptr{})
}
t, err := NewTextFromBytes(p.seg, v)
if err != nil {
return err
}
return p.SetPtr(i, t.List.ToPtr())
}
// SetData sets the i'th pointer to a newly allocated data or null if v is nil.
func (p Struct) SetData(i uint16, v []byte) error {
if v == nil {
return p.SetPtr(i, Ptr{})
}
d, err := NewData(p.seg, v)
if err != nil {
return err
}
return p.SetPtr(i, d.List.ToPtr())
}
func (p Struct) pointerAddress(i uint16) Address {
// Struct already had bounds check
ptrStart, _ := p.off.addSize(p.size.DataSize)
a, _ := ptrStart.element(int32(i), wordSize)
return a
}
// bitInData reports whether bit is inside p's data section.
func (p Struct) bitInData(bit BitOffset) bool {
return p.seg != nil && bit < BitOffset(p.size.DataSize*8)
}
// Bit returns the bit that is n bits from the start of the struct.
func (p Struct) Bit(n BitOffset) bool {
if !p.bitInData(n) {
return false
}
addr := p.off.addOffset(n.offset())
return p.seg.readUint8(addr)&n.mask() != 0
}
// SetBit sets the bit that is n bits from the start of the struct to v.
func (p Struct) SetBit(n BitOffset, v bool) {
if !p.bitInData(n) {
panic(errOutOfBounds)
}
addr := p.off.addOffset(n.offset())
b := p.seg.readUint8(addr)
if v {
b |= n.mask()
} else {
b &^= n.mask()
}
p.seg.writeUint8(addr, b)
}
func (p Struct) dataAddress(off DataOffset, sz Size) (addr Address, ok bool) {
if p.seg == nil || Size(off)+sz > p.size.DataSize {
return 0, false
}
return p.off.addOffset(off), true
}
// Uint8 returns an 8-bit integer from the struct's data section.
func (p Struct) Uint8(off DataOffset) uint8 {
addr, ok := p.dataAddress(off, 1)
if !ok {
return 0
}
return p.seg.readUint8(addr)
}
// Uint16 returns a 16-bit integer from the struct's data section.
func (p Struct) Uint16(off DataOffset) uint16 {
addr, ok := p.dataAddress(off, 2)
if !ok {
return 0
}
return p.seg.readUint16(addr)
}
// Uint32 returns a 32-bit integer from the struct's data section.
func (p Struct) Uint32(off DataOffset) uint32 {
addr, ok := p.dataAddress(off, 4)
if !ok {
return 0
}
return p.seg.readUint32(addr)
}
// Uint64 returns a 64-bit integer from the struct's data section.
func (p Struct) Uint64(off DataOffset) uint64 {
addr, ok := p.dataAddress(off, 8)
if !ok {
return 0
}
return p.seg.readUint64(addr)
}
// SetUint8 sets the 8-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint8(off DataOffset, v uint8) {
addr, ok := p.dataAddress(off, 1)
if !ok {
panic(errOutOfBounds)
}
p.seg.writeUint8(addr, v)
}
// SetUint16 sets the 16-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint16(off DataOffset, v uint16) {
addr, ok := p.dataAddress(off, 2)
if !ok {
panic(errOutOfBounds)
}
p.seg.writeUint16(addr, v)
}
// SetUint32 sets the 32-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint32(off DataOffset, v uint32) {
addr, ok := p.dataAddress(off, 4)
if !ok {
panic(errOutOfBounds)
}
p.seg.writeUint32(addr, v)
}
// SetUint64 sets the 64-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint64(off DataOffset, v uint64) {
addr, ok := p.dataAddress(off, 8)
if !ok {
panic(errOutOfBounds)
}
p.seg.writeUint64(addr, v)
}
// structFlags is a bitmask of flags for a pointer.
type structFlags uint8
// Pointer flags.
const (
isListMember structFlags = 1 << iota
)
// copyStruct makes a deep copy of src into dst.
func copyStruct(dst, src Struct) error {
if dst.seg == nil {
return nil
}
// Q: how does version handling happen here, when the
// destination toData[] slice can be bigger or smaller
// than the source data slice, which is in
// src.seg.Data[src.off:src.off+src.size.DataSize] ?
//
// A: Newer fields only come *after* old fields. Note that
// copy only copies min(len(src), len(dst)) size,
// and then we manually zero the rest in the for loop
// that writes toData[j] = 0.
//
// data section:
srcData := src.seg.slice(src.off, src.size.DataSize)
dstData := dst.seg.slice(dst.off, dst.size.DataSize)
copyCount := copy(dstData, srcData)
dstData = dstData[copyCount:]
for j := range dstData {
dstData[j] = 0
}
// ptrs section:
// version handling: we ignore any extra-newer-pointers in src,
// i.e. the case when srcPtrSize > dstPtrSize, by only
// running j over the size of dstPtrSize, the destination size.
srcPtrSect, _ := src.off.addSize(src.size.DataSize)
dstPtrSect, _ := dst.off.addSize(dst.size.DataSize)
numSrcPtrs := src.size.PointerCount
numDstPtrs := dst.size.PointerCount
for j := uint16(0); j < numSrcPtrs && j < numDstPtrs; j++ {
srcAddr, _ := srcPtrSect.element(int32(j), wordSize)
dstAddr, _ := dstPtrSect.element(int32(j), wordSize)
m, err := src.seg.readPtr(srcAddr, src.depthLimit)
if err != nil {
return err
}
err = dst.seg.writePtr(dstAddr, m, true)
if err != nil {
return err
}
}
for j := numSrcPtrs; j < numDstPtrs; j++ {
// destination p is a newer version than source so these extra new pointer fields in p must be zeroed.
addr, _ := dstPtrSect.element(int32(j), wordSize)
dst.seg.writeRawPointer(addr, 0)
}
// Nothing more here: so any other pointers in srcPtrSize beyond
// those in dstPtrSize are ignored and discarded.
return nil
}