Another vendor fix
This commit is contained in:
parent
018cba60e5
commit
a546d623df
|
@ -25,7 +25,8 @@ jobs:
|
||||||
pkg install -y sudo gmake bash git go golangci-lint curl wget fakeroot libffi rubygem-fpm
|
pkg install -y sudo gmake bash git go golangci-lint curl wget fakeroot libffi rubygem-fpm
|
||||||
git config --global --add safe.directory /home/runner/work/cloudflared/cloudflared
|
git config --global --add safe.directory /home/runner/work/cloudflared/cloudflared
|
||||||
run: |
|
run: |
|
||||||
|
gmake install-go
|
||||||
go mod download
|
go mod download
|
||||||
go mod tidy
|
go mod tidy
|
||||||
go install golang.org/x/tools/cmd/goimports@latest
|
go install golang.org/x/tools/cmd/goimports@latest
|
||||||
gmake install-go cloudflared cleanup-go
|
gmake cloudflared cleanup-go
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
/capnpc-go/capnpc-go
|
||||||
|
/internal/cmd/mktemplates/mktemplates
|
||||||
|
TAGS
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Bazel
|
||||||
|
/bazel-bin
|
||||||
|
/bazel-capnproto2
|
||||||
|
/bazel-genfiles
|
||||||
|
/bazel-out
|
||||||
|
/bazel-testlogs
|
|
@ -0,0 +1,9 @@
|
||||||
|
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
|
|
@ -0,0 +1,32 @@
|
||||||
|
# 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>
|
|
@ -0,0 +1,62 @@
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,199 @@
|
||||||
|
# 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>
|
|
@ -0,0 +1,22 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,39 @@
|
||||||
|
# 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>
|
|
@ -0,0 +1,25 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
===============================================================================
|
|
@ -0,0 +1,68 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,45 @@
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,116 @@
|
||||||
|
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))
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,541 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,434 @@
|
||||||
|
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")
|
||||||
|
)
|
|
@ -0,0 +1,384 @@
|
||||||
|
/*
|
||||||
|
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"
|
|
@ -0,0 +1,27 @@
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,582 @@
|
||||||
|
// 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(' ')
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// 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)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
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"],
|
||||||
|
)
|
329
vendor/zombiezen.com/go/capnproto2/internal/fulfiller/fulfiller.go
generated
vendored
Normal file
329
vendor/zombiezen.com/go/capnproto2/internal/fulfiller/fulfiller.go
generated
vendored
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
// 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")
|
||||||
|
)
|
|
@ -0,0 +1,13 @@
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,58 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
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"],
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
// +build go1.5
|
||||||
|
|
||||||
|
package packed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func discard(r *bufio.Reader, n int) {
|
||||||
|
r.Discard(n)
|
||||||
|
}
|
13
vendor/zombiezen.com/go/capnproto2/internal/packed/discard_go14.go
generated
vendored
Normal file
13
vendor/zombiezen.com/go/capnproto2/internal/packed/discard_go14.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// +build !go1.5
|
||||||
|
|
||||||
|
package packed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func discard(r *bufio.Reader, n int) {
|
||||||
|
io.CopyN(ioutil.Discard, r, int64(n))
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
// +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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,337 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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"],
|
||||||
|
)
|
|
@ -0,0 +1,70 @@
|
||||||
|
// 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)
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
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"],
|
||||||
|
)
|
3071
vendor/zombiezen.com/go/capnproto2/internal/schema/schema.capnp.go
generated
vendored
Normal file
3071
vendor/zombiezen.com/go/capnproto2/internal/schema/schema.capnp.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
||||||
|
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__"],
|
||||||
|
)
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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
|
@ -0,0 +1,913 @@
|
||||||
|
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")
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package capnp
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
func (e *Encoder) write(bufs [][]byte) error {
|
||||||
|
_, err := (*net.Buffers)(&bufs).WriteTo(e.w)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// +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
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
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"
|
|
@ -0,0 +1,418 @@
|
||||||
|
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()
|
||||||
|
}
|
|
@ -0,0 +1,350 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,485 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,304 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
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))
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/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 ./...
|
|
@ -0,0 +1,49 @@
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,498 @@
|
||||||
|
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?)")
|
||||||
|
)
|
|
@ -0,0 +1,102 @@
|
||||||
|
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)
|
||||||
|
}
|
16
vendor/zombiezen.com/go/capnproto2/rpc/internal/refcount/BUILD.bazel
generated
vendored
Normal file
16
vendor/zombiezen.com/go/capnproto2/rpc/internal/refcount/BUILD.bazel
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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"],
|
||||||
|
)
|
116
vendor/zombiezen.com/go/capnproto2/rpc/internal/refcount/refcount.go
generated
vendored
Normal file
116
vendor/zombiezen.com/go/capnproto2/rpc/internal/refcount/refcount.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,347 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
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
|
||||||
|
}}
|
||||||
|
}
|
|
@ -0,0 +1,442 @@
|
||||||
|
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{}
|
||||||
|
}
|
|
@ -0,0 +1,913 @@
|
||||||
|
// 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{}{}
|
||||||
|
}
|
|
@ -0,0 +1,255 @@
|
||||||
|
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")
|
|
@ -0,0 +1,175 @@
|
||||||
|
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()
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,185 @@
|
||||||
|
// 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, ¬FoundError{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)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,231 @@
|
||||||
|
// 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")
|
|
@ -0,0 +1,13 @@
|
||||||
|
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
|
@ -0,0 +1,125 @@
|
||||||
|
// +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))
|
||||||
|
}
|
|
@ -0,0 +1,368 @@
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue