add vpr8 libs and core engine for further integration
This commit is contained in:
parent
b374056e78
commit
f1bafffa87
|
@ -140,10 +140,10 @@ enable_testing()
|
||||||
# Sub-projects
|
# Sub-projects
|
||||||
#
|
#
|
||||||
#add_subdirectory(iverilog)
|
#add_subdirectory(iverilog)
|
||||||
|
add_subdirectory(libs)
|
||||||
add_subdirectory(yosys)
|
add_subdirectory(yosys)
|
||||||
add_subdirectory(abc)
|
add_subdirectory(abc)
|
||||||
add_subdirectory(ace2)
|
add_subdirectory(ace2)
|
||||||
add_subdirectory(libs)
|
|
||||||
add_subdirectory(vpr7_x2p)
|
add_subdirectory(vpr7_x2p)
|
||||||
|
|
||||||
# run make to extract compiler options, linker options and list of source files
|
# run make to extract compiler options, linker options and list of source files
|
||||||
|
@ -182,25 +182,11 @@ set_target_properties(libace ace
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ace2"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ace2"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ace2")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ace2")
|
||||||
|
|
||||||
# Set output locations to be in the main source tree under the relevant folder
|
set_target_properties(libarchfpgavpr7 read_arch_vpr7
|
||||||
set_target_properties(libini
|
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libs/external/libini"
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr7_x2p/libarchfpgavpr7"
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libs/external/libini"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr7_x2p/libarchfpgavpr7"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libs/external/libini")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr7_x2p/libarchfpgavpr7")
|
||||||
|
|
||||||
# Set output locations to be in the main source tree under the relevant folder
|
|
||||||
set_target_properties(libvtrutil
|
|
||||||
PROPERTIES
|
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libs/libvtrutil"
|
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libs/libvtrutil"
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libs/libvtrutil")
|
|
||||||
|
|
||||||
set_target_properties(libarchfpga read_arch
|
|
||||||
PROPERTIES
|
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr7_x2p/libarchfpga"
|
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr7_x2p/libarchfpga"
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/vpr7_x2p/libarchfpga")
|
|
||||||
|
|
||||||
set_target_properties(libpcre pcredemo
|
set_target_properties(libpcre pcredemo
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
#VTR developed libraries
|
#Externally developed libraries
|
||||||
#add_subdirectory(libarchfpga)
|
add_subdirectory(EXTERNAL)
|
||||||
|
|
||||||
|
# OpenFPGA-related libraries
|
||||||
|
add_subdirectory(libini)
|
||||||
|
|
||||||
|
# VTR developed libraries
|
||||||
|
# Only add warn flags for VPR internal libraries.
|
||||||
|
#add_compile_options(${WARN_FLAGS})
|
||||||
|
add_subdirectory(libarchfpga)
|
||||||
add_subdirectory(libvtrutil)
|
add_subdirectory(libvtrutil)
|
||||||
add_subdirectory(liblog)
|
add_subdirectory(liblog)
|
||||||
add_subdirectory(external)
|
add_subdirectory(libpugiutil)
|
||||||
#add_subdirectory(external)
|
add_subdirectory(librtlnumber)
|
||||||
#add_subdirectory(libpugiutil)
|
if(${VTR_ENABLE_CAPNPROTO})
|
||||||
#add_subdirectory(libeasygl)
|
add_subdirectory(libvtrcapnproto)
|
||||||
#add_subdirectory(librtlnumber)
|
endif()
|
||||||
|
|
||||||
#Externally developed libraries
|
|
||||||
#add_subdirectory(EXTERNAL)
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
#Manually synchronized external libraries
|
||||||
|
add_subdirectory(libpugixml)
|
||||||
|
add_subdirectory(libcatch)
|
||||||
|
|
||||||
|
#External libraries synchronized with 'git subtree'
|
||||||
|
add_subdirectory(libargparse)
|
||||||
|
add_subdirectory(libsdcparse)
|
||||||
|
add_subdirectory(libblifparse)
|
||||||
|
add_subdirectory(libtatum)
|
||||||
|
|
||||||
|
#VPR_USE_EZGL is initialized in the root CMakeLists.
|
||||||
|
#compile libezgl only if the user asks for or has its dependencies installed.
|
||||||
|
if(VPR_USE_EZGL STREQUAL "on")
|
||||||
|
add_subdirectory(libezgl)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(${VTR_ENABLE_CAPNPROTO})
|
||||||
|
# Override default policy for capnproto (CMake policy version 3.1)
|
||||||
|
# Enable new IPO variables
|
||||||
|
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
|
||||||
|
|
||||||
|
# Enable option overrides via variables
|
||||||
|
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||||
|
|
||||||
|
# Re-enable CXX extensions for capnproto.
|
||||||
|
set(CMAKE_CXX_EXTENSIONS ON)
|
||||||
|
|
||||||
|
# Disable capnproto tests
|
||||||
|
set(BUILD_TESTING OFF)
|
||||||
|
|
||||||
|
#Since capnproto is an externally developed library/tool, we suppress all compiler warnings
|
||||||
|
CHECK_CXX_COMPILER_FLAG("-w" CXX_COMPILER_SUPPORTS_-w)
|
||||||
|
if(CXX_COMPILER_SUPPORTS_-w)
|
||||||
|
add_compile_options("-w")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(capnproto EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
#Some capnproto kj headers (e.g. filesystem.h) generate warnings, treat them as system headers to suppress warnings
|
||||||
|
#We suppress them here since we include the capnproto sub-tree as is and do not modify its CMakeLists.txts
|
||||||
|
target_include_directories(kj SYSTEM INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/capnproto/c++/src>
|
||||||
|
$<INSTALL_INTERFACE:include>
|
||||||
|
)
|
||||||
|
endif()
|
|
@ -0,0 +1,5 @@
|
||||||
|
This folder contains libraries developed outside of the VTR repository.
|
||||||
|
|
||||||
|
Some are simply checked in manually (e.g. libpugixml), while others are kept in sync using the 'git subtree' command (see README.developers.md in the repository root for usage).
|
||||||
|
|
||||||
|
These libraries should NOT BE MODIFIED since they would diverge from their mainline versions, making it difficult to update in the future.
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Kenton's personal backup script.
|
||||||
|
/backup.sh
|
||||||
|
|
||||||
|
# Eclipse-generated stuff.
|
||||||
|
.cproject
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
.settings
|
||||||
|
.dist-buildwrapper
|
||||||
|
/c++/gen/
|
||||||
|
|
||||||
|
# Code you may want to map in from elsewhere.
|
||||||
|
/c++/src/base
|
||||||
|
/c++/src/capnp/compilerbin
|
||||||
|
/c++/src/ekam
|
||||||
|
/c++/src/os
|
||||||
|
/c++/src/protobuf
|
||||||
|
/c++/src/snappy
|
||||||
|
/c++/src/samples
|
||||||
|
|
||||||
|
# Ekam build artifacts.
|
||||||
|
/c++/tmp/
|
||||||
|
/c++/bin/
|
||||||
|
|
||||||
|
# setup-ekam.sh
|
||||||
|
/c++/.ekam
|
||||||
|
|
||||||
|
# super-test.sh
|
||||||
|
/tmp-staging
|
||||||
|
|
||||||
|
# Jekyll-generated site
|
||||||
|
/doc/_site
|
||||||
|
|
||||||
|
# Checkout of gh-pages made by /doc/push-site.sh
|
||||||
|
/doc/.gh-pages
|
||||||
|
|
||||||
|
# cabal-install artifacts
|
||||||
|
/compiler/dist/
|
||||||
|
|
||||||
|
# Make artefacts
|
||||||
|
/c++/.libs/
|
||||||
|
/c++/Makefile
|
||||||
|
/c++/Makefile.in
|
||||||
|
/c++/**/*.o
|
||||||
|
/c++/**/*.lo
|
||||||
|
/c++/**/.deps/
|
||||||
|
/c++/**/.dirstamp
|
||||||
|
/c++/stamp-h1
|
||||||
|
/c++/**/*.log
|
||||||
|
/c++/test_capnpc_middleman
|
||||||
|
/c++/**/test*.capnp.*
|
||||||
|
/c++/*.la
|
||||||
|
/c++/**/*.trs
|
||||||
|
/c++/aclocal.m4
|
||||||
|
/c++/autom4te.cache/
|
||||||
|
/c++/build-aux/
|
||||||
|
/c++/capnp
|
||||||
|
/c++/capnp-evolution-test
|
||||||
|
/c++/cmake/CapnProtoConfig.cmake
|
||||||
|
/c++/cmake/CapnProtoConfigVersion.cmake
|
||||||
|
/c++/pkgconfig/*.pc
|
||||||
|
/c++/capnp-test
|
||||||
|
/c++/capnpc-c++
|
||||||
|
/c++/capnpc-capnp
|
||||||
|
/c++/config.*
|
||||||
|
/c++/configure
|
||||||
|
/c++/libtool
|
||||||
|
/c++/m4/libtool.m4
|
||||||
|
/c++/m4/ltoptions.m4
|
||||||
|
/c++/m4/ltsugar.m4
|
||||||
|
/c++/m4/ltversion.m4
|
||||||
|
/c++/m4/lt~obsolete.m4
|
||||||
|
/c++/samples/addressbook
|
||||||
|
|
||||||
|
# editor artefacts
|
||||||
|
*~
|
|
@ -0,0 +1,77 @@
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- /release-.*/
|
||||||
|
language: cpp
|
||||||
|
dist: trusty
|
||||||
|
sudo: false
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- automake
|
||||||
|
- autoconf
|
||||||
|
- libtool
|
||||||
|
- pkg-config
|
||||||
|
# limit parallelism due to limited memory on Travis
|
||||||
|
script: CC=$MATRIX_CC CXX=$MATRIX_CXX ./super-test.sh -j2 quick
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# Old GCC
|
||||||
|
- os: linux
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
packages:
|
||||||
|
- g++-4.9
|
||||||
|
env:
|
||||||
|
- MATRIX_CC=gcc-4.9
|
||||||
|
- MATRIX_CXX=g++-4.9
|
||||||
|
|
||||||
|
# New GCC
|
||||||
|
- os: linux
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
packages:
|
||||||
|
- g++-7
|
||||||
|
env:
|
||||||
|
- MATRIX_CC=gcc-7
|
||||||
|
- MATRIX_CXX=g++-7
|
||||||
|
|
||||||
|
# Old Clang
|
||||||
|
- os: linux
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
- llvm-toolchain-trusty-3.6
|
||||||
|
packages:
|
||||||
|
- clang-3.6
|
||||||
|
- libc++-dev # clang-3.6 can't compile C++14 against libstdc++, apparently.
|
||||||
|
env:
|
||||||
|
- MATRIX_CC=clang-3.6
|
||||||
|
- MATRIX_CXX=clang++-3.6
|
||||||
|
|
||||||
|
# New Clang
|
||||||
|
- os: linux
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
- llvm-toolchain-trusty-5.0
|
||||||
|
packages:
|
||||||
|
- clang-5.0
|
||||||
|
env:
|
||||||
|
- MATRIX_CC=clang-5.0
|
||||||
|
- MATRIX_CXX=clang++-5.0
|
||||||
|
|
||||||
|
# Mac. We only test Clang because Mac builds are expensive for Travis and probably any
|
||||||
|
# compiler-specific problems will be caught on the Linux matrix anyway.
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode9.3
|
||||||
|
env:
|
||||||
|
- MATRIX_CC=clang
|
||||||
|
- MATRIX_CXX=clang++
|
|
@ -0,0 +1,3 @@
|
||||||
|
cmake_minimum_required(VERSION 3.1)
|
||||||
|
project("Cap'n Proto Root" CXX)
|
||||||
|
add_subdirectory(c++)
|
|
@ -0,0 +1,23 @@
|
||||||
|
The following people have made large code contributions to this repository.
|
||||||
|
Those contributions are copyright the respective authors and licensed by them
|
||||||
|
under the same MIT license terms as the rest of the library.
|
||||||
|
|
||||||
|
Kenton Varda <kenton@sandstorm.io> <kenton@cloudflare.com>: Primary Author
|
||||||
|
Jason Choy <jjwchoy@gmail.com>: kj/threadlocal.h and other iOS tweaks, `name` annotation in C++ code generator
|
||||||
|
Remy Blank <rblank@google.com> (contributions copyright Google Inc.): KJ Timers
|
||||||
|
Joshua Warner <joshuawarner32@gmail.com>: cmake build, AnyStruct/AnyList, other stuff
|
||||||
|
Scott Purdy <scott@fer.io>: kj/std iostream interface
|
||||||
|
Bryan Borham <bjboreham@gmail.com>: Initial MSVC support
|
||||||
|
Philip Quinn <p@partylemon.com>: cmake build and other assorted bits
|
||||||
|
Brian Taylor <el.wubo@gmail.com>: emacs syntax highlighting
|
||||||
|
Ben Laurie <ben@links.org>: discovered and responsibly disclosed security bugs
|
||||||
|
Kamal Marhubi <kamal@marhubi.com>: JSON parser
|
||||||
|
Oliver Kuckertz <oliver.kuckertz@mologie.de>: FdObserver POLLPRI support
|
||||||
|
Harris Hancock <vortrab@gmail.com>: MSVC support
|
||||||
|
Branislav Katreniak <branislav.katreniak@digitalstrom.com>: JSON decode
|
||||||
|
Matthew Maurer <matthew.r.maurer@gmail.com>: Canonicalization Support
|
||||||
|
David Renshaw <david@sandstorm.io>: bugfixes and miscellaneous maintenance
|
||||||
|
Ingvar Stepanyan <me@rreverser.com> <ingvar@cloudflare.com>: Custom handlers for JSON decode
|
||||||
|
|
||||||
|
This file does not list people who maintain their own Cap'n Proto
|
||||||
|
implementations as separate projects. Those people are awesome too! :)
|
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2013-2017 Sandstorm Development Group, Inc.; Cloudflare, Inc.;
|
||||||
|
and other contributors. Each commit is copyright by its respective author or
|
||||||
|
author's employer.
|
||||||
|
|
||||||
|
Licensed under the MIT License:
|
||||||
|
|
||||||
|
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,9 @@
|
||||||
|
Unix: [![Unix Build Status](https://travis-ci.org/capnproto/capnproto.svg?branch=master)](https://travis-ci.org/capnproto/capnproto) Windows: [![Windows Build Status](https://ci.appveyor.com/api/projects/status/9rxff2tujkae4hte?svg=true)](https://ci.appveyor.com/project/kentonv/capnproto)
|
||||||
|
|
||||||
|
<img src='http://kentonv.github.com/capnproto/images/infinity-times-faster.png' style='width:334px; height:306px; float: right;'>
|
||||||
|
|
||||||
|
Cap'n Proto is an insanely fast data interchange format and capability-based RPC system. Think
|
||||||
|
JSON, except binary. Or think [Protocol Buffers](https://github.com/google/protobuf), except faster.
|
||||||
|
In fact, in benchmarks, Cap'n Proto is INFINITY TIMES faster than Protocol Buffers.
|
||||||
|
|
||||||
|
[Read more...](http://kentonv.github.com/capnproto/)
|
|
@ -0,0 +1,67 @@
|
||||||
|
How to release
|
||||||
|
==============
|
||||||
|
|
||||||
|
**Developing**
|
||||||
|
|
||||||
|
* First, develop some new features to release! As you do, make sure to keep the documentation
|
||||||
|
up-to-date.
|
||||||
|
|
||||||
|
**Testing**
|
||||||
|
|
||||||
|
* Run `super-test.sh` on as many platforms as you have available. Remember that you can easily run
|
||||||
|
on any machine available through ssh using `./super-test.sh remote [hostname]`. Also run in
|
||||||
|
Clang mode. (If you are Kenton and running from Kenton's home machine and network, use
|
||||||
|
`./mega-test.py mega-test.cfg` to run on all supported compilers and platforms.)
|
||||||
|
|
||||||
|
* Manually test Windows/MSVC -- unfortunately this can't be automated by super-test.sh.
|
||||||
|
|
||||||
|
* Manually run the pointer fuzz tests under Valgrind. This will take 40-80 minutes.
|
||||||
|
|
||||||
|
valgrind ./capnp-test -fcapnp/fuzz-test.c++
|
||||||
|
|
||||||
|
* Manually run the AFL fuzz tests by running `afl-fuzz.sh`. There are three test cases, and ideally each should run for 24 hours or more.
|
||||||
|
|
||||||
|
**Documenting**
|
||||||
|
|
||||||
|
* Write a blog post discussing what is new, placing it in doc/_posts.
|
||||||
|
|
||||||
|
* Run jekyll locally and review the blog post and docs.
|
||||||
|
|
||||||
|
**Releasing**
|
||||||
|
|
||||||
|
* Check out the master branch in a fresh directory. Do NOT use your regular repo, as the release
|
||||||
|
script commits changes and if anything goes wrong you'll probably want to trash the whole thing
|
||||||
|
without pushing. DO NOT git clone the repo from an existing local repo -- check it out directly
|
||||||
|
from github. Otherwise, when it pushes its changes back, they'll only be pushed back to your
|
||||||
|
local repo.
|
||||||
|
|
||||||
|
* Run `./release.sh candidate`. This creates a new release branch, updates the version number to
|
||||||
|
`-rc1`, builds release tarballs, copies them to the current directory, then switches back to the
|
||||||
|
master branch and bumps the version number there. After asking for final confirmation, it will
|
||||||
|
upload the tarball to S3 and push all changes back to github.
|
||||||
|
|
||||||
|
* Install your release candidates on your local machine, as if you were a user.
|
||||||
|
|
||||||
|
* Go to `c++/samples` in the git repo and run `./test.sh`. It will try to build against your
|
||||||
|
installed copy.
|
||||||
|
|
||||||
|
* Post the release candidates somewhere public and then send links to the mailing list for people
|
||||||
|
to test. Wait a bit for bug reports.
|
||||||
|
|
||||||
|
* If there are any problems, fix them in master and start a new release candidate by running
|
||||||
|
`./release.sh candidate <commit>...` from the release branch. This will cherry-pick the specified
|
||||||
|
commits into the release branch and create a new candidate. Repeat until all problems are fixed.
|
||||||
|
Be sure that any such fixes include tests or process changes so that they don't happen again.
|
||||||
|
|
||||||
|
* You should now be ready for an official release. Run `./release.sh final`. This will remove the
|
||||||
|
"-rcN" suffix from the version number, update the version number shown on the downloads page,
|
||||||
|
build the final release package, and -- after final confirmation -- upload the binary, push
|
||||||
|
changes to git, and publish the new documentation.
|
||||||
|
|
||||||
|
* Submit the newly-published blog post to news sites and social media as you see fit.
|
||||||
|
|
||||||
|
* If problems are discovered in the release, fix them in master and run
|
||||||
|
`./release.sh candidate <commit>...` in the release branch to start a new micro release. The
|
||||||
|
script automatically sees that the current branch's version no longer contains `-rc`, so it starts
|
||||||
|
a new branch. Repeat the rest of the process above. If you decide to write a blog post (not
|
||||||
|
always necessary), do it in the master branch and cherry-pick it.
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Cap'n Proto AppVeyor configuration
|
||||||
|
#
|
||||||
|
# See https://www.appveyor.com/docs/appveyor-yml/ for configuration options.
|
||||||
|
#
|
||||||
|
# This script configures AppVeyor to:
|
||||||
|
# - Use CMake to ...
|
||||||
|
# build Cap'n Proto with VS2017.
|
||||||
|
# build Cap'n Proto samples with VS2017.
|
||||||
|
# build Cap'n Proto with MinGW.
|
||||||
|
# build Cap'n Proto with Cygwin.
|
||||||
|
|
||||||
|
version: "{build}"
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- /release-.*/
|
||||||
|
# Don't build non-master branches (unless they open a pull request).
|
||||||
|
|
||||||
|
image: Visual Studio 2017
|
||||||
|
# AppVeyor build worker image (VM template).
|
||||||
|
|
||||||
|
shallow_clone: true
|
||||||
|
# Fetch repository as zip archive.
|
||||||
|
|
||||||
|
environment:
|
||||||
|
MINGW_DIR: C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64
|
||||||
|
BUILD_TYPE: debug
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
# TODO(someday): Add MSVC x64 builds, MinGW x86 build?
|
||||||
|
|
||||||
|
- CMAKE_GENERATOR: Visual Studio 15 2017
|
||||||
|
BUILD_NAME: vs2017
|
||||||
|
EXTRA_BUILD_FLAGS: # /maxcpucount
|
||||||
|
# TODO(someday): Right now /maxcpucount occasionally expresses a filesystem-related race:
|
||||||
|
# capnp-capnpc++ complains that it can't create test.capnp.h.
|
||||||
|
|
||||||
|
- CMAKE_GENERATOR: MinGW Makefiles
|
||||||
|
BUILD_NAME: mingw
|
||||||
|
EXTRA_BUILD_FLAGS: -j2
|
||||||
|
|
||||||
|
- BUILD_NAME: cygwin
|
||||||
|
|
||||||
|
install:
|
||||||
|
- ps: Get-Command sh.exe -All | Remove-Item
|
||||||
|
# CMake refuses to generate MinGW Makefiles if sh.exe is in the PATH
|
||||||
|
|
||||||
|
before_build:
|
||||||
|
- set PATH=%MINGW_DIR%\bin;%PATH%
|
||||||
|
- set BUILD_DIR=build-%BUILD_NAME%
|
||||||
|
- set INSTALL_PREFIX=%CD%\capnproto-c++-%BUILD_NAME%
|
||||||
|
- cmake --version
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- echo "Building Cap'n Proto with %CMAKE_GENERATOR%"
|
||||||
|
- if NOT "%BUILD_NAME%"=="cygwin" cmake -Hc++ -B%BUILD_DIR% -G "%CMAKE_GENERATOR%" -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_INSTALL_PREFIX=%INSTALL_PREFIX%
|
||||||
|
- if NOT "%BUILD_NAME%"=="cygwin" cmake --build %BUILD_DIR% --config %BUILD_TYPE% --target install -- %EXTRA_BUILD_FLAGS%
|
||||||
|
# MinGW wants the build type at configure-time while MSVC wants the build type at build-time. We
|
||||||
|
# can satisfy both by passing the build type to both cmake invocations. We have to suffer a
|
||||||
|
# warning, but both generators will work.
|
||||||
|
|
||||||
|
- echo "Building Cap'n Proto samples with %CMAKE_GENERATOR%"
|
||||||
|
- if NOT "%BUILD_NAME%"=="cygwin" cmake -Hc++/samples -B%BUILD_DIR%-samples -G "%CMAKE_GENERATOR%" -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_PREFIX_PATH=%INSTALL_PREFIX%
|
||||||
|
- if NOT "%BUILD_NAME%"=="cygwin" cmake --build %BUILD_DIR%-samples --config %BUILD_TYPE%
|
||||||
|
|
||||||
|
# Cygwin build -- use super-test.sh like other Unix builds.
|
||||||
|
# But, we need to install Cygwin's cmake package in order to pass the cmake part of super-test.
|
||||||
|
# Somewhat ridiculously, this requires downloading Cygwin's setup program and running it.
|
||||||
|
- if "%BUILD_NAME%"=="cygwin" appveyor DownloadFile "https://cygwin.com/setup-x86_64.exe" -FileName "C:\cygwin64\setup-x86_64.exe"
|
||||||
|
- if "%BUILD_NAME%"=="cygwin" C:\cygwin64\setup-x86_64.exe --quiet-mode --no-shortcuts --upgrade-also --root "C:\cygwin64" --packages cmake
|
||||||
|
- if "%BUILD_NAME%"=="cygwin" C:\cygwin64\bin\bash -lc 'cd /cygdrive/c/projects/capnproto; ./super-test.sh -j2 quick'
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
# Sleep a little to prevent interleaving test output with build output.
|
||||||
|
- if NOT "%BUILD_NAME%"=="cygwin" timeout /t 2
|
||||||
|
- if NOT "%BUILD_NAME%"=="cygwin" cd %BUILD_DIR%\src
|
||||||
|
- if NOT "%BUILD_NAME%"=="cygwin" ctest -V -C %BUILD_TYPE%
|
|
@ -0,0 +1,155 @@
|
||||||
|
cmake_minimum_required(VERSION 3.1)
|
||||||
|
project("Cap'n Proto" CXX)
|
||||||
|
set(VERSION 0.7.0)
|
||||||
|
|
||||||
|
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
include(CheckIncludeFileCXX)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
if(MSVC)
|
||||||
|
check_include_file_cxx(initializer_list HAS_CXX14)
|
||||||
|
else()
|
||||||
|
check_include_file_cxx(initializer_list HAS_CXX14 "-std=gnu++1y")
|
||||||
|
endif()
|
||||||
|
if(NOT HAS_CXX14)
|
||||||
|
message(SEND_ERROR "Requires a C++14 compiler and standard library.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# these arguments are passed to all install(TARGETS) calls
|
||||||
|
set(INSTALL_TARGETS_DEFAULT_ARGS
|
||||||
|
EXPORT CapnProtoTargets
|
||||||
|
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||||
|
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||||
|
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Options ======================================================================
|
||||||
|
|
||||||
|
option(BUILD_TESTING "Build unit tests and enable CTest 'check' target." ON)
|
||||||
|
option(EXTERNAL_CAPNP "Use the system capnp binary, or the one specified in $CAPNP, instead of using the compiled one." OFF)
|
||||||
|
option(CAPNP_LITE "Compile Cap'n Proto in 'lite mode', in which all reflection APIs (schema.h, dynamic.h, etc.) are not included. Produces a smaller library at the cost of features. All programs built against the library must be compiled with -DCAPNP_LITE. Requires EXTERNAL_CAPNP." OFF)
|
||||||
|
|
||||||
|
# Check for invalid combinations of build options
|
||||||
|
if(CAPNP_LITE AND BUILD_TESTING AND NOT EXTERNAL_CAPNP)
|
||||||
|
message(SEND_ERROR "You must set EXTERNAL_CAPNP when using CAPNP_LITE and BUILD_TESTING.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CAPNP_LITE)
|
||||||
|
set(CAPNP_LITE_FLAG "-DCAPNP_LITE")
|
||||||
|
# This flag is attached as PUBLIC target_compile_definition to kj target
|
||||||
|
else()
|
||||||
|
set(CAPNP_LITE_FLAG)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
# TODO(cleanup): Enable higher warning level in MSVC, but make sure to test
|
||||||
|
# build with that warning level and clean out false positives.
|
||||||
|
|
||||||
|
add_compile_options(/wo4503)
|
||||||
|
# Only warn once on truncated decorated names. The maximum symbol length MSVC
|
||||||
|
# supports is 4k characters, which the parser framework regularly blows. The
|
||||||
|
# compiler likes to print out the entire type that went over the limit along
|
||||||
|
# with this warning, which gets unbearably spammy. That said, we don't want to
|
||||||
|
# just ignore it, so I'm letting it trigger once until we find some places to
|
||||||
|
# inject ParserRefs.
|
||||||
|
else()
|
||||||
|
# Note that it's important to add new CXXFLAGS before ones specified by the
|
||||||
|
# user, so that the user's flags override them. This is particularly
|
||||||
|
# important if -Werror was enabled and then certain warnings need to be
|
||||||
|
# disabled, as is done in super-test.sh.
|
||||||
|
#
|
||||||
|
# We enable a lot of warnings, but then disable some:
|
||||||
|
# * strict-aliasing: We use type-punning in known-safe ways that GCC doesn't
|
||||||
|
# recognize as safe.
|
||||||
|
# * sign-compare: Low S/N ratio.
|
||||||
|
# * unused-parameter: Low S/N ratio.
|
||||||
|
add_compile_options(-Wall -Wextra -Wno-strict-aliasing -Wno-sign-compare -Wno-unused-parameter)
|
||||||
|
|
||||||
|
if(DEFINED CMAKE_CXX_EXTENSIONS AND NOT CMAKE_CXX_EXTENSIONS)
|
||||||
|
message(SEND_ERROR "Cap'n Proto requires compiler-specific extensions (e.g., -std=gnu++14). Please leave CMAKE_CXX_EXTENSIONS undefined or ON.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT ANDROID)
|
||||||
|
add_compile_options(-pthread)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Source =======================================================================
|
||||||
|
include(CapnProtoMacros)
|
||||||
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
# Install ======================================================================
|
||||||
|
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
# We used to use write_basic_package_version_file(), but since the autotools build needs to install
|
||||||
|
# a config version script as well, I copied the AnyNewerVersion template from my CMake Modules
|
||||||
|
# directory to Cap'n Proto's cmake/ directory (alternatively, we could make the autotools build
|
||||||
|
# depend on CMake).
|
||||||
|
#
|
||||||
|
# We might as well use the local copy of the template. In the future we can modify the project's
|
||||||
|
# version compatibility policy just by changing that file.
|
||||||
|
set(PACKAGE_VERSION ${VERSION})
|
||||||
|
configure_file(cmake/CapnProtoConfigVersion.cmake.in cmake/CapnProtoConfigVersion.cmake @ONLY)
|
||||||
|
|
||||||
|
set(CONFIG_PACKAGE_LOCATION ${CMAKE_INSTALL_LIBDIR}/cmake/CapnProto)
|
||||||
|
|
||||||
|
configure_package_config_file(cmake/CapnProtoConfig.cmake.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cmake/CapnProtoConfig.cmake
|
||||||
|
INSTALL_DESTINATION ${CONFIG_PACKAGE_LOCATION}
|
||||||
|
PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR
|
||||||
|
)
|
||||||
|
export(EXPORT CapnProtoTargets
|
||||||
|
FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/CapnProtoTargets.cmake"
|
||||||
|
NAMESPACE CapnProto::
|
||||||
|
)
|
||||||
|
install(EXPORT CapnProtoTargets
|
||||||
|
FILE CapnProtoTargets.cmake
|
||||||
|
NAMESPACE CapnProto::
|
||||||
|
DESTINATION ${CONFIG_PACKAGE_LOCATION}
|
||||||
|
)
|
||||||
|
install(FILES
|
||||||
|
cmake/CapnProtoMacros.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cmake/CapnProtoConfig.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cmake/CapnProtoConfigVersion.cmake
|
||||||
|
DESTINATION ${CONFIG_PACKAGE_LOCATION}
|
||||||
|
)
|
||||||
|
#install CapnProtoMacros for CapnProtoConfig.cmake build directory consumers
|
||||||
|
configure_file(cmake/CapnProtoMacros.cmake cmake/CapnProtoMacros.cmake COPYONLY)
|
||||||
|
|
||||||
|
if(NOT MSVC) # Don't install pkg-config files when building with MSVC
|
||||||
|
# Variables for pkg-config files
|
||||||
|
set(prefix "${CMAKE_INSTALL_PREFIX}")
|
||||||
|
set(exec_prefix "") # not needed since we use absolute paths in libdir and includedir
|
||||||
|
set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}")
|
||||||
|
set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}")
|
||||||
|
set(PTHREAD_CFLAGS "-pthread")
|
||||||
|
set(STDLIB_FLAG) # TODO: Unsupported
|
||||||
|
|
||||||
|
set(CAPNP_PKG_CONFIG_FILES
|
||||||
|
pkgconfig/kj.pc
|
||||||
|
pkgconfig/capnp.pc
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT CAPNP_LITE)
|
||||||
|
list(APPEND CAPNP_PKG_CONFIG_FILES
|
||||||
|
pkgconfig/kj-async.pc
|
||||||
|
pkgconfig/kj-http.pc
|
||||||
|
pkgconfig/kj-test.pc
|
||||||
|
pkgconfig/capnp-rpc.pc
|
||||||
|
pkgconfig/capnp-json.pc
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(pcfile ${CAPNP_PKG_CONFIG_FILES})
|
||||||
|
configure_file(${pcfile}.in "${CMAKE_CURRENT_BINARY_DIR}/${pcfile}" @ONLY)
|
||||||
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${pcfile}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
unset(STDLIB_FLAG)
|
||||||
|
unset(PTHREAD_CFLAGS)
|
||||||
|
unset(includedir)
|
||||||
|
unset(libdir)
|
||||||
|
unset(exec_prefix)
|
||||||
|
unset(prefix)
|
||||||
|
endif()
|
|
@ -0,0 +1 @@
|
||||||
|
../LICENSE
|
|
@ -0,0 +1,556 @@
|
||||||
|
## Process this file with automake to produce Makefile.in
|
||||||
|
|
||||||
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
|
AUTOMAKE_OPTIONS = foreign subdir-objects
|
||||||
|
|
||||||
|
# When running distcheck, verify that we've included all the files needed by
|
||||||
|
# the cmake build.
|
||||||
|
distcheck-hook:
|
||||||
|
rm -rf distcheck-cmake
|
||||||
|
(mkdir distcheck-cmake && cd distcheck-cmake && cmake ../$(distdir) && make -j6 check)
|
||||||
|
rm -rf distcheck-cmake
|
||||||
|
|
||||||
|
AM_CXXFLAGS = -I$(srcdir)/src -I$(builddir)/src -DKJ_HEADER_WARNINGS -DCAPNP_HEADER_WARNINGS -DCAPNP_INCLUDE_DIR='"$(includedir)"' $(PTHREAD_CFLAGS)
|
||||||
|
|
||||||
|
AM_LDFLAGS = $(PTHREAD_CFLAGS)
|
||||||
|
|
||||||
|
EXTRA_DIST = \
|
||||||
|
README.txt \
|
||||||
|
LICENSE.txt \
|
||||||
|
$(test_capnpc_inputs) \
|
||||||
|
src/capnp/compiler/capnp-test.sh \
|
||||||
|
src/capnp/testdata/segmented-packed \
|
||||||
|
src/capnp/testdata/errors.capnp.nobuild \
|
||||||
|
src/capnp/testdata/short.txt \
|
||||||
|
src/capnp/testdata/flat \
|
||||||
|
src/capnp/testdata/binary \
|
||||||
|
src/capnp/testdata/errors.txt \
|
||||||
|
src/capnp/testdata/segmented \
|
||||||
|
src/capnp/testdata/packed \
|
||||||
|
src/capnp/testdata/pretty.txt \
|
||||||
|
src/capnp/testdata/lists.binary \
|
||||||
|
src/capnp/testdata/packedflat \
|
||||||
|
src/capnp/testdata/pretty.json \
|
||||||
|
src/capnp/testdata/short.json \
|
||||||
|
src/capnp/testdata/annotated-json.binary \
|
||||||
|
src/capnp/testdata/annotated.json \
|
||||||
|
CMakeLists.txt \
|
||||||
|
cmake/CapnProtoMacros.cmake \
|
||||||
|
cmake/CapnProtoTargets.cmake \
|
||||||
|
cmake/CapnProtoConfig.cmake.in \
|
||||||
|
cmake/CapnProtoConfigVersion.cmake.in \
|
||||||
|
src/CMakeLists.txt \
|
||||||
|
src/kj/CMakeLists.txt \
|
||||||
|
src/capnp/CMakeLists.txt
|
||||||
|
|
||||||
|
CLEANFILES = $(test_capnpc_outputs) test_capnpc_middleman distcheck-cmake
|
||||||
|
|
||||||
|
# Deletes all the files generated by autoreconf.
|
||||||
|
MAINTAINERCLEANFILES = \
|
||||||
|
aclocal.m4 \
|
||||||
|
config.guess \
|
||||||
|
config.sub \
|
||||||
|
configure \
|
||||||
|
depcomp \
|
||||||
|
install-sh \
|
||||||
|
ltmain.sh \
|
||||||
|
Makefile.in \
|
||||||
|
missing \
|
||||||
|
mkinstalldirs \
|
||||||
|
config.h.in \
|
||||||
|
stamp.h.in \
|
||||||
|
m4/ltsugar.m4 \
|
||||||
|
m4/libtool.m4 \
|
||||||
|
m4/ltversion.m4 \
|
||||||
|
m4/lt~obsolete.m4 \
|
||||||
|
m4/ltoptions.m4
|
||||||
|
|
||||||
|
maintainer-clean-local:
|
||||||
|
-rm -rf build-aux
|
||||||
|
|
||||||
|
# gmake defines an implicit rule building n from n.o. Unfortunately, this triggers on our .capnp
|
||||||
|
# files because they generate .capnp.c++ which is compiled to .capnp.o. In addition to being
|
||||||
|
# nonsense, this leads to cyclic dependency issues and could even cause the .capnp files to be
|
||||||
|
# unexpectedly overwritten! We need to cancel the implicit rule by declaring an explicit one.
|
||||||
|
#
|
||||||
|
# I want the hours of my life back that I spent figuring this out.
|
||||||
|
%.capnp:
|
||||||
|
@:
|
||||||
|
|
||||||
|
public_capnpc_inputs = \
|
||||||
|
src/capnp/c++.capnp \
|
||||||
|
src/capnp/schema.capnp \
|
||||||
|
src/capnp/rpc.capnp \
|
||||||
|
src/capnp/rpc-twoparty.capnp \
|
||||||
|
src/capnp/persistent.capnp \
|
||||||
|
src/capnp/compat/json.capnp
|
||||||
|
|
||||||
|
capnpc_inputs = \
|
||||||
|
$(public_capnpc_inputs) \
|
||||||
|
src/capnp/compiler/lexer.capnp \
|
||||||
|
src/capnp/compiler/grammar.capnp
|
||||||
|
|
||||||
|
capnpc_outputs = \
|
||||||
|
src/capnp/c++.capnp.c++ \
|
||||||
|
src/capnp/c++.capnp.h \
|
||||||
|
src/capnp/schema.capnp.c++ \
|
||||||
|
src/capnp/schema.capnp.h \
|
||||||
|
src/capnp/rpc.capnp.c++ \
|
||||||
|
src/capnp/rpc.capnp.h \
|
||||||
|
src/capnp/rpc-twoparty.capnp.c++ \
|
||||||
|
src/capnp/rpc-twoparty.capnp.h \
|
||||||
|
src/capnp/persistent.capnp.c++ \
|
||||||
|
src/capnp/persistent.capnp.h \
|
||||||
|
src/capnp/compat/json.capnp.h \
|
||||||
|
src/capnp/compat/json.capnp.c++ \
|
||||||
|
src/capnp/compiler/lexer.capnp.c++ \
|
||||||
|
src/capnp/compiler/lexer.capnp.h \
|
||||||
|
src/capnp/compiler/grammar.capnp.c++ \
|
||||||
|
src/capnp/compiler/grammar.capnp.h
|
||||||
|
|
||||||
|
includecapnpdir = $(includedir)/capnp
|
||||||
|
includecapnpcompatdir = $(includecapnpdir)/compat
|
||||||
|
includekjdir = $(includedir)/kj
|
||||||
|
includekjparsedir = $(includekjdir)/parse
|
||||||
|
includekjstddir = $(includekjdir)/std
|
||||||
|
includekjcompatdir = $(includekjdir)/compat
|
||||||
|
|
||||||
|
dist_includecapnp_DATA = $(public_capnpc_inputs)
|
||||||
|
|
||||||
|
pkgconfigdir = $(libdir)/pkgconfig
|
||||||
|
pkgconfig_DATA = $(CAPNP_PKG_CONFIG_FILES)
|
||||||
|
|
||||||
|
cmakeconfigdir = $(libdir)/cmake/CapnProto
|
||||||
|
cmakeconfig_DATA = $(CAPNP_CMAKE_CONFIG_FILES) \
|
||||||
|
cmake/CapnProtoMacros.cmake \
|
||||||
|
cmake/CapnProtoTargets.cmake
|
||||||
|
|
||||||
|
noinst_HEADERS = \
|
||||||
|
src/kj/miniposix.h \
|
||||||
|
src/kj/async-io-internal.h
|
||||||
|
|
||||||
|
includekj_HEADERS = \
|
||||||
|
src/kj/common.h \
|
||||||
|
src/kj/units.h \
|
||||||
|
src/kj/memory.h \
|
||||||
|
src/kj/refcount.h \
|
||||||
|
src/kj/array.h \
|
||||||
|
src/kj/vector.h \
|
||||||
|
src/kj/string.h \
|
||||||
|
src/kj/string-tree.h \
|
||||||
|
src/kj/hash.h \
|
||||||
|
src/kj/table.h \
|
||||||
|
src/kj/map.h \
|
||||||
|
src/kj/encoding.h \
|
||||||
|
src/kj/exception.h \
|
||||||
|
src/kj/debug.h \
|
||||||
|
src/kj/arena.h \
|
||||||
|
src/kj/io.h \
|
||||||
|
src/kj/tuple.h \
|
||||||
|
src/kj/one-of.h \
|
||||||
|
src/kj/function.h \
|
||||||
|
src/kj/mutex.h \
|
||||||
|
src/kj/thread.h \
|
||||||
|
src/kj/threadlocal.h \
|
||||||
|
src/kj/filesystem.h \
|
||||||
|
src/kj/async-prelude.h \
|
||||||
|
src/kj/async.h \
|
||||||
|
src/kj/async-inl.h \
|
||||||
|
src/kj/time.h \
|
||||||
|
src/kj/timer.h \
|
||||||
|
src/kj/async-unix.h \
|
||||||
|
src/kj/async-win32.h \
|
||||||
|
src/kj/async-io.h \
|
||||||
|
src/kj/main.h \
|
||||||
|
src/kj/test.h \
|
||||||
|
src/kj/windows-sanity.h
|
||||||
|
|
||||||
|
includekjparse_HEADERS = \
|
||||||
|
src/kj/parse/common.h \
|
||||||
|
src/kj/parse/char.h
|
||||||
|
|
||||||
|
includekjstd_HEADERS = \
|
||||||
|
src/kj/std/iostream.h
|
||||||
|
|
||||||
|
includekjcompat_HEADERS = \
|
||||||
|
src/kj/compat/gtest.h \
|
||||||
|
src/kj/compat/url.h \
|
||||||
|
src/kj/compat/http.h \
|
||||||
|
src/kj/compat/gzip.h \
|
||||||
|
src/kj/compat/readiness-io.h \
|
||||||
|
src/kj/compat/tls.h
|
||||||
|
|
||||||
|
includecapnp_HEADERS = \
|
||||||
|
src/capnp/c++.capnp.h \
|
||||||
|
src/capnp/common.h \
|
||||||
|
src/capnp/blob.h \
|
||||||
|
src/capnp/endian.h \
|
||||||
|
src/capnp/layout.h \
|
||||||
|
src/capnp/orphan.h \
|
||||||
|
src/capnp/list.h \
|
||||||
|
src/capnp/any.h \
|
||||||
|
src/capnp/message.h \
|
||||||
|
src/capnp/capability.h \
|
||||||
|
src/capnp/membrane.h \
|
||||||
|
src/capnp/schema.capnp.h \
|
||||||
|
src/capnp/schema-lite.h \
|
||||||
|
src/capnp/schema.h \
|
||||||
|
src/capnp/schema-loader.h \
|
||||||
|
src/capnp/schema-parser.h \
|
||||||
|
src/capnp/dynamic.h \
|
||||||
|
src/capnp/pretty-print.h \
|
||||||
|
src/capnp/serialize.h \
|
||||||
|
src/capnp/serialize-async.h \
|
||||||
|
src/capnp/serialize-packed.h \
|
||||||
|
src/capnp/serialize-text.h \
|
||||||
|
src/capnp/pointer-helpers.h \
|
||||||
|
src/capnp/generated-header-support.h \
|
||||||
|
src/capnp/raw-schema.h \
|
||||||
|
src/capnp/rpc-prelude.h \
|
||||||
|
src/capnp/rpc.h \
|
||||||
|
src/capnp/rpc-twoparty.h \
|
||||||
|
src/capnp/rpc.capnp.h \
|
||||||
|
src/capnp/rpc-twoparty.capnp.h \
|
||||||
|
src/capnp/persistent.capnp.h \
|
||||||
|
src/capnp/ez-rpc.h
|
||||||
|
|
||||||
|
includecapnpcompat_HEADERS = \
|
||||||
|
src/capnp/compat/json.h \
|
||||||
|
src/capnp/compat/json.capnp.h
|
||||||
|
|
||||||
|
if BUILD_KJ_TLS
|
||||||
|
MAYBE_KJ_TLS_LA=libkj-tls.la
|
||||||
|
MAYBE_KJ_TLS_TESTS= \
|
||||||
|
src/kj/compat/readiness-io-test.c++ \
|
||||||
|
src/kj/compat/tls-test.c++
|
||||||
|
else
|
||||||
|
MAYBE_KJ_TLS_LA=
|
||||||
|
MAYBE_KJ_TLS_TESTS=
|
||||||
|
endif
|
||||||
|
|
||||||
|
if LITE_MODE
|
||||||
|
lib_LTLIBRARIES = libkj.la libkj-test.la libcapnp.la
|
||||||
|
else
|
||||||
|
lib_LTLIBRARIES = libkj.la libkj-test.la libkj-async.la libkj-http.la $(MAYBE_KJ_TLS_LA) libcapnp.la libcapnp-rpc.la libcapnp-json.la libcapnpc.la
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Don't include security release in soname -- we want to replace old binaries
|
||||||
|
# in this case.
|
||||||
|
SO_VERSION = $(shell echo $(VERSION) | sed -e 's/^\([0-9]*[.][0-9]*[.][0-9]*\)\([.][0-9]*\)*\(-.*\)*$$/\1\3/g')
|
||||||
|
|
||||||
|
libkj_la_LIBADD = $(PTHREAD_LIBS)
|
||||||
|
libkj_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
|
||||||
|
libkj_la_SOURCES= \
|
||||||
|
src/kj/common.c++ \
|
||||||
|
src/kj/units.c++ \
|
||||||
|
src/kj/memory.c++ \
|
||||||
|
src/kj/refcount.c++ \
|
||||||
|
src/kj/array.c++ \
|
||||||
|
src/kj/string.c++ \
|
||||||
|
src/kj/string-tree.c++ \
|
||||||
|
src/kj/hash.c++ \
|
||||||
|
src/kj/table.c++ \
|
||||||
|
src/kj/encoding.c++ \
|
||||||
|
src/kj/exception.c++ \
|
||||||
|
src/kj/debug.c++ \
|
||||||
|
src/kj/arena.c++ \
|
||||||
|
src/kj/io.c++ \
|
||||||
|
src/kj/mutex.c++ \
|
||||||
|
src/kj/thread.c++ \
|
||||||
|
src/kj/time.c++ \
|
||||||
|
src/kj/filesystem.c++ \
|
||||||
|
src/kj/filesystem-disk-unix.c++ \
|
||||||
|
src/kj/filesystem-disk-win32.c++ \
|
||||||
|
src/kj/test-helpers.c++ \
|
||||||
|
src/kj/main.c++ \
|
||||||
|
src/kj/parse/char.c++
|
||||||
|
|
||||||
|
libkj_test_la_LIBADD = libkj.la $(PTHREAD_LIBS)
|
||||||
|
libkj_test_la_LDFLAGS = -release $(VERSION) -no-undefined
|
||||||
|
libkj_test_la_SOURCES = src/kj/test.c++
|
||||||
|
|
||||||
|
if !LITE_MODE
|
||||||
|
libkj_async_la_LIBADD = libkj.la $(ASYNC_LIBS) $(PTHREAD_LIBS)
|
||||||
|
libkj_async_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
|
||||||
|
libkj_async_la_SOURCES= \
|
||||||
|
src/kj/async.c++ \
|
||||||
|
src/kj/async-unix.c++ \
|
||||||
|
src/kj/async-win32.c++ \
|
||||||
|
src/kj/async-io.c++ \
|
||||||
|
src/kj/async-io-unix.c++ \
|
||||||
|
src/kj/async-io-win32.c++ \
|
||||||
|
src/kj/timer.c++
|
||||||
|
|
||||||
|
libkj_http_la_LIBADD = libkj-async.la libkj.la $(ASYNC_LIBS) $(PTHREAD_LIBS)
|
||||||
|
libkj_http_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
|
||||||
|
libkj_http_la_SOURCES= \
|
||||||
|
src/kj/compat/url.c++ \
|
||||||
|
src/kj/compat/http.c++ \
|
||||||
|
src/kj/compat/gzip.c++
|
||||||
|
|
||||||
|
libkj_tls_la_LIBADD = libkj-async.la libkj.la -lssl -lcrypto $(ASYNC_LIBS) $(PTHREAD_LIBS)
|
||||||
|
libkj_tls_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
|
||||||
|
libkj_tls_la_SOURCES= \
|
||||||
|
src/kj/compat/readiness-io.c++ \
|
||||||
|
src/kj/compat/tls.c++
|
||||||
|
|
||||||
|
endif !LITE_MODE
|
||||||
|
|
||||||
|
if !LITE_MODE
|
||||||
|
heavy_sources = \
|
||||||
|
src/capnp/schema.c++ \
|
||||||
|
src/capnp/schema-loader.c++ \
|
||||||
|
src/capnp/dynamic.c++ \
|
||||||
|
src/capnp/stringify.c++
|
||||||
|
endif !LITE_MODE
|
||||||
|
|
||||||
|
libcapnp_la_LIBADD = libkj.la $(PTHREAD_LIBS)
|
||||||
|
libcapnp_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
|
||||||
|
libcapnp_la_SOURCES= \
|
||||||
|
src/capnp/c++.capnp.c++ \
|
||||||
|
src/capnp/blob.c++ \
|
||||||
|
src/capnp/arena.h \
|
||||||
|
src/capnp/arena.c++ \
|
||||||
|
src/capnp/layout.c++ \
|
||||||
|
src/capnp/list.c++ \
|
||||||
|
src/capnp/any.c++ \
|
||||||
|
src/capnp/message.c++ \
|
||||||
|
src/capnp/schema.capnp.c++ \
|
||||||
|
src/capnp/serialize.c++ \
|
||||||
|
src/capnp/serialize-packed.c++ \
|
||||||
|
$(heavy_sources)
|
||||||
|
|
||||||
|
if !LITE_MODE
|
||||||
|
|
||||||
|
libcapnp_rpc_la_LIBADD = libcapnp.la libkj-async.la libkj.la $(ASYNC_LIBS) $(PTHREAD_LIBS)
|
||||||
|
libcapnp_rpc_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
|
||||||
|
libcapnp_rpc_la_SOURCES= \
|
||||||
|
src/capnp/serialize-async.c++ \
|
||||||
|
src/capnp/capability.c++ \
|
||||||
|
src/capnp/membrane.c++ \
|
||||||
|
src/capnp/dynamic-capability.c++ \
|
||||||
|
src/capnp/rpc.c++ \
|
||||||
|
src/capnp/rpc.capnp.c++ \
|
||||||
|
src/capnp/rpc-twoparty.c++ \
|
||||||
|
src/capnp/rpc-twoparty.capnp.c++ \
|
||||||
|
src/capnp/persistent.capnp.c++ \
|
||||||
|
src/capnp/ez-rpc.c++
|
||||||
|
|
||||||
|
libcapnp_json_la_LIBADD = libcapnp.la libkj.la $(PTHREAD_LIBS)
|
||||||
|
libcapnp_json_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
|
||||||
|
libcapnp_json_la_SOURCES= \
|
||||||
|
src/capnp/compat/json.c++ \
|
||||||
|
src/capnp/compat/json.capnp.c++
|
||||||
|
|
||||||
|
libcapnpc_la_LIBADD = libcapnp.la libkj.la $(PTHREAD_LIBS)
|
||||||
|
libcapnpc_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
|
||||||
|
libcapnpc_la_SOURCES= \
|
||||||
|
src/capnp/compiler/type-id.h \
|
||||||
|
src/capnp/compiler/type-id.c++ \
|
||||||
|
src/capnp/compiler/error-reporter.h \
|
||||||
|
src/capnp/compiler/error-reporter.c++ \
|
||||||
|
src/capnp/compiler/lexer.capnp.h \
|
||||||
|
src/capnp/compiler/lexer.capnp.c++ \
|
||||||
|
src/capnp/compiler/lexer.h \
|
||||||
|
src/capnp/compiler/lexer.c++ \
|
||||||
|
src/capnp/compiler/grammar.capnp.h \
|
||||||
|
src/capnp/compiler/grammar.capnp.c++ \
|
||||||
|
src/capnp/compiler/parser.h \
|
||||||
|
src/capnp/compiler/parser.c++ \
|
||||||
|
src/capnp/compiler/node-translator.h \
|
||||||
|
src/capnp/compiler/node-translator.c++ \
|
||||||
|
src/capnp/compiler/compiler.h \
|
||||||
|
src/capnp/compiler/compiler.c++ \
|
||||||
|
src/capnp/schema-parser.c++ \
|
||||||
|
src/capnp/serialize-text.c++
|
||||||
|
|
||||||
|
bin_PROGRAMS = capnp capnpc-capnp capnpc-c++
|
||||||
|
|
||||||
|
capnp_LDADD = libcapnpc.la libcapnp-json.la libcapnp.la libkj.la $(PTHREAD_LIBS)
|
||||||
|
capnp_SOURCES = \
|
||||||
|
src/capnp/compiler/module-loader.h \
|
||||||
|
src/capnp/compiler/module-loader.c++ \
|
||||||
|
src/capnp/compiler/capnp.c++
|
||||||
|
|
||||||
|
capnpc_capnp_LDADD = libcapnp.la libkj.la $(PTHREAD_LIBS)
|
||||||
|
capnpc_capnp_SOURCES = src/capnp/compiler/capnpc-capnp.c++
|
||||||
|
|
||||||
|
capnpc_c___LDADD = libcapnp.la libkj.la $(PTHREAD_LIBS)
|
||||||
|
capnpc_c___SOURCES = src/capnp/compiler/capnpc-c++.c++
|
||||||
|
|
||||||
|
# Symlink capnpc -> capnp. The capnp binary will behave like the old capnpc
|
||||||
|
# binary (i.e. like "capnp compile") when invoked via this symlink.
|
||||||
|
#
|
||||||
|
# Also attempt to run ldconfig, because otherwise users get confused. If
|
||||||
|
# it fails (e.g. because the platform doesn't have it, or because the
|
||||||
|
# user doesn't have root privileges), don't worry about it.
|
||||||
|
install-exec-hook:
|
||||||
|
ln -sf capnp $(DESTDIR)$(bindir)/capnpc
|
||||||
|
ldconfig < /dev/null > /dev/null 2>&1 || true
|
||||||
|
|
||||||
|
uninstall-hook:
|
||||||
|
rm -f $(DESTDIR)$(bindir)/capnpc
|
||||||
|
|
||||||
|
else LITE_MODE
|
||||||
|
|
||||||
|
install-exec-hook:
|
||||||
|
ldconfig < /dev/null > /dev/null 2>&1 || true
|
||||||
|
|
||||||
|
endif LITE_MODE
|
||||||
|
|
||||||
|
# Source files intentionally not included in the dist at this time:
|
||||||
|
# src/capnp/serialize-snappy*
|
||||||
|
# src/capnp/benchmark/...
|
||||||
|
# src/capnp/compiler/...
|
||||||
|
|
||||||
|
# Tests ==============================================================
|
||||||
|
|
||||||
|
test_capnpc_inputs = \
|
||||||
|
src/capnp/test.capnp \
|
||||||
|
src/capnp/test-import.capnp \
|
||||||
|
src/capnp/test-import2.capnp \
|
||||||
|
src/capnp/compat/json-test.capnp
|
||||||
|
|
||||||
|
test_capnpc_outputs = \
|
||||||
|
src/capnp/test.capnp.c++ \
|
||||||
|
src/capnp/test.capnp.h \
|
||||||
|
src/capnp/test-import.capnp.c++ \
|
||||||
|
src/capnp/test-import.capnp.h \
|
||||||
|
src/capnp/test-import2.capnp.c++ \
|
||||||
|
src/capnp/test-import2.capnp.h \
|
||||||
|
src/capnp/compat/json-test.capnp.c++ \
|
||||||
|
src/capnp/compat/json-test.capnp.h
|
||||||
|
|
||||||
|
if USE_EXTERNAL_CAPNP
|
||||||
|
|
||||||
|
test_capnpc_middleman: $(test_capnpc_inputs)
|
||||||
|
@$(MKDIR_P) src
|
||||||
|
$(CAPNP) compile --src-prefix=$(srcdir)/src -o$(CAPNPC_CXX):src -I$(srcdir)/src $^
|
||||||
|
touch test_capnpc_middleman
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
test_capnpc_middleman: capnp$(EXEEXT) capnpc-c++$(EXEEXT) $(test_capnpc_inputs)
|
||||||
|
@$(MKDIR_P) src
|
||||||
|
echo $^ | (read CAPNP CAPNPC_CXX SOURCES && ./$$CAPNP compile --src-prefix=$(srcdir)/src -o./$$CAPNPC_CXX:src -I$(srcdir)/src $$SOURCES)
|
||||||
|
touch test_capnpc_middleman
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(test_capnpc_outputs): test_capnpc_middleman
|
||||||
|
|
||||||
|
BUILT_SOURCES = $(test_capnpc_outputs)
|
||||||
|
|
||||||
|
check_LIBRARIES = libcapnp-test.a
|
||||||
|
libcapnp_test_a_SOURCES = \
|
||||||
|
src/capnp/test-util.c++ \
|
||||||
|
src/capnp/test-util.h
|
||||||
|
nodist_libcapnp_test_a_SOURCES = $(test_capnpc_outputs)
|
||||||
|
|
||||||
|
if LITE_MODE
|
||||||
|
|
||||||
|
check_PROGRAMS = capnp-test
|
||||||
|
compiler_tests =
|
||||||
|
capnp_test_LDADD = libcapnp-test.a libcapnp.la libkj-test.la libkj.la
|
||||||
|
|
||||||
|
else !LITE_MODE
|
||||||
|
|
||||||
|
check_PROGRAMS = capnp-test capnp-evolution-test capnp-afl-testcase
|
||||||
|
heavy_tests = \
|
||||||
|
src/kj/async-test.c++ \
|
||||||
|
src/kj/async-unix-test.c++ \
|
||||||
|
src/kj/async-win32-test.c++ \
|
||||||
|
src/kj/async-io-test.c++ \
|
||||||
|
src/kj/parse/common-test.c++ \
|
||||||
|
src/kj/parse/char-test.c++ \
|
||||||
|
src/kj/std/iostream-test.c++ \
|
||||||
|
src/kj/compat/url-test.c++ \
|
||||||
|
src/kj/compat/http-test.c++ \
|
||||||
|
src/kj/compat/gzip-test.c++ \
|
||||||
|
$(MAYBE_KJ_TLS_TESTS) \
|
||||||
|
src/capnp/canonicalize-test.c++ \
|
||||||
|
src/capnp/capability-test.c++ \
|
||||||
|
src/capnp/membrane-test.c++ \
|
||||||
|
src/capnp/schema-test.c++ \
|
||||||
|
src/capnp/schema-loader-test.c++ \
|
||||||
|
src/capnp/schema-parser-test.c++ \
|
||||||
|
src/capnp/dynamic-test.c++ \
|
||||||
|
src/capnp/stringify-test.c++ \
|
||||||
|
src/capnp/serialize-async-test.c++ \
|
||||||
|
src/capnp/serialize-text-test.c++ \
|
||||||
|
src/capnp/rpc-test.c++ \
|
||||||
|
src/capnp/rpc-twoparty-test.c++ \
|
||||||
|
src/capnp/ez-rpc-test.c++ \
|
||||||
|
src/capnp/compat/json-test.c++ \
|
||||||
|
src/capnp/compiler/lexer-test.c++ \
|
||||||
|
src/capnp/compiler/type-id-test.c++
|
||||||
|
capnp_test_LDADD = \
|
||||||
|
libcapnp-test.a \
|
||||||
|
libcapnpc.la \
|
||||||
|
libcapnp-rpc.la \
|
||||||
|
libcapnp-json.la \
|
||||||
|
libcapnp.la \
|
||||||
|
libkj-http.la \
|
||||||
|
$(MAYBE_KJ_TLS_LA) \
|
||||||
|
libkj-async.la \
|
||||||
|
libkj-test.la \
|
||||||
|
libkj.la \
|
||||||
|
$(ASYNC_LIBS) \
|
||||||
|
$(PTHREAD_LIBS)
|
||||||
|
|
||||||
|
endif !LITE_MODE
|
||||||
|
|
||||||
|
capnp_test_CPPFLAGS = -Wno-deprecated-declarations
|
||||||
|
capnp_test_SOURCES = \
|
||||||
|
src/kj/common-test.c++ \
|
||||||
|
src/kj/memory-test.c++ \
|
||||||
|
src/kj/refcount-test.c++ \
|
||||||
|
src/kj/array-test.c++ \
|
||||||
|
src/kj/string-test.c++ \
|
||||||
|
src/kj/string-tree-test.c++ \
|
||||||
|
src/kj/table-test.c++ \
|
||||||
|
src/kj/map-test.c++ \
|
||||||
|
src/kj/encoding-test.c++ \
|
||||||
|
src/kj/exception-test.c++ \
|
||||||
|
src/kj/debug-test.c++ \
|
||||||
|
src/kj/arena-test.c++ \
|
||||||
|
src/kj/units-test.c++ \
|
||||||
|
src/kj/tuple-test.c++ \
|
||||||
|
src/kj/one-of-test.c++ \
|
||||||
|
src/kj/function-test.c++ \
|
||||||
|
src/kj/io-test.c++ \
|
||||||
|
src/kj/mutex-test.c++ \
|
||||||
|
src/kj/threadlocal-test.c++ \
|
||||||
|
src/kj/threadlocal-pthread-test.c++ \
|
||||||
|
src/kj/filesystem-test.c++ \
|
||||||
|
src/kj/filesystem-disk-test.c++ \
|
||||||
|
src/kj/test-test.c++ \
|
||||||
|
src/capnp/common-test.c++ \
|
||||||
|
src/capnp/blob-test.c++ \
|
||||||
|
src/capnp/endian-test.c++ \
|
||||||
|
src/capnp/endian-fallback-test.c++ \
|
||||||
|
src/capnp/endian-reverse-test.c++ \
|
||||||
|
src/capnp/layout-test.c++ \
|
||||||
|
src/capnp/any-test.c++ \
|
||||||
|
src/capnp/message-test.c++ \
|
||||||
|
src/capnp/encoding-test.c++ \
|
||||||
|
src/capnp/orphan-test.c++ \
|
||||||
|
src/capnp/serialize-test.c++ \
|
||||||
|
src/capnp/serialize-packed-test.c++ \
|
||||||
|
src/capnp/fuzz-test.c++ \
|
||||||
|
$(heavy_tests)
|
||||||
|
|
||||||
|
if !LITE_MODE
|
||||||
|
capnp_evolution_test_LDADD = libcapnpc.la libcapnp.la libkj.la
|
||||||
|
capnp_evolution_test_SOURCES = src/capnp/compiler/evolution-test.c++
|
||||||
|
|
||||||
|
capnp_afl_testcase_LDADD = libcapnp-test.a libcapnp-rpc.la libcapnp.la libkj.la libkj-async.la
|
||||||
|
capnp_afl_testcase_SOURCES = src/capnp/afl-testcase.c++
|
||||||
|
endif !LITE_MODE
|
||||||
|
|
||||||
|
if LITE_MODE
|
||||||
|
TESTS = capnp-test
|
||||||
|
else !LITE_MODE
|
||||||
|
TESTS = capnp-test capnp-evolution-test src/capnp/compiler/capnp-test.sh
|
||||||
|
endif !LITE_MODE
|
|
@ -0,0 +1,34 @@
|
||||||
|
.PHONY: all once continuous continuous-opt clean
|
||||||
|
|
||||||
|
EKAM=`which ekam || echo .ekam/bin/ekam`
|
||||||
|
|
||||||
|
ifeq ($(CXX),clang++)
|
||||||
|
# Clang's verbose diagnostics don't play nice with the Ekam Eclipse plugin's error parsing,
|
||||||
|
# so disable them. Also enable some useful Clang warnings (dunno if GCC supports them, and don't
|
||||||
|
# care).
|
||||||
|
EXTRA_FLAG=-fno-caret-diagnostics -Wglobal-constructors -Wextra-semi -Werror=return-type
|
||||||
|
# EXTRA_FLAG=-fno-caret-diagnostics -Weverything -Wno-c++98-compat -Wno-shadow -Wno-c++98-compat-pedantic -Wno-padded -Wno-weak-vtables -Wno-gnu -Wno-unused-parameter -Wno-sign-conversion -Wno-undef -Wno-shorten-64-to-32 -Wno-conversion -Wno-unreachable-code -Wno-non-virtual-dtor
|
||||||
|
else
|
||||||
|
EXTRA_FLAG=
|
||||||
|
endif
|
||||||
|
|
||||||
|
all:
|
||||||
|
echo "You probably accidentally told Eclipse to build. Stopping."
|
||||||
|
|
||||||
|
once:
|
||||||
|
CXXFLAGS="$(EXTRA_FLAG) -std=c++14 -O2 -DNDEBUG -Wall" LIBS='-lz -pthread' $(EKAM) -j6
|
||||||
|
|
||||||
|
continuous:
|
||||||
|
CXXFLAGS="$(EXTRA_FLAG) -std=c++14 -g -DCAPNP_DEBUG_TYPES=1 -Wall" LIBS='-lz -pthread' $(EKAM) -j6 -c -n :51315
|
||||||
|
|
||||||
|
continuous-opt:
|
||||||
|
CXXFLAGS="$(EXTRA_FLAG) -std=c++14 -O2 -DNDEBUG -Wall" LIBS='-lz -pthread' $(EKAM) -j6 -c -n :51315
|
||||||
|
|
||||||
|
continuous-opt3:
|
||||||
|
CXXFLAGS="$(EXTRA_FLAG) -std=c++14 -O3 -DNDEBUG -Wall" LIBS='-lz -pthread' $(EKAM) -j6 -c -n :51315
|
||||||
|
|
||||||
|
continuous-opts:
|
||||||
|
CXXFLAGS="$(EXTRA_FLAG) -std=c++14 -Os -DNDEBUG -Wall" LIBS='-lz -pthread' $(EKAM) -j6 -c -n :51315
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf bin lib tmp
|
|
@ -0,0 +1,27 @@
|
||||||
|
Cap'n Proto - Insanely Fast Data Serialization Format
|
||||||
|
Copyright 2013-2015 Sandstorm Development Group, Inc.
|
||||||
|
https://capnproto.org
|
||||||
|
|
||||||
|
Cap'n Proto is an insanely fast data interchange format and capability-based
|
||||||
|
RPC system. Think JSON, except binary. Or think of Google's Protocol Buffers
|
||||||
|
(http://protobuf.googlecode.com), except faster. In fact, in benchmarks,
|
||||||
|
Cap'n Proto is INFINITY TIMES faster than Protocol Buffers.
|
||||||
|
|
||||||
|
Full installation and usage instructions and other documentation are maintained
|
||||||
|
on the Cap'n Proto web site:
|
||||||
|
http://kentonv.github.io/capnproto/install.html
|
||||||
|
|
||||||
|
WARNING: Cap'n Proto requires a modern compiler. See the above link for
|
||||||
|
detailed requirements.
|
||||||
|
|
||||||
|
To build and install (from a release package), simply do:
|
||||||
|
./configure
|
||||||
|
make -j4 check
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
The -j4 allows the build to use up to four processor cores instead of one.
|
||||||
|
You can increase this number if you have more cores. Specifying "check"
|
||||||
|
says to run tests in addition to building. This can be omitted to make the
|
||||||
|
build slightly faster, but running tests and reporting failures back to the
|
||||||
|
developers helps us out!
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "Choose test case:"
|
||||||
|
echo "1) TestAllTypes parsing"
|
||||||
|
echo "2) TestLists parsing"
|
||||||
|
echo "3) Canonicalization"
|
||||||
|
|
||||||
|
read -p "choice: " -n 1 TESTCASE
|
||||||
|
echo
|
||||||
|
|
||||||
|
case "$TESTCASE" in
|
||||||
|
1 )
|
||||||
|
TESTDATA=binary
|
||||||
|
FLAGS=
|
||||||
|
TESTNAME=default
|
||||||
|
;;
|
||||||
|
2 )
|
||||||
|
TESTDATA=lists.binary
|
||||||
|
FLAGS=--lists
|
||||||
|
TESTNAME=lists
|
||||||
|
;;
|
||||||
|
3 )
|
||||||
|
TESTDATA=binary
|
||||||
|
FLAGS=--canonicalize
|
||||||
|
TESTNAME=canonicalize
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
echo "Invalid choice: $TESTCASE" >&2
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "Choose compiler:"
|
||||||
|
echo "1) GCC"
|
||||||
|
echo "2) Clang"
|
||||||
|
|
||||||
|
read -p "choice: " -n 1 TESTCASE
|
||||||
|
echo
|
||||||
|
|
||||||
|
case "$TESTCASE" in
|
||||||
|
1 )
|
||||||
|
export CXX=afl-g++
|
||||||
|
;;
|
||||||
|
2 )
|
||||||
|
export CXX=afl-clang++
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
echo "Invalid choice: $TESTCASE" >&2
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -e Makefile ]; then
|
||||||
|
if ! grep -q '^CXX *= *'"$CXX" Makefile; then
|
||||||
|
# Wrong compiler used.
|
||||||
|
make distclean
|
||||||
|
$(dirname $0)/configure --disable-shared
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
$(dirname $0)/configure --disable-shared
|
||||||
|
fi
|
||||||
|
|
||||||
|
make -j$(nproc)
|
||||||
|
make -j$(nproc) capnp-afl-testcase
|
||||||
|
|
||||||
|
NOW=$(date +%Y-%m-%d.%H-%M-%S).$TESTNAME.$CXX
|
||||||
|
|
||||||
|
mkdir afl.$NOW.inputs afl.$NOW.findings
|
||||||
|
|
||||||
|
cp $(dirname $0)/src/capnp/testdata/$TESTDATA afl.$NOW.inputs
|
||||||
|
|
||||||
|
afl-fuzz -i afl.$NOW.inputs -o afl.$NOW.findings -- ./capnp-afl-testcase $FLAGS
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Cap'n Proto CMake Package Configuration
|
||||||
|
#
|
||||||
|
# When configured and installed, this file enables client projects to find Cap'n Proto using
|
||||||
|
# CMake's find_package() command. It adds imported targets in the CapnProto:: namespace, such as
|
||||||
|
# CapnProto::kj, CapnProto::capnp, etc. (one target for each file in pkgconfig/*.pc.in), defines
|
||||||
|
# the capnp_generate_cpp() function, and exposes some variables for compatibility with the original
|
||||||
|
# FindCapnProto.cmake module.
|
||||||
|
#
|
||||||
|
# Example usage:
|
||||||
|
# find_package(CapnProto)
|
||||||
|
# capnp_generate_cpp(CAPNP_SRCS CAPNP_HDRS schema.capnp)
|
||||||
|
# add_executable(foo main.cpp ${CAPNP_SRCS})
|
||||||
|
# target_link_libraries(foo PRIVATE CapnProto::capnp)
|
||||||
|
# target_include_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
#
|
||||||
|
# If you are using RPC features, use 'CapnProto::capnp-rpc' in the target_link_libraries() call.
|
||||||
|
#
|
||||||
|
# Paths to `capnp` and `capnpc-c++` are exposed in the following variables:
|
||||||
|
# CAPNP_EXECUTABLE
|
||||||
|
# Path to the `capnp` tool (can be set to override).
|
||||||
|
# CAPNPC_CXX_EXECUTABLE
|
||||||
|
# Path to the `capnpc-c++` tool (can be set to override).
|
||||||
|
#
|
||||||
|
# For FindCapnProto.cmake compatibility, the following variables are also provided. Please prefer
|
||||||
|
# using the imported targets in new CMake code.
|
||||||
|
# CAPNP_INCLUDE_DIRS
|
||||||
|
# Include directories for the library's headers.
|
||||||
|
# CANP_LIBRARIES
|
||||||
|
# The Cap'n Proto library paths.
|
||||||
|
# CAPNP_LIBRARIES_LITE
|
||||||
|
# Paths to only the 'lite' libraries.
|
||||||
|
# CAPNP_DEFINITIONS
|
||||||
|
# Compiler definitions required for building with the library.
|
||||||
|
# CAPNP_FOUND
|
||||||
|
# Set if the libraries have been located (prefer using CapnProto_FOUND in new code).
|
||||||
|
#
|
||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
set(CapnProto_VERSION @VERSION@)
|
||||||
|
|
||||||
|
set(CAPNP_EXECUTABLE $<TARGET_FILE:CapnProto::capnp_tool>
|
||||||
|
CACHE FILEPATH "Location of capnp executable")
|
||||||
|
set(CAPNPC_CXX_EXECUTABLE $<TARGET_FILE:CapnProto::capnpc_cpp>
|
||||||
|
CACHE FILEPATH "Location of capnpc-c++ executable")
|
||||||
|
set(CAPNP_INCLUDE_DIRECTORY "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
|
||||||
|
|
||||||
|
# work around http://public.kitware.com/Bug/view.php?id=15258
|
||||||
|
if(NOT _IMPORT_PREFIX)
|
||||||
|
set(_IMPORT_PREFIX ${PACKAGE_PREFIX_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/CapnProtoTargets.cmake")
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/CapnProtoMacros.cmake")
|
||||||
|
|
||||||
|
|
||||||
|
# FindCapnProto.cmake provides dependency information via several CAPNP_-prefixed variables. New
|
||||||
|
# code should not rely on these variables, but prefer linking directly to the imported targets we
|
||||||
|
# now provide. However, we should still set these variables to ease the transition for projects
|
||||||
|
# which currently depend on the find-module.
|
||||||
|
|
||||||
|
set(CAPNP_INCLUDE_DIRS ${CAPNP_INCLUDE_DIRECTORY})
|
||||||
|
|
||||||
|
# No need to list all libraries, just the leaves of the dependency tree.
|
||||||
|
set(CAPNP_LIBRARIES_LITE CapnProto::capnp)
|
||||||
|
set(CAPNP_LIBRARIES CapnProto::capnp-rpc CapnProto::capnp-json
|
||||||
|
CapnProto::kj-http CapnProto::kj-test)
|
||||||
|
|
||||||
|
set(CAPNP_DEFINITIONS)
|
||||||
|
if(TARGET CapnProto::capnp AND NOT TARGET CapnProto::capnp-rpc)
|
||||||
|
set(CAPNP_DEFINITIONS -DCAPNP_LITE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CAPNP_FOUND ${CapnProto_FOUND})
|
|
@ -0,0 +1,36 @@
|
||||||
|
# This is a copy of /usr/share/cmake-3.5/Modules/BasicConfigVersion-AnyNewerVersion.cmake.in, with
|
||||||
|
# the following change:
|
||||||
|
# - @CVF_VERSION renamed to @PACKAGE_VERSION@. Autoconf defines a PACKAGE_VERSION
|
||||||
|
# output variable for us, so might as well take advantage of that.
|
||||||
|
|
||||||
|
# This is a basic version file for the Config-mode of find_package().
|
||||||
|
# It is used by write_basic_package_version_file() as input file for configure_file()
|
||||||
|
# to create a version-file which can be installed along a config.cmake file.
|
||||||
|
#
|
||||||
|
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
|
||||||
|
# the requested version string are exactly the same and it sets
|
||||||
|
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version.
|
||||||
|
# The variable PACKAGE_VERSION must be set before calling configure_file().
|
||||||
|
|
||||||
|
set(PACKAGE_VERSION "@PACKAGE_VERSION@")
|
||||||
|
|
||||||
|
if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
|
||||||
|
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||||
|
else()
|
||||||
|
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||||
|
if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
|
||||||
|
set(PACKAGE_VERSION_EXACT TRUE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
|
||||||
|
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "@CMAKE_SIZEOF_VOID_P@" STREQUAL "")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
|
||||||
|
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "@CMAKE_SIZEOF_VOID_P@")
|
||||||
|
math(EXPR installedBits "@CMAKE_SIZEOF_VOID_P@ * 8")
|
||||||
|
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
|
||||||
|
set(PACKAGE_VERSION_UNSUITABLE TRUE)
|
||||||
|
endif()
|
|
@ -0,0 +1,121 @@
|
||||||
|
# CAPNP_GENERATE_CPP ===========================================================
|
||||||
|
#
|
||||||
|
# Example usage:
|
||||||
|
# find_package(CapnProto)
|
||||||
|
# capnp_generate_cpp(CAPNP_SRCS CAPNP_HDRS schema.capnp)
|
||||||
|
# add_executable(foo main.cpp ${CAPNP_SRCS})
|
||||||
|
# target_link_libraries(foo PRIVATE CapnProto::capnp-rpc)
|
||||||
|
# target_include_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
#
|
||||||
|
# If you are not using the RPC features you can use 'CapnProto::capnp' in the
|
||||||
|
# target_link_libraries call
|
||||||
|
#
|
||||||
|
# Configuration variables (optional):
|
||||||
|
# CAPNPC_OUTPUT_DIR
|
||||||
|
# Directory to place compiled schema sources (default: CMAKE_CURRENT_BINARY_DIR).
|
||||||
|
# CAPNPC_IMPORT_DIRS
|
||||||
|
# List of additional include directories for the schema compiler.
|
||||||
|
# (CAPNPC_SRC_PREFIX and CAPNP_INCLUDE_DIRECTORY are always included.)
|
||||||
|
# CAPNPC_SRC_PREFIX
|
||||||
|
# Schema file source prefix (default: CMAKE_CURRENT_SOURCE_DIR).
|
||||||
|
# CAPNPC_FLAGS
|
||||||
|
# Additional flags to pass to the schema compiler.
|
||||||
|
#
|
||||||
|
# TODO: convert to cmake_parse_arguments
|
||||||
|
|
||||||
|
function(CAPNP_GENERATE_CPP SOURCES HEADERS)
|
||||||
|
if(NOT ARGN)
|
||||||
|
message(SEND_ERROR "CAPNP_GENERATE_CPP() called without any source files.")
|
||||||
|
endif()
|
||||||
|
set(tool_depends ${EMPTY_STRING})
|
||||||
|
#Use cmake targets available
|
||||||
|
if(TARGET capnp_tool)
|
||||||
|
set(CAPNP_EXECUTABLE capnp_tool)
|
||||||
|
GET_TARGET_PROPERTY(CAPNPC_CXX_EXECUTABLE capnpc_cpp CAPNPC_CXX_EXECUTABLE)
|
||||||
|
GET_TARGET_PROPERTY(CAPNP_INCLUDE_DIRECTORY capnp_tool CAPNP_INCLUDE_DIRECTORY)
|
||||||
|
list(APPEND tool_depends capnp_tool capnpc_cpp)
|
||||||
|
endif()
|
||||||
|
if(NOT CAPNP_EXECUTABLE)
|
||||||
|
message(SEND_ERROR "Could not locate capnp executable (CAPNP_EXECUTABLE).")
|
||||||
|
endif()
|
||||||
|
if(NOT CAPNPC_CXX_EXECUTABLE)
|
||||||
|
message(SEND_ERROR "Could not locate capnpc-c++ executable (CAPNPC_CXX_EXECUTABLE).")
|
||||||
|
endif()
|
||||||
|
if(NOT CAPNP_INCLUDE_DIRECTORY)
|
||||||
|
message(SEND_ERROR "Could not locate capnp header files (CAPNP_INCLUDE_DIRECTORY).")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(DEFINED CAPNPC_OUTPUT_DIR)
|
||||||
|
# Prepend a ':' to get the format for the '-o' flag right
|
||||||
|
set(output_dir ":${CAPNPC_OUTPUT_DIR}")
|
||||||
|
else()
|
||||||
|
set(output_dir ":.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT DEFINED CAPNPC_SRC_PREFIX)
|
||||||
|
set(CAPNPC_SRC_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
endif()
|
||||||
|
get_filename_component(CAPNPC_SRC_PREFIX "${CAPNPC_SRC_PREFIX}" ABSOLUTE)
|
||||||
|
|
||||||
|
# Default compiler includes. Note that in capnp's own test usage of capnp_generate_cpp(), these
|
||||||
|
# two variables will end up evaluating to the same directory. However, it's difficult to
|
||||||
|
# deduplicate them because if CAPNP_INCLUDE_DIRECTORY came from the capnp_tool target property,
|
||||||
|
# then it must be a generator expression in order to handle usages in both the build tree and the
|
||||||
|
# install tree. This vastly overcomplicates duplication detection, so the duplication doesn't seem
|
||||||
|
# worth fixing.
|
||||||
|
set(include_path -I "${CAPNPC_SRC_PREFIX}" -I "${CAPNP_INCLUDE_DIRECTORY}")
|
||||||
|
|
||||||
|
if(DEFINED CAPNPC_IMPORT_DIRS)
|
||||||
|
# Append each directory as a series of '-I' flags in ${include_path}
|
||||||
|
foreach(directory ${CAPNPC_IMPORT_DIRS})
|
||||||
|
get_filename_component(absolute_path "${directory}" ABSOLUTE)
|
||||||
|
list(APPEND include_path -I "${absolute_path}")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(${SOURCES})
|
||||||
|
set(${HEADERS})
|
||||||
|
foreach(schema_file ${ARGN})
|
||||||
|
get_filename_component(file_path "${schema_file}" ABSOLUTE)
|
||||||
|
get_filename_component(file_dir "${file_path}" PATH)
|
||||||
|
if(NOT EXISTS "${file_path}")
|
||||||
|
message(FATAL_ERROR "Cap'n Proto schema file '${file_path}' does not exist!")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Figure out where the output files will go
|
||||||
|
if (NOT DEFINED CAPNPC_OUTPUT_DIR)
|
||||||
|
set(CAPNPC_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/")
|
||||||
|
endif()
|
||||||
|
# Output files are placed in CAPNPC_OUTPUT_DIR, at a location as if they were
|
||||||
|
# relative to CAPNPC_SRC_PREFIX.
|
||||||
|
string(LENGTH "${CAPNPC_SRC_PREFIX}" prefix_len)
|
||||||
|
string(SUBSTRING "${file_path}" 0 ${prefix_len} output_prefix)
|
||||||
|
if(NOT "${CAPNPC_SRC_PREFIX}" STREQUAL "${output_prefix}")
|
||||||
|
message(SEND_ERROR "Could not determine output path for '${schema_file}' ('${file_path}') with source prefix '${CAPNPC_SRC_PREFIX}' into '${CAPNPC_OUTPUT_DIR}'.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
string(SUBSTRING "${file_path}" ${prefix_len} -1 output_path)
|
||||||
|
set(output_base "${CAPNPC_OUTPUT_DIR}${output_path}")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${output_base}.c++" "${output_base}.h"
|
||||||
|
COMMAND "${CAPNP_EXECUTABLE}"
|
||||||
|
ARGS compile
|
||||||
|
-o ${CAPNPC_CXX_EXECUTABLE}${output_dir}
|
||||||
|
--src-prefix ${CAPNPC_SRC_PREFIX}
|
||||||
|
${include_path}
|
||||||
|
${CAPNPC_FLAGS}
|
||||||
|
${file_path}
|
||||||
|
DEPENDS "${schema_file}" ${tool_depends}
|
||||||
|
COMMENT "Compiling Cap'n Proto schema ${schema_file}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND ${SOURCES} "${output_base}.c++")
|
||||||
|
list(APPEND ${HEADERS} "${output_base}.h")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set_source_files_properties(${${SOURCES}} ${${HEADERS}} PROPERTIES GENERATED TRUE)
|
||||||
|
set(${SOURCES} ${${SOURCES}} PARENT_SCOPE)
|
||||||
|
set(${HEADERS} ${${HEADERS}} PARENT_SCOPE)
|
||||||
|
endfunction()
|
|
@ -0,0 +1,221 @@
|
||||||
|
# This CMake script adds imported targets for each shared library and executable distributed by
|
||||||
|
# Cap'n Proto's autotools build.
|
||||||
|
#
|
||||||
|
# This file IS NOT USED by the CMake build! The CMake build generates its own version of this script
|
||||||
|
# from its set of exported targets. I used such a generated script as a reference when writing this
|
||||||
|
# one.
|
||||||
|
#
|
||||||
|
# The set of library targets provided by this script is automatically generated from the list of .pc
|
||||||
|
# files maintained in configure.ac. The set of executable targets is hard-coded in this file.
|
||||||
|
#
|
||||||
|
# You can request that this script print debugging information by invoking cmake with:
|
||||||
|
#
|
||||||
|
# -DCapnProto_DEBUG=ON
|
||||||
|
#
|
||||||
|
# TODO(someday): Distinguish between debug and release builds. I.e., set IMPORTED_LOCATION_RELEASE
|
||||||
|
# rather than IMPORTED_LOCATION, etc., if this installation was configured as a release build. But
|
||||||
|
# how do we tell? grep for -g in CXXFLAGS?
|
||||||
|
|
||||||
|
if(CMAKE_VERSION VERSION_LESS 3.1)
|
||||||
|
message(FATAL_ERROR "CMake >= 3.1 required")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(forwarded_config_flags)
|
||||||
|
if(CapnProto_FIND_QUIETLY)
|
||||||
|
list(APPEND forwarded_config_flags QUIET)
|
||||||
|
endif()
|
||||||
|
if(CapnProto_FIND_REQUIRED)
|
||||||
|
list(APPEND forwarded_config_flags REQUIRED)
|
||||||
|
endif()
|
||||||
|
# If the consuming project called find_package(CapnProto) with the QUIET or REQUIRED flags, forward
|
||||||
|
# them to calls to find_package(PkgConfig) and pkg_check_modules(). Note that find_dependency()
|
||||||
|
# would do this for us in the former case, but there is no such forwarding wrapper for
|
||||||
|
# pkg_check_modules().
|
||||||
|
|
||||||
|
find_package(PkgConfig ${forwarded_config_flags})
|
||||||
|
if(NOT ${PkgConfig_FOUND})
|
||||||
|
# If we're here, the REQUIRED flag must not have been passed, else we would have had a fatal
|
||||||
|
# error. Nevertheless, a diagnostic for this case is probably nice.
|
||||||
|
if(NOT CapnProto_FIND_QUIETLY)
|
||||||
|
message(WARNING "pkg-config cannot be found")
|
||||||
|
endif()
|
||||||
|
set(CapnProto_FOUND OFF)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
function(_capnp_import_pkg_config_target target)
|
||||||
|
# Add an imported library target named CapnProto::${target}, using the output of various
|
||||||
|
# invocations of `pkg-config ${target}`. The generated imported library target tries to mimic the
|
||||||
|
# behavior of a real CMake-generated imported target as closely as possible.
|
||||||
|
#
|
||||||
|
# Usage: _capnp_import_pkg_config_target(target <all Cap'n Proto targets>)
|
||||||
|
|
||||||
|
set(all_targets ${ARGN})
|
||||||
|
|
||||||
|
pkg_check_modules(${target} ${forwarded_config_flags} ${target})
|
||||||
|
|
||||||
|
if(NOT ${${target}_FOUND})
|
||||||
|
if(NOT CapnProto_FIND_QUIETLY)
|
||||||
|
message(WARNING "CapnProtoConfig.cmake was configured to search for ${target}.pc, but pkg-config cannot find it. Ignoring this target.")
|
||||||
|
endif()
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CapnProto_DEBUG)
|
||||||
|
# Dump the information pkg-config discovered.
|
||||||
|
foreach(var VERSION LIBRARY_DIRS LIBRARIES LDFLAGS_OTHER INCLUDE_DIRS CFLAGS_OTHER)
|
||||||
|
message(STATUS "${target}_${var} = ${${target}_${var}}")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ${${target}_VERSION} VERSION_EQUAL ${CapnProto_VERSION})
|
||||||
|
if(NOT CapnProto_FIND_QUIETLY)
|
||||||
|
message(WARNING "CapnProtoConfig.cmake was configured to search for version ${CapnProto_VERSION}, but ${target} version ${${target}_VERSION} was found. Ignoring this target.")
|
||||||
|
endif()
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Make an educated guess as to what the target's .so and .a filenames must be.
|
||||||
|
set(target_name_shared
|
||||||
|
${CMAKE_SHARED_LIBRARY_PREFIX}${target}-${CapnProto_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX})
|
||||||
|
set(target_name_static
|
||||||
|
${CMAKE_STATIC_LIBRARY_PREFIX}${target}${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||||
|
|
||||||
|
# Find the actual target's file. find_library() sets a cache variable, so I made the variable name
|
||||||
|
# unique-ish.
|
||||||
|
find_library(CapnProto_${target}_IMPORTED_LOCATION
|
||||||
|
NAMES ${target_name_shared} ${target_name_static} # prefer libfoo-version.so over libfoo.a
|
||||||
|
PATHS ${${target}_LIBRARY_DIRS}
|
||||||
|
NO_DEFAULT_PATH
|
||||||
|
)
|
||||||
|
# If the installed version of Cap'n Proto is in a system location, pkg-config will not have filled
|
||||||
|
# in ${target}_LIBRARY_DIRS. To account for this, fall back to a regular search.
|
||||||
|
find_library(CapnProto_${target}_IMPORTED_LOCATION
|
||||||
|
NAMES ${target_name_shared} ${target_name_static} # prefer libfoo-version.so over libfoo.a
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT CapnProto_${target}_IMPORTED_LOCATION)
|
||||||
|
# Not an error if the library doesn't exist -- we may have found a lite mode installation.
|
||||||
|
if(CapnProto_DEBUG)
|
||||||
|
message(STATUS "${target} library does not exist")
|
||||||
|
endif()
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Record some information about this target -- shared versus static, location and soname -- which
|
||||||
|
# we'll use to build our imported target later.
|
||||||
|
|
||||||
|
set(target_location ${CapnProto_${target}_IMPORTED_LOCATION})
|
||||||
|
get_filename_component(target_name "${target_location}" NAME)
|
||||||
|
|
||||||
|
set(target_type STATIC)
|
||||||
|
set(imported_soname_property)
|
||||||
|
if(target_name STREQUAL ${target_name_shared})
|
||||||
|
set(target_type SHARED)
|
||||||
|
set(imported_soname_property IMPORTED_SONAME ${target_name})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Each library dependency of the target is either the target itself, a sibling Cap'n Proto
|
||||||
|
# library, or a system library. We ignore the first case by removing this target from the
|
||||||
|
# dependencies. The remaining dependencies are either passed through or, if they are a sibling
|
||||||
|
# Cap'n Proto library, prefixed with `CapnProto::`.
|
||||||
|
set(dependencies ${${target}_LIBRARIES})
|
||||||
|
list(REMOVE_ITEM dependencies ${target})
|
||||||
|
set(target_interface_libs)
|
||||||
|
foreach(dependency ${dependencies})
|
||||||
|
list(FIND all_targets ${dependency} target_index)
|
||||||
|
# TODO(cleanup): CMake >= 3.3 lets us write: `if(NOT ${dependency} IN_LIST all_targets)`
|
||||||
|
if(target_index EQUAL -1)
|
||||||
|
list(APPEND target_interface_libs ${dependency})
|
||||||
|
else()
|
||||||
|
list(APPEND target_interface_libs CapnProto::${dependency})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
add_library(CapnProto::${target} ${target_type} IMPORTED)
|
||||||
|
set_target_properties(CapnProto::${target} PROPERTIES
|
||||||
|
${imported_soname_property}
|
||||||
|
IMPORTED_LOCATION "${target_location}"
|
||||||
|
# TODO(cleanup): Use cxx_std_14 once it's safe to require cmake 3.8.
|
||||||
|
INTERFACE_COMPILE_FEATURES "cxx_generic_lambdas"
|
||||||
|
INTERFACE_COMPILE_OPTIONS "${${target}_CFLAGS_OTHER}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${${target}_INCLUDE_DIRS}"
|
||||||
|
|
||||||
|
# I'm dumping LDFLAGS_OTHER in with the libraries because there exists no
|
||||||
|
# INTERFACE_LINK_OPTIONS. See https://gitlab.kitware.com/cmake/cmake/issues/16543.
|
||||||
|
INTERFACE_LINK_LIBRARIES "${target_interface_libs};${${target}_LDFLAGS_OTHER}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(CapnProto_DEBUG)
|
||||||
|
# Dump all the properties we generated for the imported target.
|
||||||
|
foreach(prop
|
||||||
|
IMPORTED_LOCATION
|
||||||
|
IMPORTED_SONAME
|
||||||
|
INTERFACE_COMPILE_FEATURES
|
||||||
|
INTERFACE_COMPILE_OPTIONS
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES
|
||||||
|
INTERFACE_LINK_LIBRARIES)
|
||||||
|
get_target_property(value CapnProto::${target} ${prop})
|
||||||
|
message(STATUS "CapnProto::${target} ${prop} = ${value}")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# ========================================================================================
|
||||||
|
# Imported library targets
|
||||||
|
|
||||||
|
# Build a list of targets to search for from the list of .pc files.
|
||||||
|
# I.e. [somewhere/foo.pc, somewhere/bar.pc] -> [foo, bar]
|
||||||
|
set(library_targets)
|
||||||
|
foreach(filename ${CAPNP_PKG_CONFIG_FILES})
|
||||||
|
get_filename_component(target ${filename} NAME_WE)
|
||||||
|
list(APPEND library_targets ${target})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Try to add an imported library target CapnProto::foo for each foo.pc distributed with Cap'n Proto.
|
||||||
|
foreach(target ${library_targets})
|
||||||
|
_capnp_import_pkg_config_target(${target} ${library_targets})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Handle lite-mode and no libraries found cases. It is tempting to set a CapnProto_LITE variable
|
||||||
|
# here, but the real CMake-generated implementation does no such thing -- we'd need to set it in
|
||||||
|
# CapnProtoConfig.cmake.in itself.
|
||||||
|
if(TARGET CapnProto::capnp AND TARGET CapnProto::kj)
|
||||||
|
if(NOT TARGET CapnProto::capnp-rpc)
|
||||||
|
if(NOT CapnProto_FIND_QUIETLY)
|
||||||
|
message(STATUS "Found an installation of Cap'n Proto lite. Executable and library targets beyond libkj and libcapnp will be unavailable.")
|
||||||
|
endif()
|
||||||
|
# Lite mode doesn't include the executables, so return here.
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
# If we didn't even find capnp or kj, then we didn't find anything usable.
|
||||||
|
set(CapnProto_FOUND OFF)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ========================================================================================
|
||||||
|
# Imported executable targets
|
||||||
|
|
||||||
|
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||||
|
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||||
|
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||||
|
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||||
|
|
||||||
|
# Add executable targets for the capnp compiler and plugins. This list must be kept manually in sync
|
||||||
|
# with the rest of the project.
|
||||||
|
|
||||||
|
add_executable(CapnProto::capnp_tool IMPORTED)
|
||||||
|
set_target_properties(CapnProto::capnp_tool PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${_IMPORT_PREFIX}/bin/capnp${CMAKE_EXECUTABLE_SUFFIX}"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(CapnProto::capnpc_cpp IMPORTED)
|
||||||
|
set_target_properties(CapnProto::capnpc_cpp PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${_IMPORT_PREFIX}/bin/capnpc-c++${CMAKE_EXECUTABLE_SUFFIX}"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(CapnProto::capnpc_capnp IMPORTED)
|
||||||
|
set_target_properties(CapnProto::capnpc_capnp PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${_IMPORT_PREFIX}/bin/capnpc-capnp${CMAKE_EXECUTABLE_SUFFIX}"
|
||||||
|
)
|
|
@ -0,0 +1,166 @@
|
||||||
|
## Process this file with autoconf to produce configure.
|
||||||
|
|
||||||
|
AC_INIT([Capn Proto],[0.7.0],[capnproto@googlegroups.com],[capnproto-c++])
|
||||||
|
|
||||||
|
AC_CONFIG_SRCDIR([src/capnp/layout.c++])
|
||||||
|
AC_CONFIG_AUX_DIR([build-aux])
|
||||||
|
AC_CONFIG_HEADERS([config.h])
|
||||||
|
AC_CONFIG_MACRO_DIR([m4])
|
||||||
|
|
||||||
|
# autoconf's default CXXFLAGS are usually "-g -O2". A far more reasonable
|
||||||
|
# default is -O2 -NDEBUG.
|
||||||
|
AS_IF([test "x${ac_cv_env_CFLAGS_set}" = "x"],
|
||||||
|
[CFLAGS="-O2 -DNDEBUG"])
|
||||||
|
AS_IF([test "x${ac_cv_env_CXXFLAGS_set}" = "x"],
|
||||||
|
[CXXFLAGS="-O2 -DNDEBUG"])
|
||||||
|
|
||||||
|
AM_INIT_AUTOMAKE([tar-ustar])
|
||||||
|
|
||||||
|
AC_ARG_WITH([external-capnp],
|
||||||
|
[AS_HELP_STRING([--with-external-capnp],
|
||||||
|
[use the system capnp binary (or the one specified with $CAPNP) instead of compiling a new
|
||||||
|
one (useful for cross-compiling)])],
|
||||||
|
[external_capnp=yes],[external_capnp=no])
|
||||||
|
|
||||||
|
AC_ARG_WITH([openssl],
|
||||||
|
[AS_HELP_STRING([--with-openssl],
|
||||||
|
[build libkj-tls by linking against openssl @<:@default=check@:>@])],
|
||||||
|
[],[with_openssl=check])
|
||||||
|
|
||||||
|
AC_ARG_ENABLE([reflection], [
|
||||||
|
AS_HELP_STRING([--disable-reflection], [
|
||||||
|
compile Cap'n Proto in "lite mode", in which all reflection APIs (schema.h, dynamic.h, etc.)
|
||||||
|
are not included. Produces a smaller library at the cost of features. All programs built
|
||||||
|
against the library MUST be compiled with -DCAPNP_LITE=1. Note that because the compiler
|
||||||
|
itself uses reflection in its implementation, you must also use --with-external-capnp when
|
||||||
|
using this option.])
|
||||||
|
], [
|
||||||
|
case "${enableval}" in
|
||||||
|
yes)
|
||||||
|
lite_mode=no
|
||||||
|
;;
|
||||||
|
no)
|
||||||
|
lite_mode=yes
|
||||||
|
AS_IF([test "$external_capnp" != "yes"], [
|
||||||
|
AC_MSG_ERROR([you must specify --with-external-capnp when using --disable-reflection])
|
||||||
|
])
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
AC_MSG_ERROR([bad value ${enableval} for --enable-reflection])
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
], [lite_mode=no])
|
||||||
|
|
||||||
|
# Checks for programs.
|
||||||
|
AC_PROG_CC
|
||||||
|
AC_PROG_CXX
|
||||||
|
AC_LANG([C++])
|
||||||
|
AX_CXX_COMPILE_STDCXX_14
|
||||||
|
|
||||||
|
AS_CASE("${host_os}", *mingw*, [
|
||||||
|
# We don't use pthreads on MinGW.
|
||||||
|
PTHREAD_CFLAGS="-mthreads"
|
||||||
|
PTHREAD_LIBS=""
|
||||||
|
PTHREAD_CC=""
|
||||||
|
ASYNC_LIBS="-lws2_32"
|
||||||
|
AC_SUBST(PTHREAD_LIBS)
|
||||||
|
AC_SUBST(PTHREAD_CFLAGS)
|
||||||
|
AC_SUBST(PTHREAD_CC)
|
||||||
|
AC_SUBST(ASYNC_LIBS)
|
||||||
|
], *, [
|
||||||
|
ACX_PTHREAD
|
||||||
|
ASYNC_LIBS=""
|
||||||
|
AC_SUBST(ASYNC_LIBS)
|
||||||
|
])
|
||||||
|
|
||||||
|
LT_INIT
|
||||||
|
|
||||||
|
AS_IF([test "$external_capnp" != "no"], [
|
||||||
|
AS_IF([test "x$CAPNP" = "x"], [CAPNP="capnp"], [with_capnp=yes])
|
||||||
|
AS_IF([test "x$CAPNPC_CXX" = "x"], [
|
||||||
|
# CAPNPC_CXX was not specified. Choose a reasonable default.
|
||||||
|
AS_CASE([$CAPNP], [*/*], [
|
||||||
|
# $CAPNP contains a slash, so it's not on $PATH. Assume capnpc-c++ is not either, but is
|
||||||
|
# in the same directory.
|
||||||
|
CAPNPC_CXX=`dirname $CAPNP`/capnpc-c++
|
||||||
|
], [
|
||||||
|
# $CAPNP is on $PATH, so tell it to find the plugin on $PATH as well.
|
||||||
|
CAPNPC_CXX="c++"
|
||||||
|
])
|
||||||
|
])
|
||||||
|
AC_SUBST([CAPNP])
|
||||||
|
AC_SUBST([CAPNPC_CXX])
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL([USE_EXTERNAL_CAPNP], [test "$external_capnp" != "no"])
|
||||||
|
|
||||||
|
AM_CONDITIONAL([LITE_MODE], [test "$lite_mode" = "yes"])
|
||||||
|
|
||||||
|
AS_IF([test "$lite_mode" = "yes"], [
|
||||||
|
CXXFLAGS="-DCAPNP_LITE $CXXFLAGS"
|
||||||
|
CAPNP_LITE_FLAG=-DCAPNP_LITE
|
||||||
|
])
|
||||||
|
AC_SUBST([CAPNP_LITE_FLAG])
|
||||||
|
|
||||||
|
AC_SEARCH_LIBS(sched_yield, rt)
|
||||||
|
|
||||||
|
# Users will need to use the same -stdlib as us so we'd better let pkg-config know about it.
|
||||||
|
STDLIB_FLAG=`echo "$CXX $CXXFLAGS" | grep -o ' [[-]]stdlib=[[^ ]]*'`
|
||||||
|
AC_SUBST([STDLIB_FLAG])
|
||||||
|
|
||||||
|
LIBS="$PTHREAD_LIBS $LIBS"
|
||||||
|
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS"
|
||||||
|
|
||||||
|
AC_DEFUN([CAPNP_PKG_CONFIG_FILES], [ \
|
||||||
|
pkgconfig/capnp.pc \
|
||||||
|
pkgconfig/capnp-rpc.pc \
|
||||||
|
pkgconfig/capnp-json.pc \
|
||||||
|
pkgconfig/kj.pc \
|
||||||
|
pkgconfig/kj-async.pc \
|
||||||
|
pkgconfig/kj-http.pc \
|
||||||
|
pkgconfig/kj-test.pc \
|
||||||
|
])
|
||||||
|
AC_DEFUN([CAPNP_CMAKE_CONFIG_FILES], [ \
|
||||||
|
cmake/CapnProtoConfig.cmake \
|
||||||
|
cmake/CapnProtoConfigVersion.cmake \
|
||||||
|
])
|
||||||
|
|
||||||
|
[CAPNP_PKG_CONFIG_FILES]="CAPNP_PKG_CONFIG_FILES"
|
||||||
|
[CAPNP_CMAKE_CONFIG_FILES]="CAPNP_CMAKE_CONFIG_FILES"
|
||||||
|
AC_SUBST([CAPNP_PKG_CONFIG_FILES])
|
||||||
|
AC_SUBST([CAPNP_CMAKE_CONFIG_FILES])
|
||||||
|
|
||||||
|
# CapnProtoConfig.cmake.in needs these PACKAGE_* output variables.
|
||||||
|
PACKAGE_INIT="set([CAPNP_PKG_CONFIG_FILES] CAPNP_PKG_CONFIG_FILES)"
|
||||||
|
PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR="\${CMAKE_CURRENT_LIST_DIR}/../../../include"
|
||||||
|
AC_SUBST([PACKAGE_INIT])
|
||||||
|
AC_SUBST([PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR])
|
||||||
|
|
||||||
|
# CapnProtoConfigVersion.cmake.in needs PACKAGE_VERSION (already defined by AC_INIT) and
|
||||||
|
# CMAKE_SIZEOF_VOID_P output variables.
|
||||||
|
AC_CHECK_SIZEOF([void *])
|
||||||
|
AC_SUBST(CMAKE_SIZEOF_VOID_P, $ac_cv_sizeof_void_p)
|
||||||
|
|
||||||
|
# Detect presence of OpenSSL, if it was not specified explicitly.
|
||||||
|
AS_IF([test "$with_openssl" = check], [
|
||||||
|
AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, [:], [
|
||||||
|
with_openssl=no
|
||||||
|
])
|
||||||
|
AC_CHECK_LIB(ssl, OPENSSL_init_ssl, [:], [
|
||||||
|
with_openssl=no
|
||||||
|
], [-lcrypto])
|
||||||
|
AC_CHECK_HEADER([openssl/ssl.h], [:], [
|
||||||
|
with_openssl=no
|
||||||
|
])
|
||||||
|
AS_IF([test "$with_openssl" = no], [
|
||||||
|
AC_MSG_WARN("could not find OpenSSL -- won't build libkj-tls")
|
||||||
|
], [
|
||||||
|
with_openssl=yes
|
||||||
|
])
|
||||||
|
])
|
||||||
|
AS_IF([test "$with_openssl" != no], [
|
||||||
|
CXXFLAGS="$CXXFLAGS -DKJ_HAS_OPENSSL"
|
||||||
|
])
|
||||||
|
AM_CONDITIONAL([BUILD_KJ_TLS], [test "$with_openssl" != no])
|
||||||
|
|
||||||
|
AC_CONFIG_FILES([Makefile] CAPNP_PKG_CONFIG_FILES CAPNP_CMAKE_CONFIG_FILES)
|
||||||
|
AC_OUTPUT
|
|
@ -0,0 +1 @@
|
||||||
|
../src
|
|
@ -0,0 +1 @@
|
||||||
|
../src
|
|
@ -0,0 +1,4 @@
|
||||||
|
The sole purpose of this directory is to allow GDB to find source files when
|
||||||
|
debugging an executable compiled using Ekam, since the source locations found
|
||||||
|
in the compiled binary will correspond to Ekam's virtual filesystem, not the
|
||||||
|
real one. This is a hack, and doesn't always work.
|
|
@ -0,0 +1,405 @@
|
||||||
|
# This file was copied to Cap'n Proto from the Protocol Buffers distribution,
|
||||||
|
# version 2.3.0.
|
||||||
|
|
||||||
|
# This was retrieved from
|
||||||
|
# http://svn.0pointer.de/viewvc/trunk/common/acx_pthread.m4?revision=1277&root=avahi
|
||||||
|
# See also (perhaps for new versions?)
|
||||||
|
# http://svn.0pointer.de/viewvc/trunk/common/acx_pthread.m4?root=avahi
|
||||||
|
#
|
||||||
|
# We've rewritten the inconsistency check code (from avahi), to work
|
||||||
|
# more broadly. In particular, it no longer assumes ld accepts -zdefs.
|
||||||
|
# This caused a restructing of the code, but the functionality has only
|
||||||
|
# changed a little.
|
||||||
|
|
||||||
|
dnl @synopsis ACX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
|
||||||
|
dnl
|
||||||
|
dnl @summary figure out how to build C programs using POSIX threads
|
||||||
|
dnl
|
||||||
|
dnl This macro figures out how to build C programs using POSIX threads.
|
||||||
|
dnl It sets the PTHREAD_LIBS output variable to the threads library and
|
||||||
|
dnl linker flags, and the PTHREAD_CFLAGS output variable to any special
|
||||||
|
dnl C compiler flags that are needed. (The user can also force certain
|
||||||
|
dnl compiler flags/libs to be tested by setting these environment
|
||||||
|
dnl variables.)
|
||||||
|
dnl
|
||||||
|
dnl Also sets PTHREAD_CC to any special C compiler that is needed for
|
||||||
|
dnl multi-threaded programs (defaults to the value of CC otherwise).
|
||||||
|
dnl (This is necessary on AIX to use the special cc_r compiler alias.)
|
||||||
|
dnl
|
||||||
|
dnl NOTE: You are assumed to not only compile your program with these
|
||||||
|
dnl flags, but also link it with them as well. e.g. you should link
|
||||||
|
dnl with $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS
|
||||||
|
dnl $LIBS
|
||||||
|
dnl
|
||||||
|
dnl If you are only building threads programs, you may wish to use
|
||||||
|
dnl these variables in your default LIBS, CFLAGS, and CC:
|
||||||
|
dnl
|
||||||
|
dnl LIBS="$PTHREAD_LIBS $LIBS"
|
||||||
|
dnl CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||||
|
dnl CC="$PTHREAD_CC"
|
||||||
|
dnl
|
||||||
|
dnl In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute
|
||||||
|
dnl constant has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to
|
||||||
|
dnl that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
|
||||||
|
dnl
|
||||||
|
dnl ACTION-IF-FOUND is a list of shell commands to run if a threads
|
||||||
|
dnl library is found, and ACTION-IF-NOT-FOUND is a list of commands to
|
||||||
|
dnl run it if it is not found. If ACTION-IF-FOUND is not specified, the
|
||||||
|
dnl default action will define HAVE_PTHREAD.
|
||||||
|
dnl
|
||||||
|
dnl Please let the authors know if this macro fails on any platform, or
|
||||||
|
dnl if you have any other suggestions or comments. This macro was based
|
||||||
|
dnl on work by SGJ on autoconf scripts for FFTW (www.fftw.org) (with
|
||||||
|
dnl help from M. Frigo), as well as ac_pthread and hb_pthread macros
|
||||||
|
dnl posted by Alejandro Forero Cuervo to the autoconf macro repository.
|
||||||
|
dnl We are also grateful for the helpful feedback of numerous users.
|
||||||
|
dnl
|
||||||
|
dnl @category InstalledPackages
|
||||||
|
dnl @author Steven G. Johnson <stevenj@alum.mit.edu>
|
||||||
|
dnl @version 2006-05-29
|
||||||
|
dnl @license GPLWithACException
|
||||||
|
dnl
|
||||||
|
dnl Checks for GCC shared/pthread inconsistency based on work by
|
||||||
|
dnl Marcin Owsiany <marcin@owsiany.pl>
|
||||||
|
|
||||||
|
|
||||||
|
AC_DEFUN([ACX_PTHREAD], [
|
||||||
|
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||||
|
AC_LANG_SAVE
|
||||||
|
AC_LANG_C
|
||||||
|
acx_pthread_ok=no
|
||||||
|
|
||||||
|
# We used to check for pthread.h first, but this fails if pthread.h
|
||||||
|
# requires special compiler flags (e.g. on True64 or Sequent).
|
||||||
|
# It gets checked for in the link test anyway.
|
||||||
|
|
||||||
|
# First of all, check if the user has set any of the PTHREAD_LIBS,
|
||||||
|
# etcetera environment variables, and if threads linking works using
|
||||||
|
# them:
|
||||||
|
if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
|
||||||
|
save_CFLAGS="$CFLAGS"
|
||||||
|
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||||
|
save_LIBS="$LIBS"
|
||||||
|
LIBS="$PTHREAD_LIBS $LIBS"
|
||||||
|
AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
|
||||||
|
AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes)
|
||||||
|
AC_MSG_RESULT($acx_pthread_ok)
|
||||||
|
if test x"$acx_pthread_ok" = xno; then
|
||||||
|
PTHREAD_LIBS=""
|
||||||
|
PTHREAD_CFLAGS=""
|
||||||
|
fi
|
||||||
|
LIBS="$save_LIBS"
|
||||||
|
CFLAGS="$save_CFLAGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# We must check for the threads library under a number of different
|
||||||
|
# names; the ordering is very important because some systems
|
||||||
|
# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
|
||||||
|
# libraries is broken (non-POSIX).
|
||||||
|
|
||||||
|
# Create a list of thread flags to try. Items starting with a "-" are
|
||||||
|
# C compiler flags, and other items are library names, except for "none"
|
||||||
|
# which indicates that we try without any flags at all, and "pthread-config"
|
||||||
|
# which is a program returning the flags for the Pth emulation library.
|
||||||
|
|
||||||
|
acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
|
||||||
|
|
||||||
|
# The ordering *is* (sometimes) important. Some notes on the
|
||||||
|
# individual items follow:
|
||||||
|
|
||||||
|
# pthreads: AIX (must check this before -lpthread)
|
||||||
|
# none: in case threads are in libc; should be tried before -Kthread and
|
||||||
|
# other compiler flags to prevent continual compiler warnings
|
||||||
|
# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
|
||||||
|
# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
|
||||||
|
# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
|
||||||
|
# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
|
||||||
|
# -pthreads: Solaris/gcc
|
||||||
|
# -mthreads: Mingw32/gcc, Lynx/gcc
|
||||||
|
# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
|
||||||
|
# doesn't hurt to check since this sometimes defines pthreads too;
|
||||||
|
# also defines -D_REENTRANT)
|
||||||
|
# ... -mt is also the pthreads flag for HP/aCC
|
||||||
|
# pthread: Linux, etcetera
|
||||||
|
# --thread-safe: KAI C++
|
||||||
|
# pthread-config: use pthread-config program (for GNU Pth library)
|
||||||
|
|
||||||
|
case "${host_cpu}-${host_os}" in
|
||||||
|
*solaris*)
|
||||||
|
|
||||||
|
# On Solaris (at least, for some versions), libc contains stubbed
|
||||||
|
# (non-functional) versions of the pthreads routines, so link-based
|
||||||
|
# tests will erroneously succeed. (We need to link with -pthreads/-mt/
|
||||||
|
# -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
|
||||||
|
# a function called by this macro, so we could check for that, but
|
||||||
|
# who knows whether they'll stub that too in a future libc.) So,
|
||||||
|
# we'll just look for -pthreads and -lpthread first:
|
||||||
|
|
||||||
|
acx_pthread_flags="-pthreads pthread -mt -pthread $acx_pthread_flags"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if test x"$acx_pthread_ok" = xno; then
|
||||||
|
for flag in $acx_pthread_flags; do
|
||||||
|
|
||||||
|
case $flag in
|
||||||
|
none)
|
||||||
|
AC_MSG_CHECKING([whether pthreads work without any flags])
|
||||||
|
;;
|
||||||
|
|
||||||
|
-*)
|
||||||
|
AC_MSG_CHECKING([whether pthreads work with $flag])
|
||||||
|
PTHREAD_CFLAGS="$flag"
|
||||||
|
;;
|
||||||
|
|
||||||
|
pthread-config)
|
||||||
|
AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no)
|
||||||
|
if test x"$acx_pthread_config" = xno; then continue; fi
|
||||||
|
PTHREAD_CFLAGS="`pthread-config --cflags`"
|
||||||
|
PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
AC_MSG_CHECKING([for the pthreads library -l$flag])
|
||||||
|
PTHREAD_LIBS="-l$flag"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
save_LIBS="$LIBS"
|
||||||
|
save_CFLAGS="$CFLAGS"
|
||||||
|
LIBS="$PTHREAD_LIBS $LIBS"
|
||||||
|
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||||
|
|
||||||
|
# Check for various functions. We must include pthread.h,
|
||||||
|
# since some functions may be macros. (On the Sequent, we
|
||||||
|
# need a special flag -Kthread to make this header compile.)
|
||||||
|
# We check for pthread_join because it is in -lpthread on IRIX
|
||||||
|
# while pthread_create is in libc. We check for pthread_attr_init
|
||||||
|
# due to DEC craziness with -lpthreads. We check for
|
||||||
|
# pthread_cleanup_push because it is one of the few pthread
|
||||||
|
# functions on Solaris that doesn't have a non-functional libc stub.
|
||||||
|
# We try pthread_create on general principles.
|
||||||
|
AC_TRY_LINK([#include <pthread.h>],
|
||||||
|
[pthread_t th; pthread_join(th, 0);
|
||||||
|
pthread_attr_init(0); pthread_cleanup_push(0, 0);
|
||||||
|
pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
|
||||||
|
[acx_pthread_ok=yes])
|
||||||
|
|
||||||
|
LIBS="$save_LIBS"
|
||||||
|
CFLAGS="$save_CFLAGS"
|
||||||
|
|
||||||
|
AC_MSG_RESULT($acx_pthread_ok)
|
||||||
|
if test "x$acx_pthread_ok" = xyes; then
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
|
||||||
|
PTHREAD_LIBS=""
|
||||||
|
PTHREAD_CFLAGS=""
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Various other checks:
|
||||||
|
if test "x$acx_pthread_ok" = xyes; then
|
||||||
|
save_LIBS="$LIBS"
|
||||||
|
LIBS="$PTHREAD_LIBS $LIBS"
|
||||||
|
save_CFLAGS="$CFLAGS"
|
||||||
|
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||||
|
|
||||||
|
# Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
|
||||||
|
AC_MSG_CHECKING([for joinable pthread attribute])
|
||||||
|
attr_name=unknown
|
||||||
|
for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
|
||||||
|
AC_TRY_LINK([#include <pthread.h>], [int attr=$attr; return attr;],
|
||||||
|
[attr_name=$attr; break])
|
||||||
|
done
|
||||||
|
AC_MSG_RESULT($attr_name)
|
||||||
|
if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
|
||||||
|
AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name,
|
||||||
|
[Define to necessary symbol if this constant
|
||||||
|
uses a non-standard name on your system.])
|
||||||
|
fi
|
||||||
|
|
||||||
|
AC_MSG_CHECKING([if more special flags are required for pthreads])
|
||||||
|
flag=no
|
||||||
|
case "${host_cpu}-${host_os}" in
|
||||||
|
*-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";;
|
||||||
|
*solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";;
|
||||||
|
esac
|
||||||
|
AC_MSG_RESULT(${flag})
|
||||||
|
if test "x$flag" != xno; then
|
||||||
|
PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
LIBS="$save_LIBS"
|
||||||
|
CFLAGS="$save_CFLAGS"
|
||||||
|
# More AIX lossage: must compile with xlc_r or cc_r
|
||||||
|
if test x"$GCC" != xyes; then
|
||||||
|
AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC})
|
||||||
|
else
|
||||||
|
PTHREAD_CC=$CC
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The next part tries to detect GCC inconsistency with -shared on some
|
||||||
|
# architectures and systems. The problem is that in certain
|
||||||
|
# configurations, when -shared is specified, GCC "forgets" to
|
||||||
|
# internally use various flags which are still necessary.
|
||||||
|
|
||||||
|
#
|
||||||
|
# Prepare the flags
|
||||||
|
#
|
||||||
|
save_CFLAGS="$CFLAGS"
|
||||||
|
save_LIBS="$LIBS"
|
||||||
|
save_CC="$CC"
|
||||||
|
|
||||||
|
# Try with the flags determined by the earlier checks.
|
||||||
|
#
|
||||||
|
# -Wl,-z,defs forces link-time symbol resolution, so that the
|
||||||
|
# linking checks with -shared actually have any value
|
||||||
|
#
|
||||||
|
# FIXME: -fPIC is required for -shared on many architectures,
|
||||||
|
# so we specify it here, but the right way would probably be to
|
||||||
|
# properly detect whether it is actually required.
|
||||||
|
CFLAGS="-shared -fPIC -Wl,-z,defs $CFLAGS $PTHREAD_CFLAGS"
|
||||||
|
LIBS="$PTHREAD_LIBS $LIBS"
|
||||||
|
CC="$PTHREAD_CC"
|
||||||
|
|
||||||
|
# In order not to create several levels of indentation, we test
|
||||||
|
# the value of "$done" until we find the cure or run out of ideas.
|
||||||
|
done="no"
|
||||||
|
|
||||||
|
# First, make sure the CFLAGS we added are actually accepted by our
|
||||||
|
# compiler. If not (and OS X's ld, for instance, does not accept -z),
|
||||||
|
# then we can't do this test.
|
||||||
|
if test x"$done" = xno; then
|
||||||
|
AC_MSG_CHECKING([whether to check for GCC pthread/shared inconsistencies])
|
||||||
|
AC_TRY_LINK(,, , [done=yes])
|
||||||
|
|
||||||
|
if test "x$done" = xyes ; then
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
else
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test x"$done" = xno; then
|
||||||
|
AC_MSG_CHECKING([whether -pthread is sufficient with -shared])
|
||||||
|
AC_TRY_LINK([#include <pthread.h>],
|
||||||
|
[pthread_t th; pthread_join(th, 0);
|
||||||
|
pthread_attr_init(0); pthread_cleanup_push(0, 0);
|
||||||
|
pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
|
||||||
|
[done=yes])
|
||||||
|
|
||||||
|
if test "x$done" = xyes; then
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
else
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Linux gcc on some architectures such as mips/mipsel forgets
|
||||||
|
# about -lpthread
|
||||||
|
#
|
||||||
|
if test x"$done" = xno; then
|
||||||
|
AC_MSG_CHECKING([whether -lpthread fixes that])
|
||||||
|
LIBS="-lpthread $PTHREAD_LIBS $save_LIBS"
|
||||||
|
AC_TRY_LINK([#include <pthread.h>],
|
||||||
|
[pthread_t th; pthread_join(th, 0);
|
||||||
|
pthread_attr_init(0); pthread_cleanup_push(0, 0);
|
||||||
|
pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
|
||||||
|
[done=yes])
|
||||||
|
|
||||||
|
if test "x$done" = xyes; then
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
PTHREAD_LIBS="-lpthread $PTHREAD_LIBS"
|
||||||
|
else
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
#
|
||||||
|
# FreeBSD 4.10 gcc forgets to use -lc_r instead of -lc
|
||||||
|
#
|
||||||
|
if test x"$done" = xno; then
|
||||||
|
AC_MSG_CHECKING([whether -lc_r fixes that])
|
||||||
|
LIBS="-lc_r $PTHREAD_LIBS $save_LIBS"
|
||||||
|
AC_TRY_LINK([#include <pthread.h>],
|
||||||
|
[pthread_t th; pthread_join(th, 0);
|
||||||
|
pthread_attr_init(0); pthread_cleanup_push(0, 0);
|
||||||
|
pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
|
||||||
|
[done=yes])
|
||||||
|
|
||||||
|
if test "x$done" = xyes; then
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
PTHREAD_LIBS="-lc_r $PTHREAD_LIBS"
|
||||||
|
else
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if test x"$done" = xno; then
|
||||||
|
# OK, we have run out of ideas
|
||||||
|
AC_MSG_WARN([Impossible to determine how to use pthreads with shared libraries])
|
||||||
|
|
||||||
|
# so it's not safe to assume that we may use pthreads
|
||||||
|
acx_pthread_ok=no
|
||||||
|
fi
|
||||||
|
|
||||||
|
CFLAGS="$save_CFLAGS"
|
||||||
|
LIBS="$save_LIBS"
|
||||||
|
CC="$save_CC"
|
||||||
|
else
|
||||||
|
PTHREAD_CC="$CC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test "x$acx_pthread_ok" = xyes; then
|
||||||
|
# One more check: If we chose to use a compiler flag like -pthread but it is combined with
|
||||||
|
# -nostdlib then the compiler won't implicitly link against libpthread. This can happen
|
||||||
|
# in particular when using some versions of libtool on some distros. See:
|
||||||
|
# https://bugzilla.redhat.com/show_bug.cgi?id=661333
|
||||||
|
|
||||||
|
save_CFLAGS="$CFLAGS"
|
||||||
|
save_LIBS="$LIBS"
|
||||||
|
save_CC="$CC"
|
||||||
|
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||||
|
LIBS="-nostdlib $PTHREAD_LIBS $LIBS -lc"
|
||||||
|
CC="$PTHREAD_CC"
|
||||||
|
|
||||||
|
AC_MSG_CHECKING([whether pthread flag is sufficient with -nostdlib])
|
||||||
|
AC_TRY_LINK([#include <pthread.h>],
|
||||||
|
[pthread_t th; pthread_join(th, 0);
|
||||||
|
pthread_attr_init(0); pthread_cleanup_push(0, 0);
|
||||||
|
pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
|
||||||
|
[AC_MSG_RESULT([yes])], [
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
|
||||||
|
AC_MSG_CHECKING([whether adding -lpthread fixes that])
|
||||||
|
|
||||||
|
LIBS="-nostdlib $PTHREAD_LIBS -lpthread $save_LIBS -lc"
|
||||||
|
AC_TRY_LINK([#include <pthread.h>],
|
||||||
|
[pthread_t th; pthread_join(th, 0);
|
||||||
|
pthread_attr_init(0); pthread_cleanup_push(0, 0);
|
||||||
|
pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
|
||||||
|
[
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
PTHREAD_LIBS="$PTHREAD_LIBS -lpthread"
|
||||||
|
], [AC_MSG_RESULT([no])])
|
||||||
|
])
|
||||||
|
|
||||||
|
CFLAGS="$save_CFLAGS"
|
||||||
|
LIBS="$save_LIBS"
|
||||||
|
CC="$save_CC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
AC_SUBST(PTHREAD_LIBS)
|
||||||
|
AC_SUBST(PTHREAD_CFLAGS)
|
||||||
|
AC_SUBST(PTHREAD_CC)
|
||||||
|
|
||||||
|
# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
|
||||||
|
if test x"$acx_pthread_ok" = xyes; then
|
||||||
|
ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1])
|
||||||
|
:
|
||||||
|
else
|
||||||
|
acx_pthread_ok=no
|
||||||
|
$2
|
||||||
|
fi
|
||||||
|
AC_LANG_RESTORE
|
||||||
|
])dnl ACX_PTHREAD
|
|
@ -0,0 +1,191 @@
|
||||||
|
# ============================================================================
|
||||||
|
# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html
|
||||||
|
# Additionally modified to detect -stdlib by Kenton Varda.
|
||||||
|
# Further modified for C++14 by Kenton Varda.
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
#
|
||||||
|
# AX_CXX_COMPILE_STDCXX_14([ext|noext])
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
#
|
||||||
|
# Check for baseline language coverage in the compiler for the C++14
|
||||||
|
# standard; if necessary, add switches to CXXFLAGS to enable support.
|
||||||
|
# Errors out if no mode that supports C++14 baseline syntax can be found.
|
||||||
|
# The argument, if specified, indicates whether you insist on an extended
|
||||||
|
# mode (e.g. -std=gnu++14) or a strict conformance mode (e.g. -std=c++14).
|
||||||
|
# If neither is specified, you get whatever works, with preference for an
|
||||||
|
# extended mode.
|
||||||
|
#
|
||||||
|
# Additionally, check if the standard library supports C++11. If not,
|
||||||
|
# try adding -stdlib=libc++ to see if that fixes it. This is needed e.g.
|
||||||
|
# on Mac OSX 10.8, which ships with a very old libstdc++ but a relatively
|
||||||
|
# new libc++.
|
||||||
|
#
|
||||||
|
# Both flags are actually added to CXX rather than CXXFLAGS to work around
|
||||||
|
# a bug in libtool: -stdlib is stripped from CXXFLAGS when linking dynamic
|
||||||
|
# libraries because it is not recognized. A patch was committed to mainline
|
||||||
|
# libtool in February 2012 but as of June 2013 there has not yet been a
|
||||||
|
# release containing this patch.
|
||||||
|
# http://git.savannah.gnu.org/gitweb/?p=libtool.git;a=commit;h=c0c49f289f22ae670066657c60905986da3b555f
|
||||||
|
#
|
||||||
|
# LICENSE
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
|
||||||
|
# Copyright (c) 2012 Zack Weinberg <zackw@panix.com>
|
||||||
|
# Copyright (c) 2013 Kenton Varda <temporal@gmail.com>
|
||||||
|
#
|
||||||
|
# Copying and distribution of this file, with or without modification, are
|
||||||
|
# permitted in any medium without royalty provided the copyright notice
|
||||||
|
# and this notice are preserved. This file is offered as-is, without any
|
||||||
|
# warranty.
|
||||||
|
|
||||||
|
#serial 1
|
||||||
|
|
||||||
|
m4_define([_AX_CXX_COMPILE_STDCXX_14_testbody], [[
|
||||||
|
template <typename T>
|
||||||
|
struct check
|
||||||
|
{
|
||||||
|
static_assert(sizeof(int) <= sizeof(T), "not big enough");
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef check<check<bool>> right_angle_brackets;
|
||||||
|
|
||||||
|
int a;
|
||||||
|
decltype(a) b;
|
||||||
|
|
||||||
|
typedef check<int> check_type;
|
||||||
|
check_type c;
|
||||||
|
check_type&& cr = static_cast<check_type&&>(c);
|
||||||
|
|
||||||
|
// GCC 4.7 introduced __float128 and makes reference to it in type_traits.
|
||||||
|
// Clang doesn't implement it, so produces an error. Using -std=c++11
|
||||||
|
// instead of -std=gnu++11 works around the problem. But on some
|
||||||
|
// platforms we need -std=gnu++11. So we want to make sure the test of
|
||||||
|
// -std=gnu++11 fails only where this problem is present, and we hope that
|
||||||
|
// -std=c++11 is always an acceptable fallback in these cases. Complicating
|
||||||
|
// matters, though, is that we don't want to fail here if the platform is
|
||||||
|
// completely missing a C++11 standard library, because we want to probe that
|
||||||
|
// in a later test. It happens, though, that Clang allows us to check
|
||||||
|
// whether a header exists at all before we include it.
|
||||||
|
//
|
||||||
|
// So, if we detect that __has_include is available (which it is on Clang),
|
||||||
|
// and we use it to detect that <type_traits> (a C++11 header) exists, then
|
||||||
|
// we go ahead and #include it to see if it breaks. In all other cases, we
|
||||||
|
// don't #include it at all.
|
||||||
|
#ifdef __has_include
|
||||||
|
#if __has_include(<type_traits>)
|
||||||
|
#include <type_traits>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// C++14 stuff
|
||||||
|
auto deduceReturnType(int i) { return i; }
|
||||||
|
|
||||||
|
auto genericLambda = [](auto x, auto y) { return x + y; };
|
||||||
|
auto captureExpressions = [x = 123]() { return x; };
|
||||||
|
|
||||||
|
// Avoid unused variable warnings.
|
||||||
|
int foo() {
|
||||||
|
return genericLambda(1, 2) + captureExpressions();
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody_lib], [
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
])
|
||||||
|
|
||||||
|
AC_DEFUN([AX_CXX_COMPILE_STDCXX_14], [dnl
|
||||||
|
m4_if([$1], [], [],
|
||||||
|
[$1], [ext], [],
|
||||||
|
[$1], [noext], [],
|
||||||
|
[m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_14])])dnl
|
||||||
|
AC_LANG_ASSERT([C++])dnl
|
||||||
|
ac_success=no
|
||||||
|
AC_CACHE_CHECK(whether $CXX supports C++14 features by default,
|
||||||
|
ax_cv_cxx_compile_cxx14,
|
||||||
|
[AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_14_testbody])],
|
||||||
|
[ax_cv_cxx_compile_cxx14=yes],
|
||||||
|
[ax_cv_cxx_compile_cxx14=no])])
|
||||||
|
if test x$ax_cv_cxx_compile_cxx14 = xyes; then
|
||||||
|
ac_success=yes
|
||||||
|
fi
|
||||||
|
|
||||||
|
m4_if([$1], [noext], [], [dnl
|
||||||
|
if test x$ac_success = xno; then
|
||||||
|
for switch in -std=gnu++14 -std=gnu++1y; do
|
||||||
|
cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx14_$switch])
|
||||||
|
AC_CACHE_CHECK(whether $CXX supports C++14 features with $switch,
|
||||||
|
$cachevar,
|
||||||
|
[ac_save_CXX="$CXX"
|
||||||
|
CXX="$CXX $switch"
|
||||||
|
AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_14_testbody])],
|
||||||
|
[eval $cachevar=yes],
|
||||||
|
[eval $cachevar=no])
|
||||||
|
CXX="$ac_save_CXX"])
|
||||||
|
if eval test x\$$cachevar = xyes; then
|
||||||
|
CXX="$CXX $switch"
|
||||||
|
ac_success=yes
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi])
|
||||||
|
|
||||||
|
m4_if([$1], [ext], [], [dnl
|
||||||
|
if test x$ac_success = xno; then
|
||||||
|
for switch in -std=c++14 -std=c++1y; do
|
||||||
|
cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx14_$switch])
|
||||||
|
AC_CACHE_CHECK(whether $CXX supports C++14 features with $switch,
|
||||||
|
$cachevar,
|
||||||
|
[ac_save_CXX="$CXX"
|
||||||
|
CXX="$CXX $switch"
|
||||||
|
AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_14_testbody])],
|
||||||
|
[eval $cachevar=yes],
|
||||||
|
[eval $cachevar=no])
|
||||||
|
CXX="$ac_save_CXX"])
|
||||||
|
if eval test x\$$cachevar = xyes; then
|
||||||
|
CXX="$CXX $switch"
|
||||||
|
ac_success=yes
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi])
|
||||||
|
|
||||||
|
if test x$ac_success = xno; then
|
||||||
|
AC_MSG_ERROR([*** A compiler with support for C++14 language features is required.])
|
||||||
|
else
|
||||||
|
ac_success=no
|
||||||
|
AC_CACHE_CHECK(whether $CXX supports C++11 library features by default,
|
||||||
|
ax_cv_cxx_compile_cxx11_lib,
|
||||||
|
[AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody_lib])],
|
||||||
|
[ax_cv_cxx_compile_cxx11_lib=yes],
|
||||||
|
[ax_cv_cxx_compile_cxx11_lib=no])
|
||||||
|
])
|
||||||
|
if test x$ax_cv_cxx_compile_cxx11_lib = xyes; then
|
||||||
|
ac_success=yes
|
||||||
|
else
|
||||||
|
# Try with -stdlib=libc++
|
||||||
|
AC_CACHE_CHECK(whether $CXX supports C++11 library features with -stdlib=libc++,
|
||||||
|
ax_cv_cxx_compile_cxx11_lib_libcxx,
|
||||||
|
[ac_save_CXX="$CXX"
|
||||||
|
CXX="$CXX -stdlib=libc++"
|
||||||
|
AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody_lib])],
|
||||||
|
[eval ax_cv_cxx_compile_cxx11_lib_libcxx=yes],
|
||||||
|
[eval ax_cv_cxx_compile_cxx11_lib_libcxx=no])
|
||||||
|
CXX="$ac_save_CXX"])
|
||||||
|
if eval test x$ax_cv_cxx_compile_cxx11_lib_libcxx = xyes; then
|
||||||
|
CXX="$CXX -stdlib=libc++"
|
||||||
|
ac_success=yes
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test x$ac_success = xno; then
|
||||||
|
AC_MSG_ERROR([*** A C++ library with support for C++11 features is required.])
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
])
|
|
@ -0,0 +1,11 @@
|
||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=@exec_prefix@
|
||||||
|
libdir=@libdir@
|
||||||
|
includedir=@includedir@
|
||||||
|
|
||||||
|
Name: Cap'n Proto JSON
|
||||||
|
Description: JSON encoder and decoder for Cap'n Proto objects
|
||||||
|
Version: @VERSION@
|
||||||
|
Libs: -L${libdir} -lcapnp-json
|
||||||
|
Requires: capnp = @VERSION@ kj = @VERSION@
|
||||||
|
Cflags: -I${includedir}
|
|
@ -0,0 +1,11 @@
|
||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=@exec_prefix@
|
||||||
|
libdir=@libdir@
|
||||||
|
includedir=@includedir@
|
||||||
|
|
||||||
|
Name: Cap'n Proto RPC
|
||||||
|
Description: Fast object-oriented RPC system
|
||||||
|
Version: @VERSION@
|
||||||
|
Libs: -L${libdir} -lcapnp-rpc
|
||||||
|
Requires: capnp = @VERSION@ kj-async = @VERSION@
|
||||||
|
Cflags: -I${includedir}
|
|
@ -0,0 +1,12 @@
|
||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=@exec_prefix@
|
||||||
|
libdir=@libdir@
|
||||||
|
includedir=@includedir@
|
||||||
|
|
||||||
|
Name: Cap'n Proto
|
||||||
|
Description: Insanely fast serialization system
|
||||||
|
Version: @VERSION@
|
||||||
|
Libs: -L${libdir} -lcapnp @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ @STDLIB_FLAG@
|
||||||
|
Libs.private: @LIBS@
|
||||||
|
Requires: kj = @VERSION@
|
||||||
|
Cflags: -I${includedir} @PTHREAD_CFLAGS@ @STDLIB_FLAG@ @CAPNP_LITE_FLAG@
|
|
@ -0,0 +1,11 @@
|
||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=@exec_prefix@
|
||||||
|
libdir=@libdir@
|
||||||
|
includedir=@includedir@
|
||||||
|
|
||||||
|
Name: KJ Async Framework Library
|
||||||
|
Description: Basic utility library called KJ (async part)
|
||||||
|
Version: @VERSION@
|
||||||
|
Libs: -L${libdir} -lkj-async @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ @STDLIB_FLAG@
|
||||||
|
Requires: kj = @VERSION@
|
||||||
|
Cflags: -I${includedir} @PTHREAD_CFLAGS@ @STDLIB_FLAG@ @CAPNP_LITE_FLAG@
|
|
@ -0,0 +1,11 @@
|
||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=@exec_prefix@
|
||||||
|
libdir=@libdir@
|
||||||
|
includedir=@includedir@
|
||||||
|
|
||||||
|
Name: KJ HTTP Library
|
||||||
|
Description: Basic utility library called KJ (HTTP part)
|
||||||
|
Version: @VERSION@
|
||||||
|
Libs: -L${libdir} -lkj-http @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ @STDLIB_FLAG@
|
||||||
|
Requires: kj-async = @VERSION@
|
||||||
|
Cflags: -I${includedir} @PTHREAD_CFLAGS@ @STDLIB_FLAG@ @CAPNP_LITE_FLAG@
|
|
@ -0,0 +1,11 @@
|
||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=@exec_prefix@
|
||||||
|
libdir=@libdir@
|
||||||
|
includedir=@includedir@
|
||||||
|
|
||||||
|
Name: KJ Test Framework
|
||||||
|
Description: Basic utility library called KJ (test part)
|
||||||
|
Version: @VERSION@
|
||||||
|
Libs: -L${libdir} -lkj-test @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ @STDLIB_FLAG@
|
||||||
|
Requires: kj = @VERSION@
|
||||||
|
Cflags: -I${includedir} @PTHREAD_CFLAGS@ @STDLIB_FLAG@ @CAPNP_LITE_FLAG@
|
|
@ -0,0 +1,10 @@
|
||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=@exec_prefix@
|
||||||
|
libdir=@libdir@
|
||||||
|
includedir=@includedir@
|
||||||
|
|
||||||
|
Name: KJ Framework Library
|
||||||
|
Description: Basic utility library called KJ
|
||||||
|
Version: @VERSION@
|
||||||
|
Libs: -L${libdir} -lkj @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ @STDLIB_FLAG@
|
||||||
|
Cflags: -I${includedir} @PTHREAD_CFLAGS@ @STDLIB_FLAG@ @CAPNP_LITE_FLAG@
|
|
@ -0,0 +1,11 @@
|
||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export PATH=$PWD/bin:$PWD:$PATH
|
||||||
|
|
||||||
|
capnp compile -Isrc --no-standard-import --src-prefix=src -oc++:src \
|
||||||
|
src/capnp/c++.capnp src/capnp/schema.capnp \
|
||||||
|
src/capnp/compiler/lexer.capnp src/capnp/compiler/grammar.capnp \
|
||||||
|
src/capnp/rpc.capnp src/capnp/rpc-twoparty.capnp src/capnp/persistent.capnp \
|
||||||
|
src/capnp/compat/json.capnp
|
|
@ -0,0 +1,38 @@
|
||||||
|
# A Cap'n Proto sample project.
|
||||||
|
#
|
||||||
|
# To build (non-MSVC):
|
||||||
|
# 1. Install Cap'n Proto somewhere ($PREFIX below):
|
||||||
|
#
|
||||||
|
# mkdir capnproto/build
|
||||||
|
# cd capnproto/build
|
||||||
|
# cmake ../c++ -DCMAKE_INSTALL_PREFIX=$PREFIX
|
||||||
|
# cmake --build . --target install
|
||||||
|
#
|
||||||
|
# 2. Ensure Cap'n Proto's executables are on the PATH, then build the sample project:
|
||||||
|
#
|
||||||
|
# export PATH=$PREFIX/bin:$PATH
|
||||||
|
# mkdir ../build-samples
|
||||||
|
# cd ../build-samples
|
||||||
|
# cmake ../c++/samples
|
||||||
|
# cmake --build .
|
||||||
|
|
||||||
|
project("Cap'n Proto Samples" CXX)
|
||||||
|
cmake_minimum_required(VERSION 3.1)
|
||||||
|
|
||||||
|
find_package(CapnProto CONFIG REQUIRED)
|
||||||
|
|
||||||
|
capnp_generate_cpp(addressbookSources addressbookHeaders addressbook.capnp)
|
||||||
|
add_executable(addressbook addressbook.c++ ${addressbookSources})
|
||||||
|
target_link_libraries(addressbook PRIVATE CapnProto::capnp)
|
||||||
|
target_include_directories(addressbook PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
# Don't build the rpc sample if find_package() found an installation of Cap'n Proto lite.
|
||||||
|
if(TARGET CapnProto::capnp-rpc)
|
||||||
|
capnp_generate_cpp(calculatorSources calculatorHeaders calculator.capnp)
|
||||||
|
add_executable(calculator-client calculator-client.c++ ${calculatorSources})
|
||||||
|
add_executable(calculator-server calculator-server.c++ ${calculatorSources})
|
||||||
|
target_link_libraries(calculator-client PRIVATE CapnProto::capnp-rpc)
|
||||||
|
target_link_libraries(calculator-server PRIVATE CapnProto::capnp-rpc)
|
||||||
|
target_include_directories(calculator-client PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
target_include_directories(calculator-server PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
endif()
|
|
@ -0,0 +1,289 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// This sample code appears in the documentation for the C++ implementation.
|
||||||
|
//
|
||||||
|
// If Cap'n Proto is installed, build the sample like:
|
||||||
|
// capnp compile -oc++ addressbook.capnp
|
||||||
|
// c++ -std=c++14 -Wall addressbook.c++ addressbook.capnp.c++ `pkg-config --cflags --libs capnp` -o addressbook
|
||||||
|
//
|
||||||
|
// If Cap'n Proto is not installed, but the source is located at $SRC and has been
|
||||||
|
// compiled in $BUILD (often both are simply ".." from here), you can do:
|
||||||
|
// $BUILD/capnp compile -I$SRC/src -o$BUILD/capnpc-c++ addressbook.capnp
|
||||||
|
// c++ -std=c++14 -Wall addressbook.c++ addressbook.capnp.c++ -I$SRC/src -L$BUILD/.libs -lcapnp -lkj -o addressbook
|
||||||
|
//
|
||||||
|
// Run like:
|
||||||
|
// ./addressbook write | ./addressbook read
|
||||||
|
// Use "dwrite" and "dread" to use dynamic code instead.
|
||||||
|
|
||||||
|
// TODO(test): Needs cleanup.
|
||||||
|
|
||||||
|
#include "addressbook.capnp.h"
|
||||||
|
#include <capnp/message.h>
|
||||||
|
#include <capnp/serialize-packed.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using addressbook::Person;
|
||||||
|
using addressbook::AddressBook;
|
||||||
|
|
||||||
|
void writeAddressBook(int fd) {
|
||||||
|
::capnp::MallocMessageBuilder message;
|
||||||
|
|
||||||
|
AddressBook::Builder addressBook = message.initRoot<AddressBook>();
|
||||||
|
::capnp::List<Person>::Builder people = addressBook.initPeople(2);
|
||||||
|
|
||||||
|
Person::Builder alice = people[0];
|
||||||
|
alice.setId(123);
|
||||||
|
alice.setName("Alice");
|
||||||
|
alice.setEmail("alice@example.com");
|
||||||
|
// Type shown for explanation purposes; normally you'd use auto.
|
||||||
|
::capnp::List<Person::PhoneNumber>::Builder alicePhones =
|
||||||
|
alice.initPhones(1);
|
||||||
|
alicePhones[0].setNumber("555-1212");
|
||||||
|
alicePhones[0].setType(Person::PhoneNumber::Type::MOBILE);
|
||||||
|
alice.getEmployment().setSchool("MIT");
|
||||||
|
|
||||||
|
Person::Builder bob = people[1];
|
||||||
|
bob.setId(456);
|
||||||
|
bob.setName("Bob");
|
||||||
|
bob.setEmail("bob@example.com");
|
||||||
|
auto bobPhones = bob.initPhones(2);
|
||||||
|
bobPhones[0].setNumber("555-4567");
|
||||||
|
bobPhones[0].setType(Person::PhoneNumber::Type::HOME);
|
||||||
|
bobPhones[1].setNumber("555-7654");
|
||||||
|
bobPhones[1].setType(Person::PhoneNumber::Type::WORK);
|
||||||
|
bob.getEmployment().setUnemployed();
|
||||||
|
|
||||||
|
writePackedMessageToFd(fd, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printAddressBook(int fd) {
|
||||||
|
::capnp::PackedFdMessageReader message(fd);
|
||||||
|
|
||||||
|
AddressBook::Reader addressBook = message.getRoot<AddressBook>();
|
||||||
|
|
||||||
|
for (Person::Reader person : addressBook.getPeople()) {
|
||||||
|
std::cout << person.getName().cStr() << ": "
|
||||||
|
<< person.getEmail().cStr() << std::endl;
|
||||||
|
for (Person::PhoneNumber::Reader phone: person.getPhones()) {
|
||||||
|
const char* typeName = "UNKNOWN";
|
||||||
|
switch (phone.getType()) {
|
||||||
|
case Person::PhoneNumber::Type::MOBILE: typeName = "mobile"; break;
|
||||||
|
case Person::PhoneNumber::Type::HOME: typeName = "home"; break;
|
||||||
|
case Person::PhoneNumber::Type::WORK: typeName = "work"; break;
|
||||||
|
}
|
||||||
|
std::cout << " " << typeName << " phone: "
|
||||||
|
<< phone.getNumber().cStr() << std::endl;
|
||||||
|
}
|
||||||
|
Person::Employment::Reader employment = person.getEmployment();
|
||||||
|
switch (employment.which()) {
|
||||||
|
case Person::Employment::UNEMPLOYED:
|
||||||
|
std::cout << " unemployed" << std::endl;
|
||||||
|
break;
|
||||||
|
case Person::Employment::EMPLOYER:
|
||||||
|
std::cout << " employer: "
|
||||||
|
<< employment.getEmployer().cStr() << std::endl;
|
||||||
|
break;
|
||||||
|
case Person::Employment::SCHOOL:
|
||||||
|
std::cout << " student at: "
|
||||||
|
<< employment.getSchool().cStr() << std::endl;
|
||||||
|
break;
|
||||||
|
case Person::Employment::SELF_EMPLOYED:
|
||||||
|
std::cout << " self-employed" << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
|
||||||
|
#include "addressbook.capnp.h"
|
||||||
|
#include <capnp/message.h>
|
||||||
|
#include <capnp/serialize-packed.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <capnp/schema.h>
|
||||||
|
#include <capnp/dynamic.h>
|
||||||
|
|
||||||
|
using ::capnp::DynamicValue;
|
||||||
|
using ::capnp::DynamicStruct;
|
||||||
|
using ::capnp::DynamicEnum;
|
||||||
|
using ::capnp::DynamicList;
|
||||||
|
using ::capnp::List;
|
||||||
|
using ::capnp::Schema;
|
||||||
|
using ::capnp::StructSchema;
|
||||||
|
using ::capnp::EnumSchema;
|
||||||
|
|
||||||
|
using ::capnp::Void;
|
||||||
|
using ::capnp::Text;
|
||||||
|
using ::capnp::MallocMessageBuilder;
|
||||||
|
using ::capnp::PackedFdMessageReader;
|
||||||
|
|
||||||
|
void dynamicWriteAddressBook(int fd, StructSchema schema) {
|
||||||
|
// Write a message using the dynamic API to set each
|
||||||
|
// field by text name. This isn't something you'd
|
||||||
|
// normally want to do; it's just for illustration.
|
||||||
|
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
|
||||||
|
// Types shown for explanation purposes; normally you'd
|
||||||
|
// use auto.
|
||||||
|
DynamicStruct::Builder addressBook =
|
||||||
|
message.initRoot<DynamicStruct>(schema);
|
||||||
|
|
||||||
|
DynamicList::Builder people =
|
||||||
|
addressBook.init("people", 2).as<DynamicList>();
|
||||||
|
|
||||||
|
DynamicStruct::Builder alice =
|
||||||
|
people[0].as<DynamicStruct>();
|
||||||
|
alice.set("id", 123);
|
||||||
|
alice.set("name", "Alice");
|
||||||
|
alice.set("email", "alice@example.com");
|
||||||
|
auto alicePhones = alice.init("phones", 1).as<DynamicList>();
|
||||||
|
auto phone0 = alicePhones[0].as<DynamicStruct>();
|
||||||
|
phone0.set("number", "555-1212");
|
||||||
|
phone0.set("type", "mobile");
|
||||||
|
alice.get("employment").as<DynamicStruct>()
|
||||||
|
.set("school", "MIT");
|
||||||
|
|
||||||
|
auto bob = people[1].as<DynamicStruct>();
|
||||||
|
bob.set("id", 456);
|
||||||
|
bob.set("name", "Bob");
|
||||||
|
bob.set("email", "bob@example.com");
|
||||||
|
|
||||||
|
// Some magic: We can convert a dynamic sub-value back to
|
||||||
|
// the native type with as<T>()!
|
||||||
|
List<Person::PhoneNumber>::Builder bobPhones =
|
||||||
|
bob.init("phones", 2).as<List<Person::PhoneNumber>>();
|
||||||
|
bobPhones[0].setNumber("555-4567");
|
||||||
|
bobPhones[0].setType(Person::PhoneNumber::Type::HOME);
|
||||||
|
bobPhones[1].setNumber("555-7654");
|
||||||
|
bobPhones[1].setType(Person::PhoneNumber::Type::WORK);
|
||||||
|
bob.get("employment").as<DynamicStruct>()
|
||||||
|
.set("unemployed", ::capnp::VOID);
|
||||||
|
|
||||||
|
writePackedMessageToFd(fd, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dynamicPrintValue(DynamicValue::Reader value) {
|
||||||
|
// Print an arbitrary message via the dynamic API by
|
||||||
|
// iterating over the schema. Look at the handling
|
||||||
|
// of STRUCT in particular.
|
||||||
|
|
||||||
|
switch (value.getType()) {
|
||||||
|
case DynamicValue::VOID:
|
||||||
|
std::cout << "";
|
||||||
|
break;
|
||||||
|
case DynamicValue::BOOL:
|
||||||
|
std::cout << (value.as<bool>() ? "true" : "false");
|
||||||
|
break;
|
||||||
|
case DynamicValue::INT:
|
||||||
|
std::cout << value.as<int64_t>();
|
||||||
|
break;
|
||||||
|
case DynamicValue::UINT:
|
||||||
|
std::cout << value.as<uint64_t>();
|
||||||
|
break;
|
||||||
|
case DynamicValue::FLOAT:
|
||||||
|
std::cout << value.as<double>();
|
||||||
|
break;
|
||||||
|
case DynamicValue::TEXT:
|
||||||
|
std::cout << '\"' << value.as<Text>().cStr() << '\"';
|
||||||
|
break;
|
||||||
|
case DynamicValue::LIST: {
|
||||||
|
std::cout << "[";
|
||||||
|
bool first = true;
|
||||||
|
for (auto element: value.as<DynamicList>()) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
std::cout << ", ";
|
||||||
|
}
|
||||||
|
dynamicPrintValue(element);
|
||||||
|
}
|
||||||
|
std::cout << "]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DynamicValue::ENUM: {
|
||||||
|
auto enumValue = value.as<DynamicEnum>();
|
||||||
|
KJ_IF_MAYBE(enumerant, enumValue.getEnumerant()) {
|
||||||
|
std::cout <<
|
||||||
|
enumerant->getProto().getName().cStr();
|
||||||
|
} else {
|
||||||
|
// Unknown enum value; output raw number.
|
||||||
|
std::cout << enumValue.getRaw();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DynamicValue::STRUCT: {
|
||||||
|
std::cout << "(";
|
||||||
|
auto structValue = value.as<DynamicStruct>();
|
||||||
|
bool first = true;
|
||||||
|
for (auto field: structValue.getSchema().getFields()) {
|
||||||
|
if (!structValue.has(field)) continue;
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
std::cout << ", ";
|
||||||
|
}
|
||||||
|
std::cout << field.getProto().getName().cStr()
|
||||||
|
<< " = ";
|
||||||
|
dynamicPrintValue(structValue.get(field));
|
||||||
|
}
|
||||||
|
std::cout << ")";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// There are other types, we aren't handling them.
|
||||||
|
std::cout << "?";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dynamicPrintMessage(int fd, StructSchema schema) {
|
||||||
|
PackedFdMessageReader message(fd);
|
||||||
|
dynamicPrintValue(message.getRoot<DynamicStruct>(schema));
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc != 2) {
|
||||||
|
std::cerr << "Missing arg." << std::endl;
|
||||||
|
return 1;
|
||||||
|
} else if (strcmp(argv[1], "write") == 0) {
|
||||||
|
writeAddressBook(1);
|
||||||
|
} else if (strcmp(argv[1], "read") == 0) {
|
||||||
|
printAddressBook(0);
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
} else if (strcmp(argv[1], "dwrite") == 0) {
|
||||||
|
StructSchema schema = Schema::from<AddressBook>();
|
||||||
|
dynamicWriteAddressBook(1, schema);
|
||||||
|
} else if (strcmp(argv[1], "dread") == 0) {
|
||||||
|
StructSchema schema = Schema::from<AddressBook>();
|
||||||
|
dynamicPrintMessage(0, schema);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
std::cerr << "Invalid arg: " << argv[1] << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
@0x9eb32e19f86ee174;
|
||||||
|
|
||||||
|
using Cxx = import "/capnp/c++.capnp";
|
||||||
|
$Cxx.namespace("addressbook");
|
||||||
|
|
||||||
|
struct Person {
|
||||||
|
id @0 :UInt32;
|
||||||
|
name @1 :Text;
|
||||||
|
email @2 :Text;
|
||||||
|
phones @3 :List(PhoneNumber);
|
||||||
|
|
||||||
|
struct PhoneNumber {
|
||||||
|
number @0 :Text;
|
||||||
|
type @1 :Type;
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
mobile @0;
|
||||||
|
home @1;
|
||||||
|
work @2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
employment :union {
|
||||||
|
unemployed @4 :Void;
|
||||||
|
employer @5 :Text;
|
||||||
|
school @6 :Text;
|
||||||
|
selfEmployed @7 :Void;
|
||||||
|
# We assume that a person is only one of these.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AddressBook {
|
||||||
|
people @0 :List(Person);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,367 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "calculator.capnp.h"
|
||||||
|
#include <capnp/ez-rpc.h>
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class PowerFunction final: public Calculator::Function::Server {
|
||||||
|
// An implementation of the Function interface wrapping pow(). Note that
|
||||||
|
// we're implementing this on the client side and will pass a reference to
|
||||||
|
// the server. The server will then be able to make calls back to the client.
|
||||||
|
|
||||||
|
public:
|
||||||
|
kj::Promise<void> call(CallContext context) {
|
||||||
|
auto params = context.getParams().getParams();
|
||||||
|
KJ_REQUIRE(params.size() == 2, "Wrong number of parameters.");
|
||||||
|
context.getResults().setValue(pow(params[0], params[1]));
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[]) {
|
||||||
|
if (argc != 2) {
|
||||||
|
std::cerr << "usage: " << argv[0] << " HOST:PORT\n"
|
||||||
|
"Connects to the Calculator server at the given address and "
|
||||||
|
"does some RPCs." << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
capnp::EzRpcClient client(argv[1]);
|
||||||
|
Calculator::Client calculator = client.getMain<Calculator>();
|
||||||
|
|
||||||
|
// Keep an eye on `waitScope`. Whenever you see it used is a place where we
|
||||||
|
// stop and wait for the server to respond. If a line of code does not use
|
||||||
|
// `waitScope`, then it does not block!
|
||||||
|
auto& waitScope = client.getWaitScope();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Make a request that just evaluates the literal value 123.
|
||||||
|
//
|
||||||
|
// What's interesting here is that evaluate() returns a "Value", which is
|
||||||
|
// another interface and therefore points back to an object living on the
|
||||||
|
// server. We then have to call read() on that object to read it.
|
||||||
|
// However, even though we are making two RPC's, this block executes in
|
||||||
|
// *one* network round trip because of promise pipelining: we do not wait
|
||||||
|
// for the first call to complete before we send the second call to the
|
||||||
|
// server.
|
||||||
|
|
||||||
|
std::cout << "Evaluating a literal... ";
|
||||||
|
std::cout.flush();
|
||||||
|
|
||||||
|
// Set up the request.
|
||||||
|
auto request = calculator.evaluateRequest();
|
||||||
|
request.getExpression().setLiteral(123);
|
||||||
|
|
||||||
|
// Send it, which returns a promise for the result (without blocking).
|
||||||
|
auto evalPromise = request.send();
|
||||||
|
|
||||||
|
// Using the promise, create a pipelined request to call read() on the
|
||||||
|
// returned object, and then send that.
|
||||||
|
auto readPromise = evalPromise.getValue().readRequest().send();
|
||||||
|
|
||||||
|
// Now that we've sent all the requests, wait for the response. Until this
|
||||||
|
// point, we haven't waited at all!
|
||||||
|
auto response = readPromise.wait(waitScope);
|
||||||
|
KJ_ASSERT(response.getValue() == 123);
|
||||||
|
|
||||||
|
std::cout << "PASS" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Make a request to evaluate 123 + 45 - 67.
|
||||||
|
//
|
||||||
|
// The Calculator interface requires that we first call getOperator() to
|
||||||
|
// get the addition and subtraction functions, then call evaluate() to use
|
||||||
|
// them. But, once again, we can get both functions, call evaluate(), and
|
||||||
|
// then read() the result -- four RPCs -- in the time of *one* network
|
||||||
|
// round trip, because of promise pipelining.
|
||||||
|
|
||||||
|
std::cout << "Using add and subtract... ";
|
||||||
|
std::cout.flush();
|
||||||
|
|
||||||
|
Calculator::Function::Client add = nullptr;
|
||||||
|
Calculator::Function::Client subtract = nullptr;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the "add" function from the server.
|
||||||
|
auto request = calculator.getOperatorRequest();
|
||||||
|
request.setOp(Calculator::Operator::ADD);
|
||||||
|
add = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the "subtract" function from the server.
|
||||||
|
auto request = calculator.getOperatorRequest();
|
||||||
|
request.setOp(Calculator::Operator::SUBTRACT);
|
||||||
|
subtract = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the request to evaluate 123 + 45 - 67.
|
||||||
|
auto request = calculator.evaluateRequest();
|
||||||
|
|
||||||
|
auto subtractCall = request.getExpression().initCall();
|
||||||
|
subtractCall.setFunction(subtract);
|
||||||
|
auto subtractParams = subtractCall.initParams(2);
|
||||||
|
subtractParams[1].setLiteral(67);
|
||||||
|
|
||||||
|
auto addCall = subtractParams[0].initCall();
|
||||||
|
addCall.setFunction(add);
|
||||||
|
auto addParams = addCall.initParams(2);
|
||||||
|
addParams[0].setLiteral(123);
|
||||||
|
addParams[1].setLiteral(45);
|
||||||
|
|
||||||
|
// Send the evaluate() request, read() the result, and wait for read() to
|
||||||
|
// finish.
|
||||||
|
auto evalPromise = request.send();
|
||||||
|
auto readPromise = evalPromise.getValue().readRequest().send();
|
||||||
|
|
||||||
|
auto response = readPromise.wait(waitScope);
|
||||||
|
KJ_ASSERT(response.getValue() == 101);
|
||||||
|
|
||||||
|
std::cout << "PASS" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Make a request to evaluate 4 * 6, then use the result in two more
|
||||||
|
// requests that add 3 and 5.
|
||||||
|
//
|
||||||
|
// Since evaluate() returns its result wrapped in a `Value`, we can pass
|
||||||
|
// that `Value` back to the server in subsequent requests before the first
|
||||||
|
// `evaluate()` has actually returned. Thus, this example again does only
|
||||||
|
// one network round trip.
|
||||||
|
|
||||||
|
std::cout << "Pipelining eval() calls... ";
|
||||||
|
std::cout.flush();
|
||||||
|
|
||||||
|
Calculator::Function::Client add = nullptr;
|
||||||
|
Calculator::Function::Client multiply = nullptr;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the "add" function from the server.
|
||||||
|
auto request = calculator.getOperatorRequest();
|
||||||
|
request.setOp(Calculator::Operator::ADD);
|
||||||
|
add = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the "multiply" function from the server.
|
||||||
|
auto request = calculator.getOperatorRequest();
|
||||||
|
request.setOp(Calculator::Operator::MULTIPLY);
|
||||||
|
multiply = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the request to evaluate 4 * 6
|
||||||
|
auto request = calculator.evaluateRequest();
|
||||||
|
|
||||||
|
auto multiplyCall = request.getExpression().initCall();
|
||||||
|
multiplyCall.setFunction(multiply);
|
||||||
|
auto multiplyParams = multiplyCall.initParams(2);
|
||||||
|
multiplyParams[0].setLiteral(4);
|
||||||
|
multiplyParams[1].setLiteral(6);
|
||||||
|
|
||||||
|
auto multiplyResult = request.send().getValue();
|
||||||
|
|
||||||
|
// Use the result in two calls that add 3 and add 5.
|
||||||
|
|
||||||
|
auto add3Request = calculator.evaluateRequest();
|
||||||
|
auto add3Call = add3Request.getExpression().initCall();
|
||||||
|
add3Call.setFunction(add);
|
||||||
|
auto add3Params = add3Call.initParams(2);
|
||||||
|
add3Params[0].setPreviousResult(multiplyResult);
|
||||||
|
add3Params[1].setLiteral(3);
|
||||||
|
auto add3Promise = add3Request.send().getValue().readRequest().send();
|
||||||
|
|
||||||
|
auto add5Request = calculator.evaluateRequest();
|
||||||
|
auto add5Call = add5Request.getExpression().initCall();
|
||||||
|
add5Call.setFunction(add);
|
||||||
|
auto add5Params = add5Call.initParams(2);
|
||||||
|
add5Params[0].setPreviousResult(multiplyResult);
|
||||||
|
add5Params[1].setLiteral(5);
|
||||||
|
auto add5Promise = add5Request.send().getValue().readRequest().send();
|
||||||
|
|
||||||
|
// Now wait for the results.
|
||||||
|
KJ_ASSERT(add3Promise.wait(waitScope).getValue() == 27);
|
||||||
|
KJ_ASSERT(add5Promise.wait(waitScope).getValue() == 29);
|
||||||
|
|
||||||
|
std::cout << "PASS" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Our calculator interface supports defining functions. Here we use it
|
||||||
|
// to define two functions and then make calls to them as follows:
|
||||||
|
//
|
||||||
|
// f(x, y) = x * 100 + y
|
||||||
|
// g(x) = f(x, x + 1) * 2;
|
||||||
|
// f(12, 34)
|
||||||
|
// g(21)
|
||||||
|
//
|
||||||
|
// Once again, the whole thing takes only one network round trip.
|
||||||
|
|
||||||
|
std::cout << "Defining functions... ";
|
||||||
|
std::cout.flush();
|
||||||
|
|
||||||
|
Calculator::Function::Client add = nullptr;
|
||||||
|
Calculator::Function::Client multiply = nullptr;
|
||||||
|
Calculator::Function::Client f = nullptr;
|
||||||
|
Calculator::Function::Client g = nullptr;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the "add" function from the server.
|
||||||
|
auto request = calculator.getOperatorRequest();
|
||||||
|
request.setOp(Calculator::Operator::ADD);
|
||||||
|
add = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the "multiply" function from the server.
|
||||||
|
auto request = calculator.getOperatorRequest();
|
||||||
|
request.setOp(Calculator::Operator::MULTIPLY);
|
||||||
|
multiply = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Define f.
|
||||||
|
auto request = calculator.defFunctionRequest();
|
||||||
|
request.setParamCount(2);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Build the function body.
|
||||||
|
auto addCall = request.getBody().initCall();
|
||||||
|
addCall.setFunction(add);
|
||||||
|
auto addParams = addCall.initParams(2);
|
||||||
|
addParams[1].setParameter(1); // y
|
||||||
|
|
||||||
|
auto multiplyCall = addParams[0].initCall();
|
||||||
|
multiplyCall.setFunction(multiply);
|
||||||
|
auto multiplyParams = multiplyCall.initParams(2);
|
||||||
|
multiplyParams[0].setParameter(0); // x
|
||||||
|
multiplyParams[1].setLiteral(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
f = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Define g.
|
||||||
|
auto request = calculator.defFunctionRequest();
|
||||||
|
request.setParamCount(1);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Build the function body.
|
||||||
|
auto multiplyCall = request.getBody().initCall();
|
||||||
|
multiplyCall.setFunction(multiply);
|
||||||
|
auto multiplyParams = multiplyCall.initParams(2);
|
||||||
|
multiplyParams[1].setLiteral(2);
|
||||||
|
|
||||||
|
auto fCall = multiplyParams[0].initCall();
|
||||||
|
fCall.setFunction(f);
|
||||||
|
auto fParams = fCall.initParams(2);
|
||||||
|
fParams[0].setParameter(0);
|
||||||
|
|
||||||
|
auto addCall = fParams[1].initCall();
|
||||||
|
addCall.setFunction(add);
|
||||||
|
auto addParams = addCall.initParams(2);
|
||||||
|
addParams[0].setParameter(0);
|
||||||
|
addParams[1].setLiteral(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
g = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK, we've defined all our functions. Now create our eval requests.
|
||||||
|
|
||||||
|
// f(12, 34)
|
||||||
|
auto fEvalRequest = calculator.evaluateRequest();
|
||||||
|
auto fCall = fEvalRequest.initExpression().initCall();
|
||||||
|
fCall.setFunction(f);
|
||||||
|
auto fParams = fCall.initParams(2);
|
||||||
|
fParams[0].setLiteral(12);
|
||||||
|
fParams[1].setLiteral(34);
|
||||||
|
auto fEvalPromise = fEvalRequest.send().getValue().readRequest().send();
|
||||||
|
|
||||||
|
// g(21)
|
||||||
|
auto gEvalRequest = calculator.evaluateRequest();
|
||||||
|
auto gCall = gEvalRequest.initExpression().initCall();
|
||||||
|
gCall.setFunction(g);
|
||||||
|
gCall.initParams(1)[0].setLiteral(21);
|
||||||
|
auto gEvalPromise = gEvalRequest.send().getValue().readRequest().send();
|
||||||
|
|
||||||
|
// Wait for the results.
|
||||||
|
KJ_ASSERT(fEvalPromise.wait(waitScope).getValue() == 1234);
|
||||||
|
KJ_ASSERT(gEvalPromise.wait(waitScope).getValue() == 4244);
|
||||||
|
|
||||||
|
std::cout << "PASS" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Make a request that will call back to a function defined locally.
|
||||||
|
//
|
||||||
|
// Specifically, we will compute 2^(4 + 5). However, exponent is not
|
||||||
|
// defined by the Calculator server. So, we'll implement the Function
|
||||||
|
// interface locally and pass it to the server for it to use when
|
||||||
|
// evaluating the expression.
|
||||||
|
//
|
||||||
|
// This example requires two network round trips to complete, because the
|
||||||
|
// server calls back to the client once before finishing. In this
|
||||||
|
// particular case, this could potentially be optimized by using a tail
|
||||||
|
// call on the server side -- see CallContext::tailCall(). However, to
|
||||||
|
// keep the example simpler, we haven't implemented this optimization in
|
||||||
|
// the sample server.
|
||||||
|
|
||||||
|
std::cout << "Using a callback... ";
|
||||||
|
std::cout.flush();
|
||||||
|
|
||||||
|
Calculator::Function::Client add = nullptr;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the "add" function from the server.
|
||||||
|
auto request = calculator.getOperatorRequest();
|
||||||
|
request.setOp(Calculator::Operator::ADD);
|
||||||
|
add = request.send().getFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the eval request for 2^(4+5).
|
||||||
|
auto request = calculator.evaluateRequest();
|
||||||
|
|
||||||
|
auto powCall = request.getExpression().initCall();
|
||||||
|
powCall.setFunction(kj::heap<PowerFunction>());
|
||||||
|
auto powParams = powCall.initParams(2);
|
||||||
|
powParams[0].setLiteral(2);
|
||||||
|
|
||||||
|
auto addCall = powParams[1].initCall();
|
||||||
|
addCall.setFunction(add);
|
||||||
|
auto addParams = addCall.initParams(2);
|
||||||
|
addParams[0].setLiteral(4);
|
||||||
|
addParams[1].setLiteral(5);
|
||||||
|
|
||||||
|
// Send the request and wait.
|
||||||
|
auto response = request.send().getValue().readRequest()
|
||||||
|
.send().wait(waitScope);
|
||||||
|
KJ_ASSERT(response.getValue() == 512);
|
||||||
|
|
||||||
|
std::cout << "PASS" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "calculator.capnp.h"
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <capnp/ez-rpc.h>
|
||||||
|
#include <capnp/message.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
typedef unsigned int uint;
|
||||||
|
|
||||||
|
kj::Promise<double> readValue(Calculator::Value::Client value) {
|
||||||
|
// Helper function to asynchronously call read() on a Calculator::Value and
|
||||||
|
// return a promise for the result. (In the future, the generated code might
|
||||||
|
// include something like this automatically.)
|
||||||
|
|
||||||
|
return value.readRequest().send()
|
||||||
|
.then([](capnp::Response<Calculator::Value::ReadResults> result) {
|
||||||
|
return result.getValue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<double> evaluateImpl(
|
||||||
|
Calculator::Expression::Reader expression,
|
||||||
|
capnp::List<double>::Reader params = capnp::List<double>::Reader()) {
|
||||||
|
// Implementation of CalculatorImpl::evaluate(), also shared by
|
||||||
|
// FunctionImpl::call(). In the latter case, `params` are the parameter
|
||||||
|
// values passed to the function; in the former case, `params` is just an
|
||||||
|
// empty list.
|
||||||
|
|
||||||
|
switch (expression.which()) {
|
||||||
|
case Calculator::Expression::LITERAL:
|
||||||
|
return expression.getLiteral();
|
||||||
|
|
||||||
|
case Calculator::Expression::PREVIOUS_RESULT:
|
||||||
|
return readValue(expression.getPreviousResult());
|
||||||
|
|
||||||
|
case Calculator::Expression::PARAMETER: {
|
||||||
|
KJ_REQUIRE(expression.getParameter() < params.size(),
|
||||||
|
"Parameter index out-of-range.");
|
||||||
|
return params[expression.getParameter()];
|
||||||
|
}
|
||||||
|
|
||||||
|
case Calculator::Expression::CALL: {
|
||||||
|
auto call = expression.getCall();
|
||||||
|
auto func = call.getFunction();
|
||||||
|
|
||||||
|
// Evaluate each parameter.
|
||||||
|
kj::Array<kj::Promise<double>> paramPromises =
|
||||||
|
KJ_MAP(param, call.getParams()) {
|
||||||
|
return evaluateImpl(param, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Join the array of promises into a promise for an array.
|
||||||
|
kj::Promise<kj::Array<double>> joinedParams =
|
||||||
|
kj::joinPromises(kj::mv(paramPromises));
|
||||||
|
|
||||||
|
// When the parameters are complete, call the function.
|
||||||
|
return joinedParams.then([KJ_CPCAP(func)](kj::Array<double>&& paramValues) mutable {
|
||||||
|
auto request = func.callRequest();
|
||||||
|
request.setParams(paramValues);
|
||||||
|
return request.send().then(
|
||||||
|
[](capnp::Response<Calculator::Function::CallResults>&& result) {
|
||||||
|
return result.getValue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Throw an exception.
|
||||||
|
KJ_FAIL_REQUIRE("Unknown expression type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValueImpl final: public Calculator::Value::Server {
|
||||||
|
// Simple implementation of the Calculator.Value Cap'n Proto interface.
|
||||||
|
|
||||||
|
public:
|
||||||
|
ValueImpl(double value): value(value) {}
|
||||||
|
|
||||||
|
kj::Promise<void> read(ReadContext context) {
|
||||||
|
context.getResults().setValue(value);
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double value;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FunctionImpl final: public Calculator::Function::Server {
|
||||||
|
// Implementation of the Calculator.Function Cap'n Proto interface, where the
|
||||||
|
// function is defined by a Calculator.Expression.
|
||||||
|
|
||||||
|
public:
|
||||||
|
FunctionImpl(uint paramCount, Calculator::Expression::Reader body)
|
||||||
|
: paramCount(paramCount) {
|
||||||
|
this->body.setRoot(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> call(CallContext context) {
|
||||||
|
auto params = context.getParams().getParams();
|
||||||
|
KJ_REQUIRE(params.size() == paramCount, "Wrong number of parameters.");
|
||||||
|
|
||||||
|
return evaluateImpl(body.getRoot<Calculator::Expression>(), params)
|
||||||
|
.then([KJ_CPCAP(context)](double value) mutable {
|
||||||
|
context.getResults().setValue(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint paramCount;
|
||||||
|
// The function's arity.
|
||||||
|
|
||||||
|
capnp::MallocMessageBuilder body;
|
||||||
|
// Stores a permanent copy of the function body.
|
||||||
|
};
|
||||||
|
|
||||||
|
class OperatorImpl final: public Calculator::Function::Server {
|
||||||
|
// Implementation of the Calculator.Function Cap'n Proto interface, wrapping
|
||||||
|
// basic binary arithmetic operators.
|
||||||
|
|
||||||
|
public:
|
||||||
|
OperatorImpl(Calculator::Operator op): op(op) {}
|
||||||
|
|
||||||
|
kj::Promise<void> call(CallContext context) {
|
||||||
|
auto params = context.getParams().getParams();
|
||||||
|
KJ_REQUIRE(params.size() == 2, "Wrong number of parameters.");
|
||||||
|
|
||||||
|
double result;
|
||||||
|
switch (op) {
|
||||||
|
case Calculator::Operator::ADD: result = params[0] + params[1]; break;
|
||||||
|
case Calculator::Operator::SUBTRACT:result = params[0] - params[1]; break;
|
||||||
|
case Calculator::Operator::MULTIPLY:result = params[0] * params[1]; break;
|
||||||
|
case Calculator::Operator::DIVIDE: result = params[0] / params[1]; break;
|
||||||
|
default:
|
||||||
|
KJ_FAIL_REQUIRE("Unknown operator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.getResults().setValue(result);
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Calculator::Operator op;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CalculatorImpl final: public Calculator::Server {
|
||||||
|
// Implementation of the Calculator Cap'n Proto interface.
|
||||||
|
|
||||||
|
public:
|
||||||
|
kj::Promise<void> evaluate(EvaluateContext context) override {
|
||||||
|
return evaluateImpl(context.getParams().getExpression())
|
||||||
|
.then([KJ_CPCAP(context)](double value) mutable {
|
||||||
|
context.getResults().setValue(kj::heap<ValueImpl>(value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> defFunction(DefFunctionContext context) override {
|
||||||
|
auto params = context.getParams();
|
||||||
|
context.getResults().setFunc(kj::heap<FunctionImpl>(
|
||||||
|
params.getParamCount(), params.getBody()));
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> getOperator(GetOperatorContext context) override {
|
||||||
|
context.getResults().setFunc(kj::heap<OperatorImpl>(
|
||||||
|
context.getParams().getOp()));
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[]) {
|
||||||
|
if (argc != 2) {
|
||||||
|
std::cerr << "usage: " << argv[0] << " ADDRESS[:PORT]\n"
|
||||||
|
"Runs the server bound to the given address/port.\n"
|
||||||
|
"ADDRESS may be '*' to bind to all local addresses.\n"
|
||||||
|
":PORT may be omitted to choose a port automatically." << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up a server.
|
||||||
|
capnp::EzRpcServer server(kj::heap<CalculatorImpl>(), argv[1]);
|
||||||
|
|
||||||
|
// Write the port number to stdout, in case it was chosen automatically.
|
||||||
|
auto& waitScope = server.getWaitScope();
|
||||||
|
uint port = server.getPort().wait(waitScope);
|
||||||
|
if (port == 0) {
|
||||||
|
// The address format "unix:/path/to/socket" opens a unix domain socket,
|
||||||
|
// in which case the port will be zero.
|
||||||
|
std::cout << "Listening on Unix socket..." << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Listening on port " << port << "..." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run forever, accepting connections and handling requests.
|
||||||
|
kj::NEVER_DONE.wait(waitScope);
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
@0x85150b117366d14b;
|
||||||
|
|
||||||
|
interface Calculator {
|
||||||
|
# A "simple" mathematical calculator, callable via RPC.
|
||||||
|
#
|
||||||
|
# But, to show off Cap'n Proto, we add some twists:
|
||||||
|
#
|
||||||
|
# - You can use the result from one call as the input to the next
|
||||||
|
# without a network round trip. To accomplish this, evaluate()
|
||||||
|
# returns a `Value` object wrapping the actual numeric value.
|
||||||
|
# This object may be used in a subsequent expression. With
|
||||||
|
# promise pipelining, the Value can actually be used before
|
||||||
|
# the evaluate() call that creates it returns!
|
||||||
|
#
|
||||||
|
# - You can define new functions, and then call them. This again
|
||||||
|
# shows off pipelining, but it also gives the client the
|
||||||
|
# opportunity to define a function on the client side and have
|
||||||
|
# the server call back to it.
|
||||||
|
#
|
||||||
|
# - The basic arithmetic operators are exposed as Functions, and
|
||||||
|
# you have to call getOperator() to obtain them from the server.
|
||||||
|
# This again demonstrates pipelining -- using getOperator() to
|
||||||
|
# get each operator and then using them in evaluate() still
|
||||||
|
# only takes one network round trip.
|
||||||
|
|
||||||
|
evaluate @0 (expression :Expression) -> (value :Value);
|
||||||
|
# Evaluate the given expression and return the result. The
|
||||||
|
# result is returned wrapped in a Value interface so that you
|
||||||
|
# may pass it back to the server in a pipelined request. To
|
||||||
|
# actually get the numeric value, you must call read() on the
|
||||||
|
# Value -- but again, this can be pipelined so that it incurs
|
||||||
|
# no additional latency.
|
||||||
|
|
||||||
|
struct Expression {
|
||||||
|
# A numeric expression.
|
||||||
|
|
||||||
|
union {
|
||||||
|
literal @0 :Float64;
|
||||||
|
# A literal numeric value.
|
||||||
|
|
||||||
|
previousResult @1 :Value;
|
||||||
|
# A value that was (or, will be) returned by a previous
|
||||||
|
# evaluate().
|
||||||
|
|
||||||
|
parameter @2 :UInt32;
|
||||||
|
# A parameter to the function (only valid in function bodies;
|
||||||
|
# see defFunction).
|
||||||
|
|
||||||
|
call :group {
|
||||||
|
# Call a function on a list of parameters.
|
||||||
|
function @3 :Function;
|
||||||
|
params @4 :List(Expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Value {
|
||||||
|
# Wraps a numeric value in an RPC object. This allows the value
|
||||||
|
# to be used in subsequent evaluate() requests without the client
|
||||||
|
# waiting for the evaluate() that returns the Value to finish.
|
||||||
|
|
||||||
|
read @0 () -> (value :Float64);
|
||||||
|
# Read back the raw numeric value.
|
||||||
|
}
|
||||||
|
|
||||||
|
defFunction @1 (paramCount :Int32, body :Expression)
|
||||||
|
-> (func :Function);
|
||||||
|
# Define a function that takes `paramCount` parameters and returns the
|
||||||
|
# evaluation of `body` after substituting these parameters.
|
||||||
|
|
||||||
|
interface Function {
|
||||||
|
# An algebraic function. Can be called directly, or can be used inside
|
||||||
|
# an Expression.
|
||||||
|
#
|
||||||
|
# A client can create a Function that runs on the server side using
|
||||||
|
# `defFunction()` or `getOperator()`. Alternatively, a client can
|
||||||
|
# implement a Function on the client side and the server will call back
|
||||||
|
# to it. However, a function defined on the client side will require a
|
||||||
|
# network round trip whenever the server needs to call it, whereas
|
||||||
|
# functions defined on the server and then passed back to it are called
|
||||||
|
# locally.
|
||||||
|
|
||||||
|
call @0 (params :List(Float64)) -> (value :Float64);
|
||||||
|
# Call the function on the given parameters.
|
||||||
|
}
|
||||||
|
|
||||||
|
getOperator @2 (op :Operator) -> (func :Function);
|
||||||
|
# Get a Function representing an arithmetic operator, which can then be
|
||||||
|
# used in Expressions.
|
||||||
|
|
||||||
|
enum Operator {
|
||||||
|
add @0;
|
||||||
|
subtract @1;
|
||||||
|
multiply @2;
|
||||||
|
divide @3;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
#! /usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Quick script that compiles and runs the samples, then cleans up.
|
||||||
|
# Used for release testing.
|
||||||
|
|
||||||
|
set -exuo pipefail
|
||||||
|
|
||||||
|
capnpc -oc++ addressbook.capnp
|
||||||
|
c++ -std=c++14 -Wall addressbook.c++ addressbook.capnp.c++ \
|
||||||
|
$(pkg-config --cflags --libs capnp) -o addressbook
|
||||||
|
./addressbook write | ./addressbook read
|
||||||
|
./addressbook dwrite | ./addressbook dread
|
||||||
|
rm addressbook addressbook.capnp.c++ addressbook.capnp.h
|
||||||
|
|
||||||
|
capnpc -oc++ calculator.capnp
|
||||||
|
c++ -std=c++14 -Wall calculator-client.c++ calculator.capnp.c++ \
|
||||||
|
$(pkg-config --cflags --libs capnp-rpc) -o calculator-client
|
||||||
|
c++ -std=c++14 -Wall calculator-server.c++ calculator.capnp.c++ \
|
||||||
|
$(pkg-config --cflags --libs capnp-rpc) -o calculator-server
|
||||||
|
rm -f /tmp/capnp-calculator-example-$$
|
||||||
|
./calculator-server unix:/tmp/capnp-calculator-example-$$ &
|
||||||
|
sleep 0.1
|
||||||
|
./calculator-client unix:/tmp/capnp-calculator-example-$$
|
||||||
|
kill %+
|
||||||
|
wait %+ || true
|
||||||
|
rm calculator-client calculator-server calculator.capnp.c++ calculator.capnp.h /tmp/capnp-calculator-example-$$
|
|
@ -0,0 +1,6 @@
|
||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "This script is no longer needed. Go ahead and run autoreconf. For example:"
|
||||||
|
echo " autoreconf -i && ./configure && make -j6 check && sudo make install"
|
|
@ -0,0 +1,94 @@
|
||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! uname | grep -iq Linux; then
|
||||||
|
echo "Sorry, Ekam only works on Linux right now." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "Looking for compiler... "
|
||||||
|
if [ "x${CXX:-}" == "x" ]; then
|
||||||
|
if ! (g++ --version | grep -q ' 4[.][789][.]'); then
|
||||||
|
if which g++-4.7 > /dev/null; then
|
||||||
|
CXX=g++-4.7
|
||||||
|
elif which g++-4.8 > /dev/null; then
|
||||||
|
CXX=g++-4.8
|
||||||
|
else
|
||||||
|
echo "none"
|
||||||
|
echo "Please install G++ 4.7 or better. Or, set the environment variable CXX " >&2
|
||||||
|
echo "to a compiler that you think will work." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
CXX=g++
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$CXX"
|
||||||
|
export CXX
|
||||||
|
|
||||||
|
if [ ! -e .ekam ]; then
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "Fetching Ekam and Protobuf code..."
|
||||||
|
echo "================================================================================"
|
||||||
|
hg clone https://code.google.com/p/kentons-code/ .ekam
|
||||||
|
|
||||||
|
# You don't want these.
|
||||||
|
rm -rf .ekam/src/modc .ekam/src/evlan
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e .ekam/src/protobuf ]; then
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "Fetching Protobuf code..."
|
||||||
|
echo "================================================================================"
|
||||||
|
svn checkout http://protobuf.googlecode.com/svn/tags/2.5.0/ .ekam/src/protobuf
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e .ekam/src/protobuf/src/config.h ]; then
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "Configuring Protobuf..."
|
||||||
|
echo "================================================================================"
|
||||||
|
pushd .ekam/src/protobuf > /dev/null
|
||||||
|
./autogen.sh
|
||||||
|
./configure
|
||||||
|
cp config.h src
|
||||||
|
make maintainer-clean
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! which ekam > /dev/null; then
|
||||||
|
if [ ! -e .ekam/bin/ekam ]; then
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "Bootstrapping Ekam..."
|
||||||
|
echo "================================================================================"
|
||||||
|
pushd .ekam > /dev/null
|
||||||
|
./bootstrap.sh
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "Using already-installed ekam binary: $(which ekam)"
|
||||||
|
echo "================================================================================"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e src/base ]; then
|
||||||
|
ln -s ../.ekam/src/base src/base
|
||||||
|
fi
|
||||||
|
if [ ! -e src/os ]; then
|
||||||
|
ln -s ../.ekam/src/os src/os
|
||||||
|
fi
|
||||||
|
if [ ! -e src/ekam ]; then
|
||||||
|
ln -s ../.ekam/src/ekam src/ekam
|
||||||
|
fi
|
||||||
|
if [ ! -e src/protobuf ]; then
|
||||||
|
ln -s ../.ekam/src/protobuf src/protobuf
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "All done..."
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "Try:"
|
||||||
|
echo " make -f Makefile.ekam once"
|
||||||
|
echo " make -f Makefile.ekam continuous"
|
||||||
|
echo " make -f Makefile.ekam continuous-opt"
|
|
@ -0,0 +1,50 @@
|
||||||
|
|
||||||
|
# Tests ========================================================================
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
|
if(EXTERNAL_CAPNP)
|
||||||
|
# Setup CAPNP_GENERATE_CPP for compiling test schemas
|
||||||
|
find_package(CapnProto CONFIG QUIET)
|
||||||
|
if(NOT CapnProto_FOUND)
|
||||||
|
# No working installation of Cap'n Proto found, so fall back to searching the environment.
|
||||||
|
#
|
||||||
|
# We search for the external capnp compiler binaries via $CAPNP, $CAPNPC_CXX, and
|
||||||
|
# find_program(). find_program() will use various paths in its search, among them
|
||||||
|
# ${CMAKE_PREFIX_PATH}/bin and $PATH.
|
||||||
|
|
||||||
|
if(NOT CAPNP_EXECUTABLE)
|
||||||
|
if(DEFINED ENV{CAPNP})
|
||||||
|
set(CAPNP_EXECUTABLE "$ENV{CAPNP}")
|
||||||
|
else()
|
||||||
|
find_program(CAPNP_EXECUTABLE capnp)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CAPNPC_CXX_EXECUTABLE)
|
||||||
|
if(DEFINED ENV{CAPNPC_CXX})
|
||||||
|
set(CAPNPC_CXX_EXECUTABLE "$ENV{CAPNPC_CXX}")
|
||||||
|
else()
|
||||||
|
# Also search in the same directory that `capnp` was found in
|
||||||
|
get_filename_component(capnp_dir "${CAPNP_EXECUTABLE}" DIRECTORY)
|
||||||
|
find_program(CAPNPC_CXX_EXECUTABLE capnpc-c++ HINTS "${capnp_dir}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CAPNP_INCLUDE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
#TODO(someday) It would be nice to use targets instead of variables in CAPNP_GENERATE_CPP macro
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Sadly, we can't use the 'test' target, as that's coopted by ctest
|
||||||
|
add_custom_target(check "${CMAKE_CTEST_COMMAND}" -V)
|
||||||
|
endif() # BUILD_TESTING
|
||||||
|
|
||||||
|
# kj ===========================================================================
|
||||||
|
|
||||||
|
add_subdirectory(kj)
|
||||||
|
|
||||||
|
# capnp ========================================================================
|
||||||
|
|
||||||
|
add_subdirectory(capnp)
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "carsales.capnp.h"
|
||||||
|
#include "capnproto-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
template <typename ReaderOrBuilder>
|
||||||
|
uint64_t carValue(ReaderOrBuilder car) {
|
||||||
|
// Do not think too hard about realism.
|
||||||
|
|
||||||
|
uint64_t result = 0;
|
||||||
|
|
||||||
|
result += car.getSeats() * 200;
|
||||||
|
result += car.getDoors() * 350;
|
||||||
|
for (auto wheel: car.getWheels()) {
|
||||||
|
result += wheel.getDiameter() * wheel.getDiameter();
|
||||||
|
result += wheel.getSnowTires() ? 100 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += car.getLength() * car.getWidth() * car.getHeight() / 50;
|
||||||
|
|
||||||
|
auto engine = car.getEngine();
|
||||||
|
result += engine.getHorsepower() * 40;
|
||||||
|
if (engine.getUsesElectric()) {
|
||||||
|
if (engine.getUsesGas()) {
|
||||||
|
// hybrid
|
||||||
|
result += 5000;
|
||||||
|
} else {
|
||||||
|
result += 3000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += car.getHasPowerWindows() ? 100 : 0;
|
||||||
|
result += car.getHasPowerSteering() ? 200 : 0;
|
||||||
|
result += car.getHasCruiseControl() ? 400 : 0;
|
||||||
|
result += car.getHasNavSystem() ? 2000 : 0;
|
||||||
|
|
||||||
|
result += car.getCupHolders() * 25;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void randomCar(Car::Builder car) {
|
||||||
|
// Do not think too hard about realism.
|
||||||
|
|
||||||
|
static const char* const MAKES[] = { "Toyota", "GM", "Ford", "Honda", "Tesla" };
|
||||||
|
static const char* const MODELS[] = { "Camry", "Prius", "Volt", "Accord", "Leaf", "Model S" };
|
||||||
|
|
||||||
|
car.setMake(MAKES[fastRand(sizeof(MAKES) / sizeof(MAKES[0]))]);
|
||||||
|
car.setModel(MODELS[fastRand(sizeof(MODELS) / sizeof(MODELS[0]))]);
|
||||||
|
|
||||||
|
car.setColor((Color)fastRand((uint)Color::SILVER + 1));
|
||||||
|
car.setSeats(2 + fastRand(6));
|
||||||
|
car.setDoors(2 + fastRand(3));
|
||||||
|
|
||||||
|
for (auto wheel: car.initWheels(4)) {
|
||||||
|
wheel.setDiameter(25 + fastRand(15));
|
||||||
|
wheel.setAirPressure(30 + fastRandDouble(20));
|
||||||
|
wheel.setSnowTires(fastRand(16) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
car.setLength(170 + fastRand(150));
|
||||||
|
car.setWidth(48 + fastRand(36));
|
||||||
|
car.setHeight(54 + fastRand(48));
|
||||||
|
car.setWeight(car.getLength() * car.getWidth() * car.getHeight() / 200);
|
||||||
|
|
||||||
|
auto engine = car.initEngine();
|
||||||
|
engine.setHorsepower(100 * fastRand(400));
|
||||||
|
engine.setCylinders(4 + 2 * fastRand(3));
|
||||||
|
engine.setCc(800 + fastRand(10000));
|
||||||
|
engine.setUsesGas(true);
|
||||||
|
engine.setUsesElectric(fastRand(2));
|
||||||
|
|
||||||
|
car.setFuelCapacity(10.0 + fastRandDouble(30.0));
|
||||||
|
car.setFuelLevel(fastRandDouble(car.getFuelCapacity()));
|
||||||
|
car.setHasPowerWindows(fastRand(2));
|
||||||
|
car.setHasPowerSteering(fastRand(2));
|
||||||
|
car.setHasCruiseControl(fastRand(2));
|
||||||
|
car.setCupHolders(fastRand(12));
|
||||||
|
car.setHasNavSystem(fastRand(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
class CarSalesTestCase {
|
||||||
|
public:
|
||||||
|
typedef ParkingLot Request;
|
||||||
|
typedef TotalValue Response;
|
||||||
|
typedef uint64_t Expectation;
|
||||||
|
|
||||||
|
static uint64_t setupRequest(ParkingLot::Builder request) {
|
||||||
|
uint64_t result = 0;
|
||||||
|
for (auto car: request.initCars(fastRand(200))) {
|
||||||
|
randomCar(car);
|
||||||
|
result += carValue(car);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
static void handleRequest(ParkingLot::Reader request, TotalValue::Builder response) {
|
||||||
|
uint64_t result = 0;
|
||||||
|
for (auto car: request.getCars()) {
|
||||||
|
result += carValue(car);
|
||||||
|
}
|
||||||
|
response.setAmount(result);
|
||||||
|
}
|
||||||
|
static inline bool checkResponse(TotalValue::Reader response, uint64_t expected) {
|
||||||
|
return response.getAmount() == expected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace capnp
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::capnp::BenchmarkTypes,
|
||||||
|
capnp::benchmark::capnp::CarSalesTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "catrank.capnp.h"
|
||||||
|
#include "capnproto-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
struct ScoredResult {
|
||||||
|
double score;
|
||||||
|
SearchResult::Reader result;
|
||||||
|
|
||||||
|
ScoredResult() = default;
|
||||||
|
ScoredResult(double score, SearchResult::Reader result): score(score), result(result) {}
|
||||||
|
|
||||||
|
inline bool operator<(const ScoredResult& other) const { return score > other.score; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class CatRankTestCase {
|
||||||
|
public:
|
||||||
|
typedef SearchResultList Request;
|
||||||
|
typedef SearchResultList Response;
|
||||||
|
typedef int Expectation;
|
||||||
|
|
||||||
|
static int setupRequest(SearchResultList::Builder request) {
|
||||||
|
int count = fastRand(1000);
|
||||||
|
int goodCount = 0;
|
||||||
|
|
||||||
|
auto list = request.initResults(count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
SearchResult::Builder result = list[i];
|
||||||
|
result.setScore(1000 - i);
|
||||||
|
int urlSize = fastRand(100);
|
||||||
|
|
||||||
|
static const char URL_PREFIX[] = "http://example.com/";
|
||||||
|
size_t urlPrefixLength = strlen(URL_PREFIX);
|
||||||
|
auto url = result.initUrl(urlSize + urlPrefixLength);
|
||||||
|
|
||||||
|
strcpy(url.begin(), URL_PREFIX);
|
||||||
|
char* pos = url.begin() + urlPrefixLength;
|
||||||
|
for (int j = 0; j < urlSize; j++) {
|
||||||
|
*pos++ = 'a' + fastRand(26);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCat = fastRand(8) == 0;
|
||||||
|
bool isDog = fastRand(8) == 0;
|
||||||
|
goodCount += isCat && !isDog;
|
||||||
|
|
||||||
|
static std::string snippet;
|
||||||
|
snippet.clear();
|
||||||
|
snippet.push_back(' ');
|
||||||
|
|
||||||
|
int prefix = fastRand(20);
|
||||||
|
for (int j = 0; j < prefix; j++) {
|
||||||
|
snippet.append(WORDS[fastRand(WORDS_COUNT)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCat) snippet.append("cat ");
|
||||||
|
if (isDog) snippet.append("dog ");
|
||||||
|
|
||||||
|
int suffix = fastRand(20);
|
||||||
|
for (int j = 0; j < suffix; j++) {
|
||||||
|
snippet.append(WORDS[fastRand(WORDS_COUNT)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setSnippet(Text::Reader(snippet.c_str(), snippet.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return goodCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleRequest(SearchResultList::Reader request, SearchResultList::Builder response) {
|
||||||
|
std::vector<ScoredResult> scoredResults;
|
||||||
|
|
||||||
|
for (auto result: request.getResults()) {
|
||||||
|
double score = result.getScore();
|
||||||
|
if (strstr(result.getSnippet().cStr(), " cat ") != nullptr) {
|
||||||
|
score *= 10000;
|
||||||
|
}
|
||||||
|
if (strstr(result.getSnippet().cStr(), " dog ") != nullptr) {
|
||||||
|
score /= 10000;
|
||||||
|
}
|
||||||
|
scoredResults.emplace_back(score, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(scoredResults.begin(), scoredResults.end());
|
||||||
|
|
||||||
|
auto list = response.initResults(scoredResults.size());
|
||||||
|
auto iter = list.begin();
|
||||||
|
for (auto result: scoredResults) {
|
||||||
|
iter->setScore(result.score);
|
||||||
|
iter->setUrl(result.result.getUrl());
|
||||||
|
iter->setSnippet(result.result.getSnippet());
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkResponse(SearchResultList::Reader response, int expectedGoodCount) {
|
||||||
|
int goodCount = 0;
|
||||||
|
for (auto result: response.getResults()) {
|
||||||
|
if (result.getScore() > 1001) {
|
||||||
|
++goodCount;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return goodCount == expectedGoodCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace capnp
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::capnp::BenchmarkTypes,
|
||||||
|
capnp::benchmark::capnp::CatRankTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,420 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
|
||||||
|
#pragma GCC system_header
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include <capnp/serialize.h>
|
||||||
|
#include <capnp/serialize-packed.h>
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#if HAVE_SNAPPY
|
||||||
|
#include <capnp/serialize-snappy.h>
|
||||||
|
#endif // HAVE_SNAPPY
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
class CountingOutputStream: public kj::FdOutputStream {
|
||||||
|
public:
|
||||||
|
CountingOutputStream(int fd): FdOutputStream(fd), throughput(0) {}
|
||||||
|
|
||||||
|
uint64_t throughput;
|
||||||
|
|
||||||
|
void write(const void* buffer, size_t size) override {
|
||||||
|
FdOutputStream::write(buffer, size);
|
||||||
|
throughput += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {
|
||||||
|
FdOutputStream::write(pieces);
|
||||||
|
for (auto& piece: pieces) {
|
||||||
|
throughput += piece.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
struct Uncompressed {
|
||||||
|
typedef kj::FdInputStream& BufferedInput;
|
||||||
|
typedef InputStreamMessageReader MessageReader;
|
||||||
|
|
||||||
|
class ArrayMessageReader: public FlatArrayMessageReader {
|
||||||
|
public:
|
||||||
|
ArrayMessageReader(kj::ArrayPtr<const byte> array,
|
||||||
|
ReaderOptions options = ReaderOptions(),
|
||||||
|
kj::ArrayPtr<word> scratchSpace = nullptr)
|
||||||
|
: FlatArrayMessageReader(kj::arrayPtr(
|
||||||
|
reinterpret_cast<const word*>(array.begin()),
|
||||||
|
reinterpret_cast<const word*>(array.end())), options) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void write(kj::OutputStream& output, MessageBuilder& builder) {
|
||||||
|
writeMessage(output, builder);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Packed {
|
||||||
|
typedef kj::BufferedInputStreamWrapper BufferedInput;
|
||||||
|
typedef PackedMessageReader MessageReader;
|
||||||
|
|
||||||
|
class ArrayMessageReader: private kj::ArrayInputStream, public PackedMessageReader {
|
||||||
|
public:
|
||||||
|
ArrayMessageReader(kj::ArrayPtr<const byte> array,
|
||||||
|
ReaderOptions options = ReaderOptions(),
|
||||||
|
kj::ArrayPtr<word> scratchSpace = nullptr)
|
||||||
|
: ArrayInputStream(array),
|
||||||
|
PackedMessageReader(*this, options, scratchSpace) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void write(kj::OutputStream& output, MessageBuilder& builder) {
|
||||||
|
writePackedMessage(output, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void write(kj::BufferedOutputStream& output, MessageBuilder& builder) {
|
||||||
|
writePackedMessage(output, builder);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if HAVE_SNAPPY
|
||||||
|
static byte snappyReadBuffer[SNAPPY_BUFFER_SIZE];
|
||||||
|
static byte snappyWriteBuffer[SNAPPY_BUFFER_SIZE];
|
||||||
|
static byte snappyCompressedBuffer[SNAPPY_COMPRESSED_BUFFER_SIZE];
|
||||||
|
|
||||||
|
struct SnappyCompressed {
|
||||||
|
typedef BufferedInputStreamWrapper BufferedInput;
|
||||||
|
typedef SnappyPackedMessageReader MessageReader;
|
||||||
|
|
||||||
|
class ArrayMessageReader: private ArrayInputStream, public SnappyPackedMessageReader {
|
||||||
|
public:
|
||||||
|
ArrayMessageReader(kj::ArrayPtr<const byte> array,
|
||||||
|
ReaderOptions options = ReaderOptions(),
|
||||||
|
kj::ArrayPtr<word> scratchSpace = nullptr)
|
||||||
|
: ArrayInputStream(array),
|
||||||
|
SnappyPackedMessageReader(static_cast<ArrayInputStream&>(*this), options, scratchSpace,
|
||||||
|
kj::arrayPtr(snappyReadBuffer, SNAPPY_BUFFER_SIZE)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void write(OutputStream& output, MessageBuilder& builder) {
|
||||||
|
writeSnappyPackedMessage(output, builder,
|
||||||
|
kj::arrayPtr(snappyWriteBuffer, SNAPPY_BUFFER_SIZE),
|
||||||
|
kj::arrayPtr(snappyCompressedBuffer, SNAPPY_COMPRESSED_BUFFER_SIZE));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // HAVE_SNAPPY
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
struct NoScratch {
|
||||||
|
struct ScratchSpace {};
|
||||||
|
|
||||||
|
template <typename Compression>
|
||||||
|
class MessageReader: public Compression::MessageReader {
|
||||||
|
public:
|
||||||
|
inline MessageReader(typename Compression::BufferedInput& input, ScratchSpace& scratch)
|
||||||
|
: Compression::MessageReader(input) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Compression>
|
||||||
|
class ArrayMessageReader: public Compression::ArrayMessageReader {
|
||||||
|
public:
|
||||||
|
inline ArrayMessageReader(kj::ArrayPtr<const byte> input, ScratchSpace& scratch)
|
||||||
|
: Compression::ArrayMessageReader(input) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessageBuilder: public MallocMessageBuilder {
|
||||||
|
public:
|
||||||
|
inline MessageBuilder(ScratchSpace& scratch): MallocMessageBuilder() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ObjectSizeCounter {
|
||||||
|
public:
|
||||||
|
ObjectSizeCounter(uint64_t iters): counter(0) {}
|
||||||
|
|
||||||
|
template <typename RequestBuilder, typename ResponseBuilder>
|
||||||
|
void add(RequestBuilder& request, ResponseBuilder& response) {
|
||||||
|
for (auto segment: request.getSegmentsForOutput()) {
|
||||||
|
counter += segment.size() * sizeof(word);
|
||||||
|
}
|
||||||
|
for (auto segment: response.getSegmentsForOutput()) {
|
||||||
|
counter += segment.size() * sizeof(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t get() { return counter; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t counter;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr size_t SCRATCH_SIZE = 128 * 1024;
|
||||||
|
word scratchSpace[6 * SCRATCH_SIZE];
|
||||||
|
int scratchCounter = 0;
|
||||||
|
|
||||||
|
struct UseScratch {
|
||||||
|
struct ScratchSpace {
|
||||||
|
word* words;
|
||||||
|
|
||||||
|
ScratchSpace() {
|
||||||
|
KJ_REQUIRE(scratchCounter < 6, "Too many scratch spaces needed at once.");
|
||||||
|
words = scratchSpace + scratchCounter++ * SCRATCH_SIZE;
|
||||||
|
}
|
||||||
|
~ScratchSpace() noexcept {
|
||||||
|
--scratchCounter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Compression>
|
||||||
|
class MessageReader: public Compression::MessageReader {
|
||||||
|
public:
|
||||||
|
inline MessageReader(typename Compression::BufferedInput& input, ScratchSpace& scratch)
|
||||||
|
: Compression::MessageReader(
|
||||||
|
input, ReaderOptions(), kj::arrayPtr(scratch.words, SCRATCH_SIZE)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Compression>
|
||||||
|
class ArrayMessageReader: public Compression::ArrayMessageReader {
|
||||||
|
public:
|
||||||
|
inline ArrayMessageReader(kj::ArrayPtr<const byte> input, ScratchSpace& scratch)
|
||||||
|
: Compression::ArrayMessageReader(
|
||||||
|
input, ReaderOptions(), kj::arrayPtr(scratch.words, SCRATCH_SIZE)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessageBuilder: public MallocMessageBuilder {
|
||||||
|
public:
|
||||||
|
inline MessageBuilder(ScratchSpace& scratch)
|
||||||
|
: MallocMessageBuilder(kj::arrayPtr(scratch.words, SCRATCH_SIZE)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ObjectSizeCounter {
|
||||||
|
public:
|
||||||
|
ObjectSizeCounter(uint64_t iters): iters(iters), maxSize(0) {}
|
||||||
|
|
||||||
|
template <typename RequestBuilder, typename ResponseBuilder>
|
||||||
|
void add(RequestBuilder& request, ResponseBuilder& response) {
|
||||||
|
size_t counter = 0;
|
||||||
|
for (auto segment: request.getSegmentsForOutput()) {
|
||||||
|
counter += segment.size() * sizeof(word);
|
||||||
|
}
|
||||||
|
for (auto segment: response.getSegmentsForOutput()) {
|
||||||
|
counter += segment.size() * sizeof(word);
|
||||||
|
}
|
||||||
|
maxSize = std::max(counter, maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t get() { return iters * maxSize; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t iters;
|
||||||
|
size_t maxSize;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
template <typename TestCase, typename ReuseStrategy, typename Compression>
|
||||||
|
struct BenchmarkMethods {
|
||||||
|
static uint64_t syncClient(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
kj::FdInputStream inputStream(inputFd);
|
||||||
|
typename Compression::BufferedInput bufferedInput(inputStream);
|
||||||
|
|
||||||
|
CountingOutputStream output(outputFd);
|
||||||
|
typename ReuseStrategy::ScratchSpace builderScratch;
|
||||||
|
typename ReuseStrategy::ScratchSpace readerScratch;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
typename TestCase::Expectation expected;
|
||||||
|
{
|
||||||
|
typename ReuseStrategy::MessageBuilder builder(builderScratch);
|
||||||
|
expected = TestCase::setupRequest(
|
||||||
|
builder.template initRoot<typename TestCase::Request>());
|
||||||
|
Compression::write(output, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
typename ReuseStrategy::template MessageReader<Compression> reader(
|
||||||
|
bufferedInput, readerScratch);
|
||||||
|
if (!TestCase::checkResponse(
|
||||||
|
reader.template getRoot<typename TestCase::Response>(), expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t asyncClientSender(
|
||||||
|
int outputFd, ProducerConsumerQueue<typename TestCase::Expectation>* expectations,
|
||||||
|
uint64_t iters) {
|
||||||
|
CountingOutputStream output(outputFd);
|
||||||
|
typename ReuseStrategy::ScratchSpace scratch;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
typename ReuseStrategy::MessageBuilder builder(scratch);
|
||||||
|
expectations->post(TestCase::setupRequest(
|
||||||
|
builder.template initRoot<typename TestCase::Request>()));
|
||||||
|
Compression::write(output, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void asyncClientReceiver(
|
||||||
|
int inputFd, ProducerConsumerQueue<typename TestCase::Expectation>* expectations,
|
||||||
|
uint64_t iters) {
|
||||||
|
kj::FdInputStream inputStream(inputFd);
|
||||||
|
typename Compression::BufferedInput bufferedInput(inputStream);
|
||||||
|
|
||||||
|
typename ReuseStrategy::ScratchSpace scratch;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
typename TestCase::Expectation expected = expectations->next();
|
||||||
|
typename ReuseStrategy::template MessageReader<Compression> reader(bufferedInput, scratch);
|
||||||
|
if (!TestCase::checkResponse(
|
||||||
|
reader.template getRoot<typename TestCase::Response>(), expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t asyncClient(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
ProducerConsumerQueue<typename TestCase::Expectation> expectations;
|
||||||
|
std::thread receiverThread(asyncClientReceiver, inputFd, &expectations, iters);
|
||||||
|
uint64_t throughput = asyncClientSender(outputFd, &expectations, iters);
|
||||||
|
receiverThread.join();
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t server(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
kj::FdInputStream inputStream(inputFd);
|
||||||
|
typename Compression::BufferedInput bufferedInput(inputStream);
|
||||||
|
|
||||||
|
CountingOutputStream output(outputFd);
|
||||||
|
typename ReuseStrategy::ScratchSpace builderScratch;
|
||||||
|
typename ReuseStrategy::ScratchSpace readerScratch;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
typename ReuseStrategy::MessageBuilder builder(builderScratch);
|
||||||
|
typename ReuseStrategy::template MessageReader<Compression> reader(
|
||||||
|
bufferedInput, readerScratch);
|
||||||
|
TestCase::handleRequest(reader.template getRoot<typename TestCase::Request>(),
|
||||||
|
builder.template initRoot<typename TestCase::Response>());
|
||||||
|
Compression::write(output, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t passByObject(uint64_t iters, bool countObjectSize) {
|
||||||
|
typename ReuseStrategy::ScratchSpace requestScratch;
|
||||||
|
typename ReuseStrategy::ScratchSpace responseScratch;
|
||||||
|
|
||||||
|
typename ReuseStrategy::ObjectSizeCounter counter(iters);
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
typename ReuseStrategy::MessageBuilder requestMessage(requestScratch);
|
||||||
|
auto request = requestMessage.template initRoot<typename TestCase::Request>();
|
||||||
|
typename TestCase::Expectation expected = TestCase::setupRequest(request);
|
||||||
|
|
||||||
|
typename ReuseStrategy::MessageBuilder responseMessage(responseScratch);
|
||||||
|
auto response = responseMessage.template initRoot<typename TestCase::Response>();
|
||||||
|
TestCase::handleRequest(request.asReader(), response);
|
||||||
|
|
||||||
|
if (!TestCase::checkResponse(response.asReader(), expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countObjectSize) {
|
||||||
|
counter.add(requestMessage, responseMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return counter.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t passByBytes(uint64_t iters) {
|
||||||
|
uint64_t throughput = 0;
|
||||||
|
typename ReuseStrategy::ScratchSpace clientRequestScratch;
|
||||||
|
UseScratch::ScratchSpace requestBytesScratch;
|
||||||
|
typename ReuseStrategy::ScratchSpace serverRequestScratch;
|
||||||
|
typename ReuseStrategy::ScratchSpace serverResponseScratch;
|
||||||
|
UseScratch::ScratchSpace responseBytesScratch;
|
||||||
|
typename ReuseStrategy::ScratchSpace clientResponseScratch;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
typename ReuseStrategy::MessageBuilder requestBuilder(clientRequestScratch);
|
||||||
|
typename TestCase::Expectation expected = TestCase::setupRequest(
|
||||||
|
requestBuilder.template initRoot<typename TestCase::Request>());
|
||||||
|
|
||||||
|
kj::ArrayOutputStream requestOutput(kj::arrayPtr(
|
||||||
|
reinterpret_cast<byte*>(requestBytesScratch.words), SCRATCH_SIZE * sizeof(word)));
|
||||||
|
Compression::write(requestOutput, requestBuilder);
|
||||||
|
throughput += requestOutput.getArray().size();
|
||||||
|
typename ReuseStrategy::template ArrayMessageReader<Compression> requestReader(
|
||||||
|
requestOutput.getArray(), serverRequestScratch);
|
||||||
|
|
||||||
|
typename ReuseStrategy::MessageBuilder responseBuilder(serverResponseScratch);
|
||||||
|
TestCase::handleRequest(requestReader.template getRoot<typename TestCase::Request>(),
|
||||||
|
responseBuilder.template initRoot<typename TestCase::Response>());
|
||||||
|
|
||||||
|
kj::ArrayOutputStream responseOutput(
|
||||||
|
kj::arrayPtr(reinterpret_cast<byte*>(responseBytesScratch.words),
|
||||||
|
SCRATCH_SIZE * sizeof(word)));
|
||||||
|
Compression::write(responseOutput, responseBuilder);
|
||||||
|
throughput += responseOutput.getArray().size();
|
||||||
|
typename ReuseStrategy::template ArrayMessageReader<Compression> responseReader(
|
||||||
|
responseOutput.getArray(), clientResponseScratch);
|
||||||
|
|
||||||
|
if (!TestCase::checkResponse(
|
||||||
|
responseReader.template getRoot<typename TestCase::Response>(), expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BenchmarkTypes {
|
||||||
|
typedef capnp::Uncompressed Uncompressed;
|
||||||
|
typedef capnp::Packed Packed;
|
||||||
|
#if HAVE_SNAPPY
|
||||||
|
typedef capnp::SnappyCompressed SnappyCompressed;
|
||||||
|
#endif // HAVE_SNAPPY
|
||||||
|
|
||||||
|
typedef capnp::UseScratch ReusableResources;
|
||||||
|
typedef capnp::NoScratch SingleUseResources;
|
||||||
|
|
||||||
|
template <typename TestCase, typename ReuseStrategy, typename Compression>
|
||||||
|
struct BenchmarkMethods: public capnp::BenchmarkMethods<TestCase, ReuseStrategy, Compression> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace capnp
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "eval.capnp.h"
|
||||||
|
#include "capnproto-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
int32_t makeExpression(Expression::Builder exp, uint depth) {
|
||||||
|
exp.setOp((Operation)(fastRand((int)Operation::MODULUS + 1)));
|
||||||
|
|
||||||
|
uint32_t left, right;
|
||||||
|
|
||||||
|
if (fastRand(8) < depth) {
|
||||||
|
left = fastRand(128) + 1;
|
||||||
|
exp.getLeft().setValue(left);
|
||||||
|
} else {
|
||||||
|
left = makeExpression(exp.getLeft().initExpression(), depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fastRand(8) < depth) {
|
||||||
|
right = fastRand(128) + 1;
|
||||||
|
exp.getRight().setValue(right);
|
||||||
|
} else {
|
||||||
|
right = makeExpression(exp.getRight().initExpression(), depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (exp.getOp()) {
|
||||||
|
case Operation::ADD:
|
||||||
|
return left + right;
|
||||||
|
case Operation::SUBTRACT:
|
||||||
|
return left - right;
|
||||||
|
case Operation::MULTIPLY:
|
||||||
|
return left * right;
|
||||||
|
case Operation::DIVIDE:
|
||||||
|
return div(left, right);
|
||||||
|
case Operation::MODULUS:
|
||||||
|
return mod(left, right);
|
||||||
|
}
|
||||||
|
throw std::logic_error("Can't get here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t evaluateExpression(Expression::Reader exp) {
|
||||||
|
int32_t left = 0, right = 0;
|
||||||
|
|
||||||
|
switch (exp.getLeft().which()) {
|
||||||
|
case Expression::Left::VALUE:
|
||||||
|
left = exp.getLeft().getValue();
|
||||||
|
break;
|
||||||
|
case Expression::Left::EXPRESSION:
|
||||||
|
left = evaluateExpression(exp.getLeft().getExpression());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (exp.getRight().which()) {
|
||||||
|
case Expression::Right::VALUE:
|
||||||
|
right = exp.getRight().getValue();
|
||||||
|
break;
|
||||||
|
case Expression::Right::EXPRESSION:
|
||||||
|
right = evaluateExpression(exp.getRight().getExpression());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (exp.getOp()) {
|
||||||
|
case Operation::ADD:
|
||||||
|
return left + right;
|
||||||
|
case Operation::SUBTRACT:
|
||||||
|
return left - right;
|
||||||
|
case Operation::MULTIPLY:
|
||||||
|
return left * right;
|
||||||
|
case Operation::DIVIDE:
|
||||||
|
return div(left, right);
|
||||||
|
case Operation::MODULUS:
|
||||||
|
return mod(left, right);
|
||||||
|
}
|
||||||
|
throw std::logic_error("Can't get here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpressionTestCase {
|
||||||
|
public:
|
||||||
|
typedef Expression Request;
|
||||||
|
typedef EvaluationResult Response;
|
||||||
|
typedef int32_t Expectation;
|
||||||
|
|
||||||
|
static inline int32_t setupRequest(Expression::Builder request) {
|
||||||
|
return makeExpression(request, 0);
|
||||||
|
}
|
||||||
|
static inline void handleRequest(Expression::Reader request, EvaluationResult::Builder response) {
|
||||||
|
response.setValue(evaluateExpression(request));
|
||||||
|
}
|
||||||
|
static inline bool checkResponse(EvaluationResult::Reader response, int32_t expected) {
|
||||||
|
return response.getValue() == expected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace capnp
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::capnp::BenchmarkTypes,
|
||||||
|
capnp::benchmark::capnp::ExpressionTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
using Cxx = import "/capnp/c++.capnp";
|
||||||
|
|
||||||
|
@0xff75ddc6a36723c9;
|
||||||
|
$Cxx.namespace("capnp::benchmark::capnp");
|
||||||
|
|
||||||
|
struct ParkingLot {
|
||||||
|
cars@0: List(Car);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TotalValue {
|
||||||
|
amount@0: UInt64;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Car {
|
||||||
|
make@0: Text;
|
||||||
|
model@1: Text;
|
||||||
|
color@2: Color;
|
||||||
|
seats@3: UInt8;
|
||||||
|
doors@4: UInt8;
|
||||||
|
wheels@5: List(Wheel);
|
||||||
|
length@6: UInt16;
|
||||||
|
width@7: UInt16;
|
||||||
|
height@8: UInt16;
|
||||||
|
weight@9: UInt32;
|
||||||
|
engine@10: Engine;
|
||||||
|
fuelCapacity@11: Float32;
|
||||||
|
fuelLevel@12: Float32;
|
||||||
|
hasPowerWindows@13: Bool;
|
||||||
|
hasPowerSteering@14: Bool;
|
||||||
|
hasCruiseControl@15: Bool;
|
||||||
|
cupHolders@16: UInt8;
|
||||||
|
hasNavSystem@17: Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Color {
|
||||||
|
black @0;
|
||||||
|
white @1;
|
||||||
|
red @2;
|
||||||
|
green @3;
|
||||||
|
blue @4;
|
||||||
|
cyan @5;
|
||||||
|
magenta @6;
|
||||||
|
yellow @7;
|
||||||
|
silver @8;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Wheel {
|
||||||
|
diameter@0: UInt16;
|
||||||
|
airPressure@1: Float32;
|
||||||
|
snowTires@2: Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Engine {
|
||||||
|
horsepower@0: UInt16;
|
||||||
|
cylinders@1: UInt8;
|
||||||
|
cc@2: UInt32;
|
||||||
|
usesGas@3: Bool;
|
||||||
|
usesElectric@4: Bool;
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package capnp.benchmark.protobuf;
|
||||||
|
|
||||||
|
message ParkingLot {
|
||||||
|
repeated Car car = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TotalValue {
|
||||||
|
required uint64 amount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Car {
|
||||||
|
optional string make = 1;
|
||||||
|
optional string model = 2;
|
||||||
|
optional Color color = 3;
|
||||||
|
optional uint32 seats = 4;
|
||||||
|
optional uint32 doors = 5;
|
||||||
|
repeated Wheel wheel = 6;
|
||||||
|
optional uint32 length = 7;
|
||||||
|
optional uint32 width = 8;
|
||||||
|
optional uint32 height = 9;
|
||||||
|
optional uint32 weight = 10;
|
||||||
|
optional Engine engine = 11;
|
||||||
|
optional float fuel_capacity = 12;
|
||||||
|
optional float fuel_level = 13;
|
||||||
|
optional bool has_power_windows = 14;
|
||||||
|
optional bool has_power_steering = 15;
|
||||||
|
optional bool has_cruise_control = 16;
|
||||||
|
optional uint32 cup_holders = 17;
|
||||||
|
optional bool has_nav_system = 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Color {
|
||||||
|
BLACK = 0;
|
||||||
|
WHITE = 1;
|
||||||
|
RED = 2;
|
||||||
|
GREEN = 3;
|
||||||
|
BLUE = 4;
|
||||||
|
CYAN = 5;
|
||||||
|
MAGENTA = 6;
|
||||||
|
YELLOW = 7;
|
||||||
|
SILVER = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Wheel {
|
||||||
|
optional uint32 diameter = 1;
|
||||||
|
optional float air_pressure = 2;
|
||||||
|
optional bool snow_tires = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Engine {
|
||||||
|
optional uint32 horsepower = 1;
|
||||||
|
optional uint32 cylinders = 2;
|
||||||
|
optional uint32 cc = 3;
|
||||||
|
optional bool uses_gas = 4;
|
||||||
|
optional bool uses_electric = 5;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
using Cxx = import "/capnp/c++.capnp";
|
||||||
|
|
||||||
|
@0x82beb8e37ff79aba;
|
||||||
|
$Cxx.namespace("capnp::benchmark::capnp");
|
||||||
|
|
||||||
|
struct SearchResultList {
|
||||||
|
results@0: List(SearchResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchResult {
|
||||||
|
url@0: Text;
|
||||||
|
score@1: Float64;
|
||||||
|
snippet@2: Text;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package capnp.benchmark.protobuf;
|
||||||
|
|
||||||
|
message SearchResultList {
|
||||||
|
repeated SearchResult result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SearchResult {
|
||||||
|
optional string url = 1;
|
||||||
|
optional double score = 2;
|
||||||
|
optional string snippet = 3;
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
|
||||||
|
#pragma GCC system_header
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
|
||||||
|
// Use a 128-bit Xorshift algorithm.
|
||||||
|
static inline uint32_t nextFastRand() {
|
||||||
|
// These values are arbitrary. Any seed other than all zeroes is OK.
|
||||||
|
static uint32_t x = 0x1d2acd47;
|
||||||
|
static uint32_t y = 0x58ca3e14;
|
||||||
|
static uint32_t z = 0xf563f232;
|
||||||
|
static uint32_t w = 0x0bc76199;
|
||||||
|
|
||||||
|
uint32_t tmp = x ^ (x << 11);
|
||||||
|
x = y;
|
||||||
|
y = z;
|
||||||
|
z = w;
|
||||||
|
w = w ^ (w >> 19) ^ tmp ^ (tmp >> 8);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t fastRand(uint32_t range) {
|
||||||
|
return nextFastRand() % range;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline double fastRandDouble(double range) {
|
||||||
|
return nextFastRand() * range / std::numeric_limits<uint32_t>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int32_t div(int32_t a, int32_t b) {
|
||||||
|
if (b == 0) return std::numeric_limits<int32_t>::max();
|
||||||
|
// INT_MIN / -1 => SIGFPE. Who knew?
|
||||||
|
if (a == std::numeric_limits<int32_t>::min() && b == -1) {
|
||||||
|
return std::numeric_limits<int32_t>::max();
|
||||||
|
}
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int32_t mod(int32_t a, int32_t b) {
|
||||||
|
if (b == 0) return std::numeric_limits<int32_t>::max();
|
||||||
|
// INT_MIN % -1 => SIGFPE. Who knew?
|
||||||
|
if (a == std::numeric_limits<int32_t>::min() && b == -1) {
|
||||||
|
return std::numeric_limits<int32_t>::max();
|
||||||
|
}
|
||||||
|
return a % b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* const WORDS[] = {
|
||||||
|
"foo ", "bar ", "baz ", "qux ", "quux ", "corge ", "grault ", "garply ", "waldo ", "fred ",
|
||||||
|
"plugh ", "xyzzy ", "thud "
|
||||||
|
};
|
||||||
|
constexpr size_t WORDS_COUNT = sizeof(WORDS) / sizeof(WORDS[0]);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class ProducerConsumerQueue {
|
||||||
|
public:
|
||||||
|
ProducerConsumerQueue() {
|
||||||
|
front = new Node;
|
||||||
|
back = front;
|
||||||
|
sem_init(&semaphore, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
~ProducerConsumerQueue() noexcept(false) {
|
||||||
|
while (front != nullptr) {
|
||||||
|
Node* oldFront = front;
|
||||||
|
front = front->next;
|
||||||
|
delete oldFront;
|
||||||
|
}
|
||||||
|
sem_destroy(&semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post(T t) {
|
||||||
|
back->next = new Node(t);
|
||||||
|
back = back->next;
|
||||||
|
sem_post(&semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
T next() {
|
||||||
|
sem_wait(&semaphore);
|
||||||
|
Node* oldFront = front;
|
||||||
|
front = front->next;
|
||||||
|
delete oldFront;
|
||||||
|
return front->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Node {
|
||||||
|
T value;
|
||||||
|
Node* next;
|
||||||
|
|
||||||
|
Node(): next(nullptr) {}
|
||||||
|
Node(T value): value(value), next(nullptr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Node* front; // Last node that has been consumed.
|
||||||
|
Node* back; // Last node in list.
|
||||||
|
sem_t semaphore;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(cleanup): Use SYSCALL(), get rid of this exception class.
|
||||||
|
class OsException: public std::exception {
|
||||||
|
public:
|
||||||
|
OsException(int error): error(error) {}
|
||||||
|
~OsException() noexcept {}
|
||||||
|
|
||||||
|
const char* what() const noexcept override {
|
||||||
|
return strerror(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int error;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void writeAll(int fd, const void* buffer, size_t size) {
|
||||||
|
const char* pos = reinterpret_cast<const char*>(buffer);
|
||||||
|
while (size > 0) {
|
||||||
|
ssize_t n = write(fd, pos, size);
|
||||||
|
if (n <= 0) {
|
||||||
|
throw OsException(errno);
|
||||||
|
}
|
||||||
|
pos += n;
|
||||||
|
size -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readAll(int fd, void* buffer, size_t size) {
|
||||||
|
char* pos = reinterpret_cast<char*>(buffer);
|
||||||
|
while (size > 0) {
|
||||||
|
ssize_t n = read(fd, pos, size);
|
||||||
|
if (n <= 0) {
|
||||||
|
throw OsException(errno);
|
||||||
|
}
|
||||||
|
pos += n;
|
||||||
|
size -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BenchmarkMethods, typename Func>
|
||||||
|
uint64_t passByPipe(Func&& clientFunc, uint64_t iters) {
|
||||||
|
int clientToServer[2];
|
||||||
|
int serverToClient[2];
|
||||||
|
if (pipe(clientToServer) < 0) throw OsException(errno);
|
||||||
|
if (pipe(serverToClient) < 0) throw OsException(errno);
|
||||||
|
|
||||||
|
pid_t child = fork();
|
||||||
|
if (child == 0) {
|
||||||
|
// Client.
|
||||||
|
close(clientToServer[0]);
|
||||||
|
close(serverToClient[1]);
|
||||||
|
|
||||||
|
uint64_t throughput = clientFunc(serverToClient[0], clientToServer[1], iters);
|
||||||
|
writeAll(clientToServer[1], &throughput, sizeof(throughput));
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
// Server.
|
||||||
|
close(clientToServer[1]);
|
||||||
|
close(serverToClient[0]);
|
||||||
|
|
||||||
|
uint64_t throughput = BenchmarkMethods::server(clientToServer[0], serverToClient[1], iters);
|
||||||
|
|
||||||
|
uint64_t clientThroughput = 0;
|
||||||
|
readAll(clientToServer[0], &clientThroughput, sizeof(clientThroughput));
|
||||||
|
throughput += clientThroughput;
|
||||||
|
|
||||||
|
int status;
|
||||||
|
if (waitpid(child, &status, 0) != child) {
|
||||||
|
throw OsException(errno);
|
||||||
|
}
|
||||||
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||||
|
throw std::logic_error("Child exited abnormally.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BenchmarkTypes, typename TestCase, typename Reuse, typename Compression>
|
||||||
|
uint64_t doBenchmark(const std::string& mode, uint64_t iters) {
|
||||||
|
typedef typename BenchmarkTypes::template BenchmarkMethods<TestCase, Reuse, Compression>
|
||||||
|
BenchmarkMethods;
|
||||||
|
if (mode == "client") {
|
||||||
|
return BenchmarkMethods::syncClient(STDIN_FILENO, STDOUT_FILENO, iters);
|
||||||
|
} else if (mode == "server") {
|
||||||
|
return BenchmarkMethods::server(STDIN_FILENO, STDOUT_FILENO, iters);
|
||||||
|
} else if (mode == "object") {
|
||||||
|
return BenchmarkMethods::passByObject(iters, false);
|
||||||
|
} else if (mode == "object-size") {
|
||||||
|
return BenchmarkMethods::passByObject(iters, true);
|
||||||
|
} else if (mode == "bytes") {
|
||||||
|
return BenchmarkMethods::passByBytes(iters);
|
||||||
|
} else if (mode == "pipe") {
|
||||||
|
return passByPipe<BenchmarkMethods>(BenchmarkMethods::syncClient, iters);
|
||||||
|
} else if (mode == "pipe-async") {
|
||||||
|
return passByPipe<BenchmarkMethods>(BenchmarkMethods::asyncClient, iters);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Unknown mode: %s\n", mode.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BenchmarkTypes, typename TestCase, typename Compression>
|
||||||
|
uint64_t doBenchmark2(const std::string& mode, const std::string& reuse, uint64_t iters) {
|
||||||
|
if (reuse == "reuse") {
|
||||||
|
return doBenchmark<
|
||||||
|
BenchmarkTypes, TestCase, typename BenchmarkTypes::ReusableResources, Compression>(
|
||||||
|
mode, iters);
|
||||||
|
} else if (reuse == "no-reuse") {
|
||||||
|
return doBenchmark<
|
||||||
|
BenchmarkTypes, TestCase, typename BenchmarkTypes::SingleUseResources, Compression>(
|
||||||
|
mode, iters);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Unknown reuse mode: %s\n", reuse.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BenchmarkTypes, typename TestCase>
|
||||||
|
uint64_t doBenchmark3(const std::string& mode, const std::string& reuse,
|
||||||
|
const std::string& compression, uint64_t iters) {
|
||||||
|
if (compression == "none") {
|
||||||
|
return doBenchmark2<BenchmarkTypes, TestCase, typename BenchmarkTypes::Uncompressed>(
|
||||||
|
mode, reuse, iters);
|
||||||
|
} else if (compression == "packed") {
|
||||||
|
return doBenchmark2<BenchmarkTypes, TestCase, typename BenchmarkTypes::Packed>(
|
||||||
|
mode, reuse, iters);
|
||||||
|
#if HAVE_SNAPPY
|
||||||
|
} else if (compression == "snappy") {
|
||||||
|
return doBenchmark2<BenchmarkTypes, TestCase, typename BenchmarkTypes::SnappyCompressed>(
|
||||||
|
mode, reuse, iters);
|
||||||
|
#endif // HAVE_SNAPPY
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Unknown compression mode: %s\n", compression.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BenchmarkTypes, typename TestCase>
|
||||||
|
int benchmarkMain(int argc, char* argv[]) {
|
||||||
|
if (argc != 5) {
|
||||||
|
fprintf(stderr, "USAGE: %s MODE REUSE COMPRESSION ITERATION_COUNT\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t iters = strtoull(argv[4], nullptr, 0);
|
||||||
|
uint64_t throughput = doBenchmark3<BenchmarkTypes, TestCase>(argv[1], argv[2], argv[3], iters);
|
||||||
|
fprintf(stdout, "%llu\n", (long long unsigned int)throughput);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace capnp
|
||||||
|
} // namespace benchmark
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
using Cxx = import "/capnp/c++.capnp";
|
||||||
|
|
||||||
|
@0xe12dc4c3e70e9eda;
|
||||||
|
$Cxx.namespace("capnp::benchmark::capnp");
|
||||||
|
|
||||||
|
enum Operation {
|
||||||
|
add @0;
|
||||||
|
subtract @1;
|
||||||
|
multiply @2;
|
||||||
|
divide @3;
|
||||||
|
modulus @4;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Expression {
|
||||||
|
op@0: Operation;
|
||||||
|
|
||||||
|
left :union {
|
||||||
|
value@1: Int32;
|
||||||
|
expression@2: Expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
right :union {
|
||||||
|
value@3: Int32;
|
||||||
|
expression@4: Expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EvaluationResult {
|
||||||
|
value@0: Int32;
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package capnp.benchmark.protobuf;
|
||||||
|
|
||||||
|
enum Operation {
|
||||||
|
ADD = 0;
|
||||||
|
SUBTRACT = 1;
|
||||||
|
MULTIPLY = 2;
|
||||||
|
DIVIDE = 3;
|
||||||
|
MODULUS = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Expression {
|
||||||
|
required Operation op = 1;
|
||||||
|
|
||||||
|
optional int32 left_value = 2;
|
||||||
|
optional Expression left_expression = 3;
|
||||||
|
|
||||||
|
optional int32 right_value = 4;
|
||||||
|
optional Expression right_expression = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EvaluationResult {
|
||||||
|
required sint32 value = 1;
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "null-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace null {
|
||||||
|
|
||||||
|
enum class Color: uint8_t {
|
||||||
|
BLACK,
|
||||||
|
WHITE,
|
||||||
|
RED,
|
||||||
|
GREEN,
|
||||||
|
BLUE,
|
||||||
|
CYAN,
|
||||||
|
MAGENTA,
|
||||||
|
YELLOW,
|
||||||
|
SILVER
|
||||||
|
};
|
||||||
|
constexpr uint COLOR_RANGE = static_cast<uint>(Color::SILVER) + 1;
|
||||||
|
|
||||||
|
struct Wheel {
|
||||||
|
float airPressure;
|
||||||
|
uint16_t diameter;
|
||||||
|
bool snowTires;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Engine {
|
||||||
|
uint32_t cc;
|
||||||
|
uint16_t horsepower;
|
||||||
|
uint8_t cylinders;
|
||||||
|
uint8_t bits;
|
||||||
|
inline bool usesGas() const { return bits & 1; }
|
||||||
|
inline bool usesElectric() const { return bits & 2; }
|
||||||
|
|
||||||
|
inline void setBits(bool usesGas, bool usesElectric) {
|
||||||
|
bits = (uint8_t)usesGas | ((uint8_t)usesElectric << 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Car {
|
||||||
|
// SORT FIELDS BY SIZE since we need "theoretical best" memory usage
|
||||||
|
Engine engine;
|
||||||
|
List<Wheel> wheels;
|
||||||
|
const char* make;
|
||||||
|
const char* model;
|
||||||
|
float fuelCapacity;
|
||||||
|
float fuelLevel;
|
||||||
|
uint32_t weight;
|
||||||
|
uint16_t length;
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
Color color;
|
||||||
|
uint8_t seats;
|
||||||
|
uint8_t doors;
|
||||||
|
uint8_t cupHolders;
|
||||||
|
|
||||||
|
uint8_t bits;
|
||||||
|
|
||||||
|
inline bool hasPowerWindows() const { return bits & 1; }
|
||||||
|
inline bool hasPowerSteering() const { return bits & 2; }
|
||||||
|
inline bool hasCruiseControl() const { return bits & 4; }
|
||||||
|
inline bool hasNavSystem() const { return bits & 8; }
|
||||||
|
|
||||||
|
inline void setBits(bool hasPowerWindows, bool hasPowerSteering,
|
||||||
|
bool hasCruiseControl, bool hasNavSystem) {
|
||||||
|
bits = (uint8_t)hasPowerWindows
|
||||||
|
| ((uint8_t)hasPowerSteering << 1)
|
||||||
|
| ((uint8_t)hasCruiseControl << 2)
|
||||||
|
| ((uint8_t)hasNavSystem << 3);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
uint64_t carValue(const Car& car) {
|
||||||
|
// Do not think too hard about realism.
|
||||||
|
|
||||||
|
uint64_t result = 0;
|
||||||
|
|
||||||
|
result += car.seats * 200;
|
||||||
|
result += car.doors * 350;
|
||||||
|
for (auto wheel: car.wheels) {
|
||||||
|
result += wheel.diameter * wheel.diameter;
|
||||||
|
result += wheel.snowTires ? 100 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += car.length * car.width * car.height / 50;
|
||||||
|
|
||||||
|
auto engine = car.engine;
|
||||||
|
result += engine.horsepower * 40;
|
||||||
|
if (engine.usesElectric()) {
|
||||||
|
if (engine.usesGas()) {
|
||||||
|
// hybrid
|
||||||
|
result += 5000;
|
||||||
|
} else {
|
||||||
|
result += 3000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += car.hasPowerWindows() ? 100 : 0;
|
||||||
|
result += car.hasPowerSteering() ? 200 : 0;
|
||||||
|
result += car.hasCruiseControl() ? 400 : 0;
|
||||||
|
result += car.hasNavSystem() ? 2000 : 0;
|
||||||
|
|
||||||
|
result += car.cupHolders * 25;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void randomCar(Car* car) {
|
||||||
|
// Do not think too hard about realism.
|
||||||
|
|
||||||
|
static const char* const MAKES[] = { "Toyota", "GM", "Ford", "Honda", "Tesla" };
|
||||||
|
static const char* const MODELS[] = { "Camry", "Prius", "Volt", "Accord", "Leaf", "Model S" };
|
||||||
|
|
||||||
|
car->make = copyString(MAKES[fastRand(sizeof(MAKES) / sizeof(MAKES[0]))]);
|
||||||
|
car->model = copyString(MODELS[fastRand(sizeof(MODELS) / sizeof(MODELS[0]))]);
|
||||||
|
|
||||||
|
car->color = (Color)fastRand(COLOR_RANGE);
|
||||||
|
car->seats = 2 + fastRand(6);
|
||||||
|
car->doors = 2 + fastRand(3);
|
||||||
|
|
||||||
|
for (auto& wheel: car->wheels.init(4)) {
|
||||||
|
wheel.diameter = 25 + fastRand(15);
|
||||||
|
wheel.airPressure = 30 + fastRandDouble(20);
|
||||||
|
wheel.snowTires = fastRand(16) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
car->length = 170 + fastRand(150);
|
||||||
|
car->width = 48 + fastRand(36);
|
||||||
|
car->height = 54 + fastRand(48);
|
||||||
|
car->weight = car->length * car->width * car->height / 200;
|
||||||
|
|
||||||
|
car->engine.horsepower = 100 * fastRand(400);
|
||||||
|
car->engine.cylinders = 4 + 2 * fastRand(3);
|
||||||
|
car->engine.cc = 800 + fastRand(10000);
|
||||||
|
car->engine.setBits(true, fastRand(2));
|
||||||
|
|
||||||
|
car->fuelCapacity = 10.0 + fastRandDouble(30.0);
|
||||||
|
car->fuelLevel = fastRandDouble(car->fuelCapacity);
|
||||||
|
bool hasPowerWindows = fastRand(2);
|
||||||
|
bool hasPowerSteering = fastRand(2);
|
||||||
|
bool hasCruiseControl = fastRand(2);
|
||||||
|
car->cupHolders = fastRand(12);
|
||||||
|
bool hasNavSystem = fastRand(2);
|
||||||
|
car->setBits(hasPowerWindows, hasPowerSteering, hasCruiseControl, hasNavSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CarSalesTestCase {
|
||||||
|
public:
|
||||||
|
typedef List<Car> Request;
|
||||||
|
typedef uint64_t Response;
|
||||||
|
typedef uint64_t Expectation;
|
||||||
|
|
||||||
|
static uint64_t setupRequest(List<Car>* request) {
|
||||||
|
uint64_t result = 0;
|
||||||
|
for (auto& car: request->init(fastRand(200))) {
|
||||||
|
randomCar(&car);
|
||||||
|
result += carValue(car);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
static void handleRequest(const List<Car>& request, uint64_t* response) {
|
||||||
|
*response = 0;
|
||||||
|
for (auto& car: request) {
|
||||||
|
*response += carValue(car);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static inline bool checkResponse(uint64_t response, uint64_t expected) {
|
||||||
|
return response == expected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace null
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::null::BenchmarkTypes,
|
||||||
|
capnp::benchmark::null::CarSalesTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "null-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace null {
|
||||||
|
|
||||||
|
struct SearchResult {
|
||||||
|
const char* url;
|
||||||
|
double score;
|
||||||
|
const char* snippet;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScoredResult {
|
||||||
|
double score;
|
||||||
|
const SearchResult* result;
|
||||||
|
|
||||||
|
ScoredResult() = default;
|
||||||
|
ScoredResult(double score, const SearchResult* result): score(score), result(result) {}
|
||||||
|
|
||||||
|
inline bool operator<(const ScoredResult& other) const { return score > other.score; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class CatRankTestCase {
|
||||||
|
public:
|
||||||
|
typedef List<SearchResult> Request;
|
||||||
|
typedef List<SearchResult> Response;
|
||||||
|
typedef int Expectation;
|
||||||
|
|
||||||
|
static int setupRequest(List<SearchResult>* request) {
|
||||||
|
int count = fastRand(1000);
|
||||||
|
int goodCount = 0;
|
||||||
|
|
||||||
|
request->init(count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
SearchResult& result = request->items[i];
|
||||||
|
result.score = 1000 - i;
|
||||||
|
char* pos = reinterpret_cast<char*>(arenaPos);
|
||||||
|
result.url = pos;
|
||||||
|
|
||||||
|
strcpy(pos, "http://example.com/");
|
||||||
|
pos += strlen("http://example.com/");
|
||||||
|
int urlSize = fastRand(100);
|
||||||
|
for (int j = 0; j < urlSize; j++) {
|
||||||
|
*pos++ = 'a' + fastRand(26);
|
||||||
|
}
|
||||||
|
*pos++ = '\0';
|
||||||
|
|
||||||
|
// Retroactively allocate the space we used.
|
||||||
|
if (allocate<char>(pos - result.url) != result.url) {
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCat = fastRand(8) == 0;
|
||||||
|
bool isDog = fastRand(8) == 0;
|
||||||
|
goodCount += isCat && !isDog;
|
||||||
|
|
||||||
|
pos = reinterpret_cast<char*>(arenaPos);
|
||||||
|
result.snippet = pos;
|
||||||
|
|
||||||
|
*pos++ = ' ';
|
||||||
|
|
||||||
|
int prefix = fastRand(20);
|
||||||
|
for (int j = 0; j < prefix; j++) {
|
||||||
|
const char* word = WORDS[fastRand(WORDS_COUNT)];
|
||||||
|
size_t len = strlen(word);
|
||||||
|
memcpy(pos, word, len);
|
||||||
|
pos += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCat) {
|
||||||
|
strcpy(pos, "cat ");
|
||||||
|
pos += 4;
|
||||||
|
}
|
||||||
|
if (isDog) {
|
||||||
|
strcpy(pos, "dog ");
|
||||||
|
pos += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int suffix = fastRand(20);
|
||||||
|
for (int j = 0; j < suffix; j++) {
|
||||||
|
const char* word = WORDS[fastRand(WORDS_COUNT)];
|
||||||
|
size_t len = strlen(word);
|
||||||
|
memcpy(pos, word, len);
|
||||||
|
pos += len;
|
||||||
|
}
|
||||||
|
*pos++ = '\0';
|
||||||
|
|
||||||
|
// Retroactively allocate the space we used.
|
||||||
|
if (allocate<char>(pos - result.snippet) != result.snippet) {
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return goodCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleRequest(const List<SearchResult>& request, List<SearchResult>* response) {
|
||||||
|
std::vector<ScoredResult> scoredResults;
|
||||||
|
scoredResults.reserve(request.size);
|
||||||
|
|
||||||
|
for (auto& result: request) {
|
||||||
|
double score = result.score;
|
||||||
|
if (strstr(result.snippet, " cat ") != nullptr) {
|
||||||
|
score *= 10000;
|
||||||
|
}
|
||||||
|
if (strstr(result.snippet, " dog ") != nullptr) {
|
||||||
|
score /= 10000;
|
||||||
|
}
|
||||||
|
scoredResults.emplace_back(score, &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(scoredResults.begin(), scoredResults.end());
|
||||||
|
|
||||||
|
response->init(scoredResults.size());
|
||||||
|
SearchResult* dst = response->items;
|
||||||
|
for (auto& result: scoredResults) {
|
||||||
|
dst->url = copyString(result.result->url);
|
||||||
|
dst->score = result.score;
|
||||||
|
dst->snippet = copyString(result.result->snippet);
|
||||||
|
++dst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkResponse(const List<SearchResult>& response, int expectedGoodCount) {
|
||||||
|
int goodCount = 0;
|
||||||
|
for (auto& result: response) {
|
||||||
|
if (result.score > 1001) {
|
||||||
|
++goodCount;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return goodCount == expectedGoodCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace null
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::null::BenchmarkTypes,
|
||||||
|
capnp::benchmark::null::CatRankTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace null {
|
||||||
|
|
||||||
|
uint64_t arena[1024*1024];
|
||||||
|
uint64_t* arenaPos = arena;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* allocate(int count = 1) {
|
||||||
|
T* result = reinterpret_cast<T*>(arenaPos);
|
||||||
|
arenaPos += (sizeof(T) * count + 7) / 8;
|
||||||
|
if (arenaPos > arena + sizeof(arena) / sizeof(arena[0])) {
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* copyString(const char* str) {
|
||||||
|
size_t len = strlen(str);
|
||||||
|
char* result = allocate<char>(len);
|
||||||
|
memcpy(result, str, len + 1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct List {
|
||||||
|
size_t size;
|
||||||
|
T* items;
|
||||||
|
|
||||||
|
inline T* begin() const { return items; }
|
||||||
|
inline T* end() const { return items + size; }
|
||||||
|
|
||||||
|
inline List<T>& init(size_t size) {
|
||||||
|
this->size = size;
|
||||||
|
items = allocate<T>(size);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
struct SingleUseObjects {
|
||||||
|
class ObjectSizeCounter {
|
||||||
|
public:
|
||||||
|
ObjectSizeCounter(uint64_t iters): counter(0) {}
|
||||||
|
|
||||||
|
void add(uint64_t wordCount) {
|
||||||
|
counter += wordCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t get() { return counter; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t counter;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReusableObjects {
|
||||||
|
class ObjectSizeCounter {
|
||||||
|
public:
|
||||||
|
ObjectSizeCounter(uint64_t iters): iters(iters), maxSize(0) {}
|
||||||
|
|
||||||
|
void add(size_t wordCount) {
|
||||||
|
maxSize = std::max(wordCount, maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t get() { return iters * maxSize; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t iters;
|
||||||
|
size_t maxSize;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
template <typename TestCase, typename ReuseStrategy, typename Compression>
|
||||||
|
struct BenchmarkMethods {
|
||||||
|
static uint64_t syncClient(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t asyncClientSender(
|
||||||
|
int outputFd, ProducerConsumerQueue<typename TestCase::Expectation>* expectations,
|
||||||
|
uint64_t iters) {
|
||||||
|
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void asyncClientReceiver(
|
||||||
|
int inputFd, ProducerConsumerQueue<typename TestCase::Expectation>* expectations,
|
||||||
|
uint64_t iters) {
|
||||||
|
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t asyncClient(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t server(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t passByObject(uint64_t iters, bool countObjectSize) {
|
||||||
|
typename ReuseStrategy::ObjectSizeCounter sizeCounter(iters);
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
arenaPos = arena;
|
||||||
|
|
||||||
|
typename TestCase::Request request;
|
||||||
|
typename TestCase::Expectation expected = TestCase::setupRequest(&request);
|
||||||
|
|
||||||
|
typename TestCase::Response response;
|
||||||
|
TestCase::handleRequest(request, &response);
|
||||||
|
if (!TestCase::checkResponse(response, expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeCounter.add((arenaPos - arena) * sizeof(arena[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizeCounter.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t passByBytes(uint64_t iters) {
|
||||||
|
fprintf(stderr, "Null benchmark doesn't do I/O.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BenchmarkTypes {
|
||||||
|
typedef void Uncompressed;
|
||||||
|
typedef void Packed;
|
||||||
|
#if HAVE_SNAPPY
|
||||||
|
typedef void SnappyCompressed;
|
||||||
|
#endif // HAVE_SNAPPY
|
||||||
|
|
||||||
|
typedef ReusableObjects ReusableResources;
|
||||||
|
typedef SingleUseObjects SingleUseResources;
|
||||||
|
|
||||||
|
template <typename TestCase, typename ReuseStrategy, typename Compression>
|
||||||
|
struct BenchmarkMethods: public null::BenchmarkMethods<TestCase, ReuseStrategy, Compression> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace null
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "null-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace null {
|
||||||
|
|
||||||
|
enum class Operation {
|
||||||
|
ADD,
|
||||||
|
SUBTRACT,
|
||||||
|
MULTIPLY,
|
||||||
|
DIVIDE,
|
||||||
|
MODULUS
|
||||||
|
};
|
||||||
|
uint OPERATION_RANGE = static_cast<uint>(Operation::MODULUS) + 1;
|
||||||
|
|
||||||
|
struct Expression {
|
||||||
|
Operation op;
|
||||||
|
|
||||||
|
bool leftIsValue;
|
||||||
|
bool rightIsValue;
|
||||||
|
|
||||||
|
union {
|
||||||
|
int32_t leftValue;
|
||||||
|
Expression* leftExpression;
|
||||||
|
};
|
||||||
|
|
||||||
|
union {
|
||||||
|
int32_t rightValue;
|
||||||
|
Expression* rightExpression;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
int32_t makeExpression(Expression* exp, uint depth) {
|
||||||
|
exp->op = (Operation)(fastRand(OPERATION_RANGE));
|
||||||
|
|
||||||
|
int32_t left, right;
|
||||||
|
|
||||||
|
if (fastRand(8) < depth) {
|
||||||
|
exp->leftIsValue = true;
|
||||||
|
left = fastRand(128) + 1;
|
||||||
|
exp->leftValue = left;
|
||||||
|
} else {
|
||||||
|
exp->leftIsValue = false;
|
||||||
|
exp->leftExpression = allocate<Expression>();
|
||||||
|
left = makeExpression(exp->leftExpression, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fastRand(8) < depth) {
|
||||||
|
exp->rightIsValue = true;
|
||||||
|
right = fastRand(128) + 1;
|
||||||
|
exp->rightValue = right;
|
||||||
|
} else {
|
||||||
|
exp->rightIsValue = false;
|
||||||
|
exp->rightExpression = allocate<Expression>();
|
||||||
|
right = makeExpression(exp->rightExpression, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (exp->op) {
|
||||||
|
case Operation::ADD:
|
||||||
|
return left + right;
|
||||||
|
case Operation::SUBTRACT:
|
||||||
|
return left - right;
|
||||||
|
case Operation::MULTIPLY:
|
||||||
|
return left * right;
|
||||||
|
case Operation::DIVIDE:
|
||||||
|
return div(left, right);
|
||||||
|
case Operation::MODULUS:
|
||||||
|
return mod(left, right);
|
||||||
|
}
|
||||||
|
throw std::logic_error("Can't get here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t evaluateExpression(const Expression& exp) {
|
||||||
|
uint32_t left, right;
|
||||||
|
|
||||||
|
if (exp.leftIsValue) {
|
||||||
|
left = exp.leftValue;
|
||||||
|
} else {
|
||||||
|
left = evaluateExpression(*exp.leftExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exp.rightIsValue) {
|
||||||
|
right = exp.rightValue;
|
||||||
|
} else {
|
||||||
|
right = evaluateExpression(*exp.rightExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (exp.op) {
|
||||||
|
case Operation::ADD:
|
||||||
|
return left + right;
|
||||||
|
case Operation::SUBTRACT:
|
||||||
|
return left - right;
|
||||||
|
case Operation::MULTIPLY:
|
||||||
|
return left * right;
|
||||||
|
case Operation::DIVIDE:
|
||||||
|
return div(left, right);
|
||||||
|
case Operation::MODULUS:
|
||||||
|
return mod(left, right);
|
||||||
|
}
|
||||||
|
throw std::logic_error("Can't get here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpressionTestCase {
|
||||||
|
public:
|
||||||
|
typedef Expression Request;
|
||||||
|
typedef int32_t Response;
|
||||||
|
typedef int32_t Expectation;
|
||||||
|
|
||||||
|
static inline int32_t setupRequest(Expression* request) {
|
||||||
|
return makeExpression(request, 0);
|
||||||
|
}
|
||||||
|
static inline void handleRequest(const Expression& request, int32_t* response) {
|
||||||
|
*response = evaluateExpression(request);
|
||||||
|
}
|
||||||
|
static inline bool checkResponse(int32_t response, int32_t expected) {
|
||||||
|
return response == expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t spaceUsed(const Expression& expression) {
|
||||||
|
return sizeof(Expression) +
|
||||||
|
(expression.leftExpression == nullptr ? 0 : spaceUsed(*expression.leftExpression)) +
|
||||||
|
(expression.rightExpression == nullptr ? 0 : spaceUsed(*expression.rightExpression));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace null
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::null::BenchmarkTypes,
|
||||||
|
capnp::benchmark::null::ExpressionTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "carsales.pb.h"
|
||||||
|
#include "protobuf-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace protobuf {
|
||||||
|
|
||||||
|
uint64_t carValue(const Car& car) {
|
||||||
|
// Do not think too hard about realism.
|
||||||
|
|
||||||
|
uint64_t result = 0;
|
||||||
|
|
||||||
|
result += car.seats() * 200;
|
||||||
|
result += car.doors() * 350;
|
||||||
|
for (auto& wheel: car.wheel()) {
|
||||||
|
result += wheel.diameter() * wheel.diameter();
|
||||||
|
result += wheel.snow_tires() ? 100 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += car.length() * car.width() * car.height() / 50;
|
||||||
|
|
||||||
|
const Engine& engine = car.engine();
|
||||||
|
result += engine.horsepower() * 40;
|
||||||
|
if (engine.uses_electric()) {
|
||||||
|
if (engine.uses_gas()) {
|
||||||
|
// hybrid
|
||||||
|
result += 5000;
|
||||||
|
} else {
|
||||||
|
result += 3000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += car.has_power_windows() ? 100 : 0;
|
||||||
|
result += car.has_power_steering() ? 200 : 0;
|
||||||
|
result += car.has_cruise_control() ? 400 : 0;
|
||||||
|
result += car.has_nav_system() ? 2000 : 0;
|
||||||
|
|
||||||
|
result += car.cup_holders() * 25;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void randomCar(Car* car) {
|
||||||
|
// Do not think too hard about realism.
|
||||||
|
|
||||||
|
static const char* const MAKES[] = { "Toyota", "GM", "Ford", "Honda", "Tesla" };
|
||||||
|
static const char* const MODELS[] = { "Camry", "Prius", "Volt", "Accord", "Leaf", "Model S" };
|
||||||
|
|
||||||
|
car->set_make(MAKES[fastRand(sizeof(MAKES) / sizeof(MAKES[0]))]);
|
||||||
|
car->set_model(MODELS[fastRand(sizeof(MODELS) / sizeof(MODELS[0]))]);
|
||||||
|
|
||||||
|
car->set_color((Color)fastRand(Color_MAX));
|
||||||
|
car->set_seats(2 + fastRand(6));
|
||||||
|
car->set_doors(2 + fastRand(3));
|
||||||
|
|
||||||
|
for (uint i = 0; i < 4; i++) {
|
||||||
|
Wheel* wheel = car->add_wheel();
|
||||||
|
wheel->set_diameter(25 + fastRand(15));
|
||||||
|
wheel->set_air_pressure(30 + fastRandDouble(20));
|
||||||
|
wheel->set_snow_tires(fastRand(16) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
car->set_length(170 + fastRand(150));
|
||||||
|
car->set_width(48 + fastRand(36));
|
||||||
|
car->set_height(54 + fastRand(48));
|
||||||
|
car->set_weight(car->length() * car->width() * car->height() / 200);
|
||||||
|
|
||||||
|
Engine* engine = car->mutable_engine();
|
||||||
|
engine->set_horsepower(100 * fastRand(400));
|
||||||
|
engine->set_cylinders(4 + 2 * fastRand(3));
|
||||||
|
engine->set_cc(800 + fastRand(10000));
|
||||||
|
engine->set_uses_gas(true);
|
||||||
|
engine->set_uses_electric(fastRand(2));
|
||||||
|
|
||||||
|
car->set_fuel_capacity(10.0 + fastRandDouble(30.0));
|
||||||
|
car->set_fuel_level(fastRandDouble(car->fuel_capacity()));
|
||||||
|
car->set_has_power_windows(fastRand(2));
|
||||||
|
car->set_has_power_steering(fastRand(2));
|
||||||
|
car->set_has_cruise_control(fastRand(2));
|
||||||
|
car->set_cup_holders(fastRand(12));
|
||||||
|
car->set_has_nav_system(fastRand(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
class CarSalesTestCase {
|
||||||
|
public:
|
||||||
|
typedef ParkingLot Request;
|
||||||
|
typedef TotalValue Response;
|
||||||
|
typedef uint64_t Expectation;
|
||||||
|
|
||||||
|
static uint64_t setupRequest(ParkingLot* request) {
|
||||||
|
uint count = fastRand(200);
|
||||||
|
uint64_t result = 0;
|
||||||
|
for (uint i = 0; i < count; i++) {
|
||||||
|
Car* car = request->add_car();
|
||||||
|
randomCar(car);
|
||||||
|
result += carValue(*car);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
static void handleRequest(const ParkingLot& request, TotalValue* response) {
|
||||||
|
uint64_t result = 0;
|
||||||
|
for (auto& car: request.car()) {
|
||||||
|
result += carValue(car);
|
||||||
|
}
|
||||||
|
response->set_amount(result);
|
||||||
|
}
|
||||||
|
static inline bool checkResponse(const TotalValue& response, uint64_t expected) {
|
||||||
|
return response.amount() == expected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace protobuf
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::protobuf::BenchmarkTypes,
|
||||||
|
capnp::benchmark::protobuf::CarSalesTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "catrank.pb.h"
|
||||||
|
#include "protobuf-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace protobuf {
|
||||||
|
|
||||||
|
struct ScoredResult {
|
||||||
|
double score;
|
||||||
|
const SearchResult* result;
|
||||||
|
|
||||||
|
ScoredResult() = default;
|
||||||
|
ScoredResult(double score, const SearchResult* result): score(score), result(result) {}
|
||||||
|
|
||||||
|
inline bool operator<(const ScoredResult& other) const { return score > other.score; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class CatRankTestCase {
|
||||||
|
public:
|
||||||
|
typedef SearchResultList Request;
|
||||||
|
typedef SearchResultList Response;
|
||||||
|
typedef int Expectation;
|
||||||
|
|
||||||
|
static int setupRequest(SearchResultList* request) {
|
||||||
|
int count = fastRand(1000);
|
||||||
|
int goodCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
SearchResult* result = request->add_result();
|
||||||
|
result->set_score(1000 - i);
|
||||||
|
result->set_url("http://example.com/");
|
||||||
|
std::string* url = result->mutable_url();
|
||||||
|
int urlSize = fastRand(100);
|
||||||
|
for (int j = 0; j < urlSize; j++) {
|
||||||
|
url->push_back('a' + fastRand(26));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCat = fastRand(8) == 0;
|
||||||
|
bool isDog = fastRand(8) == 0;
|
||||||
|
goodCount += isCat && !isDog;
|
||||||
|
|
||||||
|
std::string* snippet = result->mutable_snippet();
|
||||||
|
snippet->reserve(7 * 22);
|
||||||
|
snippet->push_back(' ');
|
||||||
|
|
||||||
|
int prefix = fastRand(20);
|
||||||
|
for (int j = 0; j < prefix; j++) {
|
||||||
|
snippet->append(WORDS[fastRand(WORDS_COUNT)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCat) snippet->append("cat ");
|
||||||
|
if (isDog) snippet->append("dog ");
|
||||||
|
|
||||||
|
int suffix = fastRand(20);
|
||||||
|
for (int j = 0; j < suffix; j++) {
|
||||||
|
snippet->append(WORDS[fastRand(WORDS_COUNT)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return goodCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleRequest(const SearchResultList& request, SearchResultList* response) {
|
||||||
|
std::vector<ScoredResult> scoredResults;
|
||||||
|
|
||||||
|
for (auto& result: request.result()) {
|
||||||
|
double score = result.score();
|
||||||
|
if (result.snippet().find(" cat ") != std::string::npos) {
|
||||||
|
score *= 10000;
|
||||||
|
}
|
||||||
|
if (result.snippet().find(" dog ") != std::string::npos) {
|
||||||
|
score /= 10000;
|
||||||
|
}
|
||||||
|
scoredResults.emplace_back(score, &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(scoredResults.begin(), scoredResults.end());
|
||||||
|
|
||||||
|
for (auto& result: scoredResults) {
|
||||||
|
SearchResult* out = response->add_result();
|
||||||
|
out->set_score(result.score);
|
||||||
|
out->set_url(result.result->url());
|
||||||
|
out->set_snippet(result.result->snippet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkResponse(const SearchResultList& response, int expectedGoodCount) {
|
||||||
|
int goodCount = 0;
|
||||||
|
for (auto& result: response.result()) {
|
||||||
|
if (result.score() > 1001) {
|
||||||
|
++goodCount;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return goodCount == expectedGoodCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace protobuf
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::protobuf::BenchmarkTypes,
|
||||||
|
capnp::benchmark::protobuf::CatRankTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,359 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include <google/protobuf/io/zero_copy_stream_impl.h>
|
||||||
|
#include <google/protobuf/io/coded_stream.h>
|
||||||
|
#include <thread>
|
||||||
|
#if HAVE_SNAPPY
|
||||||
|
#include <snappy/snappy.h>
|
||||||
|
#include <snappy/snappy-sinksource.h>
|
||||||
|
#endif // HAVE_SNAPPY
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace protobuf {
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
struct SingleUseMessages {
|
||||||
|
template <typename MessageType>
|
||||||
|
struct Message {
|
||||||
|
struct Reusable {};
|
||||||
|
struct SingleUse: public MessageType {
|
||||||
|
inline SingleUse(Reusable&) {}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReusableString {};
|
||||||
|
struct SingleUseString: std::string {
|
||||||
|
inline SingleUseString(ReusableString&) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename MessageType>
|
||||||
|
static inline void doneWith(MessageType& message) {
|
||||||
|
// Don't clear -- single-use.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReusableMessages {
|
||||||
|
template <typename MessageType>
|
||||||
|
struct Message {
|
||||||
|
struct Reusable: public MessageType {};
|
||||||
|
typedef MessageType& SingleUse;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::string ReusableString;
|
||||||
|
typedef std::string& SingleUseString;
|
||||||
|
|
||||||
|
template <typename MessageType>
|
||||||
|
static inline void doneWith(MessageType& message) {
|
||||||
|
message.Clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// The protobuf Java library defines a format for writing multiple protobufs to a stream, in which
|
||||||
|
// each message is prefixed by a varint size. This was never added to the C++ library. It's easy
|
||||||
|
// to do naively, but tricky to implement without accidentally losing various optimizations. These
|
||||||
|
// two functions should be optimal.
|
||||||
|
|
||||||
|
struct Uncompressed {
|
||||||
|
typedef google::protobuf::io::FileInputStream InputStream;
|
||||||
|
typedef google::protobuf::io::FileOutputStream OutputStream;
|
||||||
|
|
||||||
|
static uint64_t write(const google::protobuf::MessageLite& message,
|
||||||
|
google::protobuf::io::FileOutputStream* rawOutput) {
|
||||||
|
google::protobuf::io::CodedOutputStream output(rawOutput);
|
||||||
|
const int size = message.ByteSize();
|
||||||
|
output.WriteVarint32(size);
|
||||||
|
uint8_t* buffer = output.GetDirectBufferForNBytesAndAdvance(size);
|
||||||
|
if (buffer != NULL) {
|
||||||
|
message.SerializeWithCachedSizesToArray(buffer);
|
||||||
|
} else {
|
||||||
|
message.SerializeWithCachedSizes(&output);
|
||||||
|
if (output.HadError()) {
|
||||||
|
throw OsException(rawOutput->GetErrno());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read(google::protobuf::io::ZeroCopyInputStream* rawInput,
|
||||||
|
google::protobuf::MessageLite* message) {
|
||||||
|
google::protobuf::io::CodedInputStream input(rawInput);
|
||||||
|
uint32_t size;
|
||||||
|
GOOGLE_CHECK(input.ReadVarint32(&size));
|
||||||
|
|
||||||
|
auto limit = input.PushLimit(size);
|
||||||
|
|
||||||
|
GOOGLE_CHECK(message->MergePartialFromCodedStream(&input) &&
|
||||||
|
input.ConsumedEntireMessage());
|
||||||
|
|
||||||
|
input.PopLimit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void flush(google::protobuf::io::FileOutputStream* output) {
|
||||||
|
if (!output->Flush()) throw OsException(output->GetErrno());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// The Snappy interface is really obnoxious. I gave up here and am just reading/writing flat
|
||||||
|
// arrays in some static scratch space. This probably gives protobufs an edge that it doesn't
|
||||||
|
// deserve.
|
||||||
|
|
||||||
|
#if HAVE_SNAPPY
|
||||||
|
|
||||||
|
static char scratch[1 << 20];
|
||||||
|
static char scratch2[1 << 20];
|
||||||
|
|
||||||
|
struct SnappyCompressed {
|
||||||
|
typedef int InputStream;
|
||||||
|
typedef int OutputStream;
|
||||||
|
|
||||||
|
static uint64_t write(const google::protobuf::MessageLite& message, int* output) {
|
||||||
|
size_t size = message.ByteSize();
|
||||||
|
GOOGLE_CHECK_LE(size, sizeof(scratch));
|
||||||
|
|
||||||
|
message.SerializeWithCachedSizesToArray(reinterpret_cast<uint8_t*>(scratch));
|
||||||
|
|
||||||
|
size_t compressedSize = 0;
|
||||||
|
snappy::RawCompress(scratch, size, scratch2 + sizeof(uint32_t), &compressedSize);
|
||||||
|
uint32_t tag = compressedSize;
|
||||||
|
memcpy(scratch2, &tag, sizeof(tag));
|
||||||
|
|
||||||
|
writeAll(*output, scratch2, compressedSize + sizeof(tag));
|
||||||
|
return compressedSize + sizeof(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read(int* input, google::protobuf::MessageLite* message) {
|
||||||
|
uint32_t size;
|
||||||
|
readAll(*input, &size, sizeof(size));
|
||||||
|
readAll(*input, scratch, size);
|
||||||
|
|
||||||
|
size_t uncompressedSize;
|
||||||
|
GOOGLE_CHECK(snappy::GetUncompressedLength(scratch, size, &uncompressedSize));
|
||||||
|
GOOGLE_CHECK(snappy::RawUncompress(scratch, size, scratch2));
|
||||||
|
|
||||||
|
GOOGLE_CHECK(message->ParsePartialFromArray(scratch2, uncompressedSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void flush(OutputStream*) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HAVE_SNAPPY
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
#define REUSABLE(type) \
|
||||||
|
typename ReuseStrategy::template Message<typename TestCase::type>::Reusable
|
||||||
|
#define SINGLE_USE(type) \
|
||||||
|
typename ReuseStrategy::template Message<typename TestCase::type>::SingleUse
|
||||||
|
|
||||||
|
template <typename TestCase, typename ReuseStrategy, typename Compression>
|
||||||
|
struct BenchmarkMethods {
|
||||||
|
static uint64_t syncClient(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
uint64_t throughput = 0;
|
||||||
|
|
||||||
|
typename Compression::OutputStream output(outputFd);
|
||||||
|
typename Compression::InputStream input(inputFd);
|
||||||
|
|
||||||
|
REUSABLE(Request) reusableRequest;
|
||||||
|
REUSABLE(Response) reusableResponse;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
SINGLE_USE(Request) request(reusableRequest);
|
||||||
|
typename TestCase::Expectation expected = TestCase::setupRequest(&request);
|
||||||
|
throughput += Compression::write(request, &output);
|
||||||
|
Compression::flush(&output);
|
||||||
|
ReuseStrategy::doneWith(request);
|
||||||
|
|
||||||
|
SINGLE_USE(Response) response(reusableResponse);
|
||||||
|
Compression::read(&input, &response);
|
||||||
|
if (!TestCase::checkResponse(response, expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
ReuseStrategy::doneWith(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t asyncClientSender(
|
||||||
|
int outputFd, ProducerConsumerQueue<typename TestCase::Expectation>* expectations,
|
||||||
|
uint64_t iters) {
|
||||||
|
uint64_t throughput = 0;
|
||||||
|
|
||||||
|
typename Compression::OutputStream output(outputFd);
|
||||||
|
REUSABLE(Request) reusableRequest;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
SINGLE_USE(Request) request(reusableRequest);
|
||||||
|
expectations->post(TestCase::setupRequest(&request));
|
||||||
|
throughput += Compression::write(request, &output);
|
||||||
|
Compression::flush(&output);
|
||||||
|
ReuseStrategy::doneWith(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void asyncClientReceiver(
|
||||||
|
int inputFd, ProducerConsumerQueue<typename TestCase::Expectation>* expectations,
|
||||||
|
uint64_t iters) {
|
||||||
|
typename Compression::InputStream input(inputFd);
|
||||||
|
REUSABLE(Response) reusableResponse;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
typename TestCase::Expectation expected = expectations->next();
|
||||||
|
SINGLE_USE(Response) response(reusableResponse);
|
||||||
|
Compression::read(&input, &response);
|
||||||
|
if (!TestCase::checkResponse(response, expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
ReuseStrategy::doneWith(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t asyncClient(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
ProducerConsumerQueue<typename TestCase::Expectation> expectations;
|
||||||
|
std::thread receiverThread(asyncClientReceiver, inputFd, &expectations, iters);
|
||||||
|
uint64_t throughput = asyncClientSender(outputFd, &expectations, iters);
|
||||||
|
receiverThread.join();
|
||||||
|
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t server(int inputFd, int outputFd, uint64_t iters) {
|
||||||
|
uint64_t throughput = 0;
|
||||||
|
|
||||||
|
typename Compression::OutputStream output(outputFd);
|
||||||
|
typename Compression::InputStream input(inputFd);
|
||||||
|
|
||||||
|
REUSABLE(Request) reusableRequest;
|
||||||
|
REUSABLE(Response) reusableResponse;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
SINGLE_USE(Request) request(reusableRequest);
|
||||||
|
Compression::read(&input, &request);
|
||||||
|
|
||||||
|
SINGLE_USE(Response) response(reusableResponse);
|
||||||
|
TestCase::handleRequest(request, &response);
|
||||||
|
ReuseStrategy::doneWith(request);
|
||||||
|
|
||||||
|
throughput += Compression::write(response, &output);
|
||||||
|
Compression::flush(&output);
|
||||||
|
ReuseStrategy::doneWith(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t passByObject(uint64_t iters, bool countObjectSize) {
|
||||||
|
uint64_t throughput = 0;
|
||||||
|
|
||||||
|
REUSABLE(Request) reusableRequest;
|
||||||
|
REUSABLE(Response) reusableResponse;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
SINGLE_USE(Request) request(reusableRequest);
|
||||||
|
typename TestCase::Expectation expected = TestCase::setupRequest(&request);
|
||||||
|
|
||||||
|
SINGLE_USE(Response) response(reusableResponse);
|
||||||
|
TestCase::handleRequest(request, &response);
|
||||||
|
ReuseStrategy::doneWith(request);
|
||||||
|
if (!TestCase::checkResponse(response, expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
ReuseStrategy::doneWith(response);
|
||||||
|
|
||||||
|
if (countObjectSize) {
|
||||||
|
throughput += request.SpaceUsed();
|
||||||
|
throughput += response.SpaceUsed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t passByBytes(uint64_t iters) {
|
||||||
|
uint64_t throughput = 0;
|
||||||
|
|
||||||
|
REUSABLE(Request) reusableClientRequest;
|
||||||
|
REUSABLE(Request) reusableServerRequest;
|
||||||
|
REUSABLE(Response) reusableServerResponse;
|
||||||
|
REUSABLE(Response) reusableClientResponse;
|
||||||
|
typename ReuseStrategy::ReusableString reusableRequestString, reusableResponseString;
|
||||||
|
|
||||||
|
for (; iters > 0; --iters) {
|
||||||
|
SINGLE_USE(Request) clientRequest(reusableClientRequest);
|
||||||
|
typename TestCase::Expectation expected = TestCase::setupRequest(&clientRequest);
|
||||||
|
|
||||||
|
typename ReuseStrategy::SingleUseString requestString(reusableRequestString);
|
||||||
|
clientRequest.SerializePartialToString(&requestString);
|
||||||
|
throughput += requestString.size();
|
||||||
|
ReuseStrategy::doneWith(clientRequest);
|
||||||
|
|
||||||
|
SINGLE_USE(Request) serverRequest(reusableServerRequest);
|
||||||
|
serverRequest.ParsePartialFromString(requestString);
|
||||||
|
|
||||||
|
SINGLE_USE(Response) serverResponse(reusableServerResponse);
|
||||||
|
TestCase::handleRequest(serverRequest, &serverResponse);
|
||||||
|
ReuseStrategy::doneWith(serverRequest);
|
||||||
|
|
||||||
|
typename ReuseStrategy::SingleUseString responseString(reusableResponseString);
|
||||||
|
serverResponse.SerializePartialToString(&responseString);
|
||||||
|
throughput += responseString.size();
|
||||||
|
ReuseStrategy::doneWith(serverResponse);
|
||||||
|
|
||||||
|
SINGLE_USE(Response) clientResponse(reusableClientResponse);
|
||||||
|
clientResponse.ParsePartialFromString(responseString);
|
||||||
|
|
||||||
|
if (!TestCase::checkResponse(clientResponse, expected)) {
|
||||||
|
throw std::logic_error("Incorrect response.");
|
||||||
|
}
|
||||||
|
ReuseStrategy::doneWith(clientResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BenchmarkTypes {
|
||||||
|
typedef protobuf::Uncompressed Uncompressed;
|
||||||
|
typedef protobuf::Uncompressed Packed;
|
||||||
|
#if HAVE_SNAPPY
|
||||||
|
typedef protobuf::SnappyCompressed SnappyCompressed;
|
||||||
|
#endif // HAVE_SNAPPY
|
||||||
|
|
||||||
|
typedef protobuf::ReusableMessages ReusableResources;
|
||||||
|
typedef protobuf::SingleUseMessages SingleUseResources;
|
||||||
|
|
||||||
|
template <typename TestCase, typename ReuseStrategy, typename Compression>
|
||||||
|
struct BenchmarkMethods
|
||||||
|
: public protobuf::BenchmarkMethods<TestCase, ReuseStrategy, Compression> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace protobuf
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "eval.pb.h"
|
||||||
|
#include "protobuf-common.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace protobuf {
|
||||||
|
|
||||||
|
int32_t makeExpression(Expression* exp, uint depth) {
|
||||||
|
exp->set_op((Operation)(fastRand(Operation_MAX + 1)));
|
||||||
|
|
||||||
|
int32_t left, right;
|
||||||
|
|
||||||
|
if (fastRand(8) < depth) {
|
||||||
|
left = fastRand(128) + 1;
|
||||||
|
exp->set_left_value(left);
|
||||||
|
} else {
|
||||||
|
left = makeExpression(exp->mutable_left_expression(), depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fastRand(8) < depth) {
|
||||||
|
right = fastRand(128) + 1;
|
||||||
|
exp->set_right_value(right);
|
||||||
|
} else {
|
||||||
|
right = makeExpression(exp->mutable_right_expression(), depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (exp->op()) {
|
||||||
|
case Operation::ADD:
|
||||||
|
return left + right;
|
||||||
|
case Operation::SUBTRACT:
|
||||||
|
return left - right;
|
||||||
|
case Operation::MULTIPLY:
|
||||||
|
return left * right;
|
||||||
|
case Operation::DIVIDE:
|
||||||
|
return div(left, right);
|
||||||
|
case Operation::MODULUS:
|
||||||
|
return mod(left, right);
|
||||||
|
}
|
||||||
|
throw std::logic_error("Can't get here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t evaluateExpression(const Expression& exp) {
|
||||||
|
uint32_t left, right;
|
||||||
|
|
||||||
|
if (exp.has_left_value()) {
|
||||||
|
left = exp.left_value();
|
||||||
|
} else {
|
||||||
|
left = evaluateExpression(exp.left_expression());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exp.has_right_value()) {
|
||||||
|
right = exp.right_value();
|
||||||
|
} else {
|
||||||
|
right = evaluateExpression(exp.right_expression());
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (exp.op()) {
|
||||||
|
case Operation::ADD:
|
||||||
|
return left + right;
|
||||||
|
case Operation::SUBTRACT:
|
||||||
|
return left - right;
|
||||||
|
case Operation::MULTIPLY:
|
||||||
|
return left * right;
|
||||||
|
case Operation::DIVIDE:
|
||||||
|
return div(left, right);
|
||||||
|
case Operation::MODULUS:
|
||||||
|
return mod(left, right);
|
||||||
|
}
|
||||||
|
throw std::logic_error("Can't get here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpressionTestCase {
|
||||||
|
public:
|
||||||
|
typedef Expression Request;
|
||||||
|
typedef EvaluationResult Response;
|
||||||
|
typedef int32_t Expectation;
|
||||||
|
|
||||||
|
static inline int32_t setupRequest(Expression* request) {
|
||||||
|
return makeExpression(request, 0);
|
||||||
|
}
|
||||||
|
static inline void handleRequest(const Expression& request, EvaluationResult* response) {
|
||||||
|
response->set_value(evaluateExpression(request));
|
||||||
|
}
|
||||||
|
static inline bool checkResponse(const EvaluationResult& response, int32_t expected) {
|
||||||
|
return response.value() == expected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace protobuf
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::benchmarkMain<
|
||||||
|
capnp::benchmark::protobuf::BenchmarkTypes,
|
||||||
|
capnp::benchmark::protobuf::ExpressionTestCase>(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,648 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace benchmark {
|
||||||
|
namespace runner {
|
||||||
|
|
||||||
|
struct Times {
|
||||||
|
uint64_t real;
|
||||||
|
uint64_t user;
|
||||||
|
uint64_t sys;
|
||||||
|
|
||||||
|
uint64_t cpu() { return user + sys; }
|
||||||
|
|
||||||
|
Times operator-(const Times& other) {
|
||||||
|
Times result;
|
||||||
|
result.real = real - other.real;
|
||||||
|
result.user = user - other.user;
|
||||||
|
result.sys = sys - other.sys;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t asNanosecs(const struct timeval& tv) {
|
||||||
|
return (uint64_t)tv.tv_sec * 1000000000 + (uint64_t)tv.tv_usec * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
Times currentTimes() {
|
||||||
|
Times result;
|
||||||
|
|
||||||
|
struct rusage self, children;
|
||||||
|
getrusage(RUSAGE_SELF, &self);
|
||||||
|
getrusage(RUSAGE_CHILDREN, &children);
|
||||||
|
|
||||||
|
struct timeval real;
|
||||||
|
gettimeofday(&real, nullptr);
|
||||||
|
|
||||||
|
result.real = asNanosecs(real);
|
||||||
|
result.user = asNanosecs(self.ru_utime) + asNanosecs(children.ru_utime);
|
||||||
|
result.sys = asNanosecs(self.ru_stime) + asNanosecs(children.ru_stime);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestResult {
|
||||||
|
uint64_t objectSize;
|
||||||
|
uint64_t messageSize;
|
||||||
|
Times time;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Product {
|
||||||
|
CAPNPROTO,
|
||||||
|
PROTOBUF,
|
||||||
|
NULLCASE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TestCase {
|
||||||
|
EVAL,
|
||||||
|
CATRANK,
|
||||||
|
CARSALES
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* testCaseName(TestCase testCase) {
|
||||||
|
switch (testCase) {
|
||||||
|
case TestCase::EVAL:
|
||||||
|
return "eval";
|
||||||
|
case TestCase::CATRANK:
|
||||||
|
return "catrank";
|
||||||
|
case TestCase::CARSALES:
|
||||||
|
return "carsales";
|
||||||
|
}
|
||||||
|
// Can't get here.
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
OBJECTS,
|
||||||
|
OBJECT_SIZE,
|
||||||
|
BYTES,
|
||||||
|
PIPE_SYNC,
|
||||||
|
PIPE_ASYNC
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Reuse {
|
||||||
|
YES,
|
||||||
|
NO
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Compression {
|
||||||
|
NONE,
|
||||||
|
PACKED,
|
||||||
|
SNAPPY
|
||||||
|
};
|
||||||
|
|
||||||
|
TestResult runTest(Product product, TestCase testCase, Mode mode, Reuse reuse,
|
||||||
|
Compression compression, uint64_t iters) {
|
||||||
|
char* argv[6];
|
||||||
|
|
||||||
|
string progName;
|
||||||
|
|
||||||
|
switch (product) {
|
||||||
|
case Product::CAPNPROTO:
|
||||||
|
progName = "capnproto-";
|
||||||
|
break;
|
||||||
|
case Product::PROTOBUF:
|
||||||
|
progName = "protobuf-";
|
||||||
|
break;
|
||||||
|
case Product::NULLCASE:
|
||||||
|
progName = "null-";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
progName += testCaseName(testCase);
|
||||||
|
argv[0] = strdup(progName.c_str());
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case Mode::OBJECTS:
|
||||||
|
argv[1] = strdup("object");
|
||||||
|
break;
|
||||||
|
case Mode::OBJECT_SIZE:
|
||||||
|
argv[1] = strdup("object-size");
|
||||||
|
break;
|
||||||
|
case Mode::BYTES:
|
||||||
|
argv[1] = strdup("bytes");
|
||||||
|
break;
|
||||||
|
case Mode::PIPE_SYNC:
|
||||||
|
argv[1] = strdup("pipe");
|
||||||
|
break;
|
||||||
|
case Mode::PIPE_ASYNC:
|
||||||
|
argv[1] = strdup("pipe-async");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (reuse) {
|
||||||
|
case Reuse::YES:
|
||||||
|
argv[2] = strdup("reuse");
|
||||||
|
break;
|
||||||
|
case Reuse::NO:
|
||||||
|
argv[2] = strdup("no-reuse");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (compression) {
|
||||||
|
case Compression::NONE:
|
||||||
|
argv[3] = strdup("none");
|
||||||
|
break;
|
||||||
|
case Compression::PACKED:
|
||||||
|
argv[3] = strdup("packed");
|
||||||
|
break;
|
||||||
|
case Compression::SNAPPY:
|
||||||
|
argv[3] = strdup("snappy");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char itersStr[64];
|
||||||
|
sprintf(itersStr, "%llu", (long long unsigned int)iters);
|
||||||
|
argv[4] = itersStr;
|
||||||
|
|
||||||
|
argv[5] = nullptr;
|
||||||
|
|
||||||
|
// Make pipe for child to write throughput.
|
||||||
|
int childPipe[2];
|
||||||
|
if (pipe(childPipe) < 0) {
|
||||||
|
perror("pipe");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn the child process.
|
||||||
|
struct timeval start, end;
|
||||||
|
gettimeofday(&start, nullptr);
|
||||||
|
pid_t child = fork();
|
||||||
|
if (child == 0) {
|
||||||
|
close(childPipe[0]);
|
||||||
|
dup2(childPipe[1], STDOUT_FILENO);
|
||||||
|
close(childPipe[1]);
|
||||||
|
execv(argv[0], argv);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(childPipe[1]);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
free(argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read throughput number written to child's stdout.
|
||||||
|
FILE* input = fdopen(childPipe[0], "r");
|
||||||
|
long long unsigned int throughput;
|
||||||
|
if (fscanf(input, "%lld", &throughput) != 1) {
|
||||||
|
fprintf(stderr, "Child didn't write throughput to stdout.");
|
||||||
|
}
|
||||||
|
char buffer[1024];
|
||||||
|
while (fgets(buffer, sizeof(buffer), input) != nullptr) {
|
||||||
|
// Loop until EOF.
|
||||||
|
}
|
||||||
|
fclose(input);
|
||||||
|
|
||||||
|
// Wait for child exit.
|
||||||
|
int status;
|
||||||
|
struct rusage usage;
|
||||||
|
wait4(child, &status, 0, &usage);
|
||||||
|
gettimeofday(&end, nullptr);
|
||||||
|
|
||||||
|
// Calculate results.
|
||||||
|
|
||||||
|
TestResult result;
|
||||||
|
result.objectSize = mode == Mode::OBJECT_SIZE ? throughput : 0;
|
||||||
|
result.messageSize = mode == Mode::OBJECT_SIZE ? 0 : throughput;
|
||||||
|
result.time.real = asNanosecs(end) - asNanosecs(start);
|
||||||
|
result.time.user = asNanosecs(usage.ru_utime);
|
||||||
|
result.time.sys = asNanosecs(usage.ru_stime);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportTableHeader() {
|
||||||
|
cout << setw(40) << left << "Test"
|
||||||
|
<< setw(10) << right << "obj size"
|
||||||
|
<< setw(10) << right << "I/O bytes"
|
||||||
|
<< setw(10) << right << "wall ns"
|
||||||
|
<< setw(10) << right << "user ns"
|
||||||
|
<< setw(10) << right << "sys ns"
|
||||||
|
<< endl;
|
||||||
|
cout << setfill('=') << setw(90) << "" << setfill(' ') << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportResults(const char* name, uint64_t iters, TestResult results) {
|
||||||
|
cout << setw(40) << left << name
|
||||||
|
<< setw(10) << right << (results.objectSize / iters)
|
||||||
|
<< setw(10) << right << (results.messageSize / iters)
|
||||||
|
<< setw(10) << right << (results.time.real / iters)
|
||||||
|
<< setw(10) << right << (results.time.user / iters)
|
||||||
|
<< setw(10) << right << (results.time.sys / iters)
|
||||||
|
<< endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportComparisonHeader() {
|
||||||
|
cout << setw(40) << left << "Measure"
|
||||||
|
<< setw(15) << right << "Protobuf"
|
||||||
|
<< setw(15) << right << "Cap'n Proto"
|
||||||
|
<< setw(15) << right << "Improvement"
|
||||||
|
<< endl;
|
||||||
|
cout << setfill('=') << setw(85) << "" << setfill(' ') << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportOldNewComparisonHeader() {
|
||||||
|
cout << setw(40) << left << "Measure"
|
||||||
|
<< setw(15) << right << "Old"
|
||||||
|
<< setw(15) << right << "New"
|
||||||
|
<< setw(15) << right << "Improvement"
|
||||||
|
<< endl;
|
||||||
|
cout << setfill('=') << setw(85) << "" << setfill(' ') << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Gain {
|
||||||
|
public:
|
||||||
|
Gain(double oldValue, double newValue)
|
||||||
|
: amount(newValue / oldValue) {}
|
||||||
|
|
||||||
|
void writeTo(std::ostream& os) {
|
||||||
|
if (amount < 2) {
|
||||||
|
double percent = (amount - 1) * 100;
|
||||||
|
os << (int)(percent + 0.5) << "%";
|
||||||
|
} else {
|
||||||
|
os << fixed << setprecision(2) << amount << "x";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double amount;
|
||||||
|
};
|
||||||
|
|
||||||
|
ostream& operator<<(ostream& os, Gain gain) {
|
||||||
|
gain.writeTo(os);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportComparison(const char* name, double base, double protobuf, double capnproto,
|
||||||
|
uint64_t iters) {
|
||||||
|
cout << setw(40) << left << name
|
||||||
|
<< setw(14) << right << Gain(base, protobuf)
|
||||||
|
<< setw(14) << right << Gain(base, capnproto);
|
||||||
|
|
||||||
|
// Since smaller is better, the "improvement" is the "gain" from capnproto to protobuf.
|
||||||
|
cout << setw(14) << right << Gain(capnproto - base, protobuf - base) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportComparison(const char* name, const char* unit, double protobuf, double capnproto,
|
||||||
|
uint64_t iters) {
|
||||||
|
cout << setw(40) << left << name
|
||||||
|
<< setw(15-strlen(unit)) << fixed << right << setprecision(2) << (protobuf / iters) << unit
|
||||||
|
<< setw(15-strlen(unit)) << fixed << right << setprecision(2) << (capnproto / iters) << unit;
|
||||||
|
|
||||||
|
// Since smaller is better, the "improvement" is the "gain" from capnproto to protobuf.
|
||||||
|
cout << setw(14) << right << Gain(capnproto, protobuf) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportIntComparison(const char* name, const char* unit, uint64_t protobuf, uint64_t capnproto,
|
||||||
|
uint64_t iters) {
|
||||||
|
cout << setw(40) << left << name
|
||||||
|
<< setw(15-strlen(unit)) << right << (protobuf / iters) << unit
|
||||||
|
<< setw(15-strlen(unit)) << right << (capnproto / iters) << unit;
|
||||||
|
|
||||||
|
// Since smaller is better, the "improvement" is the "gain" from capnproto to protobuf.
|
||||||
|
cout << setw(14) << right << Gain(capnproto, protobuf) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fileSize(const std::string& name) {
|
||||||
|
struct stat stats;
|
||||||
|
if (stat(name.c_str(), &stats) < 0) {
|
||||||
|
perror(name.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats.st_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
char* path = argv[0];
|
||||||
|
char* slashpos = strrchr(path, '/');
|
||||||
|
char origDir[1024];
|
||||||
|
if (getcwd(origDir, sizeof(origDir)) == nullptr) {
|
||||||
|
perror("getcwd");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (slashpos != nullptr) {
|
||||||
|
*slashpos = '\0';
|
||||||
|
if (chdir(path) < 0) {
|
||||||
|
perror("chdir");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
*slashpos = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestCase testCase = TestCase::CATRANK;
|
||||||
|
Mode mode = Mode::PIPE_SYNC;
|
||||||
|
Compression compression = Compression::NONE;
|
||||||
|
uint64_t iters = 1;
|
||||||
|
const char* oldDir = nullptr;
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
string arg = argv[i];
|
||||||
|
if (isdigit(argv[i][0])) {
|
||||||
|
iters = strtoul(argv[i], nullptr, 0);
|
||||||
|
} else if (arg == "async") {
|
||||||
|
mode = Mode::PIPE_ASYNC;
|
||||||
|
} else if (arg == "inmem") {
|
||||||
|
mode = Mode::BYTES;
|
||||||
|
} else if (arg == "eval") {
|
||||||
|
testCase = TestCase::EVAL;
|
||||||
|
} else if (arg == "carsales") {
|
||||||
|
testCase = TestCase::CARSALES;
|
||||||
|
} else if (arg == "snappy") {
|
||||||
|
compression = Compression::SNAPPY;
|
||||||
|
} else if (arg == "-c") {
|
||||||
|
++i;
|
||||||
|
if (i == argc) {
|
||||||
|
fprintf(stderr, "-c requires argument.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
oldDir = argv[i];
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Unknown option: %s\n", argv[i]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale iterations to something reasonable for each case.
|
||||||
|
switch (testCase) {
|
||||||
|
case TestCase::EVAL:
|
||||||
|
iters *= 100000;
|
||||||
|
break;
|
||||||
|
case TestCase::CATRANK:
|
||||||
|
iters *= 1000;
|
||||||
|
break;
|
||||||
|
case TestCase::CARSALES:
|
||||||
|
iters *= 20000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Running " << iters << " iterations of ";
|
||||||
|
switch (testCase) {
|
||||||
|
case TestCase::EVAL:
|
||||||
|
cout << "calculator";
|
||||||
|
break;
|
||||||
|
case TestCase::CATRANK:
|
||||||
|
cout << "CatRank";
|
||||||
|
break;
|
||||||
|
case TestCase::CARSALES:
|
||||||
|
cout << "car sales";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << " example case with:" << endl;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case Mode::OBJECTS:
|
||||||
|
case Mode::OBJECT_SIZE:
|
||||||
|
// Can't happen.
|
||||||
|
break;
|
||||||
|
case Mode::BYTES:
|
||||||
|
cout << "* in-memory I/O" << endl;
|
||||||
|
cout << " * with client and server in the same thread" << endl;
|
||||||
|
break;
|
||||||
|
case Mode::PIPE_SYNC:
|
||||||
|
cout << "* pipe I/O" << endl;
|
||||||
|
cout << " * with client and server in separate processes" << endl;
|
||||||
|
cout << " * client waits for each response before sending next request" << endl;
|
||||||
|
break;
|
||||||
|
case Mode::PIPE_ASYNC:
|
||||||
|
cout << "* pipe I/O" << endl;
|
||||||
|
cout << " * with client and server in separate processes" << endl;
|
||||||
|
cout << " * client sends as many simultaneous requests as it can" << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (compression) {
|
||||||
|
case Compression::NONE:
|
||||||
|
cout << "* no compression" << endl;
|
||||||
|
break;
|
||||||
|
case Compression::PACKED:
|
||||||
|
cout << "* de-zero packing for Cap'n Proto" << endl;
|
||||||
|
cout << "* standard packing for Protobuf" << endl;
|
||||||
|
break;
|
||||||
|
case Compression::SNAPPY:
|
||||||
|
cout << "* Snappy compression" << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
reportTableHeader();
|
||||||
|
|
||||||
|
TestResult nullCase = runTest(
|
||||||
|
Product::NULLCASE, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters);
|
||||||
|
reportResults("Theoretical best pass-by-object", iters, nullCase);
|
||||||
|
|
||||||
|
TestResult protobufBase = runTest(
|
||||||
|
Product::PROTOBUF, testCase, Mode::OBJECTS, Reuse::YES, compression, iters);
|
||||||
|
protobufBase.objectSize = runTest(
|
||||||
|
Product::PROTOBUF, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters).objectSize;
|
||||||
|
reportResults("Protobuf pass-by-object", iters, protobufBase);
|
||||||
|
|
||||||
|
TestResult capnpBase = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, Mode::OBJECTS, Reuse::YES, compression, iters);
|
||||||
|
capnpBase.objectSize = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters).objectSize;
|
||||||
|
reportResults("Cap'n Proto pass-by-object", iters, capnpBase);
|
||||||
|
|
||||||
|
TestResult nullCaseNoReuse = runTest(
|
||||||
|
Product::NULLCASE, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters);
|
||||||
|
reportResults("Theoretical best w/o object reuse", iters, nullCaseNoReuse);
|
||||||
|
|
||||||
|
TestResult protobufNoReuse = runTest(
|
||||||
|
Product::PROTOBUF, testCase, Mode::OBJECTS, Reuse::NO, compression, iters);
|
||||||
|
protobufNoReuse.objectSize = runTest(
|
||||||
|
Product::PROTOBUF, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters).objectSize;
|
||||||
|
reportResults("Protobuf w/o object reuse", iters, protobufNoReuse);
|
||||||
|
|
||||||
|
TestResult capnpNoReuse = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, Mode::OBJECTS, Reuse::NO, compression, iters);
|
||||||
|
capnpNoReuse.objectSize = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters).objectSize;
|
||||||
|
reportResults("Cap'n Proto w/o object reuse", iters, capnpNoReuse);
|
||||||
|
|
||||||
|
TestResult protobuf = runTest(
|
||||||
|
Product::PROTOBUF, testCase, mode, Reuse::YES, compression, iters);
|
||||||
|
protobuf.objectSize = protobufBase.objectSize;
|
||||||
|
reportResults("Protobuf I/O", iters, protobuf);
|
||||||
|
|
||||||
|
TestResult capnp = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, mode, Reuse::YES, compression, iters);
|
||||||
|
capnp.objectSize = capnpBase.objectSize;
|
||||||
|
reportResults("Cap'n Proto I/O", iters, capnp);
|
||||||
|
TestResult capnpPacked = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, mode, Reuse::YES, Compression::PACKED, iters);
|
||||||
|
capnpPacked.objectSize = capnpBase.objectSize;
|
||||||
|
reportResults("Cap'n Proto packed I/O", iters, capnpPacked);
|
||||||
|
|
||||||
|
size_t protobufBinarySize = fileSize("protobuf-" + std::string(testCaseName(testCase)));
|
||||||
|
size_t capnpBinarySize = fileSize("capnproto-" + std::string(testCaseName(testCase)));
|
||||||
|
size_t protobufCodeSize = fileSize(std::string(testCaseName(testCase)) + ".pb.cc")
|
||||||
|
+ fileSize(std::string(testCaseName(testCase)) + ".pb.h");
|
||||||
|
size_t capnpCodeSize = fileSize(std::string(testCaseName(testCase)) + ".capnp.c++")
|
||||||
|
+ fileSize(std::string(testCaseName(testCase)) + ".capnp.h");
|
||||||
|
size_t protobufObjSize = fileSize(std::string(testCaseName(testCase)) + ".pb.o");
|
||||||
|
size_t capnpObjSize = fileSize(std::string(testCaseName(testCase)) + ".capnp.o");
|
||||||
|
|
||||||
|
TestResult oldNullCase;
|
||||||
|
TestResult oldNullCaseNoReuse;
|
||||||
|
TestResult oldCapnpBase;
|
||||||
|
TestResult oldCapnpNoReuse;
|
||||||
|
TestResult oldCapnp;
|
||||||
|
TestResult oldCapnpPacked;
|
||||||
|
size_t oldCapnpBinarySize = 0;
|
||||||
|
size_t oldCapnpCodeSize = 0;
|
||||||
|
size_t oldCapnpObjSize = 0;
|
||||||
|
if (oldDir != nullptr) {
|
||||||
|
if (chdir(origDir) < 0) {
|
||||||
|
perror("chdir");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (chdir(oldDir) < 0) {
|
||||||
|
perror(oldDir);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldNullCase = runTest(
|
||||||
|
Product::NULLCASE, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters);
|
||||||
|
reportResults("Old theoretical best pass-by-object", iters, nullCase);
|
||||||
|
|
||||||
|
oldCapnpBase = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, Mode::OBJECTS, Reuse::YES, compression, iters);
|
||||||
|
oldCapnpBase.objectSize = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters)
|
||||||
|
.objectSize;
|
||||||
|
reportResults("Old Cap'n Proto pass-by-object", iters, oldCapnpBase);
|
||||||
|
|
||||||
|
oldNullCaseNoReuse = runTest(
|
||||||
|
Product::NULLCASE, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters);
|
||||||
|
reportResults("Old theoretical best w/o object reuse", iters, oldNullCaseNoReuse);
|
||||||
|
|
||||||
|
oldCapnpNoReuse = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, Mode::OBJECTS, Reuse::NO, compression, iters);
|
||||||
|
oldCapnpNoReuse.objectSize = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters).objectSize;
|
||||||
|
reportResults("Old Cap'n Proto w/o object reuse", iters, oldCapnpNoReuse);
|
||||||
|
|
||||||
|
oldCapnp = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, mode, Reuse::YES, compression, iters);
|
||||||
|
oldCapnp.objectSize = oldCapnpBase.objectSize;
|
||||||
|
reportResults("Old Cap'n Proto I/O", iters, oldCapnp);
|
||||||
|
oldCapnpPacked = runTest(
|
||||||
|
Product::CAPNPROTO, testCase, mode, Reuse::YES, Compression::PACKED, iters);
|
||||||
|
oldCapnpPacked.objectSize = oldCapnpBase.objectSize;
|
||||||
|
reportResults("Old Cap'n Proto packed I/O", iters, oldCapnpPacked);
|
||||||
|
|
||||||
|
oldCapnpBinarySize = fileSize("capnproto-" + std::string(testCaseName(testCase)));
|
||||||
|
oldCapnpCodeSize = fileSize(std::string(testCaseName(testCase)) + ".capnp.c++")
|
||||||
|
+ fileSize(std::string(testCaseName(testCase)) + ".capnp.h");
|
||||||
|
oldCapnpObjSize = fileSize(std::string(testCaseName(testCase)) + ".capnp.o");
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
reportComparisonHeader();
|
||||||
|
reportComparison("memory overhead (vs ideal)",
|
||||||
|
nullCase.objectSize, protobufBase.objectSize, capnpBase.objectSize, iters);
|
||||||
|
reportComparison("memory overhead w/o object reuse",
|
||||||
|
nullCaseNoReuse.objectSize, protobufNoReuse.objectSize, capnpNoReuse.objectSize, iters);
|
||||||
|
reportComparison("object manipulation time (us)", "",
|
||||||
|
((int64_t)protobufBase.time.user - (int64_t)nullCase.time.user) / 1000.0,
|
||||||
|
((int64_t)capnpBase.time.user - (int64_t)nullCase.time.user) / 1000.0, iters);
|
||||||
|
reportComparison("object manipulation time w/o reuse (us)", "",
|
||||||
|
((int64_t)protobufNoReuse.time.user - (int64_t)nullCaseNoReuse.time.user) / 1000.0,
|
||||||
|
((int64_t)capnpNoReuse.time.user - (int64_t)nullCaseNoReuse.time.user) / 1000.0, iters);
|
||||||
|
reportComparison("I/O time (us)", "",
|
||||||
|
((int64_t)protobuf.time.user - (int64_t)protobufBase.time.user) / 1000.0,
|
||||||
|
((int64_t)capnp.time.user - (int64_t)capnpBase.time.user) / 1000.0, iters);
|
||||||
|
reportComparison("packed I/O time (us)", "",
|
||||||
|
((int64_t)protobuf.time.user - (int64_t)protobufBase.time.user) / 1000.0,
|
||||||
|
((int64_t)capnpPacked.time.user - (int64_t)capnpBase.time.user) / 1000.0, iters);
|
||||||
|
|
||||||
|
reportIntComparison("message size (bytes)", "", protobuf.messageSize, capnp.messageSize, iters);
|
||||||
|
reportIntComparison("packed message size (bytes)", "",
|
||||||
|
protobuf.messageSize, capnpPacked.messageSize, iters);
|
||||||
|
|
||||||
|
reportComparison("binary size (KiB)", "",
|
||||||
|
protobufBinarySize / 1024.0, capnpBinarySize / 1024.0, 1);
|
||||||
|
reportComparison("generated code size (KiB)", "",
|
||||||
|
protobufCodeSize / 1024.0, capnpCodeSize / 1024.0, 1);
|
||||||
|
reportComparison("generated obj size (KiB)", "",
|
||||||
|
protobufObjSize / 1024.0, capnpObjSize / 1024.0, 1);
|
||||||
|
|
||||||
|
if (oldDir != nullptr) {
|
||||||
|
cout << endl;
|
||||||
|
reportOldNewComparisonHeader();
|
||||||
|
|
||||||
|
reportComparison("memory overhead",
|
||||||
|
oldNullCase.objectSize, oldCapnpBase.objectSize, capnpBase.objectSize, iters);
|
||||||
|
reportComparison("memory overhead w/o object reuse",
|
||||||
|
oldNullCaseNoReuse.objectSize, oldCapnpNoReuse.objectSize, capnpNoReuse.objectSize, iters);
|
||||||
|
reportComparison("object manipulation time (us)", "",
|
||||||
|
((int64_t)oldCapnpBase.time.user - (int64_t)oldNullCase.time.user) / 1000.0,
|
||||||
|
((int64_t)capnpBase.time.user - (int64_t)oldNullCase.time.user) / 1000.0, iters);
|
||||||
|
reportComparison("object manipulation time w/o reuse (us)", "",
|
||||||
|
((int64_t)oldCapnpNoReuse.time.user - (int64_t)oldNullCaseNoReuse.time.user) / 1000.0,
|
||||||
|
((int64_t)capnpNoReuse.time.user - (int64_t)oldNullCaseNoReuse.time.user) / 1000.0, iters);
|
||||||
|
reportComparison("I/O time (us)", "",
|
||||||
|
((int64_t)oldCapnp.time.user - (int64_t)oldCapnpBase.time.user) / 1000.0,
|
||||||
|
((int64_t)capnp.time.user - (int64_t)capnpBase.time.user) / 1000.0, iters);
|
||||||
|
reportComparison("packed I/O time (us)", "",
|
||||||
|
((int64_t)oldCapnpPacked.time.user - (int64_t)oldCapnpBase.time.user) / 1000.0,
|
||||||
|
((int64_t)capnpPacked.time.user - (int64_t)capnpBase.time.user) / 1000.0, iters);
|
||||||
|
|
||||||
|
reportIntComparison("message size (bytes)", "", oldCapnp.messageSize, capnp.messageSize, iters);
|
||||||
|
reportIntComparison("packed message size (bytes)", "",
|
||||||
|
oldCapnpPacked.messageSize, capnpPacked.messageSize, iters);
|
||||||
|
|
||||||
|
reportComparison("binary size (KiB)", "",
|
||||||
|
oldCapnpBinarySize / 1024.0, capnpBinarySize / 1024.0, 1);
|
||||||
|
reportComparison("generated code size (KiB)", "",
|
||||||
|
oldCapnpCodeSize / 1024.0, capnpCodeSize / 1024.0, 1);
|
||||||
|
reportComparison("generated obj size (KiB)", "",
|
||||||
|
oldCapnpObjSize / 1024.0, capnpObjSize / 1024.0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace runner
|
||||||
|
} // namespace benchmark
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
return capnp::benchmark::runner::main(argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
|
||||||
|
# capnp ========================================================================
|
||||||
|
|
||||||
|
set(capnp_sources_lite
|
||||||
|
c++.capnp.c++
|
||||||
|
blob.c++
|
||||||
|
arena.c++
|
||||||
|
layout.c++
|
||||||
|
list.c++
|
||||||
|
any.c++
|
||||||
|
message.c++
|
||||||
|
schema.capnp.c++
|
||||||
|
serialize.c++
|
||||||
|
serialize-packed.c++
|
||||||
|
)
|
||||||
|
set(capnp_sources_heavy
|
||||||
|
schema.c++
|
||||||
|
schema-loader.c++
|
||||||
|
dynamic.c++
|
||||||
|
stringify.c++
|
||||||
|
)
|
||||||
|
if(NOT CAPNP_LITE)
|
||||||
|
set(capnp_sources ${capnp_sources_lite} ${capnp_sources_heavy})
|
||||||
|
else()
|
||||||
|
set(capnp_sources ${capnp_sources_lite})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(capnp_headers
|
||||||
|
c++.capnp.h
|
||||||
|
common.h
|
||||||
|
blob.h
|
||||||
|
endian.h
|
||||||
|
layout.h
|
||||||
|
orphan.h
|
||||||
|
list.h
|
||||||
|
any.h
|
||||||
|
message.h
|
||||||
|
capability.h
|
||||||
|
membrane.h
|
||||||
|
dynamic.h
|
||||||
|
schema.h
|
||||||
|
schema.capnp.h
|
||||||
|
schema-lite.h
|
||||||
|
schema-loader.h
|
||||||
|
schema-parser.h
|
||||||
|
pretty-print.h
|
||||||
|
serialize.h
|
||||||
|
serialize-async.h
|
||||||
|
serialize-packed.h
|
||||||
|
serialize-text.h
|
||||||
|
pointer-helpers.h
|
||||||
|
generated-header-support.h
|
||||||
|
raw-schema.h
|
||||||
|
)
|
||||||
|
set(capnp_schemas
|
||||||
|
c++.capnp
|
||||||
|
schema.capnp
|
||||||
|
)
|
||||||
|
add_library(capnp ${capnp_sources})
|
||||||
|
add_library(CapnProto::capnp ALIAS capnp)
|
||||||
|
target_link_libraries(capnp PUBLIC kj)
|
||||||
|
#make sure external consumers don't need to manually set the include dirs
|
||||||
|
target_include_directories(capnp INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
|
||||||
|
$<INSTALL_INTERFACE:include>
|
||||||
|
)
|
||||||
|
# Ensure the library has a version set to match autotools build
|
||||||
|
set_target_properties(capnp PROPERTIES VERSION ${VERSION})
|
||||||
|
install(TARGETS capnp ${INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
install(FILES ${capnp_headers} ${capnp_schemas} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/capnp")
|
||||||
|
|
||||||
|
set(capnp-rpc_sources
|
||||||
|
serialize-async.c++
|
||||||
|
capability.c++
|
||||||
|
membrane.c++
|
||||||
|
dynamic-capability.c++
|
||||||
|
rpc.c++
|
||||||
|
rpc.capnp.c++
|
||||||
|
rpc-twoparty.c++
|
||||||
|
rpc-twoparty.capnp.c++
|
||||||
|
persistent.capnp.c++
|
||||||
|
ez-rpc.c++
|
||||||
|
)
|
||||||
|
set(capnp-rpc_headers
|
||||||
|
rpc-prelude.h
|
||||||
|
rpc.h
|
||||||
|
rpc-twoparty.h
|
||||||
|
rpc.capnp.h
|
||||||
|
rpc-twoparty.capnp.h
|
||||||
|
persistent.capnp.h
|
||||||
|
ez-rpc.h
|
||||||
|
)
|
||||||
|
set(capnp-rpc_schemas
|
||||||
|
rpc.capnp
|
||||||
|
rpc-twoparty.capnp
|
||||||
|
persistent.capnp
|
||||||
|
)
|
||||||
|
if(NOT CAPNP_LITE)
|
||||||
|
add_library(capnp-rpc ${capnp-rpc_sources})
|
||||||
|
add_library(CapnProto::capnp-rpc ALIAS capnp-rpc)
|
||||||
|
target_link_libraries(capnp-rpc PUBLIC capnp kj-async kj)
|
||||||
|
# Ensure the library has a version set to match autotools build
|
||||||
|
set_target_properties(capnp-rpc PROPERTIES VERSION ${VERSION})
|
||||||
|
install(TARGETS capnp-rpc ${INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
install(FILES ${capnp-rpc_headers} ${capnp-rpc_schemas} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/capnp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# capnp-json ========================================================================
|
||||||
|
|
||||||
|
set(capnp-json_sources
|
||||||
|
compat/json.c++
|
||||||
|
compat/json.capnp.c++
|
||||||
|
)
|
||||||
|
set(capnp-json_headers
|
||||||
|
compat/json.h
|
||||||
|
compat/json.capnp.h
|
||||||
|
)
|
||||||
|
set(capnp-json_schemas
|
||||||
|
compat/json.capnp
|
||||||
|
)
|
||||||
|
if(NOT CAPNP_LITE)
|
||||||
|
add_library(capnp-json ${capnp-json_sources})
|
||||||
|
add_library(CapnProto::capnp-json ALIAS capnp-json)
|
||||||
|
target_link_libraries(capnp-json PUBLIC capnp kj-async kj)
|
||||||
|
# Ensure the library has a version set to match autotools build
|
||||||
|
set_target_properties(capnp-json PROPERTIES VERSION ${VERSION})
|
||||||
|
install(TARGETS capnp-json ${INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
install(FILES ${capnp-json_headers} ${capnp-json_schemas} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/capnp/compat")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Tools/Compilers ==============================================================
|
||||||
|
|
||||||
|
set(capnpc_sources
|
||||||
|
compiler/type-id.c++
|
||||||
|
compiler/error-reporter.c++
|
||||||
|
compiler/lexer.capnp.c++
|
||||||
|
compiler/lexer.c++
|
||||||
|
compiler/grammar.capnp.c++
|
||||||
|
compiler/parser.c++
|
||||||
|
compiler/node-translator.c++
|
||||||
|
compiler/compiler.c++
|
||||||
|
schema-parser.c++
|
||||||
|
serialize-text.c++
|
||||||
|
)
|
||||||
|
if(NOT CAPNP_LITE)
|
||||||
|
add_library(capnpc ${capnpc_sources})
|
||||||
|
target_link_libraries(capnpc PUBLIC capnp kj)
|
||||||
|
# Ensure the library has a version set to match autotools build
|
||||||
|
set_target_properties(capnpc PROPERTIES VERSION ${VERSION})
|
||||||
|
install(TARGETS capnpc ${INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
install(FILES ${capnpc_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/capnp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CAPNP_LITE)
|
||||||
|
add_executable(capnp_tool
|
||||||
|
compiler/module-loader.c++
|
||||||
|
compiler/capnp.c++
|
||||||
|
)
|
||||||
|
target_link_libraries(capnp_tool capnpc capnp-json capnp kj)
|
||||||
|
set_target_properties(capnp_tool PROPERTIES OUTPUT_NAME capnp)
|
||||||
|
set_target_properties(capnp_tool PROPERTIES CAPNP_INCLUDE_DIRECTORY
|
||||||
|
$<JOIN:$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>,$<INSTALL_INTERFACE:${CMAKE_INSTALL_BINDIR}/..>>
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(capnpc_cpp
|
||||||
|
compiler/capnpc-c++.c++
|
||||||
|
)
|
||||||
|
target_link_libraries(capnpc_cpp capnp kj)
|
||||||
|
set_target_properties(capnpc_cpp PROPERTIES OUTPUT_NAME capnpc-c++)
|
||||||
|
#Capnp tool needs capnpc_cpp location. But cmake deprecated LOCATION property.
|
||||||
|
#So we use custom property to pass location
|
||||||
|
set_target_properties(capnpc_cpp PROPERTIES CAPNPC_CXX_EXECUTABLE
|
||||||
|
$<TARGET_FILE:capnpc_cpp>
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(capnpc_capnp
|
||||||
|
compiler/capnpc-capnp.c++
|
||||||
|
)
|
||||||
|
target_link_libraries(capnpc_capnp capnp kj)
|
||||||
|
set_target_properties(capnpc_capnp PROPERTIES OUTPUT_NAME capnpc-capnp)
|
||||||
|
|
||||||
|
install(TARGETS capnp_tool capnpc_cpp capnpc_capnp ${INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
|
||||||
|
# Symlink capnpc -> capnp
|
||||||
|
install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink capnp \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/capnpc\")")
|
||||||
|
endif() # NOT CAPNP_LITE
|
||||||
|
|
||||||
|
# Tests ========================================================================
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
set(test_capnp_files
|
||||||
|
test.capnp
|
||||||
|
test-import.capnp
|
||||||
|
test-import2.capnp
|
||||||
|
compat/json-test.capnp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CAPNPC_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/test_capnp")
|
||||||
|
include_directories("${CAPNPC_OUTPUT_DIR}")
|
||||||
|
file(MAKE_DIRECTORY "${CAPNPC_OUTPUT_DIR}")
|
||||||
|
# Tell capnp_generate_cpp to set --src-prefix to our parent directory. This allows us to pass our
|
||||||
|
# .capnp files relative to this directory, but have their canonical name end up as
|
||||||
|
# capnp/test.capnp, capnp/test-import.capnp, etc.
|
||||||
|
get_filename_component(CAPNPC_SRC_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}" DIRECTORY)
|
||||||
|
|
||||||
|
capnp_generate_cpp(test_capnp_cpp_files test_capnp_h_files ${test_capnp_files})
|
||||||
|
|
||||||
|
# TODO(cleanup): capnp-tests and capnp-heavy-tests both depend on the test.capnp output files. In
|
||||||
|
# a parallel Makefile-based build (maybe others?), they can race and cause the custom capnp
|
||||||
|
# command in capnp_generate_cpp() to run twice. To get around this I'm using a custom target to
|
||||||
|
# force CMake to generate race-free Makefiles. Remove this garbage when we move to a
|
||||||
|
# target-based capnp_generate() command, as that will make CMake do the right thing by default.
|
||||||
|
add_custom_target(test_capnp DEPENDS ${test_capnp_cpp_files} ${test_capnp_h_files})
|
||||||
|
|
||||||
|
if(CAPNP_LITE)
|
||||||
|
set(test_libraries capnp kj-test kj)
|
||||||
|
else()
|
||||||
|
set(test_libraries capnp-json capnp-rpc capnp capnpc kj-async kj-test kj)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(capnp-tests
|
||||||
|
common-test.c++
|
||||||
|
blob-test.c++
|
||||||
|
endian-test.c++
|
||||||
|
endian-fallback-test.c++
|
||||||
|
layout-test.c++
|
||||||
|
any-test.c++
|
||||||
|
message-test.c++
|
||||||
|
encoding-test.c++
|
||||||
|
orphan-test.c++
|
||||||
|
serialize-test.c++
|
||||||
|
serialize-packed-test.c++
|
||||||
|
canonicalize-test.c++
|
||||||
|
fuzz-test.c++
|
||||||
|
test-util.c++
|
||||||
|
${test_capnp_cpp_files}
|
||||||
|
${test_capnp_h_files}
|
||||||
|
)
|
||||||
|
target_link_libraries(capnp-tests ${test_libraries})
|
||||||
|
add_dependencies(capnp-tests test_capnp)
|
||||||
|
add_dependencies(check capnp-tests)
|
||||||
|
add_test(NAME capnp-tests-run COMMAND capnp-tests)
|
||||||
|
|
||||||
|
if(NOT CAPNP_LITE)
|
||||||
|
add_executable(capnp-heavy-tests
|
||||||
|
endian-reverse-test.c++
|
||||||
|
capability-test.c++
|
||||||
|
membrane-test.c++
|
||||||
|
schema-test.c++
|
||||||
|
schema-loader-test.c++
|
||||||
|
schema-parser-test.c++
|
||||||
|
dynamic-test.c++
|
||||||
|
stringify-test.c++
|
||||||
|
serialize-async-test.c++
|
||||||
|
serialize-text-test.c++
|
||||||
|
rpc-test.c++
|
||||||
|
rpc-twoparty-test.c++
|
||||||
|
ez-rpc-test.c++
|
||||||
|
compiler/lexer-test.c++
|
||||||
|
compiler/type-id-test.c++
|
||||||
|
test-util.c++
|
||||||
|
compat/json-test.c++
|
||||||
|
${test_capnp_cpp_files}
|
||||||
|
${test_capnp_h_files}
|
||||||
|
)
|
||||||
|
target_link_libraries(capnp-heavy-tests ${test_libraries})
|
||||||
|
if(NOT MSVC)
|
||||||
|
set_target_properties(capnp-heavy-tests
|
||||||
|
PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_dependencies(capnp-heavy-tests test_capnp)
|
||||||
|
add_dependencies(check capnp-heavy-tests)
|
||||||
|
add_test(NAME capnp-heavy-tests-run COMMAND capnp-heavy-tests)
|
||||||
|
|
||||||
|
add_executable(capnp-evolution-tests compiler/evolution-test.c++)
|
||||||
|
target_link_libraries(capnp-evolution-tests capnpc capnp kj)
|
||||||
|
add_dependencies(check capnp-evolution-tests)
|
||||||
|
add_test(NAME capnp-evolution-tests-run COMMAND capnp-evolution-tests)
|
||||||
|
endif() # NOT CAPNP_LITE
|
||||||
|
endif() # BUILD_TESTING
|
|
@ -0,0 +1,143 @@
|
||||||
|
// Copyright (c) 2017 Cloudflare, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "test-util.h"
|
||||||
|
#include <kj/main.h>
|
||||||
|
#include "serialize.h"
|
||||||
|
#include <capnp/test.capnp.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace _ {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class AflTestMain {
|
||||||
|
public:
|
||||||
|
explicit AflTestMain(kj::ProcessContext& context)
|
||||||
|
: context(context) {}
|
||||||
|
|
||||||
|
kj::MainFunc getMain() {
|
||||||
|
return kj::MainBuilder(context, "(unknown version)",
|
||||||
|
"American Fuzzy Lop test case. Pass input on stdin. Expects a binary "
|
||||||
|
"message of type TestAllTypes.")
|
||||||
|
.addOption({"lists"}, KJ_BIND_METHOD(*this, runLists),
|
||||||
|
"Expect a message of type TestLists instead of TestAllTypes.")
|
||||||
|
.addOption({"canonicalize"}, KJ_BIND_METHOD(*this, canonicalize),
|
||||||
|
"Test canonicalization code.")
|
||||||
|
.callAfterParsing(KJ_BIND_METHOD(*this, run))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::MainBuilder::Validity run() {
|
||||||
|
capnp::StreamFdMessageReader reader(STDIN_FILENO);
|
||||||
|
KJ_IF_MAYBE(e, kj::runCatchingExceptions([&]() {
|
||||||
|
checkTestMessage(reader.getRoot<TestAllTypes>());
|
||||||
|
})) {
|
||||||
|
KJ_LOG(ERROR, "threw");
|
||||||
|
}
|
||||||
|
KJ_IF_MAYBE(e, kj::runCatchingExceptions([&]() {
|
||||||
|
checkDynamicTestMessage(reader.getRoot<DynamicStruct>(Schema::from<TestAllTypes>()));
|
||||||
|
})) {
|
||||||
|
KJ_LOG(ERROR, "dynamic threw");
|
||||||
|
}
|
||||||
|
KJ_IF_MAYBE(e, kj::runCatchingExceptions([&]() {
|
||||||
|
kj::str(reader.getRoot<TestAllTypes>());
|
||||||
|
})) {
|
||||||
|
KJ_LOG(ERROR, "str threw");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::MainBuilder::Validity runLists() {
|
||||||
|
capnp::StreamFdMessageReader reader(STDIN_FILENO);
|
||||||
|
KJ_IF_MAYBE(e, kj::runCatchingExceptions([&]() {
|
||||||
|
kj::str(reader.getRoot<test::TestLists>());
|
||||||
|
})) {
|
||||||
|
KJ_LOG(ERROR, "threw");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::MainBuilder::Validity canonicalize() {
|
||||||
|
// (Test case contributed by David Renshaw.)
|
||||||
|
|
||||||
|
kj::Array<capnp::word> canonical;
|
||||||
|
bool equal = false;
|
||||||
|
KJ_IF_MAYBE(e, kj::runCatchingExceptions([&]() {
|
||||||
|
capnp::ReaderOptions options;
|
||||||
|
|
||||||
|
// The default traversal limit of 8 * 1024 * 1024 causes
|
||||||
|
// AFL to think that it has found "hang" bugs.
|
||||||
|
options.traversalLimitInWords = 8 * 1024;
|
||||||
|
|
||||||
|
capnp::StreamFdMessageReader message(0, options); // read from stdin
|
||||||
|
TestAllTypes::Reader myStruct = message.getRoot<TestAllTypes>();
|
||||||
|
canonical = capnp::canonicalize(myStruct);
|
||||||
|
|
||||||
|
kj::ArrayPtr<const capnp::word> segments[1] = {canonical.asPtr()};
|
||||||
|
capnp::SegmentArrayMessageReader reader(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
auto originalAny = message.getRoot<capnp::AnyPointer>();
|
||||||
|
|
||||||
|
// Discard cases where the original message is null.
|
||||||
|
KJ_ASSERT(!originalAny.isNull());
|
||||||
|
|
||||||
|
equal = originalAny == reader.getRoot<capnp::AnyPointer>();
|
||||||
|
})) {
|
||||||
|
// Probably some kind of decoding exception.
|
||||||
|
KJ_LOG(ERROR, "threw");
|
||||||
|
context.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_ASSERT(equal);
|
||||||
|
|
||||||
|
kj::ArrayPtr<const capnp::word> segments[1] = {canonical.asPtr()};
|
||||||
|
capnp::SegmentArrayMessageReader reader(kj::arrayPtr(segments, 1));
|
||||||
|
KJ_ASSERT(reader.isCanonical());
|
||||||
|
|
||||||
|
kj::Array<capnp::word> canonical2;
|
||||||
|
{
|
||||||
|
capnp::ReaderOptions options;
|
||||||
|
options.traversalLimitInWords = 8 * 1024;
|
||||||
|
|
||||||
|
TestAllTypes::Reader myStruct = reader.getRoot<TestAllTypes>();
|
||||||
|
canonical2 = capnp::canonicalize(myStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_ASSERT(canonical.size() == canonical2.size());
|
||||||
|
auto b1 = canonical.asBytes();
|
||||||
|
auto b2 = canonical2.asBytes();
|
||||||
|
for (int idx = 0; idx < b1.size(); ++idx) {
|
||||||
|
KJ_ASSERT(b1[idx] == b2[idx], idx, b1.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::ProcessContext& context;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace _
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
KJ_MAIN(capnp::_::AflTestMain);
|
|
@ -0,0 +1,439 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "any.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include <kj/compat/gtest.h>
|
||||||
|
#include "test-util.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace _ { // private
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(Any, AnyPointer) {
|
||||||
|
MallocMessageBuilder builder;
|
||||||
|
auto root = builder.getRoot<test::TestAnyPointer>();
|
||||||
|
|
||||||
|
initTestMessage(root.getAnyPointerField().initAs<TestAllTypes>());
|
||||||
|
checkTestMessage(root.getAnyPointerField().getAs<TestAllTypes>());
|
||||||
|
checkTestMessage(root.asReader().getAnyPointerField().getAs<TestAllTypes>());
|
||||||
|
|
||||||
|
root.getAnyPointerField().setAs<Text>("foo");
|
||||||
|
EXPECT_EQ("foo", root.getAnyPointerField().getAs<Text>());
|
||||||
|
EXPECT_EQ("foo", root.asReader().getAnyPointerField().getAs<Text>());
|
||||||
|
|
||||||
|
root.getAnyPointerField().setAs<Data>(data("foo"));
|
||||||
|
EXPECT_EQ(data("foo"), root.getAnyPointerField().getAs<Data>());
|
||||||
|
EXPECT_EQ(data("foo"), root.asReader().getAnyPointerField().getAs<Data>());
|
||||||
|
|
||||||
|
{
|
||||||
|
root.getAnyPointerField().setAs<List<uint32_t>>({123, 456, 789});
|
||||||
|
|
||||||
|
{
|
||||||
|
List<uint32_t>::Builder list = root.getAnyPointerField().getAs<List<uint32_t>>();
|
||||||
|
ASSERT_EQ(3u, list.size());
|
||||||
|
EXPECT_EQ(123u, list[0]);
|
||||||
|
EXPECT_EQ(456u, list[1]);
|
||||||
|
EXPECT_EQ(789u, list[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
List<uint32_t>::Reader list = root.asReader().getAnyPointerField().getAs<List<uint32_t>>();
|
||||||
|
ASSERT_EQ(3u, list.size());
|
||||||
|
EXPECT_EQ(123u, list[0]);
|
||||||
|
EXPECT_EQ(456u, list[1]);
|
||||||
|
EXPECT_EQ(789u, list[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
root.getAnyPointerField().setAs<List<Text>>({"foo", "bar"});
|
||||||
|
|
||||||
|
{
|
||||||
|
List<Text>::Builder list = root.getAnyPointerField().getAs<List<Text>>();
|
||||||
|
ASSERT_EQ(2u, list.size());
|
||||||
|
EXPECT_EQ("foo", list[0]);
|
||||||
|
EXPECT_EQ("bar", list[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
List<Text>::Reader list = root.asReader().getAnyPointerField().getAs<List<Text>>();
|
||||||
|
ASSERT_EQ(2u, list.size());
|
||||||
|
EXPECT_EQ("foo", list[0]);
|
||||||
|
EXPECT_EQ("bar", list[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
{
|
||||||
|
List<TestAllTypes>::Builder list = root.getAnyPointerField().initAs<List<TestAllTypes>>(2);
|
||||||
|
ASSERT_EQ(2u, list.size());
|
||||||
|
initTestMessage(list[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
List<TestAllTypes>::Builder list = root.getAnyPointerField().getAs<List<TestAllTypes>>();
|
||||||
|
ASSERT_EQ(2u, list.size());
|
||||||
|
checkTestMessage(list[0]);
|
||||||
|
checkTestMessageAllZero(list[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
List<TestAllTypes>::Reader list =
|
||||||
|
root.asReader().getAnyPointerField().getAs<List<TestAllTypes>>();
|
||||||
|
ASSERT_EQ(2u, list.size());
|
||||||
|
checkTestMessage(list[0]);
|
||||||
|
checkTestMessageAllZero(list[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Any, AnyStruct) {
|
||||||
|
MallocMessageBuilder builder;
|
||||||
|
auto root = builder.getRoot<test::TestAnyPointer>();
|
||||||
|
|
||||||
|
initTestMessage(root.getAnyPointerField().initAs<TestAllTypes>());
|
||||||
|
checkTestMessage(root.getAnyPointerField().getAs<TestAllTypes>());
|
||||||
|
checkTestMessage(root.asReader().getAnyPointerField().getAs<TestAllTypes>());
|
||||||
|
|
||||||
|
auto allTypes = root.getAnyPointerField().getAs<AnyStruct>().as<TestAllTypes>();
|
||||||
|
auto allTypesReader = root.getAnyPointerField().getAs<AnyStruct>().asReader().as<TestAllTypes>();
|
||||||
|
allTypes.setInt32Field(100);
|
||||||
|
EXPECT_EQ(100, allTypes.getInt32Field());
|
||||||
|
EXPECT_EQ(100, allTypesReader.getInt32Field());
|
||||||
|
|
||||||
|
EXPECT_EQ(48, root.getAnyPointerField().getAs<AnyStruct>().getDataSection().size());
|
||||||
|
EXPECT_EQ(20, root.getAnyPointerField().getAs<AnyStruct>().getPointerSection().size());
|
||||||
|
|
||||||
|
EXPECT_EQ(48, root.getAnyPointerField().asReader().getAs<AnyStruct>().getDataSection().size());
|
||||||
|
EXPECT_EQ(20, root.getAnyPointerField().asReader().getAs<AnyStruct>().getPointerSection().size());
|
||||||
|
|
||||||
|
auto b = toAny(root.getAnyPointerField().getAs<TestAllTypes>());
|
||||||
|
EXPECT_EQ(48, b.getDataSection().size());
|
||||||
|
EXPECT_EQ(20, b.getPointerSection().size());
|
||||||
|
|
||||||
|
#if !_MSC_VER // TODO(msvc): ICE on the necessary constructor; see any.h.
|
||||||
|
b = root.getAnyPointerField().getAs<TestAllTypes>();
|
||||||
|
EXPECT_EQ(48, b.getDataSection().size());
|
||||||
|
EXPECT_EQ(20, b.getPointerSection().size());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto r = toAny(root.getAnyPointerField().getAs<TestAllTypes>().asReader());
|
||||||
|
EXPECT_EQ(48, r.getDataSection().size());
|
||||||
|
EXPECT_EQ(20, r.getPointerSection().size());
|
||||||
|
|
||||||
|
r = toAny(root.getAnyPointerField().getAs<TestAllTypes>()).asReader();
|
||||||
|
EXPECT_EQ(48, r.getDataSection().size());
|
||||||
|
EXPECT_EQ(20, r.getPointerSection().size());
|
||||||
|
|
||||||
|
#if !_MSC_VER // TODO(msvc): ICE on the necessary constructor; see any.h.
|
||||||
|
r = root.getAnyPointerField().getAs<TestAllTypes>().asReader();
|
||||||
|
EXPECT_EQ(48, r.getDataSection().size());
|
||||||
|
EXPECT_EQ(20, r.getPointerSection().size());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder b2;
|
||||||
|
auto root2 = b2.getRoot<test::TestAnyPointer>();
|
||||||
|
auto sb = root2.getAnyPointerField().initAsAnyStruct(
|
||||||
|
r.getDataSection().size() / 8, r.getPointerSection().size());
|
||||||
|
|
||||||
|
EXPECT_EQ(48, sb.getDataSection().size());
|
||||||
|
EXPECT_EQ(20, sb.getPointerSection().size());
|
||||||
|
|
||||||
|
// TODO: is there a higher-level API for this?
|
||||||
|
memcpy(sb.getDataSection().begin(), r.getDataSection().begin(), r.getDataSection().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto ptrs = r.getPointerSection();
|
||||||
|
EXPECT_EQ("foo", ptrs[0].getAs<Text>());
|
||||||
|
EXPECT_EQ("bar", kj::heapString(ptrs[1].getAs<Data>().asChars()));
|
||||||
|
EXPECT_EQ("xyzzy", ptrs[15].getAs<List<Text>>()[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto ptrs = b.getPointerSection();
|
||||||
|
EXPECT_EQ("foo", ptrs[0].getAs<Text>());
|
||||||
|
EXPECT_EQ("bar", kj::heapString(ptrs[1].getAs<Data>().asChars()));
|
||||||
|
EXPECT_EQ("xyzzy", ptrs[15].getAs<List<Text>>()[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Any, AnyList) {
|
||||||
|
MallocMessageBuilder builder;
|
||||||
|
auto root = builder.getRoot<test::TestAnyPointer>();
|
||||||
|
List<TestAllTypes>::Builder b = root.getAnyPointerField().initAs<List<TestAllTypes>>(2);
|
||||||
|
initTestMessage(b[0]);
|
||||||
|
|
||||||
|
auto ptr = root.getAnyPointerField().getAs<AnyList>();
|
||||||
|
|
||||||
|
EXPECT_EQ(2, ptr.size());
|
||||||
|
EXPECT_EQ(48, ptr.as<List<AnyStruct>>()[0].getDataSection().size());
|
||||||
|
EXPECT_EQ(20, ptr.as<List<AnyStruct>>()[0].getPointerSection().size());
|
||||||
|
|
||||||
|
auto readPtr = root.getAnyPointerField().asReader().getAs<AnyList>();
|
||||||
|
|
||||||
|
EXPECT_EQ(2, readPtr.size());
|
||||||
|
EXPECT_EQ(48, readPtr.as<List<AnyStruct>>()[0].getDataSection().size());
|
||||||
|
EXPECT_EQ(20, readPtr.as<List<AnyStruct>>()[0].getPointerSection().size());
|
||||||
|
|
||||||
|
auto alb = toAny(root.getAnyPointerField().getAs<List<TestAllTypes>>());
|
||||||
|
EXPECT_EQ(2, alb.size());
|
||||||
|
EXPECT_EQ(48, alb.as<List<AnyStruct>>()[0].getDataSection().size());
|
||||||
|
EXPECT_EQ(20, alb.as<List<AnyStruct>>()[0].getPointerSection().size());
|
||||||
|
|
||||||
|
#if !_MSC_VER // TODO(msvc): ICE on the necessary constructor; see any.h.
|
||||||
|
alb = root.getAnyPointerField().getAs<List<TestAllTypes>>();
|
||||||
|
EXPECT_EQ(2, alb.size());
|
||||||
|
EXPECT_EQ(48, alb.as<List<AnyStruct>>()[0].getDataSection().size());
|
||||||
|
EXPECT_EQ(20, alb.as<List<AnyStruct>>()[0].getPointerSection().size());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto alr = toAny(root.getAnyPointerField().getAs<List<TestAllTypes>>().asReader());
|
||||||
|
EXPECT_EQ(2, alr.size());
|
||||||
|
EXPECT_EQ(48, alr.as<List<AnyStruct>>()[0].getDataSection().size());
|
||||||
|
EXPECT_EQ(20, alr.as<List<AnyStruct>>()[0].getPointerSection().size());
|
||||||
|
|
||||||
|
alr = toAny(root.getAnyPointerField().getAs<List<TestAllTypes>>()).asReader();
|
||||||
|
EXPECT_EQ(2, alr.size());
|
||||||
|
EXPECT_EQ(48, alr.as<List<AnyStruct>>()[0].getDataSection().size());
|
||||||
|
EXPECT_EQ(20, alr.as<List<AnyStruct>>()[0].getPointerSection().size());
|
||||||
|
|
||||||
|
#if !_MSC_VER // TODO(msvc): ICE on the necessary constructor; see any.h.
|
||||||
|
alr = root.getAnyPointerField().getAs<List<TestAllTypes>>().asReader();
|
||||||
|
EXPECT_EQ(2, alr.size());
|
||||||
|
EXPECT_EQ(48, alr.as<List<AnyStruct>>()[0].getDataSection().size());
|
||||||
|
EXPECT_EQ(20, alr.as<List<AnyStruct>>()[0].getPointerSection().size());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Any, AnyStructListCapInSchema) {
|
||||||
|
MallocMessageBuilder builder;
|
||||||
|
auto root = builder.getRoot<test::TestAnyOthers>();
|
||||||
|
|
||||||
|
{
|
||||||
|
initTestMessage(root.initAnyStructFieldAs<TestAllTypes>());
|
||||||
|
AnyStruct::Builder anyStruct = root.getAnyStructField();
|
||||||
|
checkTestMessage(anyStruct.as<TestAllTypes>());
|
||||||
|
checkTestMessage(anyStruct.asReader().as<TestAllTypes>());
|
||||||
|
|
||||||
|
EXPECT_TRUE(root.hasAnyStructField());
|
||||||
|
auto orphan = root.disownAnyStructField();
|
||||||
|
checkTestMessage(orphan.getReader().as<TestAllTypes>());
|
||||||
|
EXPECT_FALSE(root.hasAnyStructField());
|
||||||
|
|
||||||
|
root.adoptAnyStructField(kj::mv(orphan));
|
||||||
|
EXPECT_TRUE(root.hasAnyStructField());
|
||||||
|
checkTestMessage(root.getAnyStructField().as<TestAllTypes>());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
List<int>::Builder list = root.initAnyListFieldAs<List<int>>(3);
|
||||||
|
list.set(0, 123);
|
||||||
|
list.set(1, 456);
|
||||||
|
list.set(2, 789);
|
||||||
|
|
||||||
|
AnyList::Builder anyList = root.getAnyListField();
|
||||||
|
checkList(anyList.as<List<int>>(), {123, 456, 789});
|
||||||
|
|
||||||
|
EXPECT_TRUE(root.hasAnyListField());
|
||||||
|
auto orphan = root.disownAnyListField();
|
||||||
|
checkList(orphan.getReader().as<List<int>>(), {123, 456, 789});
|
||||||
|
EXPECT_FALSE(root.hasAnyListField());
|
||||||
|
|
||||||
|
root.adoptAnyListField(kj::mv(orphan));
|
||||||
|
EXPECT_TRUE(root.hasAnyListField());
|
||||||
|
checkList(root.getAnyListField().as<List<int>>(), {123, 456, 789});
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
// This portion of the test relies on a Client, not present in lite-mode.
|
||||||
|
{
|
||||||
|
kj::EventLoop loop;
|
||||||
|
kj::WaitScope waitScope(loop);
|
||||||
|
int callCount = 0;
|
||||||
|
root.setCapabilityField(kj::heap<TestInterfaceImpl>(callCount));
|
||||||
|
Capability::Client client = root.getCapabilityField();
|
||||||
|
auto req = client.castAs<test::TestInterface>().fooRequest();
|
||||||
|
req.setI(123);
|
||||||
|
req.setJ(true);
|
||||||
|
req.send().wait(waitScope);
|
||||||
|
EXPECT_EQ(1, callCount);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("Builder::isStruct() does not corrupt segment pointer") {
|
||||||
|
MallocMessageBuilder builder(1); // small first segment
|
||||||
|
auto root = builder.getRoot<AnyPointer>();
|
||||||
|
|
||||||
|
// Do a lot of allocations so that there is likely a segment with a decent
|
||||||
|
// amount of free space.
|
||||||
|
initTestMessage(root.initAs<test::TestAllTypes>());
|
||||||
|
|
||||||
|
// This will probably get allocated in a segment that still has room for the
|
||||||
|
// Data allocation below.
|
||||||
|
root.initAs<test::TestAllTypes>();
|
||||||
|
|
||||||
|
// At one point, this caused root.builder.segment to point to the segment
|
||||||
|
// where the struct is allocated, rather than segment where the root pointer
|
||||||
|
// lives, i.e. segment zero.
|
||||||
|
EXPECT_TRUE(root.isStruct());
|
||||||
|
|
||||||
|
// If root.builder.segment points to the wrong segment and that segment has free
|
||||||
|
// space, then this triggers a DREQUIRE failure in WirePointer::setKindAndTarget().
|
||||||
|
root.initAs<Data>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Any, Equals) {
|
||||||
|
MallocMessageBuilder builderA;
|
||||||
|
auto rootA = builderA.getRoot<test::TestAllTypes>();
|
||||||
|
auto anyA = builderA.getRoot<AnyPointer>();
|
||||||
|
initTestMessage(rootA);
|
||||||
|
|
||||||
|
MallocMessageBuilder builderB;
|
||||||
|
auto rootB = builderB.getRoot<test::TestAllTypes>();
|
||||||
|
auto anyB = builderB.getRoot<AnyPointer>();
|
||||||
|
initTestMessage(rootB);
|
||||||
|
|
||||||
|
EXPECT_EQ(Equality::EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootA.setBoolField(false);
|
||||||
|
EXPECT_EQ(Equality::NOT_EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootB.setBoolField(false);
|
||||||
|
EXPECT_EQ(Equality::EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootB.setEnumField(test::TestEnum::GARPLY);
|
||||||
|
EXPECT_EQ(Equality::NOT_EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootA.setEnumField(test::TestEnum::GARPLY);
|
||||||
|
EXPECT_EQ(Equality::EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootA.getStructField().setTextField("buzz");
|
||||||
|
EXPECT_EQ(Equality::NOT_EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootB.getStructField().setTextField("buzz");
|
||||||
|
EXPECT_EQ(Equality::EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootA.initVoidList(3);
|
||||||
|
EXPECT_EQ(Equality::NOT_EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootB.initVoidList(3);
|
||||||
|
EXPECT_EQ(Equality::EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootA.getBoolList().set(2, true);
|
||||||
|
EXPECT_EQ(Equality::NOT_EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootB.getBoolList().set(2, true);
|
||||||
|
EXPECT_EQ(Equality::EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootB.getStructList()[1].setTextField("my NEW structlist 2");
|
||||||
|
EXPECT_EQ(Equality::NOT_EQUAL, anyA.equals(anyB));
|
||||||
|
|
||||||
|
rootA.getStructList()[1].setTextField("my NEW structlist 2");
|
||||||
|
EXPECT_EQ(Equality::EQUAL, anyA.equals(anyB));
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("Bit list with nonzero pad bits") {
|
||||||
|
AlignedData<2> segment1 = {{
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, // eleven bit-sized elements
|
||||||
|
0xee, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // twelfth bit is set!
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments1[1] = {
|
||||||
|
kj::arrayPtr(segment1.words, 2)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader message1(kj::arrayPtr(segments1, 1));
|
||||||
|
|
||||||
|
AlignedData<2> segment2 = {{
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, // eleven bit-sized elements
|
||||||
|
0xee, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // twelfth bit is not set
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments2[1] = {
|
||||||
|
kj::arrayPtr(segment2.words, 2)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader message2(kj::arrayPtr(segments2, 1));
|
||||||
|
|
||||||
|
// Should be equal, despite nonzero padding.
|
||||||
|
KJ_ASSERT(message1.getRoot<AnyList>() == message2.getRoot<AnyList>());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("Pointer list unequal to struct list") {
|
||||||
|
AlignedData<1> segment1 = {{
|
||||||
|
// list with zero pointer-sized elements
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments1[1] = {
|
||||||
|
kj::arrayPtr(segment1.words, 1)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader message1(kj::arrayPtr(segments1, 1));
|
||||||
|
|
||||||
|
AlignedData<2> segment2 = {{
|
||||||
|
// struct list of length zero
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// struct list tag, zero elements
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments2[1] = {
|
||||||
|
kj::arrayPtr(segment2.words, 2)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader message2(kj::arrayPtr(segments2, 1));
|
||||||
|
|
||||||
|
EXPECT_EQ(Equality::NOT_EQUAL, message1.getRoot<AnyList>().equals(message2.getRoot<AnyList>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("Truncating non-null pointer fields does not preserve equality") {
|
||||||
|
AlignedData<3> segment1 = {{
|
||||||
|
// list with one data word and one pointer field
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
|
||||||
|
|
||||||
|
// data word
|
||||||
|
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
|
||||||
|
|
||||||
|
// non-null pointer to zero-sized struct
|
||||||
|
0xfc, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments1[1] = {
|
||||||
|
kj::arrayPtr(segment1.words, 3)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader message1(kj::arrayPtr(segments1, 1));
|
||||||
|
|
||||||
|
AlignedData<2> segment2 = {{
|
||||||
|
// list with one data word and zero pointers
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// data word
|
||||||
|
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments2[1] = {
|
||||||
|
kj::arrayPtr(segment2.words, 2)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader message2(kj::arrayPtr(segments2, 1));
|
||||||
|
|
||||||
|
EXPECT_EQ(Equality::NOT_EQUAL,
|
||||||
|
message1.getRoot<AnyPointer>().equals(message2.getRoot<AnyPointer>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace _ (private)
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,269 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "any.h"
|
||||||
|
|
||||||
|
#include <kj/debug.h>
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
#include "capability.h"
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
|
||||||
|
kj::Own<ClientHook> PipelineHook::getPipelinedCap(kj::Array<PipelineOp>&& ops) {
|
||||||
|
return getPipelinedCap(ops.asPtr());
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> AnyPointer::Reader::getPipelinedCap(
|
||||||
|
kj::ArrayPtr<const PipelineOp> ops) const {
|
||||||
|
_::PointerReader pointer = reader;
|
||||||
|
|
||||||
|
for (auto& op: ops) {
|
||||||
|
switch (op.type) {
|
||||||
|
case PipelineOp::Type::NOOP:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineOp::Type::GET_POINTER_FIELD:
|
||||||
|
pointer = pointer.getStruct(nullptr).getPointerField(bounded(op.pointerIndex) * POINTERS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointer.getCapability();
|
||||||
|
}
|
||||||
|
|
||||||
|
AnyPointer::Pipeline AnyPointer::Pipeline::noop() {
|
||||||
|
auto newOps = kj::heapArray<PipelineOp>(ops.size());
|
||||||
|
for (auto i: kj::indices(ops)) {
|
||||||
|
newOps[i] = ops[i];
|
||||||
|
}
|
||||||
|
return Pipeline(hook->addRef(), kj::mv(newOps));
|
||||||
|
}
|
||||||
|
|
||||||
|
AnyPointer::Pipeline AnyPointer::Pipeline::getPointerField(uint16_t pointerIndex) {
|
||||||
|
auto newOps = kj::heapArray<PipelineOp>(ops.size() + 1);
|
||||||
|
for (auto i: kj::indices(ops)) {
|
||||||
|
newOps[i] = ops[i];
|
||||||
|
}
|
||||||
|
auto& newOp = newOps[ops.size()];
|
||||||
|
newOp.type = PipelineOp::GET_POINTER_FIELD;
|
||||||
|
newOp.pointerIndex = pointerIndex;
|
||||||
|
|
||||||
|
return Pipeline(hook->addRef(), kj::mv(newOps));
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> AnyPointer::Pipeline::asCap() {
|
||||||
|
return hook->getPipelinedCap(ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
Equality AnyStruct::Reader::equals(AnyStruct::Reader right) const {
|
||||||
|
auto dataL = getDataSection();
|
||||||
|
size_t dataSizeL = dataL.size();
|
||||||
|
while(dataSizeL > 0 && dataL[dataSizeL - 1] == 0) {
|
||||||
|
-- dataSizeL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dataR = right.getDataSection();
|
||||||
|
size_t dataSizeR = dataR.size();
|
||||||
|
while(dataSizeR > 0 && dataR[dataSizeR - 1] == 0) {
|
||||||
|
-- dataSizeR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dataSizeL != dataSizeR) {
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(0 != memcmp(dataL.begin(), dataR.begin(), dataSizeL)) {
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ptrsL = getPointerSection();
|
||||||
|
size_t ptrsSizeL = ptrsL.size();
|
||||||
|
while (ptrsSizeL > 0 && ptrsL[ptrsSizeL - 1].isNull()) {
|
||||||
|
-- ptrsSizeL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ptrsR = right.getPointerSection();
|
||||||
|
size_t ptrsSizeR = ptrsR.size();
|
||||||
|
while (ptrsSizeR > 0 && ptrsR[ptrsSizeR - 1].isNull()) {
|
||||||
|
-- ptrsSizeR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ptrsSizeL != ptrsSizeR) {
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
auto eqResult = Equality::EQUAL;
|
||||||
|
for (; i < ptrsSizeL; i++) {
|
||||||
|
auto l = ptrsL[i];
|
||||||
|
auto r = ptrsR[i];
|
||||||
|
switch(l.equals(r)) {
|
||||||
|
case Equality::EQUAL:
|
||||||
|
break;
|
||||||
|
case Equality::NOT_EQUAL:
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
case Equality::UNKNOWN_CONTAINS_CAPS:
|
||||||
|
eqResult = Equality::UNKNOWN_CONTAINS_CAPS;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eqResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringPtr KJ_STRINGIFY(Equality res) {
|
||||||
|
switch(res) {
|
||||||
|
case Equality::NOT_EQUAL:
|
||||||
|
return "NOT_EQUAL";
|
||||||
|
case Equality::EQUAL:
|
||||||
|
return "EQUAL";
|
||||||
|
case Equality::UNKNOWN_CONTAINS_CAPS:
|
||||||
|
return "UNKNOWN_CONTAINS_CAPS";
|
||||||
|
}
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Equality AnyList::Reader::equals(AnyList::Reader right) const {
|
||||||
|
if(size() != right.size()) {
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getElementSize() != right.getElementSize()) {
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto eqResult = Equality::EQUAL;
|
||||||
|
switch(getElementSize()) {
|
||||||
|
case ElementSize::VOID:
|
||||||
|
case ElementSize::BIT:
|
||||||
|
case ElementSize::BYTE:
|
||||||
|
case ElementSize::TWO_BYTES:
|
||||||
|
case ElementSize::FOUR_BYTES:
|
||||||
|
case ElementSize::EIGHT_BYTES: {
|
||||||
|
size_t cmpSize = getRawBytes().size();
|
||||||
|
|
||||||
|
if (getElementSize() == ElementSize::BIT && size() % 8 != 0) {
|
||||||
|
// The list does not end on a byte boundary. We need special handling for the final
|
||||||
|
// byte because we only care about the bits that are actually elements of the list.
|
||||||
|
|
||||||
|
uint8_t mask = (1 << (size() % 8)) - 1; // lowest size() bits set
|
||||||
|
if ((getRawBytes()[cmpSize - 1] & mask) != (right.getRawBytes()[cmpSize - 1] & mask)) {
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
}
|
||||||
|
cmpSize -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(getRawBytes().begin(), right.getRawBytes().begin(), cmpSize) == 0) {
|
||||||
|
return Equality::EQUAL;
|
||||||
|
} else {
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ElementSize::POINTER:
|
||||||
|
case ElementSize::INLINE_COMPOSITE: {
|
||||||
|
auto llist = as<List<AnyStruct>>();
|
||||||
|
auto rlist = right.as<List<AnyStruct>>();
|
||||||
|
for(size_t i = 0; i < size(); i++) {
|
||||||
|
switch(llist[i].equals(rlist[i])) {
|
||||||
|
case Equality::EQUAL:
|
||||||
|
break;
|
||||||
|
case Equality::NOT_EQUAL:
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
case Equality::UNKNOWN_CONTAINS_CAPS:
|
||||||
|
eqResult = Equality::UNKNOWN_CONTAINS_CAPS;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eqResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Equality AnyPointer::Reader::equals(AnyPointer::Reader right) const {
|
||||||
|
if(getPointerType() != right.getPointerType()) {
|
||||||
|
return Equality::NOT_EQUAL;
|
||||||
|
}
|
||||||
|
switch(getPointerType()) {
|
||||||
|
case PointerType::NULL_:
|
||||||
|
return Equality::EQUAL;
|
||||||
|
case PointerType::STRUCT:
|
||||||
|
return getAs<AnyStruct>().equals(right.getAs<AnyStruct>());
|
||||||
|
case PointerType::LIST:
|
||||||
|
return getAs<AnyList>().equals(right.getAs<AnyList>());
|
||||||
|
case PointerType::CAPABILITY:
|
||||||
|
return Equality::UNKNOWN_CONTAINS_CAPS;
|
||||||
|
}
|
||||||
|
// There aren't currently any other types of pointers
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnyPointer::Reader::operator==(AnyPointer::Reader right) const {
|
||||||
|
switch(equals(right)) {
|
||||||
|
case Equality::EQUAL:
|
||||||
|
return true;
|
||||||
|
case Equality::NOT_EQUAL:
|
||||||
|
return false;
|
||||||
|
case Equality::UNKNOWN_CONTAINS_CAPS:
|
||||||
|
KJ_FAIL_REQUIRE(
|
||||||
|
"operator== cannot determine equality of capabilities; use equals() instead if you need to handle this case");
|
||||||
|
}
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnyStruct::Reader::operator==(AnyStruct::Reader right) const {
|
||||||
|
switch(equals(right)) {
|
||||||
|
case Equality::EQUAL:
|
||||||
|
return true;
|
||||||
|
case Equality::NOT_EQUAL:
|
||||||
|
return false;
|
||||||
|
case Equality::UNKNOWN_CONTAINS_CAPS:
|
||||||
|
KJ_FAIL_REQUIRE(
|
||||||
|
"operator== cannot determine equality of capabilities; use equals() instead if you need to handle this case");
|
||||||
|
}
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnyList::Reader::operator==(AnyList::Reader right) const {
|
||||||
|
switch(equals(right)) {
|
||||||
|
case Equality::EQUAL:
|
||||||
|
return true;
|
||||||
|
case Equality::NOT_EQUAL:
|
||||||
|
return false;
|
||||||
|
case Equality::UNKNOWN_CONTAINS_CAPS:
|
||||||
|
KJ_FAIL_REQUIRE(
|
||||||
|
"operator== cannot determine equality of capabilities; use equals() instead if you need to handle this case");
|
||||||
|
}
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace capnp
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,338 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#define CAPNP_PRIVATE
|
||||||
|
#include "arena.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <kj/refcount.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
#include "capability.h"
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace _ { // private
|
||||||
|
|
||||||
|
Arena::~Arena() noexcept(false) {}
|
||||||
|
|
||||||
|
void ReadLimiter::unread(WordCount64 amount) {
|
||||||
|
// Be careful not to overflow here. Since ReadLimiter has no thread-safety, it's possible that
|
||||||
|
// the limit value was not updated correctly for one or more reads, and therefore unread() could
|
||||||
|
// overflow it even if it is only unreading bytes that were actually read.
|
||||||
|
uint64_t oldValue = limit;
|
||||||
|
uint64_t newValue = oldValue + unbound(amount / WORDS);
|
||||||
|
if (newValue > oldValue) {
|
||||||
|
limit = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SegmentReader::abortCheckObjectFault() {
|
||||||
|
KJ_LOG(FATAL, "checkObject()'s parameter is not in-range; this would segfault in opt mode",
|
||||||
|
"this is a serious bug in Cap'n Proto; please notify security@sandstorm.io");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SegmentBuilder::throwNotWritable() {
|
||||||
|
KJ_FAIL_REQUIRE(
|
||||||
|
"Tried to form a Builder to an external data segment referenced by the MessageBuilder. "
|
||||||
|
"When you use Orphanage::reference*(), you are not allowed to obtain Builders to the "
|
||||||
|
"referenced data, only Readers, because that data is const.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
static SegmentWordCount verifySegmentSize(size_t size) {
|
||||||
|
auto gsize = bounded(size) * WORDS;
|
||||||
|
return assertMaxBits<SEGMENT_WORD_COUNT_BITS>(gsize, [&]() {
|
||||||
|
KJ_FAIL_REQUIRE("segment is too large", size);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ReaderArena::ReaderArena(MessageReader* message, const word* firstSegment,
|
||||||
|
SegmentWordCount firstSegmentSize)
|
||||||
|
: message(message),
|
||||||
|
readLimiter(bounded(message->getOptions().traversalLimitInWords) * WORDS),
|
||||||
|
segment0(this, SegmentId(0), firstSegment, firstSegmentSize, &readLimiter) {}
|
||||||
|
|
||||||
|
inline ReaderArena::ReaderArena(MessageReader* message, kj::ArrayPtr<const word> firstSegment)
|
||||||
|
: ReaderArena(message, firstSegment.begin(), verifySegmentSize(firstSegment.size())) {}
|
||||||
|
|
||||||
|
ReaderArena::ReaderArena(MessageReader* message)
|
||||||
|
: ReaderArena(message, message->getSegment(0)) {}
|
||||||
|
|
||||||
|
ReaderArena::~ReaderArena() noexcept(false) {}
|
||||||
|
|
||||||
|
SegmentReader* ReaderArena::tryGetSegment(SegmentId id) {
|
||||||
|
if (id == SegmentId(0)) {
|
||||||
|
if (segment0.getArray() == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return &segment0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lock = moreSegments.lockExclusive();
|
||||||
|
|
||||||
|
SegmentMap* segments = nullptr;
|
||||||
|
KJ_IF_MAYBE(s, *lock) {
|
||||||
|
KJ_IF_MAYBE(segment, s->find(id.value)) {
|
||||||
|
return *segment;
|
||||||
|
}
|
||||||
|
segments = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::ArrayPtr<const word> newSegment = message->getSegment(id.value);
|
||||||
|
if (newSegment == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SegmentWordCount newSegmentSize = verifySegmentSize(newSegment.size());
|
||||||
|
|
||||||
|
if (*lock == nullptr) {
|
||||||
|
// OK, the segment exists, so allocate the map.
|
||||||
|
segments = &lock->emplace();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto segment = kj::heap<SegmentReader>(
|
||||||
|
this, id, newSegment.begin(), newSegmentSize, &readLimiter);
|
||||||
|
SegmentReader* result = segment;
|
||||||
|
segments->insert(id.value, kj::mv(segment));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderArena::reportReadLimitReached() {
|
||||||
|
KJ_FAIL_REQUIRE("Exceeded message traversal limit. See capnp::ReaderOptions.") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
BuilderArena::BuilderArena(MessageBuilder* message)
|
||||||
|
: message(message), segment0(nullptr, SegmentId(0), nullptr, nullptr) {}
|
||||||
|
|
||||||
|
BuilderArena::BuilderArena(MessageBuilder* message,
|
||||||
|
kj::ArrayPtr<MessageBuilder::SegmentInit> segments)
|
||||||
|
: message(message),
|
||||||
|
segment0(this, SegmentId(0), segments[0].space.begin(),
|
||||||
|
verifySegmentSize(segments[0].space.size()),
|
||||||
|
&this->dummyLimiter, verifySegmentSize(segments[0].wordsUsed)) {
|
||||||
|
if (segments.size() > 1) {
|
||||||
|
kj::Vector<kj::Own<SegmentBuilder>> builders(segments.size() - 1);
|
||||||
|
|
||||||
|
uint i = 1;
|
||||||
|
for (auto& segment: segments.slice(1, segments.size())) {
|
||||||
|
builders.add(kj::heap<SegmentBuilder>(
|
||||||
|
this, SegmentId(i++), segment.space.begin(), verifySegmentSize(segment.space.size()),
|
||||||
|
&this->dummyLimiter, verifySegmentSize(segment.wordsUsed)));
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Vector<kj::ArrayPtr<const word>> forOutput;
|
||||||
|
forOutput.resize(segments.size());
|
||||||
|
|
||||||
|
segmentWithSpace = builders.back();
|
||||||
|
|
||||||
|
this->moreSegments = kj::heap<MultiSegmentState>(
|
||||||
|
MultiSegmentState { kj::mv(builders), kj::mv(forOutput) });
|
||||||
|
|
||||||
|
} else {
|
||||||
|
segmentWithSpace = &segment0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuilderArena::~BuilderArena() noexcept(false) {}
|
||||||
|
|
||||||
|
SegmentBuilder* BuilderArena::getSegment(SegmentId id) {
|
||||||
|
// This method is allowed to fail if the segment ID is not valid.
|
||||||
|
if (id == SegmentId(0)) {
|
||||||
|
return &segment0;
|
||||||
|
} else {
|
||||||
|
KJ_IF_MAYBE(s, moreSegments) {
|
||||||
|
KJ_REQUIRE(id.value - 1 < s->get()->builders.size(), "invalid segment id", id.value);
|
||||||
|
return const_cast<SegmentBuilder*>(s->get()->builders[id.value - 1].get());
|
||||||
|
} else {
|
||||||
|
KJ_FAIL_REQUIRE("invalid segment id", id.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuilderArena::AllocateResult BuilderArena::allocate(SegmentWordCount amount) {
|
||||||
|
if (segment0.getArena() == nullptr) {
|
||||||
|
// We're allocating the first segment.
|
||||||
|
kj::ArrayPtr<word> ptr = message->allocateSegment(unbound(amount / WORDS));
|
||||||
|
auto actualSize = verifySegmentSize(ptr.size());
|
||||||
|
|
||||||
|
// Re-allocate segment0 in-place. This is a bit of a hack, but we have not returned any
|
||||||
|
// pointers to this segment yet, so it should be fine.
|
||||||
|
kj::dtor(segment0);
|
||||||
|
kj::ctor(segment0, this, SegmentId(0), ptr.begin(), actualSize, &this->dummyLimiter);
|
||||||
|
|
||||||
|
segmentWithSpace = &segment0;
|
||||||
|
return AllocateResult { &segment0, segment0.allocate(amount) };
|
||||||
|
} else {
|
||||||
|
if (segmentWithSpace != nullptr) {
|
||||||
|
// Check if there is space in an existing segment.
|
||||||
|
// TODO(perf): Check for available space in more than just the last segment. We don't
|
||||||
|
// want this to be O(n), though, so we'll need to maintain some sort of table. Complicating
|
||||||
|
// matters, we want SegmentBuilders::allocate() to be fast, so we can't update any such
|
||||||
|
// table when allocation actually happens. Instead, we could have a priority queue based
|
||||||
|
// on the last-known available size, and then re-check the size when we pop segments off it
|
||||||
|
// and shove them to the back of the queue if they have become too small.
|
||||||
|
word* attempt = segmentWithSpace->allocate(amount);
|
||||||
|
if (attempt != nullptr) {
|
||||||
|
return AllocateResult { segmentWithSpace, attempt };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to allocate a new segment.
|
||||||
|
SegmentBuilder* result = addSegmentInternal(message->allocateSegment(unbound(amount / WORDS)));
|
||||||
|
|
||||||
|
// Check this new segment first the next time we need to allocate.
|
||||||
|
segmentWithSpace = result;
|
||||||
|
|
||||||
|
// Allocating from the new segment is guaranteed to succeed since we made it big enough.
|
||||||
|
return AllocateResult { result, result->allocate(amount) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SegmentBuilder* BuilderArena::addExternalSegment(kj::ArrayPtr<const word> content) {
|
||||||
|
return addSegmentInternal(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
SegmentBuilder* BuilderArena::addSegmentInternal(kj::ArrayPtr<T> content) {
|
||||||
|
// This check should never fail in practice, since you can't get an Orphanage without allocating
|
||||||
|
// the root segment.
|
||||||
|
KJ_REQUIRE(segment0.getArena() != nullptr,
|
||||||
|
"Can't allocate external segments before allocating the root segment.");
|
||||||
|
|
||||||
|
auto contentSize = verifySegmentSize(content.size());
|
||||||
|
|
||||||
|
MultiSegmentState* segmentState;
|
||||||
|
KJ_IF_MAYBE(s, moreSegments) {
|
||||||
|
segmentState = *s;
|
||||||
|
} else {
|
||||||
|
auto newSegmentState = kj::heap<MultiSegmentState>();
|
||||||
|
segmentState = newSegmentState;
|
||||||
|
moreSegments = kj::mv(newSegmentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<SegmentBuilder> newBuilder = kj::heap<SegmentBuilder>(
|
||||||
|
this, SegmentId(segmentState->builders.size() + 1),
|
||||||
|
content.begin(), contentSize, &this->dummyLimiter);
|
||||||
|
SegmentBuilder* result = newBuilder.get();
|
||||||
|
segmentState->builders.add(kj::mv(newBuilder));
|
||||||
|
|
||||||
|
// Keep forOutput the right size so that we don't have to re-allocate during
|
||||||
|
// getSegmentsForOutput(), which callers might reasonably expect is a thread-safe method.
|
||||||
|
segmentState->forOutput.resize(segmentState->builders.size() + 1);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::ArrayPtr<const kj::ArrayPtr<const word>> BuilderArena::getSegmentsForOutput() {
|
||||||
|
// Although this is a read-only method, we shouldn't need to lock a mutex here because if this
|
||||||
|
// is called multiple times simultaneously, we should only be overwriting the array with the
|
||||||
|
// exact same data. If the number or size of segments is actually changing due to an activity
|
||||||
|
// in another thread, then the caller has a problem regardless of locking here.
|
||||||
|
|
||||||
|
KJ_IF_MAYBE(segmentState, moreSegments) {
|
||||||
|
KJ_DASSERT(segmentState->get()->forOutput.size() == segmentState->get()->builders.size() + 1,
|
||||||
|
"segmentState->forOutput wasn't resized correctly when the last builder was added.",
|
||||||
|
segmentState->get()->forOutput.size(), segmentState->get()->builders.size());
|
||||||
|
|
||||||
|
kj::ArrayPtr<kj::ArrayPtr<const word>> result(
|
||||||
|
&segmentState->get()->forOutput[0], segmentState->get()->forOutput.size());
|
||||||
|
uint i = 0;
|
||||||
|
result[i++] = segment0.currentlyAllocated();
|
||||||
|
for (auto& builder: segmentState->get()->builders) {
|
||||||
|
result[i++] = builder->currentlyAllocated();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
if (segment0.getArena() == nullptr) {
|
||||||
|
// We haven't actually allocated any segments yet.
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
// We have only one segment so far.
|
||||||
|
segment0ForOutput = segment0.currentlyAllocated();
|
||||||
|
return kj::arrayPtr(&segment0ForOutput, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SegmentReader* BuilderArena::tryGetSegment(SegmentId id) {
|
||||||
|
if (id == SegmentId(0)) {
|
||||||
|
if (segment0.getArena() == nullptr) {
|
||||||
|
// We haven't allocated any segments yet.
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return &segment0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
KJ_IF_MAYBE(segmentState, moreSegments) {
|
||||||
|
if (id.value <= segmentState->get()->builders.size()) {
|
||||||
|
// TODO(cleanup): Return a const SegmentReader and tediously constify all SegmentBuilder
|
||||||
|
// pointers throughout the codebase.
|
||||||
|
return const_cast<SegmentReader*>(kj::implicitCast<const SegmentReader*>(
|
||||||
|
segmentState->get()->builders[id.value - 1].get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuilderArena::reportReadLimitReached() {
|
||||||
|
KJ_FAIL_ASSERT("Read limit reached for BuilderArena, but it should have been unlimited.") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
kj::Maybe<kj::Own<ClientHook>> BuilderArena::LocalCapTable::extractCap(uint index) {
|
||||||
|
if (index < capTable.size()) {
|
||||||
|
return capTable[index].map([](kj::Own<ClientHook>& cap) { return cap->addRef(); });
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint BuilderArena::LocalCapTable::injectCap(kj::Own<ClientHook>&& cap) {
|
||||||
|
uint result = capTable.size();
|
||||||
|
capTable.add(kj::mv(cap));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuilderArena::LocalCapTable::dropCap(uint index) {
|
||||||
|
KJ_ASSERT(index < capTable.size(), "Invalid capability descriptor in message.") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
capTable[index] = nullptr;
|
||||||
|
}
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
} // namespace _ (private)
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,493 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
|
||||||
|
#pragma GCC system_header
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CAPNP_PRIVATE
|
||||||
|
#error "This header is only meant to be included by Cap'n Proto's own source code."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <kj/common.h>
|
||||||
|
#include <kj/mutex.h>
|
||||||
|
#include <kj/exception.h>
|
||||||
|
#include <kj/vector.h>
|
||||||
|
#include <kj/units.h>
|
||||||
|
#include "common.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "layout.h"
|
||||||
|
#include <kj/map.h>
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
#include "capability.h"
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
class ClientHook;
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
namespace _ { // private
|
||||||
|
|
||||||
|
class SegmentReader;
|
||||||
|
class SegmentBuilder;
|
||||||
|
class Arena;
|
||||||
|
class BuilderArena;
|
||||||
|
class ReadLimiter;
|
||||||
|
|
||||||
|
class Segment;
|
||||||
|
typedef kj::Id<uint32_t, Segment> SegmentId;
|
||||||
|
|
||||||
|
class ReadLimiter {
|
||||||
|
// Used to keep track of how much data has been processed from a message, and cut off further
|
||||||
|
// processing if and when a particular limit is reached. This is primarily intended to guard
|
||||||
|
// against maliciously-crafted messages which contain cycles or overlapping structures. Cycles
|
||||||
|
// and overlapping are not permitted by the Cap'n Proto format because in many cases they could
|
||||||
|
// be used to craft a deceptively small message which could consume excessive server resources to
|
||||||
|
// process, perhaps even sending it into an infinite loop. Actually detecting overlaps would be
|
||||||
|
// time-consuming, so instead we just keep track of how many words worth of data structures the
|
||||||
|
// receiver has actually dereferenced and error out if this gets too high.
|
||||||
|
//
|
||||||
|
// This counting takes place as you call getters (for non-primitive values) on the message
|
||||||
|
// readers. If you call the same getter twice, the data it returns may be double-counted. This
|
||||||
|
// should not be a big deal in most cases -- just set the read limit high enough that it will
|
||||||
|
// only trigger in unreasonable cases.
|
||||||
|
//
|
||||||
|
// This class is "safe" to use from multiple threads for its intended use case. Threads may
|
||||||
|
// overwrite each others' changes to the counter, but this is OK because it only means that the
|
||||||
|
// limit is enforced a bit less strictly -- it will still kick in eventually.
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline explicit ReadLimiter(); // No limit.
|
||||||
|
inline explicit ReadLimiter(WordCount64 limit); // Limit to the given number of words.
|
||||||
|
|
||||||
|
inline void reset(WordCount64 limit);
|
||||||
|
|
||||||
|
KJ_ALWAYS_INLINE(bool canRead(WordCount64 amount, Arena* arena));
|
||||||
|
|
||||||
|
void unread(WordCount64 amount);
|
||||||
|
// Adds back some words to the limit. Useful when the caller knows they are double-reading
|
||||||
|
// some data.
|
||||||
|
|
||||||
|
private:
|
||||||
|
volatile uint64_t limit;
|
||||||
|
// Current limit, decremented each time catRead() is called. Volatile because multiple threads
|
||||||
|
// could be trying to modify it at once. (This is not real thread-safety, but good enough for
|
||||||
|
// the purpose of this class. See class comment.)
|
||||||
|
|
||||||
|
KJ_DISALLOW_COPY(ReadLimiter);
|
||||||
|
};
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
class BrokenCapFactory {
|
||||||
|
// Callback for constructing broken caps. We use this so that we can avoid arena.c++ having a
|
||||||
|
// link-time dependency on capability code that lives in libcapnp-rpc.
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual kj::Own<ClientHook> newBrokenCap(kj::StringPtr description) = 0;
|
||||||
|
virtual kj::Own<ClientHook> newNullCap() = 0;
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
class SegmentReader {
|
||||||
|
public:
|
||||||
|
inline SegmentReader(Arena* arena, SegmentId id, const word* ptr, SegmentWordCount size,
|
||||||
|
ReadLimiter* readLimiter);
|
||||||
|
|
||||||
|
KJ_ALWAYS_INLINE(const word* checkOffset(const word* from, ptrdiff_t offset));
|
||||||
|
// Adds the given offset to the given pointer, checks that it is still within the bounds of the
|
||||||
|
// segment, then returns it. Note that the "end" pointer of the segment (which technically points
|
||||||
|
// to the word after the last in the segment) is considered in-bounds for this purpose, so you
|
||||||
|
// can't necessarily dereference it. You must call checkObject() next to check that the object
|
||||||
|
// you want to read is entirely in-bounds.
|
||||||
|
//
|
||||||
|
// If `from + offset` is out-of-range, this returns a pointer to the end of the segment. Thus,
|
||||||
|
// any non-zero-sized object will fail `checkObject()`. We do this instead of throwing to save
|
||||||
|
// some code footprint.
|
||||||
|
|
||||||
|
KJ_ALWAYS_INLINE(bool checkObject(const word* start, WordCountN<31> size));
|
||||||
|
// Assuming that `start` is in-bounds for this segment (probably checked using `checkOffset()`),
|
||||||
|
// check that `start + size` is also in-bounds, and hence the whole area in-between is valid.
|
||||||
|
|
||||||
|
KJ_ALWAYS_INLINE(bool amplifiedRead(WordCount virtualAmount));
|
||||||
|
// Indicates that the reader should pretend that `virtualAmount` additional data was read even
|
||||||
|
// though no actual pointer was traversed. This is used e.g. when reading a struct list pointer
|
||||||
|
// where the element sizes are zero -- the sender could set the list size arbitrarily high and
|
||||||
|
// cause the receiver to iterate over this list even though the message itself is small, so we
|
||||||
|
// need to defend against DoS attacks based on this.
|
||||||
|
|
||||||
|
inline Arena* getArena();
|
||||||
|
inline SegmentId getSegmentId();
|
||||||
|
|
||||||
|
inline const word* getStartPtr();
|
||||||
|
inline SegmentWordCount getOffsetTo(const word* ptr);
|
||||||
|
inline SegmentWordCount getSize();
|
||||||
|
|
||||||
|
inline kj::ArrayPtr<const word> getArray();
|
||||||
|
|
||||||
|
inline void unread(WordCount64 amount);
|
||||||
|
// Add back some words to the ReadLimiter.
|
||||||
|
|
||||||
|
private:
|
||||||
|
Arena* arena;
|
||||||
|
SegmentId id;
|
||||||
|
kj::ArrayPtr<const word> ptr; // size guaranteed to fit in SEGMENT_WORD_COUNT_BITS bits
|
||||||
|
ReadLimiter* readLimiter;
|
||||||
|
|
||||||
|
KJ_DISALLOW_COPY(SegmentReader);
|
||||||
|
|
||||||
|
friend class SegmentBuilder;
|
||||||
|
|
||||||
|
static void abortCheckObjectFault();
|
||||||
|
// Called in debug mode in cases that would segfault in opt mode. (Should be impossible!)
|
||||||
|
};
|
||||||
|
|
||||||
|
class SegmentBuilder: public SegmentReader {
|
||||||
|
public:
|
||||||
|
inline SegmentBuilder(BuilderArena* arena, SegmentId id, word* ptr, SegmentWordCount size,
|
||||||
|
ReadLimiter* readLimiter, SegmentWordCount wordsUsed = ZERO * WORDS);
|
||||||
|
inline SegmentBuilder(BuilderArena* arena, SegmentId id, const word* ptr, SegmentWordCount size,
|
||||||
|
ReadLimiter* readLimiter);
|
||||||
|
inline SegmentBuilder(BuilderArena* arena, SegmentId id, decltype(nullptr),
|
||||||
|
ReadLimiter* readLimiter);
|
||||||
|
|
||||||
|
KJ_ALWAYS_INLINE(word* allocate(SegmentWordCount amount));
|
||||||
|
|
||||||
|
KJ_ALWAYS_INLINE(void checkWritable());
|
||||||
|
// Throw an exception if the segment is read-only (meaning it is a reference to external data).
|
||||||
|
|
||||||
|
KJ_ALWAYS_INLINE(word* getPtrUnchecked(SegmentWordCount offset));
|
||||||
|
// Get a writable pointer into the segment. Throws an exception if the segment is read-only (i.e.
|
||||||
|
// a reference to external immutable data).
|
||||||
|
|
||||||
|
inline BuilderArena* getArena();
|
||||||
|
|
||||||
|
inline kj::ArrayPtr<const word> currentlyAllocated();
|
||||||
|
|
||||||
|
inline void reset();
|
||||||
|
|
||||||
|
inline bool isWritable() { return !readOnly; }
|
||||||
|
|
||||||
|
inline void tryTruncate(word* from, word* to);
|
||||||
|
// If `from` points just past the current end of the segment, then move the end back to `to`.
|
||||||
|
// Otherwise, do nothing.
|
||||||
|
|
||||||
|
inline bool tryExtend(word* from, word* to);
|
||||||
|
// If `from` points just past the current end of the segment, and `to` is within the segment
|
||||||
|
// boundaries, then move the end up to `to` and return true. Otherwise, do nothing and return
|
||||||
|
// false.
|
||||||
|
|
||||||
|
private:
|
||||||
|
word* pos;
|
||||||
|
// Pointer to a pointer to the current end point of the segment, i.e. the location where the
|
||||||
|
// next object should be allocated.
|
||||||
|
|
||||||
|
bool readOnly;
|
||||||
|
|
||||||
|
void throwNotWritable();
|
||||||
|
|
||||||
|
KJ_DISALLOW_COPY(SegmentBuilder);
|
||||||
|
};
|
||||||
|
|
||||||
|
class Arena {
|
||||||
|
public:
|
||||||
|
virtual ~Arena() noexcept(false);
|
||||||
|
|
||||||
|
virtual SegmentReader* tryGetSegment(SegmentId id) = 0;
|
||||||
|
// Gets the segment with the given ID, or return nullptr if no such segment exists.
|
||||||
|
|
||||||
|
virtual void reportReadLimitReached() = 0;
|
||||||
|
// Called to report that the read limit has been reached. See ReadLimiter, below. This invokes
|
||||||
|
// the VALIDATE_INPUT() macro which may throw an exception; if it returns normally, the caller
|
||||||
|
// will need to continue with default values.
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReaderArena final: public Arena {
|
||||||
|
public:
|
||||||
|
explicit ReaderArena(MessageReader* message);
|
||||||
|
~ReaderArena() noexcept(false);
|
||||||
|
KJ_DISALLOW_COPY(ReaderArena);
|
||||||
|
|
||||||
|
// implements Arena ------------------------------------------------
|
||||||
|
SegmentReader* tryGetSegment(SegmentId id) override;
|
||||||
|
void reportReadLimitReached() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
MessageReader* message;
|
||||||
|
ReadLimiter readLimiter;
|
||||||
|
|
||||||
|
// Optimize for single-segment messages so that small messages are handled quickly.
|
||||||
|
SegmentReader segment0;
|
||||||
|
|
||||||
|
typedef kj::HashMap<uint, kj::Own<SegmentReader>> SegmentMap;
|
||||||
|
kj::MutexGuarded<kj::Maybe<SegmentMap>> moreSegments;
|
||||||
|
// We need to mutex-guard the segment map because we lazily initialize segments when they are
|
||||||
|
// first requested, but a Reader is allowed to be used concurrently in multiple threads. Luckily
|
||||||
|
// this only applies to large messages.
|
||||||
|
//
|
||||||
|
// TODO(perf): Thread-local thing instead? Some kind of lockless map? Or do sharing of data
|
||||||
|
// in a different way, where you have to construct a new MessageReader in each thread (but
|
||||||
|
// possibly backed by the same data)?
|
||||||
|
|
||||||
|
ReaderArena(MessageReader* message, kj::ArrayPtr<const word> firstSegment);
|
||||||
|
ReaderArena(MessageReader* message, const word* firstSegment, SegmentWordCount firstSegmentSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
class BuilderArena final: public Arena {
|
||||||
|
// A BuilderArena that does not allow the injection of capabilities.
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BuilderArena(MessageBuilder* message);
|
||||||
|
BuilderArena(MessageBuilder* message, kj::ArrayPtr<MessageBuilder::SegmentInit> segments);
|
||||||
|
~BuilderArena() noexcept(false);
|
||||||
|
KJ_DISALLOW_COPY(BuilderArena);
|
||||||
|
|
||||||
|
inline SegmentBuilder* getRootSegment() { return &segment0; }
|
||||||
|
|
||||||
|
kj::ArrayPtr<const kj::ArrayPtr<const word>> getSegmentsForOutput();
|
||||||
|
// Get an array of all the segments, suitable for writing out. This only returns the allocated
|
||||||
|
// portion of each segment, whereas tryGetSegment() returns something that includes
|
||||||
|
// not-yet-allocated space.
|
||||||
|
|
||||||
|
inline CapTableBuilder* getLocalCapTable() {
|
||||||
|
// Return a CapTableBuilder that merely implements local loopback. That is, you can set
|
||||||
|
// capabilities, then read the same capabilities back, but there is no intent ever to transmit
|
||||||
|
// these capabilities. A MessageBuilder that isn't imbued with some other CapTable uses this
|
||||||
|
// by default.
|
||||||
|
//
|
||||||
|
// TODO(cleanup): It's sort of a hack that this exists. In theory, perhaps, unimbued
|
||||||
|
// MessageBuilders should throw exceptions on any attempt to access capability fields, like
|
||||||
|
// unimbued MessageReaders do. However, lots of code exists which uses MallocMessageBuilder
|
||||||
|
// as a temporary holder for data to be copied in and out (without being serialized), and it
|
||||||
|
// is expected that such data can include capabilities, which is admittedly reasonable.
|
||||||
|
// Therefore, all MessageBuilders must have a cap table by default. Arguably we should
|
||||||
|
// deprecate this usage and instead define a new helper type for this exact purpose.
|
||||||
|
|
||||||
|
return &localCapTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
SegmentBuilder* getSegment(SegmentId id);
|
||||||
|
// Get the segment with the given id. Crashes or throws an exception if no such segment exists.
|
||||||
|
|
||||||
|
struct AllocateResult {
|
||||||
|
SegmentBuilder* segment;
|
||||||
|
word* words;
|
||||||
|
};
|
||||||
|
|
||||||
|
AllocateResult allocate(SegmentWordCount amount);
|
||||||
|
// Find a segment with at least the given amount of space available and allocate the space.
|
||||||
|
// Note that allocating directly from a particular segment is much faster, but allocating from
|
||||||
|
// the arena is guaranteed to succeed. Therefore callers should try to allocate from a specific
|
||||||
|
// segment first if there is one, then fall back to the arena.
|
||||||
|
|
||||||
|
SegmentBuilder* addExternalSegment(kj::ArrayPtr<const word> content);
|
||||||
|
// Add a new segment to the arena which points to some existing memory region. The segment is
|
||||||
|
// assumed to be completley full; the arena will never allocate from it. In fact, the segment
|
||||||
|
// is considered read-only. Any attempt to get a Builder pointing into this segment will throw
|
||||||
|
// an exception. Readers are allowed, however.
|
||||||
|
//
|
||||||
|
// This can be used to inject some external data into a message without a copy, e.g. embedding a
|
||||||
|
// large mmap'd file into a message as `Data` without forcing that data to actually be read in
|
||||||
|
// from disk (until the message itself is written out). `Orphanage` provides the public API for
|
||||||
|
// this feature.
|
||||||
|
|
||||||
|
// implements Arena ------------------------------------------------
|
||||||
|
SegmentReader* tryGetSegment(SegmentId id) override;
|
||||||
|
void reportReadLimitReached() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
MessageBuilder* message;
|
||||||
|
ReadLimiter dummyLimiter;
|
||||||
|
|
||||||
|
class LocalCapTable: public CapTableBuilder {
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
public:
|
||||||
|
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override;
|
||||||
|
uint injectCap(kj::Own<ClientHook>&& cap) override;
|
||||||
|
void dropCap(uint index) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Vector<kj::Maybe<kj::Own<ClientHook>>> capTable;
|
||||||
|
#endif // ! CAPNP_LITE
|
||||||
|
};
|
||||||
|
|
||||||
|
LocalCapTable localCapTable;
|
||||||
|
|
||||||
|
SegmentBuilder segment0;
|
||||||
|
kj::ArrayPtr<const word> segment0ForOutput;
|
||||||
|
|
||||||
|
struct MultiSegmentState {
|
||||||
|
kj::Vector<kj::Own<SegmentBuilder>> builders;
|
||||||
|
kj::Vector<kj::ArrayPtr<const word>> forOutput;
|
||||||
|
};
|
||||||
|
kj::Maybe<kj::Own<MultiSegmentState>> moreSegments;
|
||||||
|
|
||||||
|
SegmentBuilder* segmentWithSpace = nullptr;
|
||||||
|
// When allocating, look for space in this segment first before resorting to allocating a new
|
||||||
|
// segment. This is not necessarily the last segment because addExternalSegment() may add a
|
||||||
|
// segment that is already-full, in which case we don't update this pointer.
|
||||||
|
|
||||||
|
template <typename T> // Can be `word` or `const word`.
|
||||||
|
SegmentBuilder* addSegmentInternal(kj::ArrayPtr<T> content);
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
inline ReadLimiter::ReadLimiter()
|
||||||
|
: limit(kj::maxValue) {}
|
||||||
|
|
||||||
|
inline ReadLimiter::ReadLimiter(WordCount64 limit): limit(unbound(limit / WORDS)) {}
|
||||||
|
|
||||||
|
inline void ReadLimiter::reset(WordCount64 limit) { this->limit = unbound(limit / WORDS); }
|
||||||
|
|
||||||
|
inline bool ReadLimiter::canRead(WordCount64 amount, Arena* arena) {
|
||||||
|
// Be careful not to store an underflowed value into `limit`, even if multiple threads are
|
||||||
|
// decrementing it.
|
||||||
|
uint64_t current = limit;
|
||||||
|
if (KJ_UNLIKELY(unbound(amount / WORDS) > current)) {
|
||||||
|
arena->reportReadLimitReached();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
limit = current - unbound(amount / WORDS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline SegmentReader::SegmentReader(Arena* arena, SegmentId id, const word* ptr,
|
||||||
|
SegmentWordCount size, ReadLimiter* readLimiter)
|
||||||
|
: arena(arena), id(id), ptr(kj::arrayPtr(ptr, unbound(size / WORDS))),
|
||||||
|
readLimiter(readLimiter) {}
|
||||||
|
|
||||||
|
inline const word* SegmentReader::checkOffset(const word* from, ptrdiff_t offset) {
|
||||||
|
ptrdiff_t min = ptr.begin() - from;
|
||||||
|
ptrdiff_t max = ptr.end() - from;
|
||||||
|
if (offset >= min && offset <= max) {
|
||||||
|
return from + offset;
|
||||||
|
} else {
|
||||||
|
return ptr.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool SegmentReader::checkObject(const word* start, WordCountN<31> size) {
|
||||||
|
auto startOffset = intervalLength(ptr.begin(), start, MAX_SEGMENT_WORDS);
|
||||||
|
#ifdef KJ_DEBUG
|
||||||
|
if (startOffset > bounded(ptr.size()) * WORDS) {
|
||||||
|
abortCheckObjectFault();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return startOffset + size <= bounded(ptr.size()) * WORDS &&
|
||||||
|
readLimiter->canRead(size, arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool SegmentReader::amplifiedRead(WordCount virtualAmount) {
|
||||||
|
return readLimiter->canRead(virtualAmount, arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Arena* SegmentReader::getArena() { return arena; }
|
||||||
|
inline SegmentId SegmentReader::getSegmentId() { return id; }
|
||||||
|
inline const word* SegmentReader::getStartPtr() { return ptr.begin(); }
|
||||||
|
inline SegmentWordCount SegmentReader::getOffsetTo(const word* ptr) {
|
||||||
|
KJ_IREQUIRE(this->ptr.begin() <= ptr && ptr <= this->ptr.end());
|
||||||
|
return intervalLength(this->ptr.begin(), ptr, MAX_SEGMENT_WORDS);
|
||||||
|
}
|
||||||
|
inline SegmentWordCount SegmentReader::getSize() {
|
||||||
|
return assumeBits<SEGMENT_WORD_COUNT_BITS>(ptr.size()) * WORDS;
|
||||||
|
}
|
||||||
|
inline kj::ArrayPtr<const word> SegmentReader::getArray() { return ptr; }
|
||||||
|
inline void SegmentReader::unread(WordCount64 amount) { readLimiter->unread(amount); }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline SegmentBuilder::SegmentBuilder(
|
||||||
|
BuilderArena* arena, SegmentId id, word* ptr, SegmentWordCount size,
|
||||||
|
ReadLimiter* readLimiter, SegmentWordCount wordsUsed)
|
||||||
|
: SegmentReader(arena, id, ptr, size, readLimiter),
|
||||||
|
pos(ptr + wordsUsed), readOnly(false) {}
|
||||||
|
inline SegmentBuilder::SegmentBuilder(
|
||||||
|
BuilderArena* arena, SegmentId id, const word* ptr, SegmentWordCount size,
|
||||||
|
ReadLimiter* readLimiter)
|
||||||
|
: SegmentReader(arena, id, ptr, size, readLimiter),
|
||||||
|
// const_cast is safe here because the member won't ever be dereferenced because it appears
|
||||||
|
// to point to the end of the segment anyway.
|
||||||
|
pos(const_cast<word*>(ptr + size)), readOnly(true) {}
|
||||||
|
inline SegmentBuilder::SegmentBuilder(BuilderArena* arena, SegmentId id, decltype(nullptr),
|
||||||
|
ReadLimiter* readLimiter)
|
||||||
|
: SegmentReader(arena, id, nullptr, ZERO * WORDS, readLimiter),
|
||||||
|
pos(nullptr), readOnly(false) {}
|
||||||
|
|
||||||
|
inline word* SegmentBuilder::allocate(SegmentWordCount amount) {
|
||||||
|
if (intervalLength(pos, ptr.end(), MAX_SEGMENT_WORDS) < amount) {
|
||||||
|
// Not enough space in the segment for this allocation.
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
// Success.
|
||||||
|
word* result = pos;
|
||||||
|
pos = pos + amount;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SegmentBuilder::checkWritable() {
|
||||||
|
if (KJ_UNLIKELY(readOnly)) throwNotWritable();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline word* SegmentBuilder::getPtrUnchecked(SegmentWordCount offset) {
|
||||||
|
return const_cast<word*>(ptr.begin() + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BuilderArena* SegmentBuilder::getArena() {
|
||||||
|
// Down-cast safe because SegmentBuilder's constructor always initializes its SegmentReader base
|
||||||
|
// class with an Arena pointer that actually points to a BuilderArena.
|
||||||
|
return static_cast<BuilderArena*>(arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline kj::ArrayPtr<const word> SegmentBuilder::currentlyAllocated() {
|
||||||
|
return kj::arrayPtr(ptr.begin(), pos - ptr.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SegmentBuilder::reset() {
|
||||||
|
word* start = getPtrUnchecked(ZERO * WORDS);
|
||||||
|
memset(start, 0, (pos - start) * sizeof(word));
|
||||||
|
pos = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SegmentBuilder::tryTruncate(word* from, word* to) {
|
||||||
|
if (pos == from) pos = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool SegmentBuilder::tryExtend(word* from, word* to) {
|
||||||
|
// Careful about overflow.
|
||||||
|
if (pos == from && to <= ptr.end() && to >= from) {
|
||||||
|
pos = to;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace _ (private)
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "blob.h"
|
||||||
|
#include <kj/compat/gtest.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include "test-util.h"
|
||||||
|
|
||||||
|
// TODO(test): This test is outdated -- it predates the retrofit of Text and Data on top of
|
||||||
|
// kj::ArrayPtr/kj::StringPtr. Clean it up.
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(Blob, Text) {
|
||||||
|
std::string str = "foo";
|
||||||
|
Text::Reader text = str.c_str();
|
||||||
|
|
||||||
|
EXPECT_EQ("foo", text);
|
||||||
|
EXPECT_STREQ("foo", text.cStr());
|
||||||
|
EXPECT_STREQ("foo", text.begin());
|
||||||
|
EXPECT_EQ(3u, text.size());
|
||||||
|
|
||||||
|
Text::Reader text2 = "bar";
|
||||||
|
EXPECT_EQ("bar", text2);
|
||||||
|
|
||||||
|
char c[4] = "baz";
|
||||||
|
Text::Reader text3(c);
|
||||||
|
EXPECT_EQ("baz", text3);
|
||||||
|
|
||||||
|
Text::Builder builder(c, 3);
|
||||||
|
EXPECT_EQ("baz", builder);
|
||||||
|
|
||||||
|
EXPECT_EQ(kj::arrayPtr("az", 2), builder.slice(1, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::Reader dataLit(const char* str) {
|
||||||
|
return Data::Reader(reinterpret_cast<const byte*>(str), strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Blob, Data) {
|
||||||
|
Data::Reader data = dataLit("foo");
|
||||||
|
|
||||||
|
EXPECT_EQ(dataLit("foo"), data);
|
||||||
|
EXPECT_EQ(3u, data.size());
|
||||||
|
|
||||||
|
Data::Reader data2 = dataLit("bar");
|
||||||
|
EXPECT_EQ(dataLit("bar"), data2);
|
||||||
|
|
||||||
|
byte c[4] = "baz";
|
||||||
|
Data::Reader data3(c, 3);
|
||||||
|
EXPECT_EQ(dataLit("baz"), data3);
|
||||||
|
|
||||||
|
Data::Builder builder(c, 3);
|
||||||
|
EXPECT_EQ(dataLit("baz"), builder);
|
||||||
|
|
||||||
|
EXPECT_EQ(dataLit("az"), builder.slice(1, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Blob, Compare) {
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") == Text::Reader("foo"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") != Text::Reader("foo"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") <= Text::Reader("foo"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") >= Text::Reader("foo"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") < Text::Reader("foo"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") > Text::Reader("foo"));
|
||||||
|
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") == Text::Reader("bar"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") != Text::Reader("bar"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") <= Text::Reader("bar"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") >= Text::Reader("bar"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") < Text::Reader("bar"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") > Text::Reader("bar"));
|
||||||
|
|
||||||
|
EXPECT_FALSE(Text::Reader("bar") == Text::Reader("foo"));
|
||||||
|
EXPECT_TRUE (Text::Reader("bar") != Text::Reader("foo"));
|
||||||
|
EXPECT_TRUE (Text::Reader("bar") <= Text::Reader("foo"));
|
||||||
|
EXPECT_FALSE(Text::Reader("bar") >= Text::Reader("foo"));
|
||||||
|
EXPECT_TRUE (Text::Reader("bar") < Text::Reader("foo"));
|
||||||
|
EXPECT_FALSE(Text::Reader("bar") > Text::Reader("foo"));
|
||||||
|
|
||||||
|
EXPECT_FALSE(Text::Reader("foobar") == Text::Reader("foo"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foobar") != Text::Reader("foo"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foobar") <= Text::Reader("foo"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foobar") >= Text::Reader("foo"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foobar") < Text::Reader("foo"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foobar") > Text::Reader("foo"));
|
||||||
|
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") == Text::Reader("foobar"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") != Text::Reader("foobar"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") <= Text::Reader("foobar"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") >= Text::Reader("foobar"));
|
||||||
|
EXPECT_TRUE (Text::Reader("foo") < Text::Reader("foobar"));
|
||||||
|
EXPECT_FALSE(Text::Reader("foo") > Text::Reader("foobar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if KJ_COMPILER_SUPPORTS_STL_STRING_INTEROP
|
||||||
|
TEST(Blob, StlInterop) {
|
||||||
|
std::string foo = "foo";
|
||||||
|
Text::Reader reader = foo;
|
||||||
|
|
||||||
|
EXPECT_EQ("foo", reader);
|
||||||
|
|
||||||
|
std::string bar = reader;
|
||||||
|
EXPECT_EQ("foo", bar);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "blob.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
char Text::Builder::nulstr[1] = "";
|
||||||
|
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,219 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
|
||||||
|
#pragma GCC system_header
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <kj/common.h>
|
||||||
|
#include <kj/string.h>
|
||||||
|
#include "common.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
struct Data {
|
||||||
|
Data() = delete;
|
||||||
|
class Reader;
|
||||||
|
class Builder;
|
||||||
|
class Pipeline {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Text {
|
||||||
|
Text() = delete;
|
||||||
|
class Reader;
|
||||||
|
class Builder;
|
||||||
|
class Pipeline {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Data::Reader: public kj::ArrayPtr<const byte> {
|
||||||
|
// Points to a blob of bytes. The usual Reader rules apply -- Data::Reader behaves like a simple
|
||||||
|
// pointer which does not own its target, can be passed by value, etc.
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef Data Reads;
|
||||||
|
|
||||||
|
Reader() = default;
|
||||||
|
inline Reader(decltype(nullptr)): ArrayPtr<const byte>(nullptr) {}
|
||||||
|
inline Reader(const byte* value, size_t size): ArrayPtr<const byte>(value, size) {}
|
||||||
|
inline Reader(const kj::Array<const byte>& value): ArrayPtr<const byte>(value) {}
|
||||||
|
inline Reader(const ArrayPtr<const byte>& value): ArrayPtr<const byte>(value) {}
|
||||||
|
inline Reader(const kj::Array<byte>& value): ArrayPtr<const byte>(value) {}
|
||||||
|
inline Reader(const ArrayPtr<byte>& value): ArrayPtr<const byte>(value) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Text::Reader: public kj::StringPtr {
|
||||||
|
// Like Data::Reader, but points at NUL-terminated UTF-8 text. The NUL terminator is not counted
|
||||||
|
// in the size but must be present immediately after the last byte.
|
||||||
|
//
|
||||||
|
// Text::Reader's interface contract is that its data MUST be NUL-terminated. The producer of
|
||||||
|
// the Text::Reader must guarantee this, so that the consumer need not check. The data SHOULD
|
||||||
|
// also be valid UTF-8, but this is NOT guaranteed -- the consumer must verify if it cares.
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef Text Reads;
|
||||||
|
|
||||||
|
Reader() = default;
|
||||||
|
inline Reader(decltype(nullptr)): StringPtr(nullptr) {}
|
||||||
|
inline Reader(const char* value): StringPtr(value) {}
|
||||||
|
inline Reader(const char* value, size_t size): StringPtr(value, size) {}
|
||||||
|
inline Reader(const kj::String& value): StringPtr(value) {}
|
||||||
|
inline Reader(const StringPtr& value): StringPtr(value) {}
|
||||||
|
|
||||||
|
#if KJ_COMPILER_SUPPORTS_STL_STRING_INTEROP
|
||||||
|
template <typename T, typename = decltype(kj::instance<T>().c_str())>
|
||||||
|
inline Reader(const T& t): StringPtr(t) {}
|
||||||
|
// Allow implicit conversion from any class that has a c_str() method (namely, std::string).
|
||||||
|
// We use a template trick to detect std::string in order to avoid including the header for
|
||||||
|
// those who don't want it.
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class Data::Builder: public kj::ArrayPtr<byte> {
|
||||||
|
// Like Data::Reader except the pointers aren't const.
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef Data Builds;
|
||||||
|
|
||||||
|
Builder() = default;
|
||||||
|
inline Builder(decltype(nullptr)): ArrayPtr<byte>(nullptr) {}
|
||||||
|
inline Builder(byte* value, size_t size): ArrayPtr<byte>(value, size) {}
|
||||||
|
inline Builder(kj::Array<byte>& value): ArrayPtr<byte>(value) {}
|
||||||
|
inline Builder(ArrayPtr<byte> value): ArrayPtr<byte>(value) {}
|
||||||
|
|
||||||
|
inline Data::Reader asReader() const {
|
||||||
|
return Data::Reader(kj::implicitCast<const kj::ArrayPtr<byte>&>(*this));
|
||||||
|
}
|
||||||
|
inline operator Reader() const { return asReader(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Text::Builder: public kj::DisallowConstCopy {
|
||||||
|
// Basically identical to kj::StringPtr, except that the contents are non-const.
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline Builder(): content(nulstr, 1) {}
|
||||||
|
inline Builder(decltype(nullptr)): content(nulstr, 1) {}
|
||||||
|
inline Builder(char* value): content(value, strlen(value) + 1) {}
|
||||||
|
inline Builder(char* value, size_t size): content(value, size + 1) {
|
||||||
|
KJ_IREQUIRE(value[size] == '\0', "StringPtr must be NUL-terminated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Reader asReader() const { return Reader(content.begin(), content.size() - 1); }
|
||||||
|
inline operator Reader() const { return asReader(); }
|
||||||
|
|
||||||
|
inline operator kj::ArrayPtr<char>();
|
||||||
|
inline kj::ArrayPtr<char> asArray();
|
||||||
|
inline operator kj::ArrayPtr<const char>() const;
|
||||||
|
inline kj::ArrayPtr<const char> asArray() const;
|
||||||
|
inline kj::ArrayPtr<byte> asBytes() { return asArray().asBytes(); }
|
||||||
|
inline kj::ArrayPtr<const byte> asBytes() const { return asArray().asBytes(); }
|
||||||
|
// Result does not include NUL terminator.
|
||||||
|
|
||||||
|
inline operator kj::StringPtr() const;
|
||||||
|
inline kj::StringPtr asString() const;
|
||||||
|
|
||||||
|
inline const char* cStr() const { return content.begin(); }
|
||||||
|
// Returns NUL-terminated string.
|
||||||
|
|
||||||
|
inline size_t size() const { return content.size() - 1; }
|
||||||
|
// Result does not include NUL terminator.
|
||||||
|
|
||||||
|
inline char operator[](size_t index) const { return content[index]; }
|
||||||
|
inline char& operator[](size_t index) { return content[index]; }
|
||||||
|
|
||||||
|
inline char* begin() { return content.begin(); }
|
||||||
|
inline char* end() { return content.end() - 1; }
|
||||||
|
inline const char* begin() const { return content.begin(); }
|
||||||
|
inline const char* end() const { return content.end() - 1; }
|
||||||
|
|
||||||
|
inline bool operator==(decltype(nullptr)) const { return content.size() <= 1; }
|
||||||
|
inline bool operator!=(decltype(nullptr)) const { return content.size() > 1; }
|
||||||
|
|
||||||
|
inline bool operator==(Builder other) const { return asString() == other.asString(); }
|
||||||
|
inline bool operator!=(Builder other) const { return asString() != other.asString(); }
|
||||||
|
inline bool operator< (Builder other) const { return asString() < other.asString(); }
|
||||||
|
inline bool operator> (Builder other) const { return asString() > other.asString(); }
|
||||||
|
inline bool operator<=(Builder other) const { return asString() <= other.asString(); }
|
||||||
|
inline bool operator>=(Builder other) const { return asString() >= other.asString(); }
|
||||||
|
|
||||||
|
inline kj::StringPtr slice(size_t start) const;
|
||||||
|
inline kj::ArrayPtr<const char> slice(size_t start, size_t end) const;
|
||||||
|
inline Builder slice(size_t start);
|
||||||
|
inline kj::ArrayPtr<char> slice(size_t start, size_t end);
|
||||||
|
// A string slice is only NUL-terminated if it is a suffix, so slice() has a one-parameter
|
||||||
|
// version that assumes end = size().
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline explicit Builder(kj::ArrayPtr<char> content): content(content) {}
|
||||||
|
|
||||||
|
kj::ArrayPtr<char> content;
|
||||||
|
|
||||||
|
static char nulstr[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
inline kj::StringPtr KJ_STRINGIFY(Text::Builder builder) {
|
||||||
|
return builder.asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator==(const char* a, const Text::Builder& b) { return a == b.asString(); }
|
||||||
|
inline bool operator!=(const char* a, const Text::Builder& b) { return a != b.asString(); }
|
||||||
|
|
||||||
|
inline Text::Builder::operator kj::StringPtr() const {
|
||||||
|
return kj::StringPtr(content.begin(), content.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline kj::StringPtr Text::Builder::asString() const {
|
||||||
|
return kj::StringPtr(content.begin(), content.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Text::Builder::operator kj::ArrayPtr<char>() {
|
||||||
|
return content.slice(0, content.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline kj::ArrayPtr<char> Text::Builder::asArray() {
|
||||||
|
return content.slice(0, content.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Text::Builder::operator kj::ArrayPtr<const char>() const {
|
||||||
|
return content.slice(0, content.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline kj::ArrayPtr<const char> Text::Builder::asArray() const {
|
||||||
|
return content.slice(0, content.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline kj::StringPtr Text::Builder::slice(size_t start) const {
|
||||||
|
return asReader().slice(start);
|
||||||
|
}
|
||||||
|
inline kj::ArrayPtr<const char> Text::Builder::slice(size_t start, size_t end) const {
|
||||||
|
return content.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Text::Builder Text::Builder::slice(size_t start) {
|
||||||
|
return Text::Builder(content.slice(start, content.size()));
|
||||||
|
}
|
||||||
|
inline kj::ArrayPtr<char> Text::Builder::slice(size_t start, size_t end) {
|
||||||
|
return content.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,76 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# This is a one-off test rule.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo findProvider special:ekam-interceptor
|
||||||
|
read INTERCEPTOR
|
||||||
|
|
||||||
|
if test "$INTERCEPTOR" = ""; then
|
||||||
|
echo "error: couldn't find intercept.so." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo findProvider file:capnp
|
||||||
|
read CAPNP
|
||||||
|
|
||||||
|
if test "$CAPNP" = ""; then
|
||||||
|
echo "error: couldn't find capnp." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo findProvider file:capnpc-c++
|
||||||
|
read CAPNPC_CXX
|
||||||
|
|
||||||
|
if test "$CAPNPC_CXX" = ""; then
|
||||||
|
echo "error: couldn't find capnpc-c++." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p tmp/capnp/bootstrap-test-tmp
|
||||||
|
|
||||||
|
INPUTS="capnp/c++.capnp capnp/schema.capnp capnp/compiler/lexer.capnp capnp/compiler/grammar.capnp \
|
||||||
|
capnp/rpc.capnp capnp/rpc-twoparty.capnp capnp/persistent.capnp"
|
||||||
|
|
||||||
|
SRC_INPUTS=""
|
||||||
|
for file in $INPUTS; do
|
||||||
|
echo findProvider file:$file
|
||||||
|
read srcfile
|
||||||
|
SRC_INPUTS="$SRC_INPUTS $srcfile"
|
||||||
|
done
|
||||||
|
|
||||||
|
$CAPNP compile --src-prefix=src -Isrc --no-standard-import \
|
||||||
|
-o$CAPNPC_CXX:tmp/capnp/bootstrap-test-tmp $SRC_INPUTS
|
||||||
|
|
||||||
|
for file in $INPUTS; do
|
||||||
|
for ext in h c++; do
|
||||||
|
echo findProvider file:$file.$ext
|
||||||
|
read srcfile
|
||||||
|
test "x$srcfile" != x || (echo "missing: $file.$ext" >&2 && exit 1)
|
||||||
|
diff -u $srcfile tmp/capnp/bootstrap-test-tmp/$file.$ext >&2
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo passed
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
@0xbdf87d7bb8304e81;
|
||||||
|
$namespace("capnp::annotations");
|
||||||
|
|
||||||
|
annotation namespace(file): Text;
|
||||||
|
annotation name(field, enumerant, struct, enum, interface, method, param, group, union): Text;
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Generated by Cap'n Proto compiler, DO NOT EDIT
|
||||||
|
// source: c++.capnp
|
||||||
|
|
||||||
|
#include "c++.capnp.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace schemas {
|
||||||
|
static const ::capnp::_::AlignedData<21> b_b9c6f99ebf805f2c = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
44, 95, 128, 191, 158, 249, 198, 185,
|
||||||
|
16, 0, 0, 0, 5, 0, 1, 0,
|
||||||
|
129, 78, 48, 184, 123, 125, 248, 189,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 210, 0, 0, 0,
|
||||||
|
33, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
28, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 43,
|
||||||
|
43, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
110, 97, 109, 101, 115, 112, 97, 99,
|
||||||
|
101, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_b9c6f99ebf805f2c = b_b9c6f99ebf805f2c.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
const ::capnp::_::RawSchema s_b9c6f99ebf805f2c = {
|
||||||
|
0xb9c6f99ebf805f2c, b_b9c6f99ebf805f2c.words, 21, nullptr, nullptr,
|
||||||
|
0, 0, nullptr, nullptr, nullptr, { &s_b9c6f99ebf805f2c, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<20> b_f264a779fef191ce = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
206, 145, 241, 254, 121, 167, 100, 242,
|
||||||
|
16, 0, 0, 0, 5, 0, 252, 7,
|
||||||
|
129, 78, 48, 184, 123, 125, 248, 189,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 170, 0, 0, 0,
|
||||||
|
29, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
24, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 43,
|
||||||
|
43, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
110, 97, 109, 101, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_f264a779fef191ce = b_f264a779fef191ce.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
const ::capnp::_::RawSchema s_f264a779fef191ce = {
|
||||||
|
0xf264a779fef191ce, b_f264a779fef191ce.words, 20, nullptr, nullptr,
|
||||||
|
0, 0, nullptr, nullptr, nullptr, { &s_f264a779fef191ce, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
} // namespace schemas
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Generated by Cap'n Proto compiler, DO NOT EDIT
|
||||||
|
// source: c++.capnp
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <capnp/generated-header-support.h>
|
||||||
|
#include <kj/windows-sanity.h>
|
||||||
|
|
||||||
|
#if CAPNP_VERSION != 7000
|
||||||
|
#error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace schemas {
|
||||||
|
|
||||||
|
CAPNP_DECLARE_SCHEMA(b9c6f99ebf805f2c);
|
||||||
|
CAPNP_DECLARE_SCHEMA(f264a779fef191ce);
|
||||||
|
|
||||||
|
} // namespace schemas
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace annotations {
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace
|
||||||
|
|
|
@ -0,0 +1,392 @@
|
||||||
|
// Copyright (c) 2016 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "message.h"
|
||||||
|
#include "any.h"
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <kj/test.h>
|
||||||
|
#include "test-util.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace _ { // private
|
||||||
|
using test::TestLists;
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
|
||||||
|
KJ_TEST("canonicalize yields canonical message") {
|
||||||
|
MallocMessageBuilder builder;
|
||||||
|
auto root = builder.initRoot<TestAllTypes>();
|
||||||
|
|
||||||
|
initTestMessage(root);
|
||||||
|
|
||||||
|
auto canonicalWords = canonicalize(root.asReader());
|
||||||
|
// Throws an exception on canonicalization failure.
|
||||||
|
|
||||||
|
kj::ArrayPtr<const capnp::word> canonicalSegments[1] = {canonicalWords.asPtr()};
|
||||||
|
capnp::SegmentArrayMessageReader canonicalReader(kj::arrayPtr(canonicalSegments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(AnyStruct::Reader(root.asReader()) ==
|
||||||
|
AnyStruct::Reader(canonicalReader.getRoot<TestAllTypes>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("canonicalize succeeds on empty struct") {
|
||||||
|
MallocMessageBuilder builder;
|
||||||
|
auto root = builder.initRoot<TestAllTypes>();
|
||||||
|
|
||||||
|
canonicalize(root.asReader()); // Throws an exception on canoncalization failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("data word with only its most significant bit set does not get truncated") {
|
||||||
|
AlignedData<3> segment = {{
|
||||||
|
// Struct pointer, body immediately follows, two data words
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// First data word
|
||||||
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
||||||
|
|
||||||
|
// Second data word, all zero except most significant bit
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(segment.words, 3)};
|
||||||
|
SegmentArrayMessageReader messageReader(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(messageReader.isCanonical());
|
||||||
|
auto canonicalWords = canonicalize(messageReader.getRoot<TestAllTypes>());
|
||||||
|
|
||||||
|
// At one point this failed because an off-by-one bug in canonicalization
|
||||||
|
// caused the second word of the data section to be truncated.
|
||||||
|
ASSERT_EQ(canonicalWords.asBytes(), kj::arrayPtr(segment.bytes, 3 * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("INLINE_COMPOSITE data word with only its most significant bit set does not get truncated") {
|
||||||
|
AlignedData<5> segment = {{
|
||||||
|
// Struct pointer, body immediately follows, one pointer
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
|
||||||
|
// List pointer, no offset, inline composite, two words long
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Tag word, list has one element with two data words and no pointers
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// First data word
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||||
|
|
||||||
|
// Second data word, all zero except most significant bit
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(segment.words, 5)};
|
||||||
|
SegmentArrayMessageReader messageReader(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(messageReader.isCanonical());
|
||||||
|
auto canonicalWords = canonicalize(messageReader.getRoot<TestLists>());
|
||||||
|
|
||||||
|
// At one point this failed because an off-by-one bug in canonicalization
|
||||||
|
// caused the second word of the data section to be truncated.
|
||||||
|
ASSERT_EQ(canonicalWords.asBytes(), kj::arrayPtr(segment.bytes, 5 * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("canonical non-null empty struct field") {
|
||||||
|
AlignedData<4> nonNullEmptyStruct = {{
|
||||||
|
// Struct pointer, body immediately follows, two pointer fields, no data.
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||||
|
|
||||||
|
// First pointer field, struct, offset of 1, data size 1, no pointers.
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Non-null pointer to empty struct.
|
||||||
|
0xfc, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Body of struct filled with non-zero data.
|
||||||
|
0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(nonNullEmptyStruct.words, 4)};
|
||||||
|
SegmentArrayMessageReader messageReader(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(messageReader.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("for pointers to empty structs, preorder is not canonical") {
|
||||||
|
AlignedData<4> nonNullEmptyStruct = {{
|
||||||
|
// Struct pointer, body immediately follows, two pointer fields, no data.
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||||
|
|
||||||
|
// First pointer field, struct, offset of 1, data size 1, no pointers.
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Non-null pointer to empty struct. Offset puts it in "preorder". Would need to have
|
||||||
|
// an offset of -1 to be canonical.
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Body of struct filled with non-zero data.
|
||||||
|
0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(nonNullEmptyStruct.words, 4)};
|
||||||
|
SegmentArrayMessageReader messageReader(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!messageReader.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical requires pointer preorder") {
|
||||||
|
AlignedData<5> misorderedSegment = {{
|
||||||
|
//Struct pointer, data immediately follows, two pointer fields, no data
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||||
|
//Pointer field 1, pointing to the last entry, data size 1, no pointer
|
||||||
|
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
//Pointer field 2, pointing to the next entry, data size 2, no pointer
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
//Data for field 2
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
|
//Data for field 1
|
||||||
|
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(misorderedSegment.words,
|
||||||
|
5)};
|
||||||
|
SegmentArrayMessageReader outOfOrder(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!outOfOrder.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical requires dense packing") {
|
||||||
|
AlignedData<3> gapSegment = {{
|
||||||
|
//Struct pointer, data after a gap
|
||||||
|
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
//The gap
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
//Data for field 1
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(gapSegment.words,
|
||||||
|
3)};
|
||||||
|
SegmentArrayMessageReader gap(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!gap.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical rejects multi-segment messages") {
|
||||||
|
AlignedData<1> farPtr = {{
|
||||||
|
//Far pointer to next segment
|
||||||
|
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
|
||||||
|
AlignedData<2> farTarget = {{
|
||||||
|
//Struct pointer (needed to make the far pointer legal)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
//Dummy data
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
|
}};
|
||||||
|
|
||||||
|
kj::ArrayPtr<const word> segments[2] = {
|
||||||
|
kj::arrayPtr(farPtr.words, 1),
|
||||||
|
kj::arrayPtr(farTarget.words, 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
SegmentArrayMessageReader multiSegmentMessage(kj::arrayPtr(segments, 2));
|
||||||
|
KJ_ASSERT(!multiSegmentMessage.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical rejects zero segment messages") {
|
||||||
|
SegmentArrayMessageReader zero(kj::arrayPtr((kj::ArrayPtr<const word>*)NULL,
|
||||||
|
0));
|
||||||
|
KJ_ASSERT(!zero.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical requires truncation of 0-valued struct fields") {
|
||||||
|
AlignedData<2> nonTruncatedSegment = {{
|
||||||
|
//Struct pointer, data immediately follows
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
//Default data value, should have been truncated
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {
|
||||||
|
kj::arrayPtr(nonTruncatedSegment.words, 3)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader nonTruncated(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!nonTruncated.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical rejects unused trailing words") {
|
||||||
|
AlignedData<3> segment = {{
|
||||||
|
// Struct pointer, data in next word
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Data section of struct
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
|
|
||||||
|
// Trailing zero word
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(segment.words, 3)};
|
||||||
|
SegmentArrayMessageReader message(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!message.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical accepts empty inline composite list of zero-sized structs") {
|
||||||
|
AlignedData<3> segment = {{
|
||||||
|
// Struct pointer, pointer in next word
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
|
||||||
|
// List pointer, inline composite, zero words long
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Tag word
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(segment.words, 3)};
|
||||||
|
SegmentArrayMessageReader message(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(message.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical rejects inline composite list with inaccurate word-length") {
|
||||||
|
AlignedData<6> segment = {{
|
||||||
|
// Struct pointer, no offset, pointer section has two entries
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||||
|
|
||||||
|
// List pointer, offset of one, inline composite, two words long
|
||||||
|
// (The list only needs to be one word long to hold its actual elements;
|
||||||
|
// therefore this message is not canonical.)
|
||||||
|
0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Struct pointer, offset two, data section has one word
|
||||||
|
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Tag word, struct, one element, one word data section
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Data section of struct element of list
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
|
|
||||||
|
// Data section of struct field in top-level struct
|
||||||
|
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(segment.words, 6)};
|
||||||
|
SegmentArrayMessageReader message(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!message.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("upgraded lists can be canonicalized") {
|
||||||
|
AlignedData<7> upgradedList = {{
|
||||||
|
//Struct pointer, data immediately follows, 4 pointer fields, no data
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||||
|
//Three words of default pointers to get to the int16 list
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
//List pointer, 3 int16s.
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
|
||||||
|
//First two elements
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06,
|
||||||
|
//Last element
|
||||||
|
0x07, 0x08, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {
|
||||||
|
kj::arrayPtr(upgradedList.words, 7)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader upgraded(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
auto root = upgraded.getRoot<TestLists>();
|
||||||
|
canonicalize(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("isCanonical requires truncation of 0-valued struct fields in all list members") {
|
||||||
|
AlignedData<6> nonTruncatedList = {{
|
||||||
|
//List pointer, composite,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
|
||||||
|
//Struct tag word, 2 structs, 2 data words per struct
|
||||||
|
0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
//Data word non-null
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
|
//Null trailing word
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
//Data word non-null
|
||||||
|
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
|
||||||
|
//Null trailing word
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {
|
||||||
|
kj::arrayPtr(nonTruncatedList.words, 6)
|
||||||
|
};
|
||||||
|
SegmentArrayMessageReader nonTruncated(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!nonTruncated.isCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("primitive list with nonzero padding") {
|
||||||
|
AlignedData<3> segment = {{
|
||||||
|
// Struct, one pointer field.
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
|
||||||
|
// List of three byte-sized elements.
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Fourth byte is non-zero!
|
||||||
|
0x01, 0x02, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(segment.words, 3)};
|
||||||
|
SegmentArrayMessageReader message(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!message.isCanonical());
|
||||||
|
|
||||||
|
auto canonicalWords = canonicalize(message.getRoot<test::TestAnyPointer>());
|
||||||
|
|
||||||
|
AlignedData<3> canonicalSegment = {{
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
|
||||||
|
ASSERT_EQ(canonicalWords.asBytes(), kj::arrayPtr(canonicalSegment.bytes, 3 * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("bit list with nonzero padding") {
|
||||||
|
AlignedData<3> segment = {{
|
||||||
|
// Struct, one pointer field.
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
|
||||||
|
// List of eleven bit-sized elements.
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
// Twelfth bit is non-zero!
|
||||||
|
0xee, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(segment.words, 3)};
|
||||||
|
SegmentArrayMessageReader message(kj::arrayPtr(segments, 1));
|
||||||
|
|
||||||
|
KJ_ASSERT(!message.isCanonical());
|
||||||
|
|
||||||
|
auto canonicalWords = canonicalize(message.getRoot<test::TestAnyPointer>());
|
||||||
|
|
||||||
|
AlignedData<3> canonicalSegment = {{
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
|
||||||
|
0xee, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
|
||||||
|
ASSERT_EQ(canonicalWords.asBytes(), kj::arrayPtr(canonicalSegment.bytes, 3 * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace _ (private)
|
||||||
|
} // namespace capnp
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,729 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#define CAPNP_PRIVATE
|
||||||
|
|
||||||
|
#include "capability.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "arena.h"
|
||||||
|
#include <kj/refcount.h>
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <kj/vector.h>
|
||||||
|
#include <map>
|
||||||
|
#include "generated-header-support.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
namespace _ {
|
||||||
|
|
||||||
|
void setGlobalBrokenCapFactoryForLayoutCpp(BrokenCapFactory& factory);
|
||||||
|
// Defined in layout.c++.
|
||||||
|
|
||||||
|
} // namespace _
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static kj::Own<ClientHook> newNullCap();
|
||||||
|
|
||||||
|
class BrokenCapFactoryImpl: public _::BrokenCapFactory {
|
||||||
|
public:
|
||||||
|
kj::Own<ClientHook> newBrokenCap(kj::StringPtr description) override {
|
||||||
|
return capnp::newBrokenCap(description);
|
||||||
|
}
|
||||||
|
kj::Own<ClientHook> newNullCap() override {
|
||||||
|
return capnp::newNullCap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static BrokenCapFactoryImpl brokenCapFactory;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ClientHook::ClientHook() {
|
||||||
|
setGlobalBrokenCapFactoryForLayoutCpp(brokenCapFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
Capability::Client::Client(decltype(nullptr))
|
||||||
|
: hook(newNullCap()) {}
|
||||||
|
|
||||||
|
Capability::Client::Client(kj::Exception&& exception)
|
||||||
|
: hook(newBrokenCap(kj::mv(exception))) {}
|
||||||
|
|
||||||
|
kj::Promise<void> Capability::Server::internalUnimplemented(
|
||||||
|
const char* actualInterfaceName, uint64_t requestedTypeId) {
|
||||||
|
return KJ_EXCEPTION(UNIMPLEMENTED, "Requested interface not implemented.",
|
||||||
|
actualInterfaceName, requestedTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> Capability::Server::internalUnimplemented(
|
||||||
|
const char* interfaceName, uint64_t typeId, uint16_t methodId) {
|
||||||
|
return KJ_EXCEPTION(UNIMPLEMENTED, "Method not implemented.", interfaceName, typeId, methodId);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> Capability::Server::internalUnimplemented(
|
||||||
|
const char* interfaceName, const char* methodName, uint64_t typeId, uint16_t methodId) {
|
||||||
|
return KJ_EXCEPTION(UNIMPLEMENTED, "Method not implemented.", interfaceName,
|
||||||
|
typeId, methodName, methodId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseHook::~ResponseHook() noexcept(false) {}
|
||||||
|
|
||||||
|
kj::Promise<void> ClientHook::whenResolved() {
|
||||||
|
KJ_IF_MAYBE(promise, whenMoreResolved()) {
|
||||||
|
return promise->then([](kj::Own<ClientHook>&& resolution) {
|
||||||
|
return resolution->whenResolved();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
static inline uint firstSegmentSize(kj::Maybe<MessageSize> sizeHint) {
|
||||||
|
KJ_IF_MAYBE(s, sizeHint) {
|
||||||
|
return s->wordCount;
|
||||||
|
} else {
|
||||||
|
return SUGGESTED_FIRST_SEGMENT_WORDS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalResponse final: public ResponseHook, public kj::Refcounted {
|
||||||
|
public:
|
||||||
|
LocalResponse(kj::Maybe<MessageSize> sizeHint)
|
||||||
|
: message(firstSegmentSize(sizeHint)) {}
|
||||||
|
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalCallContext final: public CallContextHook, public kj::Refcounted {
|
||||||
|
public:
|
||||||
|
LocalCallContext(kj::Own<MallocMessageBuilder>&& request, kj::Own<ClientHook> clientRef,
|
||||||
|
kj::Own<kj::PromiseFulfiller<void>> cancelAllowedFulfiller)
|
||||||
|
: request(kj::mv(request)), clientRef(kj::mv(clientRef)),
|
||||||
|
cancelAllowedFulfiller(kj::mv(cancelAllowedFulfiller)) {}
|
||||||
|
|
||||||
|
AnyPointer::Reader getParams() override {
|
||||||
|
KJ_IF_MAYBE(r, request) {
|
||||||
|
return r->get()->getRoot<AnyPointer>();
|
||||||
|
} else {
|
||||||
|
KJ_FAIL_REQUIRE("Can't call getParams() after releaseParams().");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void releaseParams() override {
|
||||||
|
request = nullptr;
|
||||||
|
}
|
||||||
|
AnyPointer::Builder getResults(kj::Maybe<MessageSize> sizeHint) override {
|
||||||
|
if (response == nullptr) {
|
||||||
|
auto localResponse = kj::refcounted<LocalResponse>(sizeHint);
|
||||||
|
responseBuilder = localResponse->message.getRoot<AnyPointer>();
|
||||||
|
response = Response<AnyPointer>(responseBuilder.asReader(), kj::mv(localResponse));
|
||||||
|
}
|
||||||
|
return responseBuilder;
|
||||||
|
}
|
||||||
|
kj::Promise<void> tailCall(kj::Own<RequestHook>&& request) override {
|
||||||
|
auto result = directTailCall(kj::mv(request));
|
||||||
|
KJ_IF_MAYBE(f, tailCallPipelineFulfiller) {
|
||||||
|
f->get()->fulfill(AnyPointer::Pipeline(kj::mv(result.pipeline)));
|
||||||
|
}
|
||||||
|
return kj::mv(result.promise);
|
||||||
|
}
|
||||||
|
ClientHook::VoidPromiseAndPipeline directTailCall(kj::Own<RequestHook>&& request) override {
|
||||||
|
KJ_REQUIRE(response == nullptr, "Can't call tailCall() after initializing the results struct.");
|
||||||
|
|
||||||
|
auto promise = request->send();
|
||||||
|
|
||||||
|
auto voidPromise = promise.then([this](Response<AnyPointer>&& tailResponse) {
|
||||||
|
response = kj::mv(tailResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { kj::mv(voidPromise), PipelineHook::from(kj::mv(promise)) };
|
||||||
|
}
|
||||||
|
kj::Promise<AnyPointer::Pipeline> onTailCall() override {
|
||||||
|
auto paf = kj::newPromiseAndFulfiller<AnyPointer::Pipeline>();
|
||||||
|
tailCallPipelineFulfiller = kj::mv(paf.fulfiller);
|
||||||
|
return kj::mv(paf.promise);
|
||||||
|
}
|
||||||
|
void allowCancellation() override {
|
||||||
|
cancelAllowedFulfiller->fulfill();
|
||||||
|
}
|
||||||
|
kj::Own<CallContextHook> addRef() override {
|
||||||
|
return kj::addRef(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<kj::Own<MallocMessageBuilder>> request;
|
||||||
|
kj::Maybe<Response<AnyPointer>> response;
|
||||||
|
AnyPointer::Builder responseBuilder = nullptr; // only valid if `response` is non-null
|
||||||
|
kj::Own<ClientHook> clientRef;
|
||||||
|
kj::Maybe<kj::Own<kj::PromiseFulfiller<AnyPointer::Pipeline>>> tailCallPipelineFulfiller;
|
||||||
|
kj::Own<kj::PromiseFulfiller<void>> cancelAllowedFulfiller;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalRequest final: public RequestHook {
|
||||||
|
public:
|
||||||
|
inline LocalRequest(uint64_t interfaceId, uint16_t methodId,
|
||||||
|
kj::Maybe<MessageSize> sizeHint, kj::Own<ClientHook> client)
|
||||||
|
: message(kj::heap<MallocMessageBuilder>(firstSegmentSize(sizeHint))),
|
||||||
|
interfaceId(interfaceId), methodId(methodId), client(kj::mv(client)) {}
|
||||||
|
|
||||||
|
RemotePromise<AnyPointer> send() override {
|
||||||
|
KJ_REQUIRE(message.get() != nullptr, "Already called send() on this request.");
|
||||||
|
|
||||||
|
// For the lambda capture.
|
||||||
|
uint64_t interfaceId = this->interfaceId;
|
||||||
|
uint16_t methodId = this->methodId;
|
||||||
|
|
||||||
|
auto cancelPaf = kj::newPromiseAndFulfiller<void>();
|
||||||
|
|
||||||
|
auto context = kj::refcounted<LocalCallContext>(
|
||||||
|
kj::mv(message), client->addRef(), kj::mv(cancelPaf.fulfiller));
|
||||||
|
auto promiseAndPipeline = client->call(interfaceId, methodId, kj::addRef(*context));
|
||||||
|
|
||||||
|
// We have to make sure the call is not canceled unless permitted. We need to fork the promise
|
||||||
|
// so that if the client drops their copy, the promise isn't necessarily canceled.
|
||||||
|
auto forked = promiseAndPipeline.promise.fork();
|
||||||
|
|
||||||
|
// We daemonize one branch, but only after joining it with the promise that fires if
|
||||||
|
// cancellation is allowed.
|
||||||
|
forked.addBranch()
|
||||||
|
.attach(kj::addRef(*context))
|
||||||
|
.exclusiveJoin(kj::mv(cancelPaf.promise))
|
||||||
|
.detach([](kj::Exception&&) {}); // ignore exceptions
|
||||||
|
|
||||||
|
// Now the other branch returns the response from the context.
|
||||||
|
auto promise = forked.addBranch().then(kj::mvCapture(context,
|
||||||
|
[](kj::Own<LocalCallContext>&& context) {
|
||||||
|
context->getResults(MessageSize { 0, 0 }); // force response allocation
|
||||||
|
return kj::mv(KJ_ASSERT_NONNULL(context->response));
|
||||||
|
}));
|
||||||
|
|
||||||
|
// We return the other branch.
|
||||||
|
return RemotePromise<AnyPointer>(
|
||||||
|
kj::mv(promise), AnyPointer::Pipeline(kj::mv(promiseAndPipeline.pipeline)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* getBrand() override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<MallocMessageBuilder> message;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t interfaceId;
|
||||||
|
uint16_t methodId;
|
||||||
|
kj::Own<ClientHook> client;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// Call queues
|
||||||
|
//
|
||||||
|
// These classes handle pipelining in the case where calls need to be queued in-memory until some
|
||||||
|
// local operation completes.
|
||||||
|
|
||||||
|
class QueuedPipeline final: public PipelineHook, public kj::Refcounted {
|
||||||
|
// A PipelineHook which simply queues calls while waiting for a PipelineHook to which to forward
|
||||||
|
// them.
|
||||||
|
|
||||||
|
public:
|
||||||
|
QueuedPipeline(kj::Promise<kj::Own<PipelineHook>>&& promiseParam)
|
||||||
|
: promise(promiseParam.fork()),
|
||||||
|
selfResolutionOp(promise.addBranch().then([this](kj::Own<PipelineHook>&& inner) {
|
||||||
|
redirect = kj::mv(inner);
|
||||||
|
}, [this](kj::Exception&& exception) {
|
||||||
|
redirect = newBrokenPipeline(kj::mv(exception));
|
||||||
|
}).eagerlyEvaluate(nullptr)) {}
|
||||||
|
|
||||||
|
kj::Own<PipelineHook> addRef() override {
|
||||||
|
return kj::addRef(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) override {
|
||||||
|
auto copy = kj::heapArrayBuilder<PipelineOp>(ops.size());
|
||||||
|
for (auto& op: ops) {
|
||||||
|
copy.add(op);
|
||||||
|
}
|
||||||
|
return getPipelinedCap(copy.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> getPipelinedCap(kj::Array<PipelineOp>&& ops) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::ForkedPromise<kj::Own<PipelineHook>> promise;
|
||||||
|
|
||||||
|
kj::Maybe<kj::Own<PipelineHook>> redirect;
|
||||||
|
// Once the promise resolves, this will become non-null and point to the underlying object.
|
||||||
|
|
||||||
|
kj::Promise<void> selfResolutionOp;
|
||||||
|
// Represents the operation which will set `redirect` when possible.
|
||||||
|
};
|
||||||
|
|
||||||
|
class QueuedClient final: public ClientHook, public kj::Refcounted {
|
||||||
|
// A ClientHook which simply queues calls while waiting for a ClientHook to which to forward
|
||||||
|
// them.
|
||||||
|
|
||||||
|
public:
|
||||||
|
QueuedClient(kj::Promise<kj::Own<ClientHook>>&& promiseParam)
|
||||||
|
: promise(promiseParam.fork()),
|
||||||
|
selfResolutionOp(promise.addBranch().then([this](kj::Own<ClientHook>&& inner) {
|
||||||
|
redirect = kj::mv(inner);
|
||||||
|
}, [this](kj::Exception&& exception) {
|
||||||
|
redirect = newBrokenCap(kj::mv(exception));
|
||||||
|
}).eagerlyEvaluate(nullptr)),
|
||||||
|
promiseForCallForwarding(promise.addBranch().fork()),
|
||||||
|
promiseForClientResolution(promise.addBranch().fork()) {}
|
||||||
|
|
||||||
|
Request<AnyPointer, AnyPointer> newCall(
|
||||||
|
uint64_t interfaceId, uint16_t methodId, kj::Maybe<MessageSize> sizeHint) override {
|
||||||
|
auto hook = kj::heap<LocalRequest>(
|
||||||
|
interfaceId, methodId, sizeHint, kj::addRef(*this));
|
||||||
|
auto root = hook->message->getRoot<AnyPointer>();
|
||||||
|
return Request<AnyPointer, AnyPointer>(root, kj::mv(hook));
|
||||||
|
}
|
||||||
|
|
||||||
|
VoidPromiseAndPipeline call(uint64_t interfaceId, uint16_t methodId,
|
||||||
|
kj::Own<CallContextHook>&& context) override {
|
||||||
|
// This is a bit complicated. We need to initiate this call later on. When we initiate the
|
||||||
|
// call, we'll get a void promise for its completion and a pipeline object. Right now, we have
|
||||||
|
// to produce a similar void promise and pipeline that will eventually be chained to those.
|
||||||
|
// The problem is, these are two independent objects, but they both depend on the result of
|
||||||
|
// one future call.
|
||||||
|
//
|
||||||
|
// So, we need to set up a continuation that will initiate the call later, then we need to
|
||||||
|
// fork the promise for that continuation in order to send the completion promise and the
|
||||||
|
// pipeline to their respective places.
|
||||||
|
//
|
||||||
|
// TODO(perf): Too much reference counting? Can we do better? Maybe a way to fork
|
||||||
|
// Promise<Tuple<T, U>> into Tuple<Promise<T>, Promise<U>>?
|
||||||
|
|
||||||
|
struct CallResultHolder: public kj::Refcounted {
|
||||||
|
// Essentially acts as a refcounted \VoidPromiseAndPipeline, so that we can create a promise
|
||||||
|
// for it and fork that promise.
|
||||||
|
|
||||||
|
VoidPromiseAndPipeline content;
|
||||||
|
// One branch of the fork will use content.promise, the other branch will use
|
||||||
|
// content.pipeline. Neither branch will touch the other's piece.
|
||||||
|
|
||||||
|
inline CallResultHolder(VoidPromiseAndPipeline&& content): content(kj::mv(content)) {}
|
||||||
|
|
||||||
|
kj::Own<CallResultHolder> addRef() { return kj::addRef(*this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a promise for the call initiation.
|
||||||
|
kj::ForkedPromise<kj::Own<CallResultHolder>> callResultPromise =
|
||||||
|
promiseForCallForwarding.addBranch().then(kj::mvCapture(context,
|
||||||
|
[=](kj::Own<CallContextHook>&& context, kj::Own<ClientHook>&& client){
|
||||||
|
return kj::refcounted<CallResultHolder>(
|
||||||
|
client->call(interfaceId, methodId, kj::mv(context)));
|
||||||
|
})).fork();
|
||||||
|
|
||||||
|
// Create a promise that extracts the pipeline from the call initiation, and construct our
|
||||||
|
// QueuedPipeline to chain to it.
|
||||||
|
auto pipelinePromise = callResultPromise.addBranch().then(
|
||||||
|
[](kj::Own<CallResultHolder>&& callResult){
|
||||||
|
return kj::mv(callResult->content.pipeline);
|
||||||
|
});
|
||||||
|
auto pipeline = kj::refcounted<QueuedPipeline>(kj::mv(pipelinePromise));
|
||||||
|
|
||||||
|
// Create a promise that simply chains to the void promise produced by the call initiation.
|
||||||
|
auto completionPromise = callResultPromise.addBranch().then(
|
||||||
|
[](kj::Own<CallResultHolder>&& callResult){
|
||||||
|
return kj::mv(callResult->content.promise);
|
||||||
|
});
|
||||||
|
|
||||||
|
// OK, now we can actually return our thing.
|
||||||
|
return VoidPromiseAndPipeline { kj::mv(completionPromise), kj::mv(pipeline) };
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<ClientHook&> getResolved() override {
|
||||||
|
KJ_IF_MAYBE(inner, redirect) {
|
||||||
|
return **inner;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<kj::Promise<kj::Own<ClientHook>>> whenMoreResolved() override {
|
||||||
|
return promiseForClientResolution.addBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> addRef() override {
|
||||||
|
return kj::addRef(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* getBrand() override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef kj::ForkedPromise<kj::Own<ClientHook>> ClientHookPromiseFork;
|
||||||
|
|
||||||
|
kj::Maybe<kj::Own<ClientHook>> redirect;
|
||||||
|
// Once the promise resolves, this will become non-null and point to the underlying object.
|
||||||
|
|
||||||
|
ClientHookPromiseFork promise;
|
||||||
|
// Promise that resolves when we have a new ClientHook to forward to.
|
||||||
|
//
|
||||||
|
// This fork shall only have three branches: `selfResolutionOp`, `promiseForCallForwarding`, and
|
||||||
|
// `promiseForClientResolution`, in that order.
|
||||||
|
|
||||||
|
kj::Promise<void> selfResolutionOp;
|
||||||
|
// Represents the operation which will set `redirect` when possible.
|
||||||
|
|
||||||
|
ClientHookPromiseFork promiseForCallForwarding;
|
||||||
|
// When this promise resolves, each queued call will be forwarded to the real client. This needs
|
||||||
|
// to occur *before* any 'whenMoreResolved()' promises resolve, because we want to make sure
|
||||||
|
// previously-queued calls are delivered before any new calls made in response to the resolution.
|
||||||
|
|
||||||
|
ClientHookPromiseFork promiseForClientResolution;
|
||||||
|
// whenMoreResolved() returns forks of this promise. These must resolve *after* queued calls
|
||||||
|
// have been initiated (so that any calls made in the whenMoreResolved() handler are correctly
|
||||||
|
// delivered after calls made earlier), but *before* any queued calls return (because it might
|
||||||
|
// confuse the application if a queued call returns before the capability on which it was made
|
||||||
|
// resolves). Luckily, we know that queued calls will involve, at the very least, an
|
||||||
|
// eventLoop.evalLater.
|
||||||
|
};
|
||||||
|
|
||||||
|
kj::Own<ClientHook> QueuedPipeline::getPipelinedCap(kj::Array<PipelineOp>&& ops) {
|
||||||
|
KJ_IF_MAYBE(r, redirect) {
|
||||||
|
return r->get()->getPipelinedCap(kj::mv(ops));
|
||||||
|
} else {
|
||||||
|
auto clientPromise = promise.addBranch().then(kj::mvCapture(ops,
|
||||||
|
[](kj::Array<PipelineOp>&& ops, kj::Own<PipelineHook> pipeline) {
|
||||||
|
return pipeline->getPipelinedCap(kj::mv(ops));
|
||||||
|
}));
|
||||||
|
|
||||||
|
return kj::refcounted<QueuedClient>(kj::mv(clientPromise));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
class LocalPipeline final: public PipelineHook, public kj::Refcounted {
|
||||||
|
public:
|
||||||
|
inline LocalPipeline(kj::Own<CallContextHook>&& contextParam)
|
||||||
|
: context(kj::mv(contextParam)),
|
||||||
|
results(context->getResults(MessageSize { 0, 0 })) {}
|
||||||
|
|
||||||
|
kj::Own<PipelineHook> addRef() {
|
||||||
|
return kj::addRef(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) {
|
||||||
|
return results.getPipelinedCap(ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Own<CallContextHook> context;
|
||||||
|
AnyPointer::Reader results;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalClient final: public ClientHook, public kj::Refcounted {
|
||||||
|
public:
|
||||||
|
LocalClient(kj::Own<Capability::Server>&& serverParam)
|
||||||
|
: server(kj::mv(serverParam)) {
|
||||||
|
server->thisHook = this;
|
||||||
|
}
|
||||||
|
LocalClient(kj::Own<Capability::Server>&& serverParam,
|
||||||
|
_::CapabilityServerSetBase& capServerSet, void* ptr)
|
||||||
|
: server(kj::mv(serverParam)), capServerSet(&capServerSet), ptr(ptr) {
|
||||||
|
server->thisHook = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~LocalClient() noexcept(false) {
|
||||||
|
server->thisHook = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request<AnyPointer, AnyPointer> newCall(
|
||||||
|
uint64_t interfaceId, uint16_t methodId, kj::Maybe<MessageSize> sizeHint) override {
|
||||||
|
auto hook = kj::heap<LocalRequest>(
|
||||||
|
interfaceId, methodId, sizeHint, kj::addRef(*this));
|
||||||
|
auto root = hook->message->getRoot<AnyPointer>();
|
||||||
|
return Request<AnyPointer, AnyPointer>(root, kj::mv(hook));
|
||||||
|
}
|
||||||
|
|
||||||
|
VoidPromiseAndPipeline call(uint64_t interfaceId, uint16_t methodId,
|
||||||
|
kj::Own<CallContextHook>&& context) override {
|
||||||
|
auto contextPtr = context.get();
|
||||||
|
|
||||||
|
// We don't want to actually dispatch the call synchronously, because we don't want the callee
|
||||||
|
// to have any side effects before the promise is returned to the caller. This helps avoid
|
||||||
|
// race conditions.
|
||||||
|
//
|
||||||
|
// So, we do an evalLater() here.
|
||||||
|
//
|
||||||
|
// Note also that QueuedClient depends on this evalLater() to ensure that pipelined calls don't
|
||||||
|
// complete before 'whenMoreResolved()' promises resolve.
|
||||||
|
auto promise = kj::evalLater([this,interfaceId,methodId,contextPtr]() {
|
||||||
|
return server->dispatchCall(interfaceId, methodId,
|
||||||
|
CallContext<AnyPointer, AnyPointer>(*contextPtr));
|
||||||
|
}).attach(kj::addRef(*this));
|
||||||
|
|
||||||
|
// We have to fork this promise for the pipeline to receive a copy of the answer.
|
||||||
|
auto forked = promise.fork();
|
||||||
|
|
||||||
|
auto pipelinePromise = forked.addBranch().then(kj::mvCapture(context->addRef(),
|
||||||
|
[=](kj::Own<CallContextHook>&& context) -> kj::Own<PipelineHook> {
|
||||||
|
context->releaseParams();
|
||||||
|
return kj::refcounted<LocalPipeline>(kj::mv(context));
|
||||||
|
}));
|
||||||
|
|
||||||
|
auto tailPipelinePromise = context->onTailCall().then([](AnyPointer::Pipeline&& pipeline) {
|
||||||
|
return kj::mv(pipeline.hook);
|
||||||
|
});
|
||||||
|
|
||||||
|
pipelinePromise = pipelinePromise.exclusiveJoin(kj::mv(tailPipelinePromise));
|
||||||
|
|
||||||
|
auto completionPromise = forked.addBranch().attach(kj::mv(context));
|
||||||
|
|
||||||
|
return VoidPromiseAndPipeline { kj::mv(completionPromise),
|
||||||
|
kj::refcounted<QueuedPipeline>(kj::mv(pipelinePromise)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<ClientHook&> getResolved() override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<kj::Promise<kj::Own<ClientHook>>> whenMoreResolved() override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> addRef() override {
|
||||||
|
return kj::addRef(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* getBrand() override {
|
||||||
|
// We have no need to detect local objects.
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* getLocalServer(_::CapabilityServerSetBase& capServerSet) override {
|
||||||
|
if (this->capServerSet == &capServerSet) {
|
||||||
|
return ptr;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Own<Capability::Server> server;
|
||||||
|
_::CapabilityServerSetBase* capServerSet = nullptr;
|
||||||
|
void* ptr = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
kj::Own<ClientHook> Capability::Client::makeLocalClient(kj::Own<Capability::Server>&& server) {
|
||||||
|
return kj::refcounted<LocalClient>(kj::mv(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> newLocalPromiseClient(kj::Promise<kj::Own<ClientHook>>&& promise) {
|
||||||
|
return kj::refcounted<QueuedClient>(kj::mv(promise));
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<PipelineHook> newLocalPromisePipeline(kj::Promise<kj::Own<PipelineHook>>&& promise) {
|
||||||
|
return kj::refcounted<QueuedPipeline>(kj::mv(promise));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class BrokenPipeline final: public PipelineHook, public kj::Refcounted {
|
||||||
|
public:
|
||||||
|
BrokenPipeline(const kj::Exception& exception): exception(exception) {}
|
||||||
|
|
||||||
|
kj::Own<PipelineHook> addRef() override {
|
||||||
|
return kj::addRef(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Exception exception;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BrokenRequest final: public RequestHook {
|
||||||
|
public:
|
||||||
|
BrokenRequest(const kj::Exception& exception, kj::Maybe<MessageSize> sizeHint)
|
||||||
|
: exception(exception), message(firstSegmentSize(sizeHint)) {}
|
||||||
|
|
||||||
|
RemotePromise<AnyPointer> send() override {
|
||||||
|
return RemotePromise<AnyPointer>(kj::cp(exception),
|
||||||
|
AnyPointer::Pipeline(kj::refcounted<BrokenPipeline>(exception)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* getBrand() override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Exception exception;
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BrokenClient final: public ClientHook, public kj::Refcounted {
|
||||||
|
public:
|
||||||
|
BrokenClient(const kj::Exception& exception, bool resolved, const void* brand = nullptr)
|
||||||
|
: exception(exception), resolved(resolved), brand(brand) {}
|
||||||
|
BrokenClient(const kj::StringPtr description, bool resolved, const void* brand = nullptr)
|
||||||
|
: exception(kj::Exception::Type::FAILED, "", 0, kj::str(description)),
|
||||||
|
resolved(resolved), brand(brand) {}
|
||||||
|
|
||||||
|
Request<AnyPointer, AnyPointer> newCall(
|
||||||
|
uint64_t interfaceId, uint16_t methodId, kj::Maybe<MessageSize> sizeHint) override {
|
||||||
|
return newBrokenRequest(kj::cp(exception), sizeHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoidPromiseAndPipeline call(uint64_t interfaceId, uint16_t methodId,
|
||||||
|
kj::Own<CallContextHook>&& context) override {
|
||||||
|
return VoidPromiseAndPipeline { kj::cp(exception), kj::refcounted<BrokenPipeline>(exception) };
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<ClientHook&> getResolved() override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<kj::Promise<kj::Own<ClientHook>>> whenMoreResolved() override {
|
||||||
|
if (resolved) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return kj::Promise<kj::Own<ClientHook>>(kj::cp(exception));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> addRef() override {
|
||||||
|
return kj::addRef(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* getBrand() override {
|
||||||
|
return brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Exception exception;
|
||||||
|
bool resolved;
|
||||||
|
const void* brand;
|
||||||
|
};
|
||||||
|
|
||||||
|
kj::Own<ClientHook> BrokenPipeline::getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) {
|
||||||
|
return kj::refcounted<BrokenClient>(exception, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> newNullCap() {
|
||||||
|
// A null capability, unlike other broken capabilities, is considered resolved.
|
||||||
|
return kj::refcounted<BrokenClient>("Called null capability.", true,
|
||||||
|
&ClientHook::NULL_CAPABILITY_BRAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
kj::Own<ClientHook> newBrokenCap(kj::StringPtr reason) {
|
||||||
|
return kj::refcounted<BrokenClient>(reason, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<ClientHook> newBrokenCap(kj::Exception&& reason) {
|
||||||
|
return kj::refcounted<BrokenClient>(kj::mv(reason), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Own<PipelineHook> newBrokenPipeline(kj::Exception&& reason) {
|
||||||
|
return kj::refcounted<BrokenPipeline>(kj::mv(reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
Request<AnyPointer, AnyPointer> newBrokenRequest(
|
||||||
|
kj::Exception&& reason, kj::Maybe<MessageSize> sizeHint) {
|
||||||
|
auto hook = kj::heap<BrokenRequest>(kj::mv(reason), sizeHint);
|
||||||
|
auto root = hook->message.getRoot<AnyPointer>();
|
||||||
|
return Request<AnyPointer, AnyPointer>(root, kj::mv(hook));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
ReaderCapabilityTable::ReaderCapabilityTable(
|
||||||
|
kj::Array<kj::Maybe<kj::Own<ClientHook>>> table)
|
||||||
|
: table(kj::mv(table)) {
|
||||||
|
setGlobalBrokenCapFactoryForLayoutCpp(brokenCapFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<kj::Own<ClientHook>> ReaderCapabilityTable::extractCap(uint index) {
|
||||||
|
if (index < table.size()) {
|
||||||
|
return table[index].map([](kj::Own<ClientHook>& cap) { return cap->addRef(); });
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuilderCapabilityTable::BuilderCapabilityTable() {
|
||||||
|
setGlobalBrokenCapFactoryForLayoutCpp(brokenCapFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Maybe<kj::Own<ClientHook>> BuilderCapabilityTable::extractCap(uint index) {
|
||||||
|
if (index < table.size()) {
|
||||||
|
return table[index].map([](kj::Own<ClientHook>& cap) { return cap->addRef(); });
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint BuilderCapabilityTable::injectCap(kj::Own<ClientHook>&& cap) {
|
||||||
|
uint result = table.size();
|
||||||
|
table.add(kj::mv(cap));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuilderCapabilityTable::dropCap(uint index) {
|
||||||
|
KJ_ASSERT(index < table.size(), "Invalid capability descriptor in message.") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
table[index] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// CapabilityServerSet
|
||||||
|
|
||||||
|
namespace _ { // private
|
||||||
|
|
||||||
|
Capability::Client CapabilityServerSetBase::addInternal(
|
||||||
|
kj::Own<Capability::Server>&& server, void* ptr) {
|
||||||
|
return Capability::Client(kj::refcounted<LocalClient>(kj::mv(server), *this, ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void*> CapabilityServerSetBase::getLocalServerInternal(Capability::Client& client) {
|
||||||
|
ClientHook* hook = client.hook.get();
|
||||||
|
|
||||||
|
// Get the most-resolved-so-far version of the hook.
|
||||||
|
KJ_IF_MAYBE(h, hook->getResolved()) {
|
||||||
|
hook = h;
|
||||||
|
};
|
||||||
|
|
||||||
|
KJ_IF_MAYBE(p, hook->whenMoreResolved()) {
|
||||||
|
// This hook is an unresolved promise. We need to wait for it.
|
||||||
|
return p->attach(hook->addRef())
|
||||||
|
.then([this](kj::Own<ClientHook>&& resolved) {
|
||||||
|
Capability::Client client(kj::mv(resolved));
|
||||||
|
return getLocalServerInternal(client);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return hook->getLocalServer(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace _ (private)
|
||||||
|
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,907 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
|
||||||
|
#pragma GCC system_header
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CAPNP_LITE
|
||||||
|
#error "RPC APIs, including this header, are not available in lite mode."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <kj/async.h>
|
||||||
|
#include <kj/vector.h>
|
||||||
|
#include "raw-schema.h"
|
||||||
|
#include "any.h"
|
||||||
|
#include "pointer-helpers.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
template <typename Results>
|
||||||
|
class Response;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class RemotePromise: public kj::Promise<Response<T>>, public T::Pipeline {
|
||||||
|
// A Promise which supports pipelined calls. T is typically a struct type. T must declare
|
||||||
|
// an inner "mix-in" type "Pipeline" which implements pipelining; RemotePromise simply
|
||||||
|
// multiply-inherits that type along with Promise<Response<T>>. T::Pipeline must be movable,
|
||||||
|
// but does not need to be copyable (i.e. just like Promise<T>).
|
||||||
|
//
|
||||||
|
// The promise is for an owned pointer so that the RPC system can allocate the MessageReader
|
||||||
|
// itself.
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline RemotePromise(kj::Promise<Response<T>>&& promise, typename T::Pipeline&& pipeline)
|
||||||
|
: kj::Promise<Response<T>>(kj::mv(promise)),
|
||||||
|
T::Pipeline(kj::mv(pipeline)) {}
|
||||||
|
inline RemotePromise(decltype(nullptr))
|
||||||
|
: kj::Promise<Response<T>>(nullptr),
|
||||||
|
T::Pipeline(nullptr) {}
|
||||||
|
KJ_DISALLOW_COPY(RemotePromise);
|
||||||
|
RemotePromise(RemotePromise&& other) = default;
|
||||||
|
RemotePromise& operator=(RemotePromise&& other) = default;
|
||||||
|
|
||||||
|
static RemotePromise<T> reducePromise(kj::Promise<RemotePromise>&& promise);
|
||||||
|
// Hook for KJ so that Promise<RemotePromise<T>> automatically reduces to RemotePromise<T>.
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalClient;
|
||||||
|
namespace _ { // private
|
||||||
|
extern const RawSchema NULL_INTERFACE_SCHEMA; // defined in schema.c++
|
||||||
|
class CapabilityServerSetBase;
|
||||||
|
} // namespace _ (private)
|
||||||
|
|
||||||
|
struct Capability {
|
||||||
|
// A capability without type-safe methods. Typed capability clients wrap `Client` and typed
|
||||||
|
// capability servers subclass `Server` to dispatch to the regular, typed methods.
|
||||||
|
|
||||||
|
class Client;
|
||||||
|
class Server;
|
||||||
|
|
||||||
|
struct _capnpPrivate {
|
||||||
|
struct IsInterface;
|
||||||
|
static constexpr uint64_t typeId = 0x3;
|
||||||
|
static constexpr Kind kind = Kind::INTERFACE;
|
||||||
|
static constexpr _::RawSchema const* schema = &_::NULL_INTERFACE_SCHEMA;
|
||||||
|
|
||||||
|
static const _::RawBrandedSchema* brand() {
|
||||||
|
return &_::NULL_INTERFACE_SCHEMA.defaultBrand;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// Capability clients
|
||||||
|
|
||||||
|
class RequestHook;
|
||||||
|
class ResponseHook;
|
||||||
|
class PipelineHook;
|
||||||
|
class ClientHook;
|
||||||
|
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
class Request: public Params::Builder {
|
||||||
|
// A call that hasn't been sent yet. This class extends a Builder for the call's "Params"
|
||||||
|
// structure with a method send() that actually sends it.
|
||||||
|
//
|
||||||
|
// Given a Cap'n Proto method `foo(a :A, b :B): C`, the generated client interface will have
|
||||||
|
// a method `Request<FooParams, C> fooRequest()` (as well as a convenience method
|
||||||
|
// `RemotePromise<C> foo(A::Reader a, B::Reader b)`).
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline Request(typename Params::Builder builder, kj::Own<RequestHook>&& hook)
|
||||||
|
: Params::Builder(builder), hook(kj::mv(hook)) {}
|
||||||
|
inline Request(decltype(nullptr)): Params::Builder(nullptr) {}
|
||||||
|
|
||||||
|
RemotePromise<Results> send() KJ_WARN_UNUSED_RESULT;
|
||||||
|
// Send the call and return a promise for the results.
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Own<RequestHook> hook;
|
||||||
|
|
||||||
|
friend class Capability::Client;
|
||||||
|
friend struct DynamicCapability;
|
||||||
|
template <typename, typename>
|
||||||
|
friend class CallContext;
|
||||||
|
friend class RequestHook;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Results>
|
||||||
|
class Response: public Results::Reader {
|
||||||
|
// A completed call. This class extends a Reader for the call's answer structure. The Response
|
||||||
|
// is move-only -- once it goes out-of-scope, the underlying message will be freed.
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline Response(typename Results::Reader reader, kj::Own<ResponseHook>&& hook)
|
||||||
|
: Results::Reader(reader), hook(kj::mv(hook)) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Own<ResponseHook> hook;
|
||||||
|
|
||||||
|
template <typename, typename>
|
||||||
|
friend class Request;
|
||||||
|
friend class ResponseHook;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Capability::Client {
|
||||||
|
// Base type for capability clients.
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef Capability Reads;
|
||||||
|
typedef Capability Calls;
|
||||||
|
|
||||||
|
Client(decltype(nullptr));
|
||||||
|
// If you need to declare a Client before you have anything to assign to it (perhaps because
|
||||||
|
// the assignment is going to occur in an if/else scope), you can start by initializing it to
|
||||||
|
// `nullptr`. The resulting client is not meant to be called and throws exceptions from all
|
||||||
|
// methods.
|
||||||
|
|
||||||
|
template <typename T, typename = kj::EnableIf<kj::canConvert<T*, Capability::Server*>()>>
|
||||||
|
Client(kj::Own<T>&& server);
|
||||||
|
// Make a client capability that wraps the given server capability. The server's methods will
|
||||||
|
// only be executed in the given EventLoop, regardless of what thread calls the client's methods.
|
||||||
|
|
||||||
|
template <typename T, typename = kj::EnableIf<kj::canConvert<T*, Client*>()>>
|
||||||
|
Client(kj::Promise<T>&& promise);
|
||||||
|
// Make a client from a promise for a future client. The resulting client queues calls until the
|
||||||
|
// promise resolves.
|
||||||
|
|
||||||
|
Client(kj::Exception&& exception);
|
||||||
|
// Make a broken client that throws the given exception from all calls.
|
||||||
|
|
||||||
|
Client(Client& other);
|
||||||
|
Client& operator=(Client& other);
|
||||||
|
// Copies by reference counting. Warning: This refcounting is not thread-safe. All copies of
|
||||||
|
// the client must remain in one thread.
|
||||||
|
|
||||||
|
Client(Client&&) = default;
|
||||||
|
Client& operator=(Client&&) = default;
|
||||||
|
// Move constructor avoids reference counting.
|
||||||
|
|
||||||
|
explicit Client(kj::Own<ClientHook>&& hook);
|
||||||
|
// For use by the RPC implementation: Wrap a ClientHook.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
typename T::Client castAs();
|
||||||
|
// Reinterpret the capability as implementing the given interface. Note that no error will occur
|
||||||
|
// here if the capability does not actually implement this interface, but later method calls will
|
||||||
|
// fail. It's up to the application to decide how indicate that additional interfaces are
|
||||||
|
// supported.
|
||||||
|
//
|
||||||
|
// TODO(perf): GCC 4.8 / Clang 3.3: rvalue-qualified version for better performance.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
typename T::Client castAs(InterfaceSchema schema);
|
||||||
|
// Dynamic version. `T` must be `DynamicCapability`, and you must `#include <capnp/dynamic.h>`.
|
||||||
|
|
||||||
|
kj::Promise<void> whenResolved();
|
||||||
|
// If the capability is actually only a promise, the returned promise resolves once the
|
||||||
|
// capability itself has resolved to its final destination (or propagates the exception if
|
||||||
|
// the capability promise is rejected). This is mainly useful for error-checking in the case
|
||||||
|
// where no calls are being made. There is no reason to wait for this before making calls; if
|
||||||
|
// the capability does not resolve, the call results will propagate the error.
|
||||||
|
|
||||||
|
Request<AnyPointer, AnyPointer> typelessRequest(
|
||||||
|
uint64_t interfaceId, uint16_t methodId,
|
||||||
|
kj::Maybe<MessageSize> sizeHint);
|
||||||
|
// Make a request without knowing the types of the params or results. You specify the type ID
|
||||||
|
// and method number manually.
|
||||||
|
|
||||||
|
// TODO(someday): method(s) for Join
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Client() = default;
|
||||||
|
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
Request<Params, Results> newCall(uint64_t interfaceId, uint16_t methodId,
|
||||||
|
kj::Maybe<MessageSize> sizeHint);
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Own<ClientHook> hook;
|
||||||
|
|
||||||
|
static kj::Own<ClientHook> makeLocalClient(kj::Own<Capability::Server>&& server);
|
||||||
|
|
||||||
|
template <typename, Kind>
|
||||||
|
friend struct _::PointerHelpers;
|
||||||
|
friend struct DynamicCapability;
|
||||||
|
friend class Orphanage;
|
||||||
|
friend struct DynamicStruct;
|
||||||
|
friend struct DynamicList;
|
||||||
|
template <typename, Kind>
|
||||||
|
friend struct List;
|
||||||
|
friend class _::CapabilityServerSetBase;
|
||||||
|
friend class ClientHook;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// Capability servers
|
||||||
|
|
||||||
|
class CallContextHook;
|
||||||
|
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
class CallContext: public kj::DisallowConstCopy {
|
||||||
|
// Wrapper around CallContextHook with a specific return type.
|
||||||
|
//
|
||||||
|
// Methods of this class may only be called from within the server's event loop, not from other
|
||||||
|
// threads.
|
||||||
|
//
|
||||||
|
// The CallContext becomes invalid as soon as the call reports completion.
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CallContext(CallContextHook& hook);
|
||||||
|
|
||||||
|
typename Params::Reader getParams();
|
||||||
|
// Get the params payload.
|
||||||
|
|
||||||
|
void releaseParams();
|
||||||
|
// Release the params payload. getParams() will throw an exception after this is called.
|
||||||
|
// Releasing the params may allow the RPC system to free up buffer space to handle other
|
||||||
|
// requests. Long-running asynchronous methods should try to call this as early as is
|
||||||
|
// convenient.
|
||||||
|
|
||||||
|
typename Results::Builder getResults(kj::Maybe<MessageSize> sizeHint = nullptr);
|
||||||
|
typename Results::Builder initResults(kj::Maybe<MessageSize> sizeHint = nullptr);
|
||||||
|
void setResults(typename Results::Reader value);
|
||||||
|
void adoptResults(Orphan<Results>&& value);
|
||||||
|
Orphanage getResultsOrphanage(kj::Maybe<MessageSize> sizeHint = nullptr);
|
||||||
|
// Manipulate the results payload. The "Return" message (part of the RPC protocol) will
|
||||||
|
// typically be allocated the first time one of these is called. Some RPC systems may
|
||||||
|
// allocate these messages in a limited space (such as a shared memory segment), therefore the
|
||||||
|
// application should delay calling these as long as is convenient to do so (but don't delay
|
||||||
|
// if doing so would require extra copies later).
|
||||||
|
//
|
||||||
|
// `sizeHint` indicates a guess at the message size. This will usually be used to decide how
|
||||||
|
// much space to allocate for the first message segment (don't worry: only space that is actually
|
||||||
|
// used will be sent on the wire). If omitted, the system decides. The message root pointer
|
||||||
|
// should not be included in the size. So, if you are simply going to copy some existing message
|
||||||
|
// directly into the results, just call `.totalSize()` and pass that in.
|
||||||
|
|
||||||
|
template <typename SubParams>
|
||||||
|
kj::Promise<void> tailCall(Request<SubParams, Results>&& tailRequest);
|
||||||
|
// Resolve the call by making a tail call. `tailRequest` is a request that has been filled in
|
||||||
|
// but not yet sent. The context will send the call, then fill in the results with the result
|
||||||
|
// of the call. If tailCall() is used, {get,init,set,adopt}Results (above) *must not* be called.
|
||||||
|
//
|
||||||
|
// The RPC implementation may be able to optimize a tail call to another machine such that the
|
||||||
|
// results never actually pass through this machine. Even if no such optimization is possible,
|
||||||
|
// `tailCall()` may allow pipelined calls to be forwarded optimistically to the new call site.
|
||||||
|
//
|
||||||
|
// In general, this should be the last thing a method implementation calls, and the promise
|
||||||
|
// returned from `tailCall()` should then be returned by the method implementation.
|
||||||
|
|
||||||
|
void allowCancellation();
|
||||||
|
// Indicate that it is OK for the RPC system to discard its Promise for this call's result if
|
||||||
|
// the caller cancels the call, thereby transitively canceling any asynchronous operations the
|
||||||
|
// call implementation was performing. This is not done by default because it could represent a
|
||||||
|
// security risk: applications must be carefully written to ensure that they do not end up in
|
||||||
|
// a bad state if an operation is canceled at an arbitrary point. However, for long-running
|
||||||
|
// method calls that hold significant resources, prompt cancellation is often useful.
|
||||||
|
//
|
||||||
|
// Keep in mind that asynchronous cancellation cannot occur while the method is synchronously
|
||||||
|
// executing on a local thread. The method must perform an asynchronous operation or call
|
||||||
|
// `EventLoop::current().evalLater()` to yield control.
|
||||||
|
//
|
||||||
|
// Note: You might think that we should offer `onCancel()` and/or `isCanceled()` methods that
|
||||||
|
// provide notification when the caller cancels the request without forcefully killing off the
|
||||||
|
// promise chain. Unfortunately, this composes poorly with promise forking: the canceled
|
||||||
|
// path may be just one branch of a fork of the result promise. The other branches still want
|
||||||
|
// the call to continue. Promise forking is used within the Cap'n Proto implementation -- in
|
||||||
|
// particular each pipelined call forks the result promise. So, if a caller made a pipelined
|
||||||
|
// call and then dropped the original object, the call should not be canceled, but it would be
|
||||||
|
// excessively complicated for the framework to avoid notififying of cancellation as long as
|
||||||
|
// pipelined calls still exist.
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallContextHook* hook;
|
||||||
|
|
||||||
|
friend class Capability::Server;
|
||||||
|
friend struct DynamicCapability;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Capability::Server {
|
||||||
|
// Objects implementing a Cap'n Proto interface must subclass this. Typically, such objects
|
||||||
|
// will instead subclass a typed Server interface which will take care of implementing
|
||||||
|
// dispatchCall().
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef Capability Serves;
|
||||||
|
|
||||||
|
virtual kj::Promise<void> dispatchCall(uint64_t interfaceId, uint16_t methodId,
|
||||||
|
CallContext<AnyPointer, AnyPointer> context) = 0;
|
||||||
|
// Call the given method. `params` is the input struct, and should be released as soon as it
|
||||||
|
// is no longer needed. `context` may be used to allocate the output struct and deal with
|
||||||
|
// cancellation.
|
||||||
|
|
||||||
|
// TODO(someday): Method which can optionally be overridden to implement Join when the object is
|
||||||
|
// a proxy.
|
||||||
|
|
||||||
|
protected:
|
||||||
|
inline Capability::Client thisCap();
|
||||||
|
// Get a capability pointing to this object, much like the `this` keyword.
|
||||||
|
//
|
||||||
|
// The effect of this method is undefined if:
|
||||||
|
// - No capability client has been created pointing to this object. (This is always the case in
|
||||||
|
// the server's constructor.)
|
||||||
|
// - The capability client pointing at this object has been destroyed. (This is always the case
|
||||||
|
// in the server's destructor.)
|
||||||
|
// - Multiple capability clients have been created around the same server (possible if the server
|
||||||
|
// is refcounted, which is not recommended since the client itself provides refcounting).
|
||||||
|
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
CallContext<Params, Results> internalGetTypedContext(
|
||||||
|
CallContext<AnyPointer, AnyPointer> typeless);
|
||||||
|
kj::Promise<void> internalUnimplemented(const char* actualInterfaceName,
|
||||||
|
uint64_t requestedTypeId);
|
||||||
|
kj::Promise<void> internalUnimplemented(const char* interfaceName,
|
||||||
|
uint64_t typeId, uint16_t methodId);
|
||||||
|
kj::Promise<void> internalUnimplemented(const char* interfaceName, const char* methodName,
|
||||||
|
uint64_t typeId, uint16_t methodId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ClientHook* thisHook = nullptr;
|
||||||
|
friend class LocalClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
class ReaderCapabilityTable: private _::CapTableReader {
|
||||||
|
// Class which imbues Readers with the ability to read capabilities.
|
||||||
|
//
|
||||||
|
// In Cap'n Proto format, the encoding of a capability pointer is simply an integer index into
|
||||||
|
// an external table. Since these pointers fundamentally point outside the message, a
|
||||||
|
// MessageReader by default has no idea what they point at, and therefore reading capabilities
|
||||||
|
// from such a reader will throw exceptions.
|
||||||
|
//
|
||||||
|
// In order to be able to read capabilities, you must first attach a capability table, using
|
||||||
|
// this class. By "imbuing" a Reader, you get a new Reader which will interpret capability
|
||||||
|
// pointers by treating them as indexes into the ReaderCapabilityTable.
|
||||||
|
//
|
||||||
|
// Note that when using Cap'n Proto's RPC system, this is handled automatically.
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ReaderCapabilityTable(kj::Array<kj::Maybe<kj::Own<ClientHook>>> table);
|
||||||
|
KJ_DISALLOW_COPY(ReaderCapabilityTable);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T imbue(T reader);
|
||||||
|
// Return a reader equivalent to `reader` except that when reading capability-valued fields,
|
||||||
|
// the capabilities are looked up in this table.
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Array<kj::Maybe<kj::Own<ClientHook>>> table;
|
||||||
|
|
||||||
|
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BuilderCapabilityTable: private _::CapTableBuilder {
|
||||||
|
// Class which imbues Builders with the ability to read and write capabilities.
|
||||||
|
//
|
||||||
|
// This is much like ReaderCapabilityTable, except for builders. The table starts out empty,
|
||||||
|
// but capabilities can be added to it over time.
|
||||||
|
|
||||||
|
public:
|
||||||
|
BuilderCapabilityTable();
|
||||||
|
KJ_DISALLOW_COPY(BuilderCapabilityTable);
|
||||||
|
|
||||||
|
inline kj::ArrayPtr<kj::Maybe<kj::Own<ClientHook>>> getTable() { return table; }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T imbue(T builder);
|
||||||
|
// Return a builder equivalent to `builder` except that when reading capability-valued fields,
|
||||||
|
// the capabilities are looked up in this table.
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Vector<kj::Maybe<kj::Own<ClientHook>>> table;
|
||||||
|
|
||||||
|
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override;
|
||||||
|
uint injectCap(kj::Own<ClientHook>&& cap) override;
|
||||||
|
void dropCap(uint index) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
namespace _ { // private
|
||||||
|
|
||||||
|
class CapabilityServerSetBase {
|
||||||
|
public:
|
||||||
|
Capability::Client addInternal(kj::Own<Capability::Server>&& server, void* ptr);
|
||||||
|
kj::Promise<void*> getLocalServerInternal(Capability::Client& client);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace _ (private)
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class CapabilityServerSet: private _::CapabilityServerSetBase {
|
||||||
|
// Allows a server to recognize its own capabilities when passed back to it, and obtain the
|
||||||
|
// underlying Server objects associated with them.
|
||||||
|
//
|
||||||
|
// All objects in the set must have the same interface type T. The objects may implement various
|
||||||
|
// interfaces derived from T (and in fact T can be `capnp::Capability` to accept all objects),
|
||||||
|
// but note that if you compile with RTTI disabled then you will not be able to down-cast through
|
||||||
|
// virtual inheritance, and all inheritance between server interfaces is virtual. So, with RTTI
|
||||||
|
// disabled, you will likely need to set T to be the most-derived Cap'n Proto interface type,
|
||||||
|
// and you server class will need to be directly derived from that, so that you can use
|
||||||
|
// static_cast (or kj::downcast) to cast to it after calling getLocalServer(). (If you compile
|
||||||
|
// with RTTI, then you can freely dynamic_cast and ignore this issue!)
|
||||||
|
|
||||||
|
public:
|
||||||
|
CapabilityServerSet() = default;
|
||||||
|
KJ_DISALLOW_COPY(CapabilityServerSet);
|
||||||
|
|
||||||
|
typename T::Client add(kj::Own<typename T::Server>&& server);
|
||||||
|
// Create a new capability Client for the given Server and also add this server to the set.
|
||||||
|
|
||||||
|
kj::Promise<kj::Maybe<typename T::Server&>> getLocalServer(typename T::Client& client);
|
||||||
|
// Given a Client pointing to a server previously passed to add(), return the corresponding
|
||||||
|
// Server. This returns a promise because if the input client is itself a promise, this must
|
||||||
|
// wait for it to resolve. Keep in mind that the server will be deleted when all clients are
|
||||||
|
// gone, so the caller should make sure to keep the client alive (hence why this method only
|
||||||
|
// accepts an lvalue input).
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// Hook interfaces which must be implemented by the RPC system. Applications never call these
|
||||||
|
// directly; the RPC system implements them and the types defined earlier in this file wrap them.
|
||||||
|
|
||||||
|
class RequestHook {
|
||||||
|
// Hook interface implemented by RPC system representing a request being built.
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual RemotePromise<AnyPointer> send() = 0;
|
||||||
|
// Send the call and return a promise for the result.
|
||||||
|
|
||||||
|
virtual const void* getBrand() = 0;
|
||||||
|
// Returns a void* that identifies who made this request. This can be used by an RPC adapter to
|
||||||
|
// discover when tail call is going to be sent over its own connection and therefore can be
|
||||||
|
// optimized into a remote tail call.
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline static kj::Own<RequestHook> from(Request<T, U>&& request) {
|
||||||
|
return kj::mv(request.hook);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResponseHook {
|
||||||
|
// Hook interface implemented by RPC system representing a response.
|
||||||
|
//
|
||||||
|
// At present this class has no methods. It exists only for garbage collection -- when the
|
||||||
|
// ResponseHook is destroyed, the results can be freed.
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~ResponseHook() noexcept(false);
|
||||||
|
// Just here to make sure the type is dynamic.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline static kj::Own<ResponseHook> from(Response<T>&& response) {
|
||||||
|
return kj::mv(response.hook);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// class PipelineHook is declared in any.h because it is needed there.
|
||||||
|
|
||||||
|
class ClientHook {
|
||||||
|
public:
|
||||||
|
ClientHook();
|
||||||
|
|
||||||
|
virtual Request<AnyPointer, AnyPointer> newCall(
|
||||||
|
uint64_t interfaceId, uint16_t methodId, kj::Maybe<MessageSize> sizeHint) = 0;
|
||||||
|
// Start a new call, allowing the client to allocate request/response objects as it sees fit.
|
||||||
|
// This version is used when calls are made from application code in the local process.
|
||||||
|
|
||||||
|
struct VoidPromiseAndPipeline {
|
||||||
|
kj::Promise<void> promise;
|
||||||
|
kj::Own<PipelineHook> pipeline;
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual VoidPromiseAndPipeline call(uint64_t interfaceId, uint16_t methodId,
|
||||||
|
kj::Own<CallContextHook>&& context) = 0;
|
||||||
|
// Call the object, but the caller controls allocation of the request/response objects. If the
|
||||||
|
// callee insists on allocating these objects itself, it must make a copy. This version is used
|
||||||
|
// when calls come in over the network via an RPC system. Note that even if the returned
|
||||||
|
// `Promise<void>` is discarded, the call may continue executing if any pipelined calls are
|
||||||
|
// waiting for it.
|
||||||
|
//
|
||||||
|
// Since the caller of this method chooses the CallContext implementation, it is the caller's
|
||||||
|
// responsibility to ensure that the returned promise is not canceled unless allowed via
|
||||||
|
// the context's `allowCancellation()`.
|
||||||
|
//
|
||||||
|
// The call must not begin synchronously; the callee must arrange for the call to begin in a
|
||||||
|
// later turn of the event loop. Otherwise, application code may call back and affect the
|
||||||
|
// callee's state in an unexpected way.
|
||||||
|
|
||||||
|
virtual kj::Maybe<ClientHook&> getResolved() = 0;
|
||||||
|
// If this ClientHook is a promise that has already resolved, returns the inner, resolved version
|
||||||
|
// of the capability. The caller may permanently replace this client with the resolved one if
|
||||||
|
// desired. Returns null if the client isn't a promise or hasn't resolved yet -- use
|
||||||
|
// `whenMoreResolved()` to distinguish between them.
|
||||||
|
|
||||||
|
virtual kj::Maybe<kj::Promise<kj::Own<ClientHook>>> whenMoreResolved() = 0;
|
||||||
|
// If this client is a settled reference (not a promise), return nullptr. Otherwise, return a
|
||||||
|
// promise that eventually resolves to a new client that is closer to being the final, settled
|
||||||
|
// client (i.e. the value eventually returned by `getResolved()`). Calling this repeatedly
|
||||||
|
// should eventually produce a settled client.
|
||||||
|
|
||||||
|
kj::Promise<void> whenResolved();
|
||||||
|
// Repeatedly calls whenMoreResolved() until it returns nullptr.
|
||||||
|
|
||||||
|
virtual kj::Own<ClientHook> addRef() = 0;
|
||||||
|
// Return a new reference to the same capability.
|
||||||
|
|
||||||
|
virtual const void* getBrand() = 0;
|
||||||
|
// Returns a void* that identifies who made this client. This can be used by an RPC adapter to
|
||||||
|
// discover when a capability it needs to marshal is one that it created in the first place, and
|
||||||
|
// therefore it can transfer the capability without proxying.
|
||||||
|
|
||||||
|
static const uint NULL_CAPABILITY_BRAND;
|
||||||
|
// Value is irrelevant; used for pointer.
|
||||||
|
|
||||||
|
inline bool isNull() { return getBrand() == &NULL_CAPABILITY_BRAND; }
|
||||||
|
// Returns true if the capability was created as a result of assigning a Client to null or by
|
||||||
|
// reading a null pointer out of a Cap'n Proto message.
|
||||||
|
|
||||||
|
virtual void* getLocalServer(_::CapabilityServerSetBase& capServerSet);
|
||||||
|
// If this is a local capability created through `capServerSet`, return the underlying Server.
|
||||||
|
// Otherwise, return nullptr. Default implementation (which everyone except LocalClient should
|
||||||
|
// use) always returns nullptr.
|
||||||
|
|
||||||
|
static kj::Own<ClientHook> from(Capability::Client client) { return kj::mv(client.hook); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class CallContextHook {
|
||||||
|
// Hook interface implemented by RPC system to manage a call on the server side. See
|
||||||
|
// CallContext<T>.
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual AnyPointer::Reader getParams() = 0;
|
||||||
|
virtual void releaseParams() = 0;
|
||||||
|
virtual AnyPointer::Builder getResults(kj::Maybe<MessageSize> sizeHint) = 0;
|
||||||
|
virtual kj::Promise<void> tailCall(kj::Own<RequestHook>&& request) = 0;
|
||||||
|
virtual void allowCancellation() = 0;
|
||||||
|
|
||||||
|
virtual kj::Promise<AnyPointer::Pipeline> onTailCall() = 0;
|
||||||
|
// If `tailCall()` is called, resolves to the PipelineHook from the tail call. An
|
||||||
|
// implementation of `ClientHook::call()` is allowed to call this at most once.
|
||||||
|
|
||||||
|
virtual ClientHook::VoidPromiseAndPipeline directTailCall(kj::Own<RequestHook>&& request) = 0;
|
||||||
|
// Call this when you would otherwise call onTailCall() immediately followed by tailCall().
|
||||||
|
// Implementations of tailCall() should typically call directTailCall() and then fulfill the
|
||||||
|
// promise fulfiller for onTailCall() with the returned pipeline.
|
||||||
|
|
||||||
|
virtual kj::Own<CallContextHook> addRef() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
kj::Own<ClientHook> newLocalPromiseClient(kj::Promise<kj::Own<ClientHook>>&& promise);
|
||||||
|
// Returns a ClientHook that queues up calls until `promise` resolves, then forwards them to
|
||||||
|
// the new client. This hook's `getResolved()` and `whenMoreResolved()` methods will reflect the
|
||||||
|
// redirection to the eventual replacement client.
|
||||||
|
|
||||||
|
kj::Own<PipelineHook> newLocalPromisePipeline(kj::Promise<kj::Own<PipelineHook>>&& promise);
|
||||||
|
// Returns a PipelineHook that queues up calls until `promise` resolves, then forwards them to
|
||||||
|
// the new pipeline.
|
||||||
|
|
||||||
|
kj::Own<ClientHook> newBrokenCap(kj::StringPtr reason);
|
||||||
|
kj::Own<ClientHook> newBrokenCap(kj::Exception&& reason);
|
||||||
|
// Helper function that creates a capability which simply throws exceptions when called.
|
||||||
|
|
||||||
|
kj::Own<PipelineHook> newBrokenPipeline(kj::Exception&& reason);
|
||||||
|
// Helper function that creates a pipeline which simply throws exceptions when called.
|
||||||
|
|
||||||
|
Request<AnyPointer, AnyPointer> newBrokenRequest(
|
||||||
|
kj::Exception&& reason, kj::Maybe<MessageSize> sizeHint);
|
||||||
|
// Helper function that creates a Request object that simply throws exceptions when sent.
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// Extend PointerHelpers for interfaces
|
||||||
|
|
||||||
|
namespace _ { // private
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct PointerHelpers<T, Kind::INTERFACE> {
|
||||||
|
static inline typename T::Client get(PointerReader reader) {
|
||||||
|
return typename T::Client(reader.getCapability());
|
||||||
|
}
|
||||||
|
static inline typename T::Client get(PointerBuilder builder) {
|
||||||
|
return typename T::Client(builder.getCapability());
|
||||||
|
}
|
||||||
|
static inline void set(PointerBuilder builder, typename T::Client&& value) {
|
||||||
|
builder.setCapability(kj::mv(value.Capability::Client::hook));
|
||||||
|
}
|
||||||
|
static inline void set(PointerBuilder builder, typename T::Client& value) {
|
||||||
|
builder.setCapability(value.Capability::Client::hook->addRef());
|
||||||
|
}
|
||||||
|
static inline void adopt(PointerBuilder builder, Orphan<T>&& value) {
|
||||||
|
builder.adopt(kj::mv(value.builder));
|
||||||
|
}
|
||||||
|
static inline Orphan<T> disown(PointerBuilder builder) {
|
||||||
|
return Orphan<T>(builder.disown());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace _ (private)
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// Extend List for interfaces
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct List<T, Kind::INTERFACE> {
|
||||||
|
List() = delete;
|
||||||
|
|
||||||
|
class Reader {
|
||||||
|
public:
|
||||||
|
typedef List<T> Reads;
|
||||||
|
|
||||||
|
Reader() = default;
|
||||||
|
inline explicit Reader(_::ListReader reader): reader(reader) {}
|
||||||
|
|
||||||
|
inline uint size() const { return unbound(reader.size() / ELEMENTS); }
|
||||||
|
inline typename T::Client operator[](uint index) const {
|
||||||
|
KJ_IREQUIRE(index < size());
|
||||||
|
return typename T::Client(reader.getPointerElement(
|
||||||
|
bounded(index) * ELEMENTS).getCapability());
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef _::IndexingIterator<const Reader, typename T::Client> Iterator;
|
||||||
|
inline Iterator begin() const { return Iterator(this, 0); }
|
||||||
|
inline Iterator end() const { return Iterator(this, size()); }
|
||||||
|
|
||||||
|
inline MessageSize totalSize() const {
|
||||||
|
return reader.totalSize().asPublic();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
_::ListReader reader;
|
||||||
|
template <typename U, Kind K>
|
||||||
|
friend struct _::PointerHelpers;
|
||||||
|
template <typename U, Kind K>
|
||||||
|
friend struct List;
|
||||||
|
friend class Orphanage;
|
||||||
|
template <typename U, Kind K>
|
||||||
|
friend struct ToDynamic_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
public:
|
||||||
|
typedef List<T> Builds;
|
||||||
|
|
||||||
|
Builder() = delete;
|
||||||
|
inline Builder(decltype(nullptr)) {}
|
||||||
|
inline explicit Builder(_::ListBuilder builder): builder(builder) {}
|
||||||
|
|
||||||
|
inline operator Reader() const { return Reader(builder.asReader()); }
|
||||||
|
inline Reader asReader() const { return Reader(builder.asReader()); }
|
||||||
|
|
||||||
|
inline uint size() const { return unbound(builder.size() / ELEMENTS); }
|
||||||
|
inline typename T::Client operator[](uint index) {
|
||||||
|
KJ_IREQUIRE(index < size());
|
||||||
|
return typename T::Client(builder.getPointerElement(
|
||||||
|
bounded(index) * ELEMENTS).getCapability());
|
||||||
|
}
|
||||||
|
inline void set(uint index, typename T::Client value) {
|
||||||
|
KJ_IREQUIRE(index < size());
|
||||||
|
builder.getPointerElement(bounded(index) * ELEMENTS).setCapability(kj::mv(value.hook));
|
||||||
|
}
|
||||||
|
inline void adopt(uint index, Orphan<T>&& value) {
|
||||||
|
KJ_IREQUIRE(index < size());
|
||||||
|
builder.getPointerElement(bounded(index) * ELEMENTS).adopt(kj::mv(value));
|
||||||
|
}
|
||||||
|
inline Orphan<T> disown(uint index) {
|
||||||
|
KJ_IREQUIRE(index < size());
|
||||||
|
return Orphan<T>(builder.getPointerElement(bounded(index) * ELEMENTS).disown());
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef _::IndexingIterator<Builder, typename T::Client> Iterator;
|
||||||
|
inline Iterator begin() { return Iterator(this, 0); }
|
||||||
|
inline Iterator end() { return Iterator(this, size()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
_::ListBuilder builder;
|
||||||
|
friend class Orphanage;
|
||||||
|
template <typename U, Kind K>
|
||||||
|
friend struct ToDynamic_;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline static _::ListBuilder initPointer(_::PointerBuilder builder, uint size) {
|
||||||
|
return builder.initList(ElementSize::POINTER, bounded(size) * ELEMENTS);
|
||||||
|
}
|
||||||
|
inline static _::ListBuilder getFromPointer(_::PointerBuilder builder, const word* defaultValue) {
|
||||||
|
return builder.getList(ElementSize::POINTER, defaultValue);
|
||||||
|
}
|
||||||
|
inline static _::ListReader getFromPointer(
|
||||||
|
const _::PointerReader& reader, const word* defaultValue) {
|
||||||
|
return reader.getList(ElementSize::POINTER, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U, Kind k>
|
||||||
|
friend struct List;
|
||||||
|
template <typename U, Kind K>
|
||||||
|
friend struct _::PointerHelpers;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// Inline implementation details
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
RemotePromise<T> RemotePromise<T>::reducePromise(kj::Promise<RemotePromise>&& promise) {
|
||||||
|
kj::Tuple<kj::Promise<Response<T>>, kj::Promise<kj::Own<PipelineHook>>> splitPromise =
|
||||||
|
promise.then([](RemotePromise&& inner) {
|
||||||
|
// `inner` is multiply-inherited, and we want to move away each superclass separately.
|
||||||
|
// Let's create two references to make clear what we're doing (though this is not strictly
|
||||||
|
// necessary).
|
||||||
|
kj::Promise<Response<T>>& innerPromise = inner;
|
||||||
|
typename T::Pipeline& innerPipeline = inner;
|
||||||
|
return kj::tuple(kj::mv(innerPromise), PipelineHook::from(kj::mv(innerPipeline)));
|
||||||
|
}).split();
|
||||||
|
|
||||||
|
return RemotePromise(kj::mv(kj::get<0>(splitPromise)),
|
||||||
|
typename T::Pipeline(AnyPointer::Pipeline(
|
||||||
|
newLocalPromisePipeline(kj::mv(kj::get<1>(splitPromise))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
RemotePromise<Results> Request<Params, Results>::send() {
|
||||||
|
auto typelessPromise = hook->send();
|
||||||
|
hook = nullptr; // prevent reuse
|
||||||
|
|
||||||
|
// Convert the Promise to return the correct response type.
|
||||||
|
// Explicitly upcast to kj::Promise to make clear that calling .then() doesn't invalidate the
|
||||||
|
// Pipeline part of the RemotePromise.
|
||||||
|
auto typedPromise = kj::implicitCast<kj::Promise<Response<AnyPointer>>&>(typelessPromise)
|
||||||
|
.then([](Response<AnyPointer>&& response) -> Response<Results> {
|
||||||
|
return Response<Results>(response.getAs<Results>(), kj::mv(response.hook));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wrap the typeless pipeline in a typed wrapper.
|
||||||
|
typename Results::Pipeline typedPipeline(
|
||||||
|
kj::mv(kj::implicitCast<AnyPointer::Pipeline&>(typelessPromise)));
|
||||||
|
|
||||||
|
return RemotePromise<Results>(kj::mv(typedPromise), kj::mv(typedPipeline));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Capability::Client::Client(kj::Own<ClientHook>&& hook): hook(kj::mv(hook)) {}
|
||||||
|
template <typename T, typename>
|
||||||
|
inline Capability::Client::Client(kj::Own<T>&& server)
|
||||||
|
: hook(makeLocalClient(kj::mv(server))) {}
|
||||||
|
template <typename T, typename>
|
||||||
|
inline Capability::Client::Client(kj::Promise<T>&& promise)
|
||||||
|
: hook(newLocalPromiseClient(promise.then([](T&& t) { return kj::mv(t.hook); }))) {}
|
||||||
|
inline Capability::Client::Client(Client& other): hook(other.hook->addRef()) {}
|
||||||
|
inline Capability::Client& Capability::Client::operator=(Client& other) {
|
||||||
|
hook = other.hook->addRef();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
inline typename T::Client Capability::Client::castAs() {
|
||||||
|
return typename T::Client(hook->addRef());
|
||||||
|
}
|
||||||
|
inline kj::Promise<void> Capability::Client::whenResolved() {
|
||||||
|
return hook->whenResolved();
|
||||||
|
}
|
||||||
|
inline Request<AnyPointer, AnyPointer> Capability::Client::typelessRequest(
|
||||||
|
uint64_t interfaceId, uint16_t methodId,
|
||||||
|
kj::Maybe<MessageSize> sizeHint) {
|
||||||
|
return newCall<AnyPointer, AnyPointer>(interfaceId, methodId, sizeHint);
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline Request<Params, Results> Capability::Client::newCall(
|
||||||
|
uint64_t interfaceId, uint16_t methodId, kj::Maybe<MessageSize> sizeHint) {
|
||||||
|
auto typeless = hook->newCall(interfaceId, methodId, sizeHint);
|
||||||
|
return Request<Params, Results>(typeless.template getAs<Params>(), kj::mv(typeless.hook));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline CallContext<Params, Results>::CallContext(CallContextHook& hook): hook(&hook) {}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline typename Params::Reader CallContext<Params, Results>::getParams() {
|
||||||
|
return hook->getParams().template getAs<Params>();
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline void CallContext<Params, Results>::releaseParams() {
|
||||||
|
hook->releaseParams();
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline typename Results::Builder CallContext<Params, Results>::getResults(
|
||||||
|
kj::Maybe<MessageSize> sizeHint) {
|
||||||
|
// `template` keyword needed due to: http://llvm.org/bugs/show_bug.cgi?id=17401
|
||||||
|
return hook->getResults(sizeHint).template getAs<Results>();
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline typename Results::Builder CallContext<Params, Results>::initResults(
|
||||||
|
kj::Maybe<MessageSize> sizeHint) {
|
||||||
|
// `template` keyword needed due to: http://llvm.org/bugs/show_bug.cgi?id=17401
|
||||||
|
return hook->getResults(sizeHint).template initAs<Results>();
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline void CallContext<Params, Results>::setResults(typename Results::Reader value) {
|
||||||
|
hook->getResults(value.totalSize()).template setAs<Results>(value);
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline void CallContext<Params, Results>::adoptResults(Orphan<Results>&& value) {
|
||||||
|
hook->getResults(nullptr).adopt(kj::mv(value));
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline Orphanage CallContext<Params, Results>::getResultsOrphanage(
|
||||||
|
kj::Maybe<MessageSize> sizeHint) {
|
||||||
|
return Orphanage::getForMessageContaining(hook->getResults(sizeHint));
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
template <typename SubParams>
|
||||||
|
inline kj::Promise<void> CallContext<Params, Results>::tailCall(
|
||||||
|
Request<SubParams, Results>&& tailRequest) {
|
||||||
|
return hook->tailCall(kj::mv(tailRequest.hook));
|
||||||
|
}
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
inline void CallContext<Params, Results>::allowCancellation() {
|
||||||
|
hook->allowCancellation();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Params, typename Results>
|
||||||
|
CallContext<Params, Results> Capability::Server::internalGetTypedContext(
|
||||||
|
CallContext<AnyPointer, AnyPointer> typeless) {
|
||||||
|
return CallContext<Params, Results>(*typeless.hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
Capability::Client Capability::Server::thisCap() {
|
||||||
|
return Client(thisHook->addRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T ReaderCapabilityTable::imbue(T reader) {
|
||||||
|
return T(_::PointerHelpers<FromReader<T>>::getInternalReader(reader).imbue(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T BuilderCapabilityTable::imbue(T builder) {
|
||||||
|
return T(_::PointerHelpers<FromBuilder<T>>::getInternalBuilder(kj::mv(builder)).imbue(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
typename T::Client CapabilityServerSet<T>::add(kj::Own<typename T::Server>&& server) {
|
||||||
|
void* ptr = reinterpret_cast<void*>(server.get());
|
||||||
|
// Clang insists that `castAs` is a template-dependent member and therefore we need the
|
||||||
|
// `template` keyword here, but AFAICT this is wrong: addImpl() is not a template.
|
||||||
|
return addInternal(kj::mv(server), ptr).template castAs<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
kj::Promise<kj::Maybe<typename T::Server&>> CapabilityServerSet<T>::getLocalServer(
|
||||||
|
typename T::Client& client) {
|
||||||
|
return getLocalServerInternal(client)
|
||||||
|
.then([](void* server) -> kj::Maybe<typename T::Server&> {
|
||||||
|
if (server == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return *reinterpret_cast<typename T::Server*>(server);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct Orphanage::GetInnerReader<T, Kind::INTERFACE> {
|
||||||
|
static inline kj::Own<ClientHook> apply(typename T::Client t) {
|
||||||
|
return ClientHook::from(kj::mv(t));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CAPNP_CAPABILITY_H_INCLUDED // for testing includes in unit test
|
||||||
|
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,71 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if test $# = 0; then
|
||||||
|
echo trigger filetype:.capnp
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
INPUT=$1
|
||||||
|
|
||||||
|
case "$INPUT" in
|
||||||
|
*capnp/c++.capnp | \
|
||||||
|
*capnp/schema.capnp | \
|
||||||
|
*capnp/rpc.capnp | \
|
||||||
|
*capnp/rpc-twoparty.capnp | \
|
||||||
|
*capnp/persistent.capnp | \
|
||||||
|
*capnp/compiler/lexer.capnp | \
|
||||||
|
*capnp/compiler/grammar.capnp | \
|
||||||
|
*capnp/compat/json.capnp )
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo findProvider special:ekam-interceptor
|
||||||
|
read INTERCEPTOR
|
||||||
|
|
||||||
|
if test "$INTERCEPTOR" = ""; then
|
||||||
|
echo "error: couldn't find intercept.so." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo findProvider file:compiler/capnp
|
||||||
|
read CAPNP
|
||||||
|
|
||||||
|
if test "$CAPNP" = ""; then
|
||||||
|
echo "error: couldn't find capnp." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo findProvider file:capnpc-c++
|
||||||
|
read CAPNPC_CXX
|
||||||
|
|
||||||
|
if test "$CAPNPC_CXX" = ""; then
|
||||||
|
echo "error: couldn't find capnpc-c++." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
LD_PRELOAD=$INTERCEPTOR DYLD_FORCE_FLAT_NAMESPACE= DYLD_INSERT_LIBRARIES=$INTERCEPTOR \
|
||||||
|
$CAPNP compile -I. -o$CAPNPC_CXX "$INPUT" 3>&1 4<&0 >&2
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include <kj/compat/gtest.h>
|
||||||
|
#include <kj/string.h>
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <capnp/test.capnp.h>
|
||||||
|
|
||||||
|
#if HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(Common, Version) {
|
||||||
|
#ifdef VERSION
|
||||||
|
auto expectedVersion =
|
||||||
|
kj::str(CAPNP_VERSION_MAJOR, '.', CAPNP_VERSION_MINOR, '.', CAPNP_VERSION_MICRO);
|
||||||
|
auto devVersion =
|
||||||
|
kj::str(CAPNP_VERSION_MAJOR, '.', CAPNP_VERSION_MINOR, "-dev");
|
||||||
|
kj::StringPtr actualVersion = VERSION;
|
||||||
|
KJ_ASSERT(actualVersion == expectedVersion ||
|
||||||
|
actualVersion.startsWith(kj::str(expectedVersion, '-')) ||
|
||||||
|
actualVersion.startsWith(kj::str(expectedVersion, '.')) ||
|
||||||
|
(actualVersion == devVersion && CAPNP_VERSION_MICRO == 0),
|
||||||
|
expectedVersion, actualVersion);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExampleStruct {
|
||||||
|
struct _capnpPrivate {
|
||||||
|
struct IsStruct;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
struct ExampleInterface {
|
||||||
|
struct _capnpPrivate {
|
||||||
|
struct IsInterface;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(_::Kind_<ExampleStruct>::kind == Kind::STRUCT, "Kind SFINAE failed.");
|
||||||
|
static_assert(_::Kind_<ExampleInterface>::kind == Kind::INTERFACE, "Kind SFINAE failed.");
|
||||||
|
|
||||||
|
// Test FromAnay<>
|
||||||
|
template <typename T, typename U>
|
||||||
|
struct EqualTypes_ { static constexpr bool value = false; };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct EqualTypes_<T, T> { static constexpr bool value = true; };
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr bool equalTypes() { return EqualTypes_<T, U>::value; }
|
||||||
|
|
||||||
|
using capnproto_test::capnp::test::TestAllTypes;
|
||||||
|
using capnproto_test::capnp::test::TestInterface;
|
||||||
|
|
||||||
|
static_assert(equalTypes<FromAny<int>, int>(), "");
|
||||||
|
static_assert(equalTypes<FromAny<TestAllTypes::Reader>, TestAllTypes>(), "");
|
||||||
|
static_assert(equalTypes<FromAny<TestAllTypes::Builder>, TestAllTypes>(), "");
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
static_assert(equalTypes<FromAny<TestAllTypes::Pipeline>, TestAllTypes>(), "");
|
||||||
|
static_assert(equalTypes<FromAny<TestInterface::Client>, TestInterface>(), "");
|
||||||
|
static_assert(equalTypes<FromAny<kj::Own<TestInterface::Server>>, TestInterface>(), "");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,743 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// This file contains types which are intended to help detect incorrect usage at compile
|
||||||
|
// time, but should then be optimized down to basic primitives (usually, integers) by the
|
||||||
|
// compiler.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
|
||||||
|
#pragma GCC system_header
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <kj/string.h>
|
||||||
|
#include <kj/memory.h>
|
||||||
|
#include <kj/windows-sanity.h> // work-around macro conflict with `VOID`
|
||||||
|
|
||||||
|
#if CAPNP_DEBUG_TYPES
|
||||||
|
#include <kj/units.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
#define CAPNP_VERSION_MAJOR 0
|
||||||
|
#define CAPNP_VERSION_MINOR 7
|
||||||
|
#define CAPNP_VERSION_MICRO 0
|
||||||
|
|
||||||
|
#define CAPNP_VERSION \
|
||||||
|
(CAPNP_VERSION_MAJOR * 1000000 + CAPNP_VERSION_MINOR * 1000 + CAPNP_VERSION_MICRO)
|
||||||
|
|
||||||
|
#ifndef CAPNP_LITE
|
||||||
|
#define CAPNP_LITE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CAPNP_TESTING_CAPNP // defined in Cap'n Proto's own unit tests; others should not define this
|
||||||
|
#define CAPNP_DEPRECATED(reason)
|
||||||
|
#else
|
||||||
|
#define CAPNP_DEPRECATED KJ_DEPRECATED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef unsigned int uint;
|
||||||
|
|
||||||
|
struct Void {
|
||||||
|
// Type used for Void fields. Using C++'s "void" type creates a bunch of issues since it behaves
|
||||||
|
// differently from other types.
|
||||||
|
|
||||||
|
inline constexpr bool operator==(Void other) const { return true; }
|
||||||
|
inline constexpr bool operator!=(Void other) const { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr Void VOID = Void();
|
||||||
|
// Constant value for `Void`, which is an empty struct.
|
||||||
|
|
||||||
|
inline kj::StringPtr KJ_STRINGIFY(Void) { return "void"; }
|
||||||
|
|
||||||
|
struct Text;
|
||||||
|
struct Data;
|
||||||
|
|
||||||
|
enum class Kind: uint8_t {
|
||||||
|
PRIMITIVE,
|
||||||
|
BLOB,
|
||||||
|
ENUM,
|
||||||
|
STRUCT,
|
||||||
|
UNION,
|
||||||
|
INTERFACE,
|
||||||
|
LIST,
|
||||||
|
|
||||||
|
OTHER
|
||||||
|
// Some other type which is often a type parameter to Cap'n Proto templates, but which needs
|
||||||
|
// special handling. This includes types like AnyPointer, Dynamic*, etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Style: uint8_t {
|
||||||
|
PRIMITIVE,
|
||||||
|
POINTER, // other than struct
|
||||||
|
STRUCT,
|
||||||
|
CAPABILITY
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ElementSize: uint8_t {
|
||||||
|
// Size of a list element.
|
||||||
|
|
||||||
|
VOID = 0,
|
||||||
|
BIT = 1,
|
||||||
|
BYTE = 2,
|
||||||
|
TWO_BYTES = 3,
|
||||||
|
FOUR_BYTES = 4,
|
||||||
|
EIGHT_BYTES = 5,
|
||||||
|
|
||||||
|
POINTER = 6,
|
||||||
|
|
||||||
|
INLINE_COMPOSITE = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PointerType {
|
||||||
|
// Various wire types a pointer field can take
|
||||||
|
|
||||||
|
NULL_,
|
||||||
|
// Should be NULL, but that's #defined in stddef.h
|
||||||
|
|
||||||
|
STRUCT,
|
||||||
|
LIST,
|
||||||
|
CAPABILITY
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace schemas {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct EnumInfo;
|
||||||
|
|
||||||
|
} // namespace schemas
|
||||||
|
|
||||||
|
namespace _ { // private
|
||||||
|
|
||||||
|
template <typename T, typename = void> struct Kind_;
|
||||||
|
|
||||||
|
template <> struct Kind_<Void> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<bool> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<int8_t> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<int16_t> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<int32_t> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<int64_t> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<uint8_t> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<uint16_t> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<uint32_t> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<uint64_t> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<float> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<double> { static constexpr Kind kind = Kind::PRIMITIVE; };
|
||||||
|
template <> struct Kind_<Text> { static constexpr Kind kind = Kind::BLOB; };
|
||||||
|
template <> struct Kind_<Data> { static constexpr Kind kind = Kind::BLOB; };
|
||||||
|
|
||||||
|
template <typename T> struct Kind_<T, kj::VoidSfinae<typename T::_capnpPrivate::IsStruct>> {
|
||||||
|
static constexpr Kind kind = Kind::STRUCT;
|
||||||
|
};
|
||||||
|
template <typename T> struct Kind_<T, kj::VoidSfinae<typename T::_capnpPrivate::IsInterface>> {
|
||||||
|
static constexpr Kind kind = Kind::INTERFACE;
|
||||||
|
};
|
||||||
|
template <typename T> struct Kind_<T, kj::VoidSfinae<typename schemas::EnumInfo<T>::IsEnum>> {
|
||||||
|
static constexpr Kind kind = Kind::ENUM;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace _ (private)
|
||||||
|
|
||||||
|
template <typename T, Kind k = _::Kind_<T>::kind>
|
||||||
|
inline constexpr Kind kind() {
|
||||||
|
// This overload of kind() matches types which have a Kind_ specialization.
|
||||||
|
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if _MSC_VER
|
||||||
|
|
||||||
|
#define CAPNP_KIND(T) ::capnp::_::Kind_<T>::kind
|
||||||
|
// Avoid constexpr methods in MSVC (it remains buggy in many situations).
|
||||||
|
|
||||||
|
#else // _MSC_VER
|
||||||
|
|
||||||
|
#define CAPNP_KIND(T) ::capnp::kind<T>()
|
||||||
|
// Use this macro rather than kind<T>() in any code which must work in MSVC.
|
||||||
|
|
||||||
|
#endif // _MSC_VER, else
|
||||||
|
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
|
||||||
|
template <typename T, Kind k = kind<T>()>
|
||||||
|
inline constexpr Style style() {
|
||||||
|
return k == Kind::PRIMITIVE || k == Kind::ENUM ? Style::PRIMITIVE
|
||||||
|
: k == Kind::STRUCT ? Style::STRUCT
|
||||||
|
: k == Kind::INTERFACE ? Style::CAPABILITY : Style::POINTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
template <typename T, Kind k = CAPNP_KIND(T)>
|
||||||
|
struct List;
|
||||||
|
|
||||||
|
#if _MSC_VER
|
||||||
|
|
||||||
|
template <typename T, Kind k>
|
||||||
|
struct List {};
|
||||||
|
// For some reason, without this declaration, MSVC will error out on some uses of List
|
||||||
|
// claiming that "T" -- as used in the default initializer for the second template param, "k" --
|
||||||
|
// is not defined. I do not understand this error, but adding this empty default declaration fixes
|
||||||
|
// it.
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T> struct ListElementType_;
|
||||||
|
template <typename T> struct ListElementType_<List<T>> { typedef T Type; };
|
||||||
|
template <typename T> using ListElementType = typename ListElementType_<T>::Type;
|
||||||
|
|
||||||
|
namespace _ { // private
|
||||||
|
template <typename T, Kind k> struct Kind_<List<T, k>> {
|
||||||
|
static constexpr Kind kind = Kind::LIST;
|
||||||
|
};
|
||||||
|
} // namespace _ (private)
|
||||||
|
|
||||||
|
template <typename T, Kind k = CAPNP_KIND(T)> struct ReaderFor_ { typedef typename T::Reader Type; };
|
||||||
|
template <typename T> struct ReaderFor_<T, Kind::PRIMITIVE> { typedef T Type; };
|
||||||
|
template <typename T> struct ReaderFor_<T, Kind::ENUM> { typedef T Type; };
|
||||||
|
template <typename T> struct ReaderFor_<T, Kind::INTERFACE> { typedef typename T::Client Type; };
|
||||||
|
template <typename T> using ReaderFor = typename ReaderFor_<T>::Type;
|
||||||
|
// The type returned by List<T>::Reader::operator[].
|
||||||
|
|
||||||
|
template <typename T, Kind k = CAPNP_KIND(T)> struct BuilderFor_ { typedef typename T::Builder Type; };
|
||||||
|
template <typename T> struct BuilderFor_<T, Kind::PRIMITIVE> { typedef T Type; };
|
||||||
|
template <typename T> struct BuilderFor_<T, Kind::ENUM> { typedef T Type; };
|
||||||
|
template <typename T> struct BuilderFor_<T, Kind::INTERFACE> { typedef typename T::Client Type; };
|
||||||
|
template <typename T> using BuilderFor = typename BuilderFor_<T>::Type;
|
||||||
|
// The type returned by List<T>::Builder::operator[].
|
||||||
|
|
||||||
|
template <typename T, Kind k = CAPNP_KIND(T)> struct PipelineFor_ { typedef typename T::Pipeline Type;};
|
||||||
|
template <typename T> struct PipelineFor_<T, Kind::INTERFACE> { typedef typename T::Client Type; };
|
||||||
|
template <typename T> using PipelineFor = typename PipelineFor_<T>::Type;
|
||||||
|
|
||||||
|
template <typename T, Kind k = CAPNP_KIND(T)> struct TypeIfEnum_;
|
||||||
|
template <typename T> struct TypeIfEnum_<T, Kind::ENUM> { typedef T Type; };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using TypeIfEnum = typename TypeIfEnum_<kj::Decay<T>>::Type;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using FromReader = typename kj::Decay<T>::Reads;
|
||||||
|
// FromReader<MyType::Reader> = MyType (for any Cap'n Proto type).
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using FromBuilder = typename kj::Decay<T>::Builds;
|
||||||
|
// FromBuilder<MyType::Builder> = MyType (for any Cap'n Proto type).
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using FromPipeline = typename kj::Decay<T>::Pipelines;
|
||||||
|
// FromBuilder<MyType::Pipeline> = MyType (for any Cap'n Proto type).
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using FromClient = typename kj::Decay<T>::Calls;
|
||||||
|
// FromReader<MyType::Client> = MyType (for any Cap'n Proto interface type).
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using FromServer = typename kj::Decay<T>::Serves;
|
||||||
|
// FromBuilder<MyType::Server> = MyType (for any Cap'n Proto interface type).
|
||||||
|
|
||||||
|
template <typename T, typename = void>
|
||||||
|
struct FromAny_;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct FromAny_<T, kj::VoidSfinae<FromReader<T>>> {
|
||||||
|
using Type = FromReader<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct FromAny_<T, kj::VoidSfinae<FromBuilder<T>>> {
|
||||||
|
using Type = FromBuilder<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct FromAny_<T, kj::VoidSfinae<FromPipeline<T>>> {
|
||||||
|
using Type = FromPipeline<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note that T::Client is covered by FromReader
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct FromAny_<kj::Own<T>, kj::VoidSfinae<FromServer<T>>> {
|
||||||
|
using Type = FromServer<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct FromAny_<T,
|
||||||
|
kj::EnableIf<_::Kind_<T>::kind == Kind::PRIMITIVE || _::Kind_<T>::kind == Kind::ENUM>> {
|
||||||
|
// TODO(msvc): Ideally the EnableIf condition would be `style<T>() == Style::PRIMITIVE`, but MSVC
|
||||||
|
// cannot yet use style<T>() in this constexpr context.
|
||||||
|
|
||||||
|
using Type = kj::Decay<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using FromAny = typename FromAny_<T>::Type;
|
||||||
|
// Given any Cap'n Proto value type as an input, return the Cap'n Proto base type. That is:
|
||||||
|
//
|
||||||
|
// Foo::Reader -> Foo
|
||||||
|
// Foo::Builder -> Foo
|
||||||
|
// Foo::Pipeline -> Foo
|
||||||
|
// Foo::Client -> Foo
|
||||||
|
// Own<Foo::Server> -> Foo
|
||||||
|
// uint32_t -> uint32_t
|
||||||
|
|
||||||
|
namespace _ { // private
|
||||||
|
|
||||||
|
template <typename T, Kind k = CAPNP_KIND(T)>
|
||||||
|
struct PointerHelpers;
|
||||||
|
|
||||||
|
#if _MSC_VER
|
||||||
|
|
||||||
|
template <typename T, Kind k>
|
||||||
|
struct PointerHelpers {};
|
||||||
|
// For some reason, without this declaration, MSVC will error out on some uses of PointerHelpers
|
||||||
|
// claiming that "T" -- as used in the default initializer for the second template param, "k" --
|
||||||
|
// is not defined. I do not understand this error, but adding this empty default declaration fixes
|
||||||
|
// it.
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace _ (private)
|
||||||
|
|
||||||
|
struct MessageSize {
|
||||||
|
// Size of a message. Every struct and list type has a method `.totalSize()` that returns this.
|
||||||
|
uint64_t wordCount;
|
||||||
|
uint capCount;
|
||||||
|
|
||||||
|
inline constexpr MessageSize operator+(const MessageSize& other) const {
|
||||||
|
return { wordCount + other.wordCount, capCount + other.capCount };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// Raw memory types and measures
|
||||||
|
|
||||||
|
using kj::byte;
|
||||||
|
|
||||||
|
class word {
|
||||||
|
// word is an opaque type with size of 64 bits. This type is useful only to make pointer
|
||||||
|
// arithmetic clearer. Since the contents are private, the only way to access them is to first
|
||||||
|
// reinterpret_cast to some other pointer type.
|
||||||
|
//
|
||||||
|
// Copying is disallowed because you should always use memcpy(). Otherwise, you may run afoul of
|
||||||
|
// aliasing rules.
|
||||||
|
//
|
||||||
|
// A pointer of type word* should always be word-aligned even if won't actually be dereferenced
|
||||||
|
// as that type.
|
||||||
|
public:
|
||||||
|
word() = default;
|
||||||
|
private:
|
||||||
|
uint64_t content KJ_UNUSED_MEMBER;
|
||||||
|
#if __GNUC__ < 8 || __clang__
|
||||||
|
// GCC 8's -Wclass-memaccess complains whenever we try to memcpy() a `word` if we've disallowed
|
||||||
|
// the copy constructor. We don't want to disable the warning becaues it's a useful warning and
|
||||||
|
// we'd have to disable it for all applications that include this header. Instead we allow `word`
|
||||||
|
// to be copyable on GCC.
|
||||||
|
KJ_DISALLOW_COPY(word);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(byte) == 1, "uint8_t is not one byte?");
|
||||||
|
static_assert(sizeof(word) == 8, "uint64_t is not 8 bytes?");
|
||||||
|
|
||||||
|
#if CAPNP_DEBUG_TYPES
|
||||||
|
// Set CAPNP_DEBUG_TYPES to 1 to use kj::Quantity for "count" types. Otherwise, plain integers are
|
||||||
|
// used. All the code should still operate exactly the same, we just lose compile-time checking.
|
||||||
|
// Note that this will also change symbol names, so it's important that the library and any clients
|
||||||
|
// be compiled with the same setting here.
|
||||||
|
//
|
||||||
|
// We disable this by default to reduce symbol name size and avoid any possibility of the compiler
|
||||||
|
// failing to fully-optimize the types, but anyone modifying Cap'n Proto itself should enable this
|
||||||
|
// during development and testing.
|
||||||
|
|
||||||
|
namespace _ { class BitLabel; class ElementLabel; struct WirePointer; }
|
||||||
|
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using BitCountN = kj::Quantity<kj::Bounded<kj::maxValueForBits<width>(), T>, _::BitLabel>;
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using ByteCountN = kj::Quantity<kj::Bounded<kj::maxValueForBits<width>(), T>, byte>;
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using WordCountN = kj::Quantity<kj::Bounded<kj::maxValueForBits<width>(), T>, word>;
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using ElementCountN = kj::Quantity<kj::Bounded<kj::maxValueForBits<width>(), T>, _::ElementLabel>;
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using WirePointerCountN = kj::Quantity<kj::Bounded<kj::maxValueForBits<width>(), T>, _::WirePointer>;
|
||||||
|
|
||||||
|
typedef BitCountN<8, uint8_t> BitCount8;
|
||||||
|
typedef BitCountN<16, uint16_t> BitCount16;
|
||||||
|
typedef BitCountN<32, uint32_t> BitCount32;
|
||||||
|
typedef BitCountN<64, uint64_t> BitCount64;
|
||||||
|
typedef BitCountN<sizeof(uint) * 8, uint> BitCount;
|
||||||
|
|
||||||
|
typedef ByteCountN<8, uint8_t> ByteCount8;
|
||||||
|
typedef ByteCountN<16, uint16_t> ByteCount16;
|
||||||
|
typedef ByteCountN<32, uint32_t> ByteCount32;
|
||||||
|
typedef ByteCountN<64, uint64_t> ByteCount64;
|
||||||
|
typedef ByteCountN<sizeof(uint) * 8, uint> ByteCount;
|
||||||
|
|
||||||
|
typedef WordCountN<8, uint8_t> WordCount8;
|
||||||
|
typedef WordCountN<16, uint16_t> WordCount16;
|
||||||
|
typedef WordCountN<32, uint32_t> WordCount32;
|
||||||
|
typedef WordCountN<64, uint64_t> WordCount64;
|
||||||
|
typedef WordCountN<sizeof(uint) * 8, uint> WordCount;
|
||||||
|
|
||||||
|
typedef ElementCountN<8, uint8_t> ElementCount8;
|
||||||
|
typedef ElementCountN<16, uint16_t> ElementCount16;
|
||||||
|
typedef ElementCountN<32, uint32_t> ElementCount32;
|
||||||
|
typedef ElementCountN<64, uint64_t> ElementCount64;
|
||||||
|
typedef ElementCountN<sizeof(uint) * 8, uint> ElementCount;
|
||||||
|
|
||||||
|
typedef WirePointerCountN<8, uint8_t> WirePointerCount8;
|
||||||
|
typedef WirePointerCountN<16, uint16_t> WirePointerCount16;
|
||||||
|
typedef WirePointerCountN<32, uint32_t> WirePointerCount32;
|
||||||
|
typedef WirePointerCountN<64, uint64_t> WirePointerCount64;
|
||||||
|
typedef WirePointerCountN<sizeof(uint) * 8, uint> WirePointerCount;
|
||||||
|
|
||||||
|
template <uint width>
|
||||||
|
using BitsPerElementN = decltype(BitCountN<width>() / ElementCountN<width>());
|
||||||
|
template <uint width>
|
||||||
|
using BytesPerElementN = decltype(ByteCountN<width>() / ElementCountN<width>());
|
||||||
|
template <uint width>
|
||||||
|
using WordsPerElementN = decltype(WordCountN<width>() / ElementCountN<width>());
|
||||||
|
template <uint width>
|
||||||
|
using PointersPerElementN = decltype(WirePointerCountN<width>() / ElementCountN<width>());
|
||||||
|
|
||||||
|
using kj::bounded;
|
||||||
|
using kj::unbound;
|
||||||
|
using kj::unboundAs;
|
||||||
|
using kj::unboundMax;
|
||||||
|
using kj::unboundMaxBits;
|
||||||
|
using kj::assertMax;
|
||||||
|
using kj::assertMaxBits;
|
||||||
|
using kj::upgradeBound;
|
||||||
|
using kj::ThrowOverflow;
|
||||||
|
using kj::assumeBits;
|
||||||
|
using kj::assumeMax;
|
||||||
|
using kj::subtractChecked;
|
||||||
|
using kj::trySubtract;
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr U* operator+(U* ptr, kj::Quantity<T, U> offset) {
|
||||||
|
return ptr + unbound(offset / kj::unit<kj::Quantity<T, U>>());
|
||||||
|
}
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr const U* operator+(const U* ptr, kj::Quantity<T, U> offset) {
|
||||||
|
return ptr + unbound(offset / kj::unit<kj::Quantity<T, U>>());
|
||||||
|
}
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr U* operator+=(U*& ptr, kj::Quantity<T, U> offset) {
|
||||||
|
return ptr = ptr + unbound(offset / kj::unit<kj::Quantity<T, U>>());
|
||||||
|
}
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr const U* operator+=(const U*& ptr, kj::Quantity<T, U> offset) {
|
||||||
|
return ptr = ptr + unbound(offset / kj::unit<kj::Quantity<T, U>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr U* operator-(U* ptr, kj::Quantity<T, U> offset) {
|
||||||
|
return ptr - unbound(offset / kj::unit<kj::Quantity<T, U>>());
|
||||||
|
}
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr const U* operator-(const U* ptr, kj::Quantity<T, U> offset) {
|
||||||
|
return ptr - unbound(offset / kj::unit<kj::Quantity<T, U>>());
|
||||||
|
}
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr U* operator-=(U*& ptr, kj::Quantity<T, U> offset) {
|
||||||
|
return ptr = ptr - unbound(offset / kj::unit<kj::Quantity<T, U>>());
|
||||||
|
}
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr const U* operator-=(const U*& ptr, kj::Quantity<T, U> offset) {
|
||||||
|
return ptr = ptr - unbound(offset / kj::unit<kj::Quantity<T, U>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto BITS = kj::unit<BitCountN<1>>();
|
||||||
|
constexpr auto BYTES = kj::unit<ByteCountN<1>>();
|
||||||
|
constexpr auto WORDS = kj::unit<WordCountN<1>>();
|
||||||
|
constexpr auto ELEMENTS = kj::unit<ElementCountN<1>>();
|
||||||
|
constexpr auto POINTERS = kj::unit<WirePointerCountN<1>>();
|
||||||
|
|
||||||
|
constexpr auto ZERO = kj::bounded<0>();
|
||||||
|
constexpr auto ONE = kj::bounded<1>();
|
||||||
|
|
||||||
|
// GCC 4.7 actually gives unused warnings on these constants in opt mode...
|
||||||
|
constexpr auto BITS_PER_BYTE KJ_UNUSED = bounded<8>() * BITS / BYTES;
|
||||||
|
constexpr auto BITS_PER_WORD KJ_UNUSED = bounded<64>() * BITS / WORDS;
|
||||||
|
constexpr auto BYTES_PER_WORD KJ_UNUSED = bounded<8>() * BYTES / WORDS;
|
||||||
|
|
||||||
|
constexpr auto BITS_PER_POINTER KJ_UNUSED = bounded<64>() * BITS / POINTERS;
|
||||||
|
constexpr auto BYTES_PER_POINTER KJ_UNUSED = bounded<8>() * BYTES / POINTERS;
|
||||||
|
constexpr auto WORDS_PER_POINTER KJ_UNUSED = ONE * WORDS / POINTERS;
|
||||||
|
|
||||||
|
constexpr auto POINTER_SIZE_IN_WORDS = ONE * POINTERS * WORDS_PER_POINTER;
|
||||||
|
|
||||||
|
constexpr uint SEGMENT_WORD_COUNT_BITS = 29; // Number of words in a segment.
|
||||||
|
constexpr uint LIST_ELEMENT_COUNT_BITS = 29; // Number of elements in a list.
|
||||||
|
constexpr uint STRUCT_DATA_WORD_COUNT_BITS = 16; // Number of words in a Struct data section.
|
||||||
|
constexpr uint STRUCT_POINTER_COUNT_BITS = 16; // Number of pointers in a Struct pointer section.
|
||||||
|
constexpr uint BLOB_SIZE_BITS = 29; // Number of bytes in a blob.
|
||||||
|
|
||||||
|
typedef WordCountN<SEGMENT_WORD_COUNT_BITS> SegmentWordCount;
|
||||||
|
typedef ElementCountN<LIST_ELEMENT_COUNT_BITS> ListElementCount;
|
||||||
|
typedef WordCountN<STRUCT_DATA_WORD_COUNT_BITS, uint16_t> StructDataWordCount;
|
||||||
|
typedef WirePointerCountN<STRUCT_POINTER_COUNT_BITS, uint16_t> StructPointerCount;
|
||||||
|
typedef ByteCountN<BLOB_SIZE_BITS> BlobSize;
|
||||||
|
|
||||||
|
constexpr auto MAX_SEGMENT_WORDS =
|
||||||
|
bounded<kj::maxValueForBits<SEGMENT_WORD_COUNT_BITS>()>() * WORDS;
|
||||||
|
constexpr auto MAX_LIST_ELEMENTS =
|
||||||
|
bounded<kj::maxValueForBits<LIST_ELEMENT_COUNT_BITS>()>() * ELEMENTS;
|
||||||
|
constexpr auto MAX_STUCT_DATA_WORDS =
|
||||||
|
bounded<kj::maxValueForBits<STRUCT_DATA_WORD_COUNT_BITS>()>() * WORDS;
|
||||||
|
constexpr auto MAX_STRUCT_POINTER_COUNT =
|
||||||
|
bounded<kj::maxValueForBits<STRUCT_POINTER_COUNT_BITS>()>() * POINTERS;
|
||||||
|
|
||||||
|
using StructDataBitCount = decltype(WordCountN<STRUCT_POINTER_COUNT_BITS>() * BITS_PER_WORD);
|
||||||
|
// Number of bits in a Struct data segment (should come out to BitCountN<22>).
|
||||||
|
|
||||||
|
using StructDataOffset = decltype(StructDataBitCount() * (ONE * ELEMENTS / BITS));
|
||||||
|
using StructPointerOffset = StructPointerCount;
|
||||||
|
// Type of a field offset.
|
||||||
|
|
||||||
|
inline StructDataOffset assumeDataOffset(uint32_t offset) {
|
||||||
|
return assumeMax(MAX_STUCT_DATA_WORDS * BITS_PER_WORD * (ONE * ELEMENTS / BITS),
|
||||||
|
bounded(offset) * ELEMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline StructPointerOffset assumePointerOffset(uint32_t offset) {
|
||||||
|
return assumeMax(MAX_STRUCT_POINTER_COUNT, bounded(offset) * POINTERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint MAX_TEXT_SIZE = kj::maxValueForBits<BLOB_SIZE_BITS>() - 1;
|
||||||
|
typedef kj::Quantity<kj::Bounded<MAX_TEXT_SIZE, uint>, byte> TextSize;
|
||||||
|
// Not including NUL terminator.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline KJ_CONSTEXPR() decltype(bounded<sizeof(T)>() * BYTES / ELEMENTS) bytesPerElement() {
|
||||||
|
return bounded<sizeof(T)>() * BYTES / ELEMENTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline KJ_CONSTEXPR() decltype(bounded<sizeof(T) * 8>() * BITS / ELEMENTS) bitsPerElement() {
|
||||||
|
return bounded<sizeof(T) * 8>() * BITS / ELEMENTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, uint maxN>
|
||||||
|
inline constexpr kj::Quantity<kj::Bounded<maxN, size_t>, T>
|
||||||
|
intervalLength(const T* a, const T* b, kj::Quantity<kj::BoundedConst<maxN>, T>) {
|
||||||
|
return kj::assumeMax<maxN>(b - a) * kj::unit<kj::Quantity<kj::BoundedConst<1u>, T>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr kj::ArrayPtr<const U> arrayPtr(const U* ptr, kj::Quantity<T, U> size) {
|
||||||
|
return kj::ArrayPtr<const U>(ptr, unbound(size / kj::unit<kj::Quantity<T, U>>()));
|
||||||
|
}
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr kj::ArrayPtr<U> arrayPtr(U* ptr, kj::Quantity<T, U> size) {
|
||||||
|
return kj::ArrayPtr<U>(ptr, unbound(size / kj::unit<kj::Quantity<T, U>>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using BitCountN = T;
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using ByteCountN = T;
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using WordCountN = T;
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using ElementCountN = T;
|
||||||
|
template <uint width, typename T = uint>
|
||||||
|
using WirePointerCountN = T;
|
||||||
|
|
||||||
|
|
||||||
|
// XXX
|
||||||
|
typedef BitCountN<8, uint8_t> BitCount8;
|
||||||
|
typedef BitCountN<16, uint16_t> BitCount16;
|
||||||
|
typedef BitCountN<32, uint32_t> BitCount32;
|
||||||
|
typedef BitCountN<64, uint64_t> BitCount64;
|
||||||
|
typedef BitCountN<sizeof(uint) * 8, uint> BitCount;
|
||||||
|
|
||||||
|
typedef ByteCountN<8, uint8_t> ByteCount8;
|
||||||
|
typedef ByteCountN<16, uint16_t> ByteCount16;
|
||||||
|
typedef ByteCountN<32, uint32_t> ByteCount32;
|
||||||
|
typedef ByteCountN<64, uint64_t> ByteCount64;
|
||||||
|
typedef ByteCountN<sizeof(uint) * 8, uint> ByteCount;
|
||||||
|
|
||||||
|
typedef WordCountN<8, uint8_t> WordCount8;
|
||||||
|
typedef WordCountN<16, uint16_t> WordCount16;
|
||||||
|
typedef WordCountN<32, uint32_t> WordCount32;
|
||||||
|
typedef WordCountN<64, uint64_t> WordCount64;
|
||||||
|
typedef WordCountN<sizeof(uint) * 8, uint> WordCount;
|
||||||
|
|
||||||
|
typedef ElementCountN<8, uint8_t> ElementCount8;
|
||||||
|
typedef ElementCountN<16, uint16_t> ElementCount16;
|
||||||
|
typedef ElementCountN<32, uint32_t> ElementCount32;
|
||||||
|
typedef ElementCountN<64, uint64_t> ElementCount64;
|
||||||
|
typedef ElementCountN<sizeof(uint) * 8, uint> ElementCount;
|
||||||
|
|
||||||
|
typedef WirePointerCountN<8, uint8_t> WirePointerCount8;
|
||||||
|
typedef WirePointerCountN<16, uint16_t> WirePointerCount16;
|
||||||
|
typedef WirePointerCountN<32, uint32_t> WirePointerCount32;
|
||||||
|
typedef WirePointerCountN<64, uint64_t> WirePointerCount64;
|
||||||
|
typedef WirePointerCountN<sizeof(uint) * 8, uint> WirePointerCount;
|
||||||
|
|
||||||
|
template <uint width>
|
||||||
|
using BitsPerElementN = decltype(BitCountN<width>() / ElementCountN<width>());
|
||||||
|
template <uint width>
|
||||||
|
using BytesPerElementN = decltype(ByteCountN<width>() / ElementCountN<width>());
|
||||||
|
template <uint width>
|
||||||
|
using WordsPerElementN = decltype(WordCountN<width>() / ElementCountN<width>());
|
||||||
|
template <uint width>
|
||||||
|
using PointersPerElementN = decltype(WirePointerCountN<width>() / ElementCountN<width>());
|
||||||
|
|
||||||
|
using kj::ThrowOverflow;
|
||||||
|
// YYY
|
||||||
|
|
||||||
|
template <uint i> inline constexpr uint bounded() { return i; }
|
||||||
|
template <typename T> inline constexpr T bounded(T i) { return i; }
|
||||||
|
template <typename T> inline constexpr T unbound(T i) { return i; }
|
||||||
|
|
||||||
|
template <typename T, typename U> inline constexpr T unboundAs(U i) { return i; }
|
||||||
|
|
||||||
|
template <uint64_t requestedMax, typename T> inline constexpr uint unboundMax(T i) { return i; }
|
||||||
|
template <uint bits, typename T> inline constexpr uint unboundMaxBits(T i) { return i; }
|
||||||
|
|
||||||
|
template <uint newMax, typename T, typename ErrorFunc>
|
||||||
|
inline T assertMax(T value, ErrorFunc&& func) {
|
||||||
|
if (KJ_UNLIKELY(value > newMax)) func();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename ErrorFunc>
|
||||||
|
inline T assertMax(uint newMax, T value, ErrorFunc&& func) {
|
||||||
|
if (KJ_UNLIKELY(value > newMax)) func();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <uint bits, typename T, typename ErrorFunc = ThrowOverflow>
|
||||||
|
inline T assertMaxBits(T value, ErrorFunc&& func = ErrorFunc()) {
|
||||||
|
if (KJ_UNLIKELY(value > kj::maxValueForBits<bits>())) func();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename ErrorFunc = ThrowOverflow>
|
||||||
|
inline T assertMaxBits(uint bits, T value, ErrorFunc&& func = ErrorFunc()) {
|
||||||
|
if (KJ_UNLIKELY(value > (1ull << bits) - 1)) func();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U> inline constexpr T upgradeBound(U i) { return i; }
|
||||||
|
|
||||||
|
template <uint bits, typename T> inline constexpr T assumeBits(T i) { return i; }
|
||||||
|
template <uint64_t max, typename T> inline constexpr T assumeMax(T i) { return i; }
|
||||||
|
|
||||||
|
template <typename T, typename U, typename ErrorFunc = ThrowOverflow>
|
||||||
|
inline auto subtractChecked(T a, U b, ErrorFunc&& errorFunc = ErrorFunc())
|
||||||
|
-> decltype(a - b) {
|
||||||
|
if (b > a) errorFunc();
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline auto trySubtract(T a, U b) -> kj::Maybe<decltype(a - b)> {
|
||||||
|
if (b > a) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint BITS = 1;
|
||||||
|
constexpr uint BYTES = 1;
|
||||||
|
constexpr uint WORDS = 1;
|
||||||
|
constexpr uint ELEMENTS = 1;
|
||||||
|
constexpr uint POINTERS = 1;
|
||||||
|
|
||||||
|
constexpr uint ZERO = 0;
|
||||||
|
constexpr uint ONE = 1;
|
||||||
|
|
||||||
|
// GCC 4.7 actually gives unused warnings on these constants in opt mode...
|
||||||
|
constexpr uint BITS_PER_BYTE KJ_UNUSED = 8;
|
||||||
|
constexpr uint BITS_PER_WORD KJ_UNUSED = 64;
|
||||||
|
constexpr uint BYTES_PER_WORD KJ_UNUSED = 8;
|
||||||
|
|
||||||
|
constexpr uint BITS_PER_POINTER KJ_UNUSED = 64;
|
||||||
|
constexpr uint BYTES_PER_POINTER KJ_UNUSED = 8;
|
||||||
|
constexpr uint WORDS_PER_POINTER KJ_UNUSED = 1;
|
||||||
|
|
||||||
|
// XXX
|
||||||
|
constexpr uint POINTER_SIZE_IN_WORDS = ONE * POINTERS * WORDS_PER_POINTER;
|
||||||
|
|
||||||
|
constexpr uint SEGMENT_WORD_COUNT_BITS = 29; // Number of words in a segment.
|
||||||
|
constexpr uint LIST_ELEMENT_COUNT_BITS = 29; // Number of elements in a list.
|
||||||
|
constexpr uint STRUCT_DATA_WORD_COUNT_BITS = 16; // Number of words in a Struct data section.
|
||||||
|
constexpr uint STRUCT_POINTER_COUNT_BITS = 16; // Number of pointers in a Struct pointer section.
|
||||||
|
constexpr uint BLOB_SIZE_BITS = 29; // Number of bytes in a blob.
|
||||||
|
|
||||||
|
typedef WordCountN<SEGMENT_WORD_COUNT_BITS> SegmentWordCount;
|
||||||
|
typedef ElementCountN<LIST_ELEMENT_COUNT_BITS> ListElementCount;
|
||||||
|
typedef WordCountN<STRUCT_DATA_WORD_COUNT_BITS, uint16_t> StructDataWordCount;
|
||||||
|
typedef WirePointerCountN<STRUCT_POINTER_COUNT_BITS, uint16_t> StructPointerCount;
|
||||||
|
typedef ByteCountN<BLOB_SIZE_BITS> BlobSize;
|
||||||
|
// YYY
|
||||||
|
|
||||||
|
constexpr auto MAX_SEGMENT_WORDS = kj::maxValueForBits<SEGMENT_WORD_COUNT_BITS>();
|
||||||
|
constexpr auto MAX_LIST_ELEMENTS = kj::maxValueForBits<LIST_ELEMENT_COUNT_BITS>();
|
||||||
|
constexpr auto MAX_STUCT_DATA_WORDS = kj::maxValueForBits<STRUCT_DATA_WORD_COUNT_BITS>();
|
||||||
|
constexpr auto MAX_STRUCT_POINTER_COUNT = kj::maxValueForBits<STRUCT_POINTER_COUNT_BITS>();
|
||||||
|
|
||||||
|
typedef uint StructDataBitCount;
|
||||||
|
typedef uint StructDataOffset;
|
||||||
|
typedef uint StructPointerOffset;
|
||||||
|
|
||||||
|
inline StructDataOffset assumeDataOffset(uint32_t offset) { return offset; }
|
||||||
|
inline StructPointerOffset assumePointerOffset(uint32_t offset) { return offset; }
|
||||||
|
|
||||||
|
constexpr uint MAX_TEXT_SIZE = kj::maxValueForBits<BLOB_SIZE_BITS>() - 1;
|
||||||
|
typedef uint TextSize;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline KJ_CONSTEXPR() size_t bytesPerElement() { return sizeof(T); }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline KJ_CONSTEXPR() size_t bitsPerElement() { return sizeof(T) * 8; }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline constexpr ptrdiff_t intervalLength(const T* a, const T* b, uint) {
|
||||||
|
return b - a;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr kj::ArrayPtr<const U> arrayPtr(const U* ptr, T size) {
|
||||||
|
return kj::arrayPtr(ptr, size);
|
||||||
|
}
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline constexpr kj::ArrayPtr<U> arrayPtr(U* ptr, T size) {
|
||||||
|
return kj::arrayPtr(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,980 @@
|
||||||
|
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "json.h"
|
||||||
|
#include <capnp/test-util.h>
|
||||||
|
#include <capnp/compat/json.capnp.h>
|
||||||
|
#include <capnp/compat/json-test.capnp.h>
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <kj/string.h>
|
||||||
|
#include <kj/test.h>
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace _ { // private
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
KJ_TEST("basic json encoding") {
|
||||||
|
JsonCodec json;
|
||||||
|
|
||||||
|
KJ_EXPECT(json.encode(VOID) == "null");
|
||||||
|
KJ_EXPECT(json.encode(true) == "true");
|
||||||
|
KJ_EXPECT(json.encode(false) == "false");
|
||||||
|
KJ_EXPECT(json.encode(123) == "123");
|
||||||
|
KJ_EXPECT(json.encode(-5.5) == "-5.5");
|
||||||
|
KJ_EXPECT(json.encode(Text::Reader("foo")) == "\"foo\"");
|
||||||
|
KJ_EXPECT(json.encode(Text::Reader("ab\"cd\\ef\x03")) == "\"ab\\\"cd\\\\ef\\u0003\"");
|
||||||
|
KJ_EXPECT(json.encode(test::TestEnum::CORGE) == "\"corge\"");
|
||||||
|
|
||||||
|
byte bytes[] = {12, 34, 56};
|
||||||
|
KJ_EXPECT(json.encode(Data::Reader(bytes, 3)) == "[12,34,56]");
|
||||||
|
|
||||||
|
json.setPrettyPrint(true);
|
||||||
|
KJ_EXPECT(json.encode(Data::Reader(bytes, 3)) == "[12, 34, 56]");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char ALL_TYPES_JSON[] =
|
||||||
|
"{ \"voidField\": null,\n"
|
||||||
|
" \"boolField\": true,\n"
|
||||||
|
" \"int8Field\": -123,\n"
|
||||||
|
" \"int16Field\": -12345,\n"
|
||||||
|
" \"int32Field\": -12345678,\n"
|
||||||
|
" \"int64Field\": \"-123456789012345\",\n"
|
||||||
|
" \"uInt8Field\": 234,\n"
|
||||||
|
" \"uInt16Field\": 45678,\n"
|
||||||
|
" \"uInt32Field\": 3456789012,\n"
|
||||||
|
" \"uInt64Field\": \"12345678901234567890\",\n"
|
||||||
|
" \"float32Field\": 1234.5,\n"
|
||||||
|
" \"float64Field\": -1.23e47,\n"
|
||||||
|
" \"textField\": \"foo\",\n"
|
||||||
|
" \"dataField\": [98, 97, 114],\n"
|
||||||
|
" \"structField\": {\n"
|
||||||
|
" \"voidField\": null,\n"
|
||||||
|
" \"boolField\": true,\n"
|
||||||
|
" \"int8Field\": -12,\n"
|
||||||
|
" \"int16Field\": 3456,\n"
|
||||||
|
" \"int32Field\": -78901234,\n"
|
||||||
|
" \"int64Field\": \"56789012345678\",\n"
|
||||||
|
" \"uInt8Field\": 90,\n"
|
||||||
|
" \"uInt16Field\": 1234,\n"
|
||||||
|
" \"uInt32Field\": 56789012,\n"
|
||||||
|
" \"uInt64Field\": \"345678901234567890\",\n"
|
||||||
|
" \"float32Field\": -1.2499999646475857e-10,\n"
|
||||||
|
" \"float64Field\": 345,\n"
|
||||||
|
" \"textField\": \"baz\",\n"
|
||||||
|
" \"dataField\": [113, 117, 120],\n"
|
||||||
|
" \"structField\": {\n"
|
||||||
|
" \"voidField\": null,\n"
|
||||||
|
" \"boolField\": false,\n"
|
||||||
|
" \"int8Field\": 0,\n"
|
||||||
|
" \"int16Field\": 0,\n"
|
||||||
|
" \"int32Field\": 0,\n"
|
||||||
|
" \"int64Field\": \"0\",\n"
|
||||||
|
" \"uInt8Field\": 0,\n"
|
||||||
|
" \"uInt16Field\": 0,\n"
|
||||||
|
" \"uInt32Field\": 0,\n"
|
||||||
|
" \"uInt64Field\": \"0\",\n"
|
||||||
|
" \"float32Field\": 0,\n"
|
||||||
|
" \"float64Field\": 0,\n"
|
||||||
|
" \"textField\": \"nested\",\n"
|
||||||
|
" \"structField\": {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"really nested\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
|
||||||
|
" \"enumField\": \"foo\",\n"
|
||||||
|
" \"interfaceField\": null },\n"
|
||||||
|
" \"enumField\": \"baz\",\n"
|
||||||
|
" \"interfaceField\": null,\n"
|
||||||
|
" \"voidList\": [null, null, null],\n"
|
||||||
|
" \"boolList\": [false, true, false, true, true],\n"
|
||||||
|
" \"int8List\": [12, -34, -128, 127],\n"
|
||||||
|
" \"int16List\": [1234, -5678, -32768, 32767],\n"
|
||||||
|
" \"int32List\": [12345678, -90123456, -2147483648, 2147483647],\n"
|
||||||
|
" \"int64List\": [\"123456789012345\", \"-678901234567890\", \"-9223372036854775808\", \"9223372036854775807\"],\n"
|
||||||
|
" \"uInt8List\": [12, 34, 0, 255],\n"
|
||||||
|
" \"uInt16List\": [1234, 5678, 0, 65535],\n"
|
||||||
|
" \"uInt32List\": [12345678, 90123456, 0, 4294967295],\n"
|
||||||
|
" \"uInt64List\": [\"123456789012345\", \"678901234567890\", \"0\", \"18446744073709551615\"],\n"
|
||||||
|
" \"float32List\": [0, 1234567, 9.9999999338158125e36, -9.9999999338158125e36, 9.99999991097579e-38, -9.99999991097579e-38],\n"
|
||||||
|
" \"float64List\": [0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306],\n"
|
||||||
|
" \"textList\": [\"quux\", \"corge\", \"grault\"],\n"
|
||||||
|
" \"dataList\": [[103, 97, 114, 112, 108, 121], [119, 97, 108, 100, 111], [102, 114, 101, 100]],\n"
|
||||||
|
" \"structList\": [\n"
|
||||||
|
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"x structlist 1\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
|
||||||
|
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"x structlist 2\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
|
||||||
|
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"x structlist 3\", \"enumField\": \"foo\", \"interfaceField\": null} ],\n"
|
||||||
|
" \"enumList\": [\"qux\", \"bar\", \"grault\"] },\n"
|
||||||
|
" \"enumField\": \"corge\",\n"
|
||||||
|
" \"interfaceField\": null,\n"
|
||||||
|
" \"voidList\": [null, null, null, null, null, null],\n"
|
||||||
|
" \"boolList\": [true, false, false, true],\n"
|
||||||
|
" \"int8List\": [111, -111],\n"
|
||||||
|
" \"int16List\": [11111, -11111],\n"
|
||||||
|
" \"int32List\": [111111111, -111111111],\n"
|
||||||
|
" \"int64List\": [\"1111111111111111111\", \"-1111111111111111111\"],\n"
|
||||||
|
" \"uInt8List\": [111, 222],\n"
|
||||||
|
" \"uInt16List\": [33333, 44444],\n"
|
||||||
|
" \"uInt32List\": [3333333333],\n"
|
||||||
|
" \"uInt64List\": [\"11111111111111111111\"],\n"
|
||||||
|
" \"float32List\": [5555.5, \"Infinity\", \"-Infinity\", \"NaN\"],\n"
|
||||||
|
" \"float64List\": [7777.75, \"Infinity\", \"-Infinity\", \"NaN\"],\n"
|
||||||
|
" \"textList\": [\"plugh\", \"xyzzy\", \"thud\"],\n"
|
||||||
|
" \"dataList\": [[111, 111, 112, 115], [101, 120, 104, 97, 117, 115, 116, 101, 100], [114, 102, 99, 51, 48, 57, 50]],\n"
|
||||||
|
" \"structList\": [\n"
|
||||||
|
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"structlist 1\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
|
||||||
|
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"structlist 2\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
|
||||||
|
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"structlist 3\", \"enumField\": \"foo\", \"interfaceField\": null} ],\n"
|
||||||
|
" \"enumList\": [\"foo\", \"garply\"] }";
|
||||||
|
|
||||||
|
KJ_TEST("encode all types") {
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<TestAllTypes>();
|
||||||
|
initTestMessage(root);
|
||||||
|
|
||||||
|
JsonCodec json;
|
||||||
|
json.setPrettyPrint(true);
|
||||||
|
KJ_EXPECT(json.encode(root) == ALL_TYPES_JSON);
|
||||||
|
|
||||||
|
// Verify that if we strip out the non-string spaces, we get the non-pretty-print version.
|
||||||
|
kj::Vector<char> chars;
|
||||||
|
bool inQuotes = false;
|
||||||
|
for (char c: ALL_TYPES_JSON) {
|
||||||
|
if (c == '\"') inQuotes = !inQuotes;
|
||||||
|
|
||||||
|
if ((c == '\n' || c == ' ') && !inQuotes) {
|
||||||
|
// skip space
|
||||||
|
} else {
|
||||||
|
chars.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kj::String nospaces(chars.releaseAsArray());
|
||||||
|
|
||||||
|
json.setPrettyPrint(false);
|
||||||
|
KJ_EXPECT(json.encode(root) == nospaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("encode union") {
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<test::TestUnnamedUnion>();
|
||||||
|
|
||||||
|
root.setBefore("a");
|
||||||
|
root.setMiddle(44);
|
||||||
|
root.setAfter("c");
|
||||||
|
|
||||||
|
JsonCodec json;
|
||||||
|
|
||||||
|
root.setFoo(123);
|
||||||
|
KJ_EXPECT(json.encode(root) == "{\"before\":\"a\",\"foo\":123,\"middle\":44,\"after\":\"c\"}");
|
||||||
|
|
||||||
|
root.setBar(321);
|
||||||
|
KJ_EXPECT(json.encode(root) == "{\"before\":\"a\",\"middle\":44,\"bar\":321,\"after\":\"c\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("decode all types") {
|
||||||
|
JsonCodec json;
|
||||||
|
json.setHasMode(HasMode::NON_DEFAULT);
|
||||||
|
|
||||||
|
#define CASE_MAYBE_ROUNDTRIP(s, f, roundtrip) \
|
||||||
|
{ \
|
||||||
|
MallocMessageBuilder message; \
|
||||||
|
auto root = message.initRoot<TestAllTypes>(); \
|
||||||
|
kj::StringPtr input = s; \
|
||||||
|
json.decode(input, root); \
|
||||||
|
KJ_EXPECT((f), input, root); \
|
||||||
|
auto reencoded = json.encode(root); \
|
||||||
|
KJ_EXPECT(roundtrip == (input == reencoded), roundtrip, input, reencoded); \
|
||||||
|
}
|
||||||
|
#define CASE_NO_ROUNDTRIP(s, f) CASE_MAYBE_ROUNDTRIP(s, f, false)
|
||||||
|
#define CASE(s, f) CASE_MAYBE_ROUNDTRIP(s, f, true)
|
||||||
|
#define CASE_THROW(s, errorMessage) \
|
||||||
|
{ \
|
||||||
|
MallocMessageBuilder message; \
|
||||||
|
auto root = message.initRoot<TestAllTypes>(); \
|
||||||
|
KJ_EXPECT_THROW_MESSAGE(errorMessage, json.decode(s, root)); \
|
||||||
|
}
|
||||||
|
#define CASE_THROW_RECOVERABLE(s, errorMessage) \
|
||||||
|
{ \
|
||||||
|
MallocMessageBuilder message; \
|
||||||
|
auto root = message.initRoot<TestAllTypes>(); \
|
||||||
|
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE(errorMessage, json.decode(s, root)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
CASE(R"({})", root.getBoolField() == false);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"unknownField":7})", root.getBoolField() == false);
|
||||||
|
CASE(R"({"boolField":true})", root.getBoolField() == true);
|
||||||
|
CASE(R"({"int8Field":-128})", root.getInt8Field() == -128);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int8Field":"127"})", root.getInt8Field() == 127);
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"int8Field":"-129"})", "Value out-of-range");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"int8Field":128})", "Value out-of-range");
|
||||||
|
CASE(R"({"int16Field":-32768})", root.getInt16Field() == -32768);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int16Field":"32767"})", root.getInt16Field() == 32767);
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"int16Field":"-32769"})", "Value out-of-range");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"int16Field":32768})", "Value out-of-range");
|
||||||
|
CASE(R"({"int32Field":-2147483648})", root.getInt32Field() == -2147483648);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int32Field":"2147483647"})", root.getInt32Field() == 2147483647);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int64Field":-9007199254740992})", root.getInt64Field() == -9007199254740992LL);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int64Field":9007199254740991})", root.getInt64Field() == 9007199254740991LL);
|
||||||
|
CASE(R"({"int64Field":"-9223372036854775808"})", root.getInt64Field() == -9223372036854775808ULL);
|
||||||
|
CASE(R"({"int64Field":"9223372036854775807"})", root.getInt64Field() == 9223372036854775807LL);
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"int64Field":"-9223372036854775809"})", "Value out-of-range");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"int64Field":"9223372036854775808"})", "Value out-of-range");
|
||||||
|
CASE(R"({"uInt8Field":255})", root.getUInt8Field() == 255);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt8Field":"0"})", root.getUInt8Field() == 0);
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"uInt8Field":"256"})", "Value out-of-range");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"uInt8Field":-1})", "Value out-of-range");
|
||||||
|
CASE(R"({"uInt16Field":65535})", root.getUInt16Field() == 65535);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt16Field":"0"})", root.getUInt16Field() == 0);
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"uInt16Field":"655356"})", "Value out-of-range");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"uInt16Field":-1})", "Value out-of-range");
|
||||||
|
CASE(R"({"uInt32Field":4294967295})", root.getUInt32Field() == 4294967295);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt32Field":"0"})", root.getUInt32Field() == 0);
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"uInt32Field":"42949672956"})", "Value out-of-range");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"uInt32Field":-1})", "Value out-of-range");
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt64Field":9007199254740991})", root.getUInt64Field() == 9007199254740991ULL);
|
||||||
|
CASE(R"({"uInt64Field":"18446744073709551615"})", root.getUInt64Field() == 18446744073709551615ULL);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt64Field":"0"})", root.getUInt64Field() == 0);
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"uInt64Field":"18446744073709551616"})", "Value out-of-range");
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":0})", root.getFloat32Field() == 0);
|
||||||
|
CASE(R"({"float32Field":4.5})", root.getFloat32Field() == 4.5);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":null})", kj::isNaN(root.getFloat32Field()));
|
||||||
|
CASE(R"({"float32Field":"NaN"})", kj::isNaN(root.getFloat32Field()));
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":"nan"})", kj::isNaN(root.getFloat32Field()));
|
||||||
|
CASE(R"({"float32Field":"Infinity"})", root.getFloat32Field() == kj::inf());
|
||||||
|
CASE(R"({"float32Field":"-Infinity"})", root.getFloat32Field() == -kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":"infinity"})", root.getFloat32Field() == kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":"-infinity"})", root.getFloat32Field() == -kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":"INF"})", root.getFloat32Field() == kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":"-INF"})", root.getFloat32Field() == -kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":1e39})", root.getFloat32Field() == kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32Field":-1e39})", root.getFloat32Field() == -kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":0})", root.getFloat64Field() == 0);
|
||||||
|
CASE(R"({"float64Field":4.5})", root.getFloat64Field() == 4.5);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":null})", kj::isNaN(root.getFloat64Field()));
|
||||||
|
CASE(R"({"float64Field":"NaN"})", kj::isNaN(root.getFloat64Field()));
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":"nan"})", kj::isNaN(root.getFloat64Field()));
|
||||||
|
CASE(R"({"float64Field":"Infinity"})", root.getFloat64Field() == kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":"infinity"})", root.getFloat64Field() == kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":"-infinity"})", root.getFloat64Field() == -kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":"INF"})", root.getFloat64Field() == kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":"-INF"})", root.getFloat64Field() == -kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":1e309})", root.getFloat64Field() == kj::inf());
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64Field":-1e309})", root.getFloat64Field() == -kj::inf());
|
||||||
|
CASE(R"({"textField":"hello"})", kj::str("hello") == root.getTextField());
|
||||||
|
CASE(R"({"dataField":[7,0,122]})",
|
||||||
|
kj::heapArray<byte>({7,0,122}).asPtr() == root.getDataField());
|
||||||
|
CASE(R"({"structField":{}})", root.hasStructField() == true);
|
||||||
|
CASE(R"({"structField":{}})", root.getStructField().getBoolField() == false);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"structField":{"boolField":false}})", root.getStructField().getBoolField() == false);
|
||||||
|
CASE(R"({"structField":{"boolField":true}})", root.getStructField().getBoolField() == true);
|
||||||
|
CASE(R"({"enumField":"bar"})", root.getEnumField() == TestEnum::BAR);
|
||||||
|
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"textField":"foo\u1234bar"})",
|
||||||
|
kj::str(u8"foo\u1234bar") == root.getTextField());
|
||||||
|
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"structField":null})", "Expected object value");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"structList":null})", "Expected list value");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"boolList":null})", "Expected list value");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"structList":[null]})", "Expected object value");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"int64Field":"177a"})", "String does not contain valid");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"uInt64Field":"177a"})", "String does not contain valid");
|
||||||
|
CASE_THROW_RECOVERABLE(R"({"float64Field":"177a"})", "String does not contain valid");
|
||||||
|
|
||||||
|
CASE(R"({})", root.hasBoolList() == false);
|
||||||
|
CASE(R"({"boolList":[]})", root.hasBoolList() == true);
|
||||||
|
CASE(R"({"boolList":[]})", root.getBoolList().size() == 0);
|
||||||
|
CASE(R"({"boolList":[false]})", root.getBoolList().size() == 1);
|
||||||
|
CASE(R"({"boolList":[false]})", root.getBoolList()[0] == false);
|
||||||
|
CASE(R"({"boolList":[true]})", root.getBoolList()[0] == true);
|
||||||
|
CASE(R"({"int8List":[7]})", root.getInt8List()[0] == 7);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int8List":["7"]})", root.getInt8List()[0] == 7);
|
||||||
|
CASE(R"({"int16List":[7]})", root.getInt16List()[0] == 7);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int16List":["7"]})", root.getInt16List()[0] == 7);
|
||||||
|
CASE(R"({"int32List":[7]})", root.getInt32List()[0] == 7);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int32List":["7"]})", root.getInt32List()[0] == 7);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"int64List":[7]})", root.getInt64List()[0] == 7);
|
||||||
|
CASE(R"({"int64List":["7"]})", root.getInt64List()[0] == 7);
|
||||||
|
CASE(R"({"uInt8List":[7]})", root.getUInt8List()[0] == 7);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt8List":["7"]})", root.getUInt8List()[0] == 7);
|
||||||
|
CASE(R"({"uInt16List":[7]})", root.getUInt16List()[0] == 7);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt16List":["7"]})", root.getUInt16List()[0] == 7);
|
||||||
|
CASE(R"({"uInt32List":[7]})", root.getUInt32List()[0] == 7);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt32List":["7"]})", root.getUInt32List()[0] == 7);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"uInt64List":[7]})", root.getUInt64List()[0] == 7);
|
||||||
|
CASE(R"({"uInt64List":["7"]})", root.getUInt64List()[0] == 7);
|
||||||
|
CASE(R"({"float32List":[4.5]})", root.getFloat32List()[0] == 4.5);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32List":["4.5"]})", root.getFloat32List()[0] == 4.5);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float32List":[null]})", kj::isNaN(root.getFloat32List()[0]));
|
||||||
|
CASE(R"({"float32List":["NaN"]})", kj::isNaN(root.getFloat32List()[0]));
|
||||||
|
CASE(R"({"float32List":["Infinity"]})", root.getFloat32List()[0] == kj::inf());
|
||||||
|
CASE(R"({"float32List":["-Infinity"]})", root.getFloat32List()[0] == -kj::inf());
|
||||||
|
CASE(R"({"float64List":[4.5]})", root.getFloat64List()[0] == 4.5);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64List":["4.5"]})", root.getFloat64List()[0] == 4.5);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"float64List":[null]})", kj::isNaN(root.getFloat64List()[0]));
|
||||||
|
CASE(R"({"float64List":["NaN"]})", kj::isNaN(root.getFloat64List()[0]));
|
||||||
|
CASE(R"({"float64List":["Infinity"]})", root.getFloat64List()[0] == kj::inf());
|
||||||
|
CASE(R"({"float64List":["-Infinity"]})", root.getFloat64List()[0] == -kj::inf());
|
||||||
|
CASE(R"({"textList":["hello"]})", kj::str("hello") == root.getTextList()[0]);
|
||||||
|
CASE(R"({"dataList":[[7,0,122]]})",
|
||||||
|
kj::heapArray<byte>({7,0,122}).asPtr() == root.getDataList()[0]);
|
||||||
|
CASE(R"({"structList":[{}]})", root.hasStructList() == true);
|
||||||
|
CASE(R"({"structList":[{}]})", root.getStructList()[0].getBoolField() == false);
|
||||||
|
CASE_NO_ROUNDTRIP(R"({"structList":[{"boolField":false}]})", root.getStructList()[0].getBoolField() == false);
|
||||||
|
CASE(R"({"structList":[{"boolField":true}]})", root.getStructList()[0].getBoolField() == true);
|
||||||
|
CASE(R"({"enumList":["bar"]})", root.getEnumList()[0] == TestEnum::BAR);
|
||||||
|
#undef CASE_MAYBE_ROUNDTRIP
|
||||||
|
#undef CASE_NO_ROUNDTRIP
|
||||||
|
#undef CASE
|
||||||
|
#undef CASE_THROW
|
||||||
|
#undef CASE_THROW_RECOVERABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("decode test message") {
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<TestAllTypes>();
|
||||||
|
initTestMessage(root);
|
||||||
|
|
||||||
|
JsonCodec json;
|
||||||
|
auto encoded = json.encode(root);
|
||||||
|
|
||||||
|
MallocMessageBuilder decodedMessage;
|
||||||
|
auto decodedRoot = decodedMessage.initRoot<TestAllTypes>();
|
||||||
|
json.decode(encoded, decodedRoot);
|
||||||
|
|
||||||
|
KJ_EXPECT(root.toString().flatten() == decodedRoot.toString().flatten());
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("basic json decoding") {
|
||||||
|
// TODO(cleanup): this test is a mess!
|
||||||
|
JsonCodec json;
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
json.decodeRaw("null", root);
|
||||||
|
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::NULL_);
|
||||||
|
KJ_EXPECT(root.getNull() == VOID);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("false", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::BOOLEAN);
|
||||||
|
KJ_EXPECT(root.getBoolean() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("true", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::BOOLEAN);
|
||||||
|
KJ_EXPECT(root.getBoolean() == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("\"foo\"", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::STRING);
|
||||||
|
KJ_EXPECT(kj::str("foo") == root.getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw(R"("\"")", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::STRING);
|
||||||
|
KJ_EXPECT(kj::str("\"") == root.getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw(R"("\\abc\"d\\e")", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::STRING);
|
||||||
|
KJ_EXPECT(kj::str("\\abc\"d\\e") == root.getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw(R"("\"\\\/\b\f\n\r\t\u0003abc\u0064\u0065f")", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::STRING);
|
||||||
|
KJ_EXPECT(kj::str("\"\\/\b\f\n\r\t\x03""abcdef") == root.getString(), root.getString());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("[]", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::ARRAY, (uint)root.which());
|
||||||
|
KJ_EXPECT(root.getArray().size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("[true]", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::ARRAY);
|
||||||
|
auto array = root.getArray();
|
||||||
|
KJ_EXPECT(array.size() == 1, array.size());
|
||||||
|
KJ_EXPECT(root.getArray()[0].which() == JsonValue::BOOLEAN);
|
||||||
|
KJ_EXPECT(root.getArray()[0].getBoolean() == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw(" [ true , false\t\n , null]", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::ARRAY);
|
||||||
|
auto array = root.getArray();
|
||||||
|
KJ_EXPECT(array.size() == 3);
|
||||||
|
KJ_EXPECT(array[0].which() == JsonValue::BOOLEAN);
|
||||||
|
KJ_EXPECT(array[0].getBoolean() == true);
|
||||||
|
KJ_EXPECT(array[1].which() == JsonValue::BOOLEAN);
|
||||||
|
KJ_EXPECT(array[1].getBoolean() == false);
|
||||||
|
KJ_EXPECT(array[2].which() == JsonValue::NULL_);
|
||||||
|
KJ_EXPECT(array[2].getNull() == VOID);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("{}", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::OBJECT, (uint)root.which());
|
||||||
|
KJ_EXPECT(root.getObject().size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("\r\n\t {\r\n\t }\r\n\t ", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::OBJECT, (uint)root.which());
|
||||||
|
KJ_EXPECT(root.getObject().size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw(R"({"some": null})", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::OBJECT, (uint)root.which());
|
||||||
|
auto object = root.getObject();
|
||||||
|
KJ_EXPECT(object.size() == 1);
|
||||||
|
KJ_EXPECT(kj::str("some") == object[0].getName());
|
||||||
|
KJ_EXPECT(object[0].getValue().which() == JsonValue::NULL_);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw(R"({"foo\n\tbaz": "a val", "bar": ["a", -5.5e11, { "z": {}}]})", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::OBJECT, (uint)root.which());
|
||||||
|
auto object = root.getObject();
|
||||||
|
KJ_EXPECT(object.size() == 2);
|
||||||
|
KJ_EXPECT(kj::str("foo\n\tbaz") == object[0].getName());
|
||||||
|
KJ_EXPECT(object[0].getValue().which() == JsonValue::STRING);
|
||||||
|
KJ_EXPECT(kj::str("a val") == object[0].getValue().getString());
|
||||||
|
|
||||||
|
KJ_EXPECT(kj::str("bar") == object[1].getName());
|
||||||
|
KJ_EXPECT(object[1].getValue().which() == JsonValue::ARRAY);
|
||||||
|
auto array = object[1].getValue().getArray();
|
||||||
|
KJ_EXPECT(array.size() == 3, array.size());
|
||||||
|
KJ_EXPECT(array[0].which() == JsonValue::STRING);
|
||||||
|
KJ_EXPECT(kj::str("a") == array[0].getString());
|
||||||
|
KJ_EXPECT(array[1].which() == JsonValue::NUMBER);
|
||||||
|
KJ_EXPECT(array[1].getNumber() == -5.5e11);
|
||||||
|
KJ_EXPECT(array[2].which() == JsonValue::OBJECT);
|
||||||
|
KJ_EXPECT(array[2].getObject().size() == 1);
|
||||||
|
KJ_EXPECT(array[2].getObject()[0].getValue().which() == JsonValue::OBJECT);
|
||||||
|
KJ_EXPECT(array[2].getObject()[0].getValue().getObject().size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("123", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::NUMBER);
|
||||||
|
KJ_EXPECT(root.getNumber() == 123);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("input", json.decodeRaw("z", root));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
// Leading + not allowed in numbers.
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected", json.decodeRaw("+123", root));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected", json.decodeRaw("[00]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected", json.decodeRaw("[01]", root));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("-", root));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("-5", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::NUMBER);
|
||||||
|
KJ_EXPECT(root.getNumber() == -5);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw("-5.5", root);
|
||||||
|
KJ_EXPECT(root.which() == JsonValue::NUMBER);
|
||||||
|
KJ_EXPECT(root.getNumber() == -5.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("a", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("[", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("{", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("\"\\u\"", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[}", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("{]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[}]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[1, , ]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[,]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[true,]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[, 1]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[1\"\"]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[1,, \"\"]", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("{\"a\"1: 0}", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw(R"({"some": null,})", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Input remains", json.decodeRaw("11a", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Invalid escape", json.decodeRaw(R"("\z")", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Invalid escape", json.decodeRaw(R"("\z")", root));
|
||||||
|
{
|
||||||
|
// TODO(msvc): This raw string literal currently confuses MSVC's preprocessor, so I hoisted
|
||||||
|
// it outside the macro.
|
||||||
|
auto f = [&] { json.decodeRaw(R"(["\n\", 3])", root); };
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("ends prematurely", f());
|
||||||
|
}
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Invalid hex", json.decodeRaw(R"("\u12zz")", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("-", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("--", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("\f{}", root));
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("{\v}", root));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("maximum nesting depth") {
|
||||||
|
JsonCodec json;
|
||||||
|
auto input = kj::str(R"({"foo": "a", "bar": ["b", { "baz": [-5.5e11] }, [ [ 1 ], { "z": 2 }]]})");
|
||||||
|
// `input` has a maximum nesting depth of 4, reached 3 times.
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw(input, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
json.setMaxNestingDepth(0);
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("nest",
|
||||||
|
json.decodeRaw(input, root));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
json.setMaxNestingDepth(3);
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
KJ_EXPECT_THROW_MESSAGE("nest",
|
||||||
|
json.decodeRaw(input, root));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
json.setMaxNestingDepth(4);
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.initRoot<JsonValue>();
|
||||||
|
|
||||||
|
json.decodeRaw(input, root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestCallHandler: public JsonCodec::Handler<Text> {
|
||||||
|
public:
|
||||||
|
void encode(const JsonCodec& codec, Text::Reader input,
|
||||||
|
JsonValue::Builder output) const override {
|
||||||
|
auto call = output.initCall();
|
||||||
|
call.setFunction("Frob");
|
||||||
|
auto params = call.initParams(2);
|
||||||
|
params[0].setNumber(123);
|
||||||
|
params[1].setString(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
Orphan<Text> decode(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Orphanage orphanage) const override {
|
||||||
|
KJ_UNIMPLEMENTED("TestHandler::decode");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestDynamicStructHandler: public JsonCodec::Handler<DynamicStruct> {
|
||||||
|
public:
|
||||||
|
void encode(const JsonCodec& codec, DynamicStruct::Reader input,
|
||||||
|
JsonValue::Builder output) const override {
|
||||||
|
auto fields = input.getSchema().getFields();
|
||||||
|
auto items = output.initArray(fields.size());
|
||||||
|
for (auto field: fields) {
|
||||||
|
KJ_REQUIRE(field.getIndex() < items.size());
|
||||||
|
auto item = items[field.getIndex()];
|
||||||
|
if (input.has(field)) {
|
||||||
|
codec.encode(input.get(field), field.getType(), item);
|
||||||
|
} else {
|
||||||
|
item.setNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decode(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
DynamicStruct::Builder output) const override {
|
||||||
|
auto orphanage = Orphanage::getForMessageContaining(output);
|
||||||
|
auto fields = output.getSchema().getFields();
|
||||||
|
auto items = input.getArray();
|
||||||
|
for (auto field: fields) {
|
||||||
|
KJ_REQUIRE(field.getIndex() < items.size());
|
||||||
|
auto item = items[field.getIndex()];
|
||||||
|
if (!item.isNull()) {
|
||||||
|
output.adopt(field, codec.decode(item, field.getType(), orphanage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestStructHandler: public JsonCodec::Handler<test::TestOldVersion> {
|
||||||
|
public:
|
||||||
|
void encode(const JsonCodec& codec, test::TestOldVersion::Reader input, JsonValue::Builder output) const override {
|
||||||
|
dynamicHandler.encode(codec, input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void decode(const JsonCodec& codec, JsonValue::Reader input, test::TestOldVersion::Builder output) const override {
|
||||||
|
dynamicHandler.decode(codec, input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TestDynamicStructHandler dynamicHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
KJ_TEST("register custom encoding handlers") {
|
||||||
|
JsonCodec json;
|
||||||
|
|
||||||
|
TestStructHandler structHandler;
|
||||||
|
json.addTypeHandler(structHandler);
|
||||||
|
|
||||||
|
// JSON decoder can't parse calls back, so test only encoder here
|
||||||
|
TestCallHandler callHandler;
|
||||||
|
json.addTypeHandler(callHandler);
|
||||||
|
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<test::TestOldVersion>();
|
||||||
|
root.setOld1(123);
|
||||||
|
root.setOld2("foo");
|
||||||
|
|
||||||
|
KJ_EXPECT(json.encode(root) == "[\"123\",Frob(123,\"foo\"),null]");
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("register custom roundtrip handler") {
|
||||||
|
for (auto i = 1; i <= 2; i++) {
|
||||||
|
JsonCodec json;
|
||||||
|
TestStructHandler staticHandler;
|
||||||
|
TestDynamicStructHandler dynamicHandler;
|
||||||
|
kj::String encoded;
|
||||||
|
|
||||||
|
if (i == 1) {
|
||||||
|
// first iteration: test with explicit struct handler
|
||||||
|
json.addTypeHandler(staticHandler);
|
||||||
|
} else {
|
||||||
|
// second iteration: same checks, but with DynamicStruct handler
|
||||||
|
json.addTypeHandler(StructSchema::from<test::TestOldVersion>(), dynamicHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<test::TestOldVersion>();
|
||||||
|
root.setOld1(123);
|
||||||
|
root.initOld3().setOld2("foo");
|
||||||
|
|
||||||
|
encoded = json.encode(root);
|
||||||
|
|
||||||
|
KJ_EXPECT(encoded == "[\"123\",null,[\"0\",\"foo\",null]]");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<test::TestOldVersion>();
|
||||||
|
json.decode(encoded, root);
|
||||||
|
|
||||||
|
KJ_EXPECT(root.getOld1() == 123);
|
||||||
|
KJ_EXPECT(!root.hasOld2());
|
||||||
|
auto nested = root.getOld3();
|
||||||
|
KJ_EXPECT(nested.getOld1() == 0);
|
||||||
|
KJ_EXPECT("foo" == nested.getOld2());
|
||||||
|
KJ_EXPECT(!nested.hasOld3());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KJ_TEST("register field handler") {
|
||||||
|
TestStructHandler handler;
|
||||||
|
JsonCodec json;
|
||||||
|
json.addFieldHandler(StructSchema::from<test::TestOldVersion>().getFieldByName("old3"),
|
||||||
|
handler);
|
||||||
|
|
||||||
|
kj::String encoded;
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<test::TestOldVersion>();
|
||||||
|
root.setOld1(123);
|
||||||
|
root.setOld2("foo");
|
||||||
|
auto nested = root.initOld3();
|
||||||
|
nested.setOld2("bar");
|
||||||
|
|
||||||
|
encoded = json.encode(root);
|
||||||
|
|
||||||
|
KJ_EXPECT(encoded == "{\"old1\":\"123\",\"old2\":\"foo\",\"old3\":[\"0\",\"bar\",null]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<test::TestOldVersion>();
|
||||||
|
json.decode(encoded, root);
|
||||||
|
|
||||||
|
KJ_EXPECT(root.getOld1() == 123);
|
||||||
|
KJ_EXPECT("foo" == root.getOld2());
|
||||||
|
auto nested = root.getOld3();
|
||||||
|
KJ_EXPECT(nested.getOld1() == 0);
|
||||||
|
KJ_EXPECT("bar" == nested.getOld2());
|
||||||
|
KJ_EXPECT(!nested.hasOld3());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestCapabilityHandler: public JsonCodec::Handler<test::TestInterface> {
|
||||||
|
public:
|
||||||
|
void encode(const JsonCodec& codec, test::TestInterface::Client input,
|
||||||
|
JsonValue::Builder output) const override {
|
||||||
|
KJ_UNIMPLEMENTED("TestCapabilityHandler::encode");
|
||||||
|
}
|
||||||
|
|
||||||
|
test::TestInterface::Client decode(
|
||||||
|
const JsonCodec& codec, JsonValue::Reader input) const override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
KJ_TEST("register capability handler") {
|
||||||
|
// This test currently only checks that this compiles, which at one point wasn't the caes.
|
||||||
|
// TODO(test): Actually run some code here.
|
||||||
|
|
||||||
|
TestCapabilityHandler handler;
|
||||||
|
JsonCodec json;
|
||||||
|
json.addTypeHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr kj::StringPtr GOLDEN_ANNOTATED =
|
||||||
|
R"({ "names-can_contain!anything Really": "foo",
|
||||||
|
"flatFoo": 123,
|
||||||
|
"flatBar": "abc",
|
||||||
|
"renamed-flatBaz": {"hello": true},
|
||||||
|
"flatQux": "cba",
|
||||||
|
"pfx.foo": "this is a long string in order to force multi-line pretty printing",
|
||||||
|
"pfx.renamed-bar": 321,
|
||||||
|
"pfx.baz": {"hello": true},
|
||||||
|
"pfx.xfp.qux": "fed",
|
||||||
|
"union-type": "renamed-bar",
|
||||||
|
"barMember": 789,
|
||||||
|
"multiMember": "ghi",
|
||||||
|
"dependency": {"renamed-foo": "corge"},
|
||||||
|
"simpleGroup": {"renamed-grault": "garply"},
|
||||||
|
"enums": ["qux", "renamed-bar", "foo", "renamed-baz"],
|
||||||
|
"innerJson": [123, "hello", {"object": true}],
|
||||||
|
"customFieldHandler": "add-prefix-waldo",
|
||||||
|
"testBase64": "ZnJlZA==",
|
||||||
|
"testHex": "706c756768",
|
||||||
|
"bUnion": "renamed-bar",
|
||||||
|
"bValue": 678,
|
||||||
|
"externalUnion": {"type": "bar", "value": "cba"},
|
||||||
|
"unionWithVoid": {"type": "voidValue"} })"_kj;
|
||||||
|
|
||||||
|
static constexpr kj::StringPtr GOLDEN_ANNOTATED_REVERSE =
|
||||||
|
R"({
|
||||||
|
"unionWithVoid": {"type": "voidValue"},
|
||||||
|
"externalUnion": {"type": "bar", "value": "cba"},
|
||||||
|
"bValue": 678,
|
||||||
|
"bUnion": "renamed-bar",
|
||||||
|
"testHex": "706c756768",
|
||||||
|
"testBase64": "ZnJlZA==",
|
||||||
|
"customFieldHandler": "add-prefix-waldo",
|
||||||
|
"innerJson": [123, "hello", {"object": true}],
|
||||||
|
"enums": ["qux", "renamed-bar", "foo", "renamed-baz"],
|
||||||
|
"simpleGroup": { "renamed-grault": "garply" },
|
||||||
|
"dependency": { "renamed-foo": "corge" },
|
||||||
|
"multiMember": "ghi",
|
||||||
|
"barMember": 789,
|
||||||
|
"union-type": "renamed-bar",
|
||||||
|
"pfx.xfp.qux": "fed",
|
||||||
|
"pfx.baz": {"hello": true},
|
||||||
|
"pfx.renamed-bar": 321,
|
||||||
|
"pfx.foo": "this is a long string in order to force multi-line pretty printing",
|
||||||
|
"flatQux": "cba",
|
||||||
|
"renamed-flatBaz": {"hello": true},
|
||||||
|
"flatBar": "abc",
|
||||||
|
"flatFoo": 123,
|
||||||
|
"names-can_contain!anything Really": "foo"
|
||||||
|
})"_kj;
|
||||||
|
|
||||||
|
class PrefixAdder: public JsonCodec::Handler<capnp::Text> {
|
||||||
|
public:
|
||||||
|
void encode(const JsonCodec& codec, capnp::Text::Reader input, JsonValue::Builder output) const {
|
||||||
|
output.setString(kj::str("add-prefix-", input));
|
||||||
|
}
|
||||||
|
|
||||||
|
Orphan<capnp::Text> decode(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Orphanage orphanage) const {
|
||||||
|
return orphanage.newOrphanCopy(capnp::Text::Reader(input.getString().slice(11)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
KJ_TEST("rename fields") {
|
||||||
|
JsonCodec json;
|
||||||
|
json.handleByAnnotation<TestJsonAnnotations>();
|
||||||
|
json.setPrettyPrint(true);
|
||||||
|
|
||||||
|
PrefixAdder customHandler;
|
||||||
|
json.addFieldHandler(Schema::from<TestJsonAnnotations>().getFieldByName("customFieldHandler"),
|
||||||
|
customHandler);
|
||||||
|
|
||||||
|
kj::String goldenText;
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<TestJsonAnnotations>();
|
||||||
|
root.setSomeField("foo");
|
||||||
|
|
||||||
|
auto aGroup = root.getAGroup();
|
||||||
|
aGroup.setFlatFoo(123);
|
||||||
|
aGroup.setFlatBar("abc");
|
||||||
|
aGroup.getFlatBaz().setHello(true);
|
||||||
|
aGroup.getDoubleFlat().setFlatQux("cba");
|
||||||
|
|
||||||
|
auto prefixedGroup = root.getPrefixedGroup();
|
||||||
|
prefixedGroup.setFoo("this is a long string in order to force multi-line pretty printing");
|
||||||
|
prefixedGroup.setBar(321);
|
||||||
|
prefixedGroup.getBaz().setHello(true);
|
||||||
|
prefixedGroup.getMorePrefix().setQux("fed");
|
||||||
|
|
||||||
|
auto unionBar = root.getAUnion().initBar();
|
||||||
|
unionBar.setBarMember(789);
|
||||||
|
unionBar.setMultiMember("ghi");
|
||||||
|
|
||||||
|
root.initDependency().setFoo("corge");
|
||||||
|
root.initSimpleGroup().setGrault("garply");
|
||||||
|
|
||||||
|
root.setEnums({
|
||||||
|
TestJsonAnnotatedEnum::QUX,
|
||||||
|
TestJsonAnnotatedEnum::BAR,
|
||||||
|
TestJsonAnnotatedEnum::FOO,
|
||||||
|
TestJsonAnnotatedEnum::BAZ
|
||||||
|
});
|
||||||
|
|
||||||
|
auto val = root.initInnerJson();
|
||||||
|
auto arr = val.initArray(3);
|
||||||
|
arr[0].setNumber(123);
|
||||||
|
arr[1].setString("hello");
|
||||||
|
auto field = arr[2].initObject(1)[0];
|
||||||
|
field.setName("object");
|
||||||
|
field.initValue().setBoolean(true);
|
||||||
|
|
||||||
|
root.setCustomFieldHandler("waldo");
|
||||||
|
|
||||||
|
root.setTestBase64("fred"_kj.asBytes());
|
||||||
|
root.setTestHex("plugh"_kj.asBytes());
|
||||||
|
|
||||||
|
root.getBUnion().setBar(678);
|
||||||
|
|
||||||
|
root.initExternalUnion().initBar().setValue("cba");
|
||||||
|
|
||||||
|
root.initUnionWithVoid().setVoidValue();
|
||||||
|
|
||||||
|
auto encoded = json.encode(root.asReader());
|
||||||
|
KJ_EXPECT(encoded == GOLDEN_ANNOTATED, encoded);
|
||||||
|
|
||||||
|
goldenText = kj::str(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<TestJsonAnnotations>();
|
||||||
|
json.decode(GOLDEN_ANNOTATED, root);
|
||||||
|
|
||||||
|
KJ_EXPECT(kj::str(root) == goldenText, root, goldenText);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Try parsing in reverse, mostly to test that union tags can come after content.
|
||||||
|
MallocMessageBuilder message;
|
||||||
|
auto root = message.getRoot<TestJsonAnnotations>();
|
||||||
|
json.decode(GOLDEN_ANNOTATED_REVERSE, root);
|
||||||
|
|
||||||
|
KJ_EXPECT(kj::str(root) == goldenText, root, goldenText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace _ (private)
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,116 @@
|
||||||
|
# Copyright (c) 2018 Cloudflare, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
@0xc9d405cf4333e4c9;
|
||||||
|
|
||||||
|
using Json = import "/capnp/compat/json.capnp";
|
||||||
|
|
||||||
|
$import "/capnp/c++.capnp".namespace("capnp");
|
||||||
|
|
||||||
|
struct TestJsonAnnotations {
|
||||||
|
someField @0 :Text $Json.name("names-can_contain!anything Really");
|
||||||
|
|
||||||
|
aGroup :group $Json.flatten() {
|
||||||
|
flatFoo @1 :UInt32;
|
||||||
|
flatBar @2 :Text;
|
||||||
|
flatBaz :group $Json.name("renamed-flatBaz") {
|
||||||
|
hello @3 :Bool;
|
||||||
|
}
|
||||||
|
doubleFlat :group $Json.flatten() {
|
||||||
|
flatQux @4 :Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixedGroup :group $Json.flatten(prefix = "pfx.") {
|
||||||
|
foo @5 :Text;
|
||||||
|
bar @6 :UInt32 $Json.name("renamed-bar");
|
||||||
|
baz :group {
|
||||||
|
hello @7 :Bool;
|
||||||
|
}
|
||||||
|
morePrefix :group $Json.flatten(prefix = "xfp.") {
|
||||||
|
qux @8 :Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aUnion :union $Json.flatten() $Json.discriminator(name = "union-type") {
|
||||||
|
foo :group $Json.flatten() {
|
||||||
|
fooMember @9 :Text;
|
||||||
|
multiMember @10 :UInt32;
|
||||||
|
}
|
||||||
|
bar :group $Json.flatten() $Json.name("renamed-bar") {
|
||||||
|
barMember @11 :UInt32;
|
||||||
|
multiMember @12 :Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependency @13 :TestJsonAnnotations2;
|
||||||
|
# To test that dependencies are loaded even if not flattened.
|
||||||
|
|
||||||
|
simpleGroup :group {
|
||||||
|
# To test that group types are loaded even if not flattened.
|
||||||
|
grault @14 :Text $Json.name("renamed-grault");
|
||||||
|
}
|
||||||
|
|
||||||
|
enums @15 :List(TestJsonAnnotatedEnum);
|
||||||
|
|
||||||
|
innerJson @16 :Json.Value;
|
||||||
|
|
||||||
|
customFieldHandler @17 :Text;
|
||||||
|
|
||||||
|
testBase64 @18 :Data $Json.base64;
|
||||||
|
testHex @19 :Data $Json.hex;
|
||||||
|
|
||||||
|
bUnion :union $Json.flatten() $Json.discriminator(valueName = "bValue") {
|
||||||
|
foo @20 :Text;
|
||||||
|
bar @21 :UInt32 $Json.name("renamed-bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
externalUnion @22 :TestJsonAnnotations3;
|
||||||
|
|
||||||
|
unionWithVoid :union $Json.discriminator(name = "type") {
|
||||||
|
intValue @23 :UInt32;
|
||||||
|
voidValue @24 :Void;
|
||||||
|
textValue @25 :Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestJsonAnnotations2 {
|
||||||
|
foo @0 :Text $Json.name("renamed-foo");
|
||||||
|
cycle @1 :TestJsonAnnotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestJsonAnnotations3 $Json.discriminator(name = "type") {
|
||||||
|
union {
|
||||||
|
foo @0 :UInt32;
|
||||||
|
bar @1 :TestFlattenedStruct $Json.flatten();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestFlattenedStruct {
|
||||||
|
value @0 :Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TestJsonAnnotatedEnum {
|
||||||
|
foo @0;
|
||||||
|
bar @1 $Json.name("renamed-bar");
|
||||||
|
baz @2 $Json.name("renamed-baz");
|
||||||
|
qux @3;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,112 @@
|
||||||
|
# Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
@0x8ef99297a43a5e34;
|
||||||
|
|
||||||
|
$import "/capnp/c++.capnp".namespace("capnp::json");
|
||||||
|
|
||||||
|
struct Value {
|
||||||
|
union {
|
||||||
|
null @0 :Void;
|
||||||
|
boolean @1 :Bool;
|
||||||
|
number @2 :Float64;
|
||||||
|
string @3 :Text;
|
||||||
|
array @4 :List(Value);
|
||||||
|
object @5 :List(Field);
|
||||||
|
# Standard JSON values.
|
||||||
|
|
||||||
|
call @6 :Call;
|
||||||
|
# Non-standard: A "function call", applying a named function (named by a single identifier)
|
||||||
|
# to a parameter list. Examples:
|
||||||
|
#
|
||||||
|
# BinData(0, "Zm9vCg==")
|
||||||
|
# ISODate("2015-04-15T08:44:50.218Z")
|
||||||
|
#
|
||||||
|
# Mongo DB users will recognize the above as exactly the syntax Mongo uses to represent BSON
|
||||||
|
# "binary" and "date" types in text, since JSON has no analog of these. This is basically the
|
||||||
|
# reason this extension exists. We do NOT recommend using `call` unless you specifically need
|
||||||
|
# to be compatible with some silly format that uses this syntax.
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Field {
|
||||||
|
name @0 :Text;
|
||||||
|
value @1 :Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Call {
|
||||||
|
function @0 :Text;
|
||||||
|
params @1 :List(Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========================================================================================
|
||||||
|
# Annotations to control parsing. Typical usage:
|
||||||
|
#
|
||||||
|
# using Json = import "/capnp/compat/json.capnp";
|
||||||
|
#
|
||||||
|
# And then later on:
|
||||||
|
#
|
||||||
|
# myField @0 :Text $Json.name("my_field");
|
||||||
|
|
||||||
|
annotation name @0xfa5b1fd61c2e7c3d (field, enumerant, method, group, union): Text;
|
||||||
|
# Define an alternative name to use when encoding the given item in JSON. This can be used, for
|
||||||
|
# example, to use snake_case names where needed, even though Cap'n Proto uses strictly camelCase.
|
||||||
|
#
|
||||||
|
# (However, because JSON is derived from JavaScript, you *should* use camelCase names when
|
||||||
|
# defining JSON-based APIs. But, when supporting a pre-existing API you may not have a choice.)
|
||||||
|
|
||||||
|
annotation flatten @0x82d3e852af0336bf (field, group, union): FlattenOptions;
|
||||||
|
# Specifies that an aggregate field should be flattened into its parent.
|
||||||
|
#
|
||||||
|
# In order to flatten a member of a union, the union (or, for an anonymous union, the parent
|
||||||
|
# struct type) must have the $jsonDiscriminator annotation.
|
||||||
|
#
|
||||||
|
# TODO(someday): Maybe support "flattening" a List(Value.Field) as a way to support unknown JSON
|
||||||
|
# fields?
|
||||||
|
|
||||||
|
struct FlattenOptions {
|
||||||
|
prefix @0 :Text = "";
|
||||||
|
# Optional: Adds the given prefix to flattened field names.
|
||||||
|
}
|
||||||
|
|
||||||
|
annotation discriminator @0xcfa794e8d19a0162 (struct, union): DiscriminatorOptions;
|
||||||
|
# Specifies that a union's variant will be decided not by which fields are present, but instead
|
||||||
|
# by a special discriminator field. The value of the discriminator field is a string naming which
|
||||||
|
# variant is active. This allows the members of the union to have the $jsonFlatten annotation, or
|
||||||
|
# to all have the same name.
|
||||||
|
|
||||||
|
struct DiscriminatorOptions {
|
||||||
|
name @0 :Text;
|
||||||
|
# The name of the discriminator field. Defaults to matching the name of the union.
|
||||||
|
|
||||||
|
valueName @1 :Text;
|
||||||
|
# If non-null, specifies that the union's value shall have the given field name, rather than the
|
||||||
|
# value's name. In this case the union's variant can only be determined by looking at the
|
||||||
|
# discriminant field, not by inspecting which value field is present.
|
||||||
|
#
|
||||||
|
# It is an error to use `valueName` while also declaring some variants as $flatten.
|
||||||
|
}
|
||||||
|
|
||||||
|
annotation base64 @0xd7d879450a253e4b (field): Void;
|
||||||
|
# Place on a field of type `Data` to indicate that its JSON representation is a Base64 string.
|
||||||
|
|
||||||
|
annotation hex @0xf061e22f0ae5c7b5 (field): Void;
|
||||||
|
# Place on a field of type `Data` to indicate that its JSON representation is a hex string.
|
|
@ -0,0 +1,602 @@
|
||||||
|
// Generated by Cap'n Proto compiler, DO NOT EDIT
|
||||||
|
// source: json.capnp
|
||||||
|
|
||||||
|
#include "json.capnp.h"
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace schemas {
|
||||||
|
static const ::capnp::_::AlignedData<137> b_a3fa7845f919dd83 = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
131, 221, 25, 249, 69, 120, 250, 163,
|
||||||
|
24, 0, 0, 0, 1, 0, 2, 0,
|
||||||
|
52, 94, 58, 164, 151, 146, 249, 142,
|
||||||
|
1, 0, 7, 0, 0, 0, 7, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 242, 0, 0, 0,
|
||||||
|
33, 0, 0, 0, 39, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
53, 0, 0, 0, 143, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
86, 97, 108, 117, 101, 0, 0, 0,
|
||||||
|
8, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
223, 157, 214, 53, 231, 38, 16, 227,
|
||||||
|
9, 0, 0, 0, 50, 0, 0, 0,
|
||||||
|
72, 61, 201, 161, 236, 246, 217, 160,
|
||||||
|
5, 0, 0, 0, 42, 0, 0, 0,
|
||||||
|
70, 105, 101, 108, 100, 0, 0, 0,
|
||||||
|
67, 97, 108, 108, 0, 0, 0, 0,
|
||||||
|
28, 0, 0, 0, 3, 0, 4, 0,
|
||||||
|
0, 0, 255, 255, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
181, 0, 0, 0, 42, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
176, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
188, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
1, 0, 254, 255, 16, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
185, 0, 0, 0, 66, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
180, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
192, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
2, 0, 253, 255, 1, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 2, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
189, 0, 0, 0, 58, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
184, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
196, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
3, 0, 252, 255, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 3, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
193, 0, 0, 0, 58, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
188, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
200, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
4, 0, 251, 255, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 4, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
197, 0, 0, 0, 50, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
192, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
220, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
5, 0, 250, 255, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 5, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
217, 0, 0, 0, 58, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
212, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
240, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
6, 0, 249, 255, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 6, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
237, 0, 0, 0, 42, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
232, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
244, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
110, 117, 108, 108, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
98, 111, 111, 108, 101, 97, 110, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
110, 117, 109, 98, 101, 114, 0, 0,
|
||||||
|
11, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
11, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
115, 116, 114, 105, 110, 103, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
97, 114, 114, 97, 121, 0, 0, 0,
|
||||||
|
14, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
131, 221, 25, 249, 69, 120, 250, 163,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
14, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
111, 98, 106, 101, 99, 116, 0, 0,
|
||||||
|
14, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
223, 157, 214, 53, 231, 38, 16, 227,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
14, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 108, 108, 0, 0, 0, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
72, 61, 201, 161, 236, 246, 217, 160,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_a3fa7845f919dd83 = b_a3fa7845f919dd83.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
static const ::capnp::_::RawSchema* const d_a3fa7845f919dd83[] = {
|
||||||
|
&s_a0d9f6eca1c93d48,
|
||||||
|
&s_a3fa7845f919dd83,
|
||||||
|
&s_e31026e735d69ddf,
|
||||||
|
};
|
||||||
|
static const uint16_t m_a3fa7845f919dd83[] = {4, 1, 6, 0, 2, 5, 3};
|
||||||
|
static const uint16_t i_a3fa7845f919dd83[] = {0, 1, 2, 3, 4, 5, 6};
|
||||||
|
const ::capnp::_::RawSchema s_a3fa7845f919dd83 = {
|
||||||
|
0xa3fa7845f919dd83, b_a3fa7845f919dd83.words, 137, d_a3fa7845f919dd83, m_a3fa7845f919dd83,
|
||||||
|
3, 7, i_a3fa7845f919dd83, nullptr, nullptr, { &s_a3fa7845f919dd83, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<49> b_e31026e735d69ddf = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
223, 157, 214, 53, 231, 38, 16, 227,
|
||||||
|
30, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
131, 221, 25, 249, 69, 120, 250, 163,
|
||||||
|
2, 0, 7, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 34, 1, 0, 0,
|
||||||
|
37, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
33, 0, 0, 0, 119, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
86, 97, 108, 117, 101, 46, 70, 105,
|
||||||
|
101, 108, 100, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
8, 0, 0, 0, 3, 0, 4, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
41, 0, 0, 0, 42, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
36, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
48, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
1, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
45, 0, 0, 0, 50, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
40, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
52, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
110, 97, 109, 101, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
118, 97, 108, 117, 101, 0, 0, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
131, 221, 25, 249, 69, 120, 250, 163,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_e31026e735d69ddf = b_e31026e735d69ddf.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
static const ::capnp::_::RawSchema* const d_e31026e735d69ddf[] = {
|
||||||
|
&s_a3fa7845f919dd83,
|
||||||
|
};
|
||||||
|
static const uint16_t m_e31026e735d69ddf[] = {0, 1};
|
||||||
|
static const uint16_t i_e31026e735d69ddf[] = {0, 1};
|
||||||
|
const ::capnp::_::RawSchema s_e31026e735d69ddf = {
|
||||||
|
0xe31026e735d69ddf, b_e31026e735d69ddf.words, 49, d_e31026e735d69ddf, m_e31026e735d69ddf,
|
||||||
|
1, 2, i_e31026e735d69ddf, nullptr, nullptr, { &s_e31026e735d69ddf, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<54> b_a0d9f6eca1c93d48 = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
72, 61, 201, 161, 236, 246, 217, 160,
|
||||||
|
30, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
131, 221, 25, 249, 69, 120, 250, 163,
|
||||||
|
2, 0, 7, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 26, 1, 0, 0,
|
||||||
|
37, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
33, 0, 0, 0, 119, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
86, 97, 108, 117, 101, 46, 67, 97,
|
||||||
|
108, 108, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
8, 0, 0, 0, 3, 0, 4, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
41, 0, 0, 0, 74, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
40, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
52, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
1, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
49, 0, 0, 0, 58, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
44, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
72, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
102, 117, 110, 99, 116, 105, 111, 110,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
112, 97, 114, 97, 109, 115, 0, 0,
|
||||||
|
14, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
131, 221, 25, 249, 69, 120, 250, 163,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
14, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_a0d9f6eca1c93d48 = b_a0d9f6eca1c93d48.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
static const ::capnp::_::RawSchema* const d_a0d9f6eca1c93d48[] = {
|
||||||
|
&s_a3fa7845f919dd83,
|
||||||
|
};
|
||||||
|
static const uint16_t m_a0d9f6eca1c93d48[] = {0, 1};
|
||||||
|
static const uint16_t i_a0d9f6eca1c93d48[] = {0, 1};
|
||||||
|
const ::capnp::_::RawSchema s_a0d9f6eca1c93d48 = {
|
||||||
|
0xa0d9f6eca1c93d48, b_a0d9f6eca1c93d48.words, 54, d_a0d9f6eca1c93d48, m_a0d9f6eca1c93d48,
|
||||||
|
1, 2, i_a0d9f6eca1c93d48, nullptr, nullptr, { &s_a0d9f6eca1c93d48, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<21> b_fa5b1fd61c2e7c3d = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
61, 124, 46, 28, 214, 31, 91, 250,
|
||||||
|
24, 0, 0, 0, 5, 0, 232, 2,
|
||||||
|
52, 94, 58, 164, 151, 146, 249, 142,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 234, 0, 0, 0,
|
||||||
|
33, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
28, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
110, 97, 109, 101, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_fa5b1fd61c2e7c3d = b_fa5b1fd61c2e7c3d.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
const ::capnp::_::RawSchema s_fa5b1fd61c2e7c3d = {
|
||||||
|
0xfa5b1fd61c2e7c3d, b_fa5b1fd61c2e7c3d.words, 21, nullptr, nullptr,
|
||||||
|
0, 0, nullptr, nullptr, nullptr, { &s_fa5b1fd61c2e7c3d, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<21> b_82d3e852af0336bf = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
191, 54, 3, 175, 82, 232, 211, 130,
|
||||||
|
24, 0, 0, 0, 5, 0, 224, 0,
|
||||||
|
52, 94, 58, 164, 151, 146, 249, 142,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 2, 1, 0, 0,
|
||||||
|
33, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
28, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
102, 108, 97, 116, 116, 101, 110, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
97, 234, 194, 123, 37, 19, 223, 196,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_82d3e852af0336bf = b_82d3e852af0336bf.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
const ::capnp::_::RawSchema s_82d3e852af0336bf = {
|
||||||
|
0x82d3e852af0336bf, b_82d3e852af0336bf.words, 21, nullptr, nullptr,
|
||||||
|
0, 0, nullptr, nullptr, nullptr, { &s_82d3e852af0336bf, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<35> b_c4df13257bc2ea61 = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
97, 234, 194, 123, 37, 19, 223, 196,
|
||||||
|
24, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
52, 94, 58, 164, 151, 146, 249, 142,
|
||||||
|
1, 0, 7, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 58, 1, 0, 0,
|
||||||
|
37, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
33, 0, 0, 0, 63, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
70, 108, 97, 116, 116, 101, 110, 79,
|
||||||
|
112, 116, 105, 111, 110, 115, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
4, 0, 0, 0, 3, 0, 4, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
13, 0, 0, 0, 58, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
8, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
20, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
112, 114, 101, 102, 105, 120, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 10, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_c4df13257bc2ea61 = b_c4df13257bc2ea61.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
static const uint16_t m_c4df13257bc2ea61[] = {0};
|
||||||
|
static const uint16_t i_c4df13257bc2ea61[] = {0};
|
||||||
|
const ::capnp::_::RawSchema s_c4df13257bc2ea61 = {
|
||||||
|
0xc4df13257bc2ea61, b_c4df13257bc2ea61.words, 35, nullptr, m_c4df13257bc2ea61,
|
||||||
|
0, 1, i_c4df13257bc2ea61, nullptr, nullptr, { &s_c4df13257bc2ea61, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<22> b_cfa794e8d19a0162 = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
98, 1, 154, 209, 232, 148, 167, 207,
|
||||||
|
24, 0, 0, 0, 5, 0, 80, 0,
|
||||||
|
52, 94, 58, 164, 151, 146, 249, 142,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 50, 1, 0, 0,
|
||||||
|
37, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
32, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
100, 105, 115, 99, 114, 105, 109, 105,
|
||||||
|
110, 97, 116, 111, 114, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
25, 83, 62, 41, 12, 194, 248, 194,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_cfa794e8d19a0162 = b_cfa794e8d19a0162.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
const ::capnp::_::RawSchema s_cfa794e8d19a0162 = {
|
||||||
|
0xcfa794e8d19a0162, b_cfa794e8d19a0162.words, 22, nullptr, nullptr,
|
||||||
|
0, 0, nullptr, nullptr, nullptr, { &s_cfa794e8d19a0162, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<51> b_c2f8c20c293e5319 = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
25, 83, 62, 41, 12, 194, 248, 194,
|
||||||
|
24, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
52, 94, 58, 164, 151, 146, 249, 142,
|
||||||
|
2, 0, 7, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 106, 1, 0, 0,
|
||||||
|
41, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
37, 0, 0, 0, 119, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
68, 105, 115, 99, 114, 105, 109, 105,
|
||||||
|
110, 97, 116, 111, 114, 79, 112, 116,
|
||||||
|
105, 111, 110, 115, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
8, 0, 0, 0, 3, 0, 4, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
41, 0, 0, 0, 42, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
36, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
48, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
1, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
45, 0, 0, 0, 82, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
44, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
56, 0, 0, 0, 2, 0, 1, 0,
|
||||||
|
110, 97, 109, 101, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
118, 97, 108, 117, 101, 78, 97, 109,
|
||||||
|
101, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
12, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_c2f8c20c293e5319 = b_c2f8c20c293e5319.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
static const uint16_t m_c2f8c20c293e5319[] = {0, 1};
|
||||||
|
static const uint16_t i_c2f8c20c293e5319[] = {0, 1};
|
||||||
|
const ::capnp::_::RawSchema s_c2f8c20c293e5319 = {
|
||||||
|
0xc2f8c20c293e5319, b_c2f8c20c293e5319.words, 51, nullptr, m_c2f8c20c293e5319,
|
||||||
|
0, 2, i_c2f8c20c293e5319, nullptr, nullptr, { &s_c2f8c20c293e5319, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<21> b_d7d879450a253e4b = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
75, 62, 37, 10, 69, 121, 216, 215,
|
||||||
|
24, 0, 0, 0, 5, 0, 32, 0,
|
||||||
|
52, 94, 58, 164, 151, 146, 249, 142,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 250, 0, 0, 0,
|
||||||
|
33, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
28, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
98, 97, 115, 101, 54, 52, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_d7d879450a253e4b = b_d7d879450a253e4b.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
const ::capnp::_::RawSchema s_d7d879450a253e4b = {
|
||||||
|
0xd7d879450a253e4b, b_d7d879450a253e4b.words, 21, nullptr, nullptr,
|
||||||
|
0, 0, nullptr, nullptr, nullptr, { &s_d7d879450a253e4b, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
static const ::capnp::_::AlignedData<21> b_f061e22f0ae5c7b5 = {
|
||||||
|
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
181, 199, 229, 10, 47, 226, 97, 240,
|
||||||
|
24, 0, 0, 0, 5, 0, 32, 0,
|
||||||
|
52, 94, 58, 164, 151, 146, 249, 142,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
21, 0, 0, 0, 226, 0, 0, 0,
|
||||||
|
33, 0, 0, 0, 7, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
28, 0, 0, 0, 3, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
99, 97, 112, 110, 112, 47, 99, 111,
|
||||||
|
109, 112, 97, 116, 47, 106, 115, 111,
|
||||||
|
110, 46, 99, 97, 112, 110, 112, 58,
|
||||||
|
104, 101, 120, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, }
|
||||||
|
};
|
||||||
|
::capnp::word const* const bp_f061e22f0ae5c7b5 = b_f061e22f0ae5c7b5.words;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
const ::capnp::_::RawSchema s_f061e22f0ae5c7b5 = {
|
||||||
|
0xf061e22f0ae5c7b5, b_f061e22f0ae5c7b5.words, 21, nullptr, nullptr,
|
||||||
|
0, 0, nullptr, nullptr, nullptr, { &s_f061e22f0ae5c7b5, nullptr, nullptr, 0, 0, nullptr }
|
||||||
|
};
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
} // namespace schemas
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace json {
|
||||||
|
|
||||||
|
// Value
|
||||||
|
constexpr uint16_t Value::_capnpPrivate::dataWordSize;
|
||||||
|
constexpr uint16_t Value::_capnpPrivate::pointerCount;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
constexpr ::capnp::Kind Value::_capnpPrivate::kind;
|
||||||
|
constexpr ::capnp::_::RawSchema const* Value::_capnpPrivate::schema;
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
// Value::Field
|
||||||
|
constexpr uint16_t Value::Field::_capnpPrivate::dataWordSize;
|
||||||
|
constexpr uint16_t Value::Field::_capnpPrivate::pointerCount;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
constexpr ::capnp::Kind Value::Field::_capnpPrivate::kind;
|
||||||
|
constexpr ::capnp::_::RawSchema const* Value::Field::_capnpPrivate::schema;
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
// Value::Call
|
||||||
|
constexpr uint16_t Value::Call::_capnpPrivate::dataWordSize;
|
||||||
|
constexpr uint16_t Value::Call::_capnpPrivate::pointerCount;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
constexpr ::capnp::Kind Value::Call::_capnpPrivate::kind;
|
||||||
|
constexpr ::capnp::_::RawSchema const* Value::Call::_capnpPrivate::schema;
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
// FlattenOptions
|
||||||
|
constexpr uint16_t FlattenOptions::_capnpPrivate::dataWordSize;
|
||||||
|
constexpr uint16_t FlattenOptions::_capnpPrivate::pointerCount;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
constexpr ::capnp::Kind FlattenOptions::_capnpPrivate::kind;
|
||||||
|
constexpr ::capnp::_::RawSchema const* FlattenOptions::_capnpPrivate::schema;
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
// DiscriminatorOptions
|
||||||
|
constexpr uint16_t DiscriminatorOptions::_capnpPrivate::dataWordSize;
|
||||||
|
constexpr uint16_t DiscriminatorOptions::_capnpPrivate::pointerCount;
|
||||||
|
#if !CAPNP_LITE
|
||||||
|
constexpr ::capnp::Kind DiscriminatorOptions::_capnpPrivate::kind;
|
||||||
|
constexpr ::capnp::_::RawSchema const* DiscriminatorOptions::_capnpPrivate::schema;
|
||||||
|
#endif // !CAPNP_LITE
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,516 @@
|
||||||
|
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <capnp/schema.h>
|
||||||
|
#include <capnp/dynamic.h>
|
||||||
|
#include <capnp/compat/json.capnp.h>
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
|
||||||
|
typedef json::Value JsonValue;
|
||||||
|
// For backwards-compatibility.
|
||||||
|
//
|
||||||
|
// TODO(cleanup): Consider replacing all uses of JsonValue with json::Value?
|
||||||
|
|
||||||
|
class JsonCodec {
|
||||||
|
// Flexible class for encoding Cap'n Proto types as JSON, and decoding JSON back to Cap'n Proto.
|
||||||
|
//
|
||||||
|
// Typical usage:
|
||||||
|
//
|
||||||
|
// JsonCodec json;
|
||||||
|
//
|
||||||
|
// // encode
|
||||||
|
// kj::String encoded = json.encode(someStructReader);
|
||||||
|
//
|
||||||
|
// // decode
|
||||||
|
// json.decode(encoded, someStructBuilder);
|
||||||
|
//
|
||||||
|
// Advanced users can do fancy things like override the way certain types or fields are
|
||||||
|
// represented in JSON by registering handlers. See the unit test for an example.
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// - When encoding, all primitive fields are always encoded, even if default-valued. Pointer
|
||||||
|
// fields are only encoded if they are non-null.
|
||||||
|
// - 64-bit integers are encoded as strings, since JSON "numbers" are double-precision floating
|
||||||
|
// points which cannot store a 64-bit integer without losing data.
|
||||||
|
// - NaNs and infinite floating point numbers are not allowed by the JSON spec, and so are encoded
|
||||||
|
// as strings.
|
||||||
|
// - Data is encoded as an array of numbers in the range [0,255]. You probably want to register
|
||||||
|
// a handler that does something better, like maybe base64 encoding, but there are a zillion
|
||||||
|
// different ways people do this.
|
||||||
|
// - Encoding/decoding capabilities and AnyPointers requires registering a Handler, since there's
|
||||||
|
// no obvious default behavior.
|
||||||
|
// - When decoding, unrecognized field names are ignored. Note: This means that JSON is NOT a
|
||||||
|
// good format for receiving input from a human. Consider `capnp eval` or the SchemaParser
|
||||||
|
// library for human input.
|
||||||
|
|
||||||
|
public:
|
||||||
|
JsonCodec();
|
||||||
|
~JsonCodec() noexcept(false);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// standard API
|
||||||
|
|
||||||
|
void setPrettyPrint(bool enabled);
|
||||||
|
// Enable to insert newlines, indentation, and other extra spacing into the output. The default
|
||||||
|
// is to use minimal whitespace.
|
||||||
|
|
||||||
|
void setMaxNestingDepth(size_t maxNestingDepth);
|
||||||
|
// Set maximum nesting depth when decoding JSON to prevent highly nested input from overflowing
|
||||||
|
// the call stack. The default is 64.
|
||||||
|
|
||||||
|
void setHasMode(HasMode mode);
|
||||||
|
// Normally, primitive field values are always included even if they are equal to the default
|
||||||
|
// value (HasMode::NON_NULL -- only null pointers are omitted). You can use
|
||||||
|
// setHasMode(HasMode::NON_DEFAULT) to specify that default-valued primitive fields should be
|
||||||
|
// omitted as well.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
kj::String encode(T&& value) const;
|
||||||
|
// Encode any Cap'n Proto value to JSON, including primitives and
|
||||||
|
// Dynamic{Enum,Struct,List,Capability}, but not DynamicValue (see below).
|
||||||
|
|
||||||
|
kj::String encode(DynamicValue::Reader value, Type type) const;
|
||||||
|
// Encode a DynamicValue to JSON. `type` is needed because `DynamicValue` itself does
|
||||||
|
// not distinguish between e.g. int32 and int64, which in JSON are handled differently. Most
|
||||||
|
// of the time, though, you can use the single-argument templated version of `encode()` instead.
|
||||||
|
|
||||||
|
void decode(kj::ArrayPtr<const char> input, DynamicStruct::Builder output) const;
|
||||||
|
// Decode JSON text directly into a struct builder. This only works for structs since lists
|
||||||
|
// need to be allocated with the correct size in advance.
|
||||||
|
//
|
||||||
|
// (Remember that any Cap'n Proto struct reader type can be implicitly cast to
|
||||||
|
// DynamicStruct::Reader.)
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Orphan<T> decode(kj::ArrayPtr<const char> input, Orphanage orphanage) const;
|
||||||
|
// Decode JSON text to any Cap'n Proto object (pointer value), allocated using the given
|
||||||
|
// orphanage. T must be specified explicitly and cannot be dynamic, e.g.:
|
||||||
|
//
|
||||||
|
// Orphan<MyType> orphan = json.decode<MyType>(text, orphanage);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ReaderFor<T> decode(kj::ArrayPtr<const char> input) const;
|
||||||
|
// Decode JSON text into a primitive or capability value. T must be specified explicitly and
|
||||||
|
// cannot be dynamic, e.g.:
|
||||||
|
//
|
||||||
|
// uint32_t n = json.decode<uint32_t>(text);
|
||||||
|
|
||||||
|
Orphan<DynamicValue> decode(kj::ArrayPtr<const char> input, Type type, Orphanage orphanage) const;
|
||||||
|
Orphan<DynamicList> decode(
|
||||||
|
kj::ArrayPtr<const char> input, ListSchema type, Orphanage orphanage) const;
|
||||||
|
Orphan<DynamicStruct> decode(
|
||||||
|
kj::ArrayPtr<const char> input, StructSchema type, Orphanage orphanage) const;
|
||||||
|
DynamicCapability::Client decode(kj::ArrayPtr<const char> input, InterfaceSchema type) const;
|
||||||
|
DynamicEnum decode(kj::ArrayPtr<const char> input, EnumSchema type) const;
|
||||||
|
// Decode to a dynamic value, specifying the type schema.
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// layered API
|
||||||
|
//
|
||||||
|
// You can separate text <-> JsonValue from JsonValue <-> T. These are particularly useful
|
||||||
|
// for calling from Handler implementations.
|
||||||
|
|
||||||
|
kj::String encodeRaw(JsonValue::Reader value) const;
|
||||||
|
void decodeRaw(kj::ArrayPtr<const char> input, JsonValue::Builder output) const;
|
||||||
|
// Translate JsonValue <-> text.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void encode(T&& value, JsonValue::Builder output) const;
|
||||||
|
void encode(DynamicValue::Reader input, Type type, JsonValue::Builder output) const;
|
||||||
|
void decode(JsonValue::Reader input, DynamicStruct::Builder output) const;
|
||||||
|
template <typename T>
|
||||||
|
Orphan<T> decode(JsonValue::Reader input, Orphanage orphanage) const;
|
||||||
|
template <typename T>
|
||||||
|
ReaderFor<T> decode(JsonValue::Reader input) const;
|
||||||
|
|
||||||
|
Orphan<DynamicValue> decode(JsonValue::Reader input, Type type, Orphanage orphanage) const;
|
||||||
|
Orphan<DynamicList> decode(JsonValue::Reader input, ListSchema type, Orphanage orphanage) const;
|
||||||
|
Orphan<DynamicStruct> decode(
|
||||||
|
JsonValue::Reader input, StructSchema type, Orphanage orphanage) const;
|
||||||
|
DynamicCapability::Client decode(JsonValue::Reader input, InterfaceSchema type) const;
|
||||||
|
DynamicEnum decode(JsonValue::Reader input, EnumSchema type) const;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// specializing particular types
|
||||||
|
|
||||||
|
template <typename T, Style s = style<T>()>
|
||||||
|
class Handler;
|
||||||
|
// Implement this interface to specify a special encoding for a particular type or field.
|
||||||
|
//
|
||||||
|
// The templates are a bit ugly, but subclasses of this type essentially implement two methods,
|
||||||
|
// one to encode values of this type and one to decode values of this type. `encode()` is simple:
|
||||||
|
//
|
||||||
|
// void encode(const JsonCodec& codec, ReaderFor<T> input, JsonValue::Builder output) const;
|
||||||
|
//
|
||||||
|
// `decode()` is a bit trickier. When T is a struct (including DynamicStruct), it is:
|
||||||
|
//
|
||||||
|
// void decode(const JsonCodec& codec, JsonValue::Reader input, BuilderFor<T> output) const;
|
||||||
|
//
|
||||||
|
// However, when T is a primitive, decode() is:
|
||||||
|
//
|
||||||
|
// T decode(const JsonCodec& codec, JsonValue::Reader input) const;
|
||||||
|
//
|
||||||
|
// Or when T is any non-struct object (list, blob), decode() is:
|
||||||
|
//
|
||||||
|
// Orphan<T> decode(const JsonCodec& codec, JsonValue::Reader input, Orphanage orphanage) const;
|
||||||
|
//
|
||||||
|
// Or when T is an interface:
|
||||||
|
//
|
||||||
|
// T::Client decode(const JsonCodec& codec, JsonValue::Reader input) const;
|
||||||
|
//
|
||||||
|
// Additionally, when T is a struct you can *optionally* also implement the orphan-returning form
|
||||||
|
// of decode(), but it will only be called when the struct would be allocated as an individual
|
||||||
|
// object, not as part of a list. This allows you to return "nullptr" in these cases to say that
|
||||||
|
// the pointer value should be null. This does not apply to list elements because struct list
|
||||||
|
// elements cannot ever be null (since Cap'n Proto encodes struct lists as a flat list rather
|
||||||
|
// than list-of-pointers).
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void addTypeHandler(Handler<T>& handler);
|
||||||
|
void addTypeHandler(Type type, Handler<DynamicValue>& handler);
|
||||||
|
void addTypeHandler(EnumSchema type, Handler<DynamicEnum>& handler);
|
||||||
|
void addTypeHandler(StructSchema type, Handler<DynamicStruct>& handler);
|
||||||
|
void addTypeHandler(ListSchema type, Handler<DynamicList>& handler);
|
||||||
|
void addTypeHandler(InterfaceSchema type, Handler<DynamicCapability>& handler);
|
||||||
|
// Arrange that whenever the type T appears in the message, your handler will be used to
|
||||||
|
// encode/decode it.
|
||||||
|
//
|
||||||
|
// Note that if you register a handler for a capability type, it will also apply to subtypes.
|
||||||
|
// Thus Handler<Capability> handles all capabilities.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void addFieldHandler(StructSchema::Field field, Handler<T>& handler);
|
||||||
|
// Matches only the specific field. T can be a dynamic type. T must match the field's type.
|
||||||
|
|
||||||
|
void handleByAnnotation(Schema schema);
|
||||||
|
template <typename T> void handleByAnnotation();
|
||||||
|
// Inspects the given type (as specified by type parameter or dynamic schema) and all its
|
||||||
|
// dependencies looking for JSON annotations (see json.capnp), building and registering Handlers
|
||||||
|
// based on these annotations.
|
||||||
|
//
|
||||||
|
// If you'd like to use annotations to control JSON, you must call these functions before you
|
||||||
|
// start using the codec. They are not loaded "on demand" because that would require mutex
|
||||||
|
// locking.
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Hack to support string literal parameters
|
||||||
|
|
||||||
|
template <size_t size, typename... Params>
|
||||||
|
auto decode(const char (&input)[size], Params&&... params) const
|
||||||
|
-> decltype(decode(kj::arrayPtr(input, size), kj::fwd<Params>(params)...)) {
|
||||||
|
return decode(kj::arrayPtr(input, size - 1), kj::fwd<Params>(params)...);
|
||||||
|
}
|
||||||
|
template <size_t size, typename... Params>
|
||||||
|
auto decodeRaw(const char (&input)[size], Params&&... params) const
|
||||||
|
-> decltype(decodeRaw(kj::arrayPtr(input, size), kj::fwd<Params>(params)...)) {
|
||||||
|
return decodeRaw(kj::arrayPtr(input, size - 1), kj::fwd<Params>(params)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
class HandlerBase;
|
||||||
|
class AnnotatedHandler;
|
||||||
|
class AnnotatedEnumHandler;
|
||||||
|
class Base64Handler;
|
||||||
|
class HexHandler;
|
||||||
|
class JsonValueHandler;
|
||||||
|
struct Impl;
|
||||||
|
|
||||||
|
kj::Own<Impl> impl;
|
||||||
|
|
||||||
|
void encodeField(StructSchema::Field field, DynamicValue::Reader input,
|
||||||
|
JsonValue::Builder output) const;
|
||||||
|
Orphan<DynamicList> decodeArray(List<JsonValue>::Reader input, ListSchema type, Orphanage orphanage) const;
|
||||||
|
void decodeObject(JsonValue::Reader input, StructSchema type, Orphanage orphanage, DynamicStruct::Builder output) const;
|
||||||
|
void decodeField(StructSchema::Field fieldSchema, JsonValue::Reader fieldValue,
|
||||||
|
Orphanage orphanage, DynamicStruct::Builder output) const;
|
||||||
|
void addTypeHandlerImpl(Type type, HandlerBase& handler);
|
||||||
|
void addFieldHandlerImpl(StructSchema::Field field, Type type, HandlerBase& handler);
|
||||||
|
|
||||||
|
AnnotatedHandler& loadAnnotatedHandler(
|
||||||
|
StructSchema schema,
|
||||||
|
kj::Maybe<json::DiscriminatorOptions::Reader> discriminator,
|
||||||
|
kj::Maybe<kj::StringPtr> unionDeclName,
|
||||||
|
kj::Vector<Schema>& dependencies);
|
||||||
|
};
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
// inline implementation details
|
||||||
|
|
||||||
|
template <bool isDynamic>
|
||||||
|
struct EncodeImpl;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
kj::String JsonCodec::encode(T&& value) const {
|
||||||
|
Type type = Type::from(value);
|
||||||
|
typedef FromAny<kj::Decay<T>> Base;
|
||||||
|
return encode(DynamicValue::Reader(ReaderFor<Base>(kj::fwd<T>(value))), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline Orphan<T> JsonCodec::decode(kj::ArrayPtr<const char> input, Orphanage orphanage) const {
|
||||||
|
return decode(input, Type::from<T>(), orphanage).template releaseAs<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline ReaderFor<T> JsonCodec::decode(kj::ArrayPtr<const char> input) const {
|
||||||
|
static_assert(style<T>() == Style::PRIMITIVE || style<T>() == Style::CAPABILITY,
|
||||||
|
"must specify an orphanage to decode an object type");
|
||||||
|
return decode(input, Type::from<T>(), Orphanage()).getReader().template as<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Orphan<DynamicList> JsonCodec::decode(
|
||||||
|
kj::ArrayPtr<const char> input, ListSchema type, Orphanage orphanage) const {
|
||||||
|
return decode(input, Type(type), orphanage).releaseAs<DynamicList>();
|
||||||
|
}
|
||||||
|
inline Orphan<DynamicStruct> JsonCodec::decode(
|
||||||
|
kj::ArrayPtr<const char> input, StructSchema type, Orphanage orphanage) const {
|
||||||
|
return decode(input, Type(type), orphanage).releaseAs<DynamicStruct>();
|
||||||
|
}
|
||||||
|
inline DynamicCapability::Client JsonCodec::decode(
|
||||||
|
kj::ArrayPtr<const char> input, InterfaceSchema type) const {
|
||||||
|
return decode(input, Type(type), Orphanage()).getReader().as<DynamicCapability>();
|
||||||
|
}
|
||||||
|
inline DynamicEnum JsonCodec::decode(kj::ArrayPtr<const char> input, EnumSchema type) const {
|
||||||
|
return decode(input, Type(type), Orphanage()).getReader().as<DynamicEnum>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void JsonCodec::encode(T&& value, JsonValue::Builder output) const {
|
||||||
|
typedef FromAny<kj::Decay<T>> Base;
|
||||||
|
encode(DynamicValue::Reader(ReaderFor<Base>(kj::fwd<T>(value))), Type::from<Base>(), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline Orphan<T> JsonCodec::decode(JsonValue::Reader input, Orphanage orphanage) const {
|
||||||
|
return decode(input, Type::from<T>(), orphanage).template releaseAs<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline ReaderFor<T> JsonCodec::decode(JsonValue::Reader input) const {
|
||||||
|
static_assert(style<T>() == Style::PRIMITIVE || style<T>() == Style::CAPABILITY,
|
||||||
|
"must specify an orphanage to decode an object type");
|
||||||
|
return decode(input, Type::from<T>(), Orphanage()).getReader().template as<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Orphan<DynamicList> JsonCodec::decode(
|
||||||
|
JsonValue::Reader input, ListSchema type, Orphanage orphanage) const {
|
||||||
|
return decode(input, Type(type), orphanage).releaseAs<DynamicList>();
|
||||||
|
}
|
||||||
|
inline Orphan<DynamicStruct> JsonCodec::decode(
|
||||||
|
JsonValue::Reader input, StructSchema type, Orphanage orphanage) const {
|
||||||
|
return decode(input, Type(type), orphanage).releaseAs<DynamicStruct>();
|
||||||
|
}
|
||||||
|
inline DynamicCapability::Client JsonCodec::decode(
|
||||||
|
JsonValue::Reader input, InterfaceSchema type) const {
|
||||||
|
return decode(input, Type(type), Orphanage()).getReader().as<DynamicCapability>();
|
||||||
|
}
|
||||||
|
inline DynamicEnum JsonCodec::decode(JsonValue::Reader input, EnumSchema type) const {
|
||||||
|
return decode(input, Type(type), Orphanage()).getReader().as<DynamicEnum>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class JsonCodec::HandlerBase {
|
||||||
|
// Internal helper; ignore.
|
||||||
|
public:
|
||||||
|
virtual void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
|
||||||
|
JsonValue::Builder output) const = 0;
|
||||||
|
virtual Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Type type, Orphanage orphanage) const;
|
||||||
|
virtual void decodeStructBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
DynamicStruct::Builder output) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class JsonCodec::Handler<T, Style::POINTER>: private JsonCodec::HandlerBase {
|
||||||
|
public:
|
||||||
|
virtual void encode(const JsonCodec& codec, ReaderFor<T> input,
|
||||||
|
JsonValue::Builder output) const = 0;
|
||||||
|
virtual Orphan<T> decode(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Orphanage orphanage) const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
|
||||||
|
JsonValue::Builder output) const override final {
|
||||||
|
encode(codec, input.as<T>(), output);
|
||||||
|
}
|
||||||
|
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Type type, Orphanage orphanage) const override final {
|
||||||
|
return decode(codec, input, orphanage);
|
||||||
|
}
|
||||||
|
friend class JsonCodec;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class JsonCodec::Handler<T, Style::STRUCT>: private JsonCodec::HandlerBase {
|
||||||
|
public:
|
||||||
|
virtual void encode(const JsonCodec& codec, ReaderFor<T> input,
|
||||||
|
JsonValue::Builder output) const = 0;
|
||||||
|
virtual void decode(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
BuilderFor<T> output) const = 0;
|
||||||
|
virtual Orphan<T> decode(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Orphanage orphanage) const {
|
||||||
|
// If subclass does not override, fall back to regular version.
|
||||||
|
auto result = orphanage.newOrphan<T>();
|
||||||
|
decode(codec, input, result.get());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
|
||||||
|
JsonValue::Builder output) const override final {
|
||||||
|
encode(codec, input.as<T>(), output);
|
||||||
|
}
|
||||||
|
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Type type, Orphanage orphanage) const override final {
|
||||||
|
return decode(codec, input, orphanage);
|
||||||
|
}
|
||||||
|
void decodeStructBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
DynamicStruct::Builder output) const override final {
|
||||||
|
decode(codec, input, output.as<T>());
|
||||||
|
}
|
||||||
|
friend class JsonCodec;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class JsonCodec::Handler<DynamicStruct>: private JsonCodec::HandlerBase {
|
||||||
|
// Almost identical to Style::STRUCT except that we pass the struct type to decode().
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void encode(const JsonCodec& codec, DynamicStruct::Reader input,
|
||||||
|
JsonValue::Builder output) const = 0;
|
||||||
|
virtual void decode(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
DynamicStruct::Builder output) const = 0;
|
||||||
|
virtual Orphan<DynamicStruct> decode(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
StructSchema type, Orphanage orphanage) const {
|
||||||
|
// If subclass does not override, fall back to regular version.
|
||||||
|
auto result = orphanage.newOrphan(type);
|
||||||
|
decode(codec, input, result.get());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
|
||||||
|
JsonValue::Builder output) const override final {
|
||||||
|
encode(codec, input.as<DynamicStruct>(), output);
|
||||||
|
}
|
||||||
|
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Type type, Orphanage orphanage) const override final {
|
||||||
|
return decode(codec, input, type.asStruct(), orphanage);
|
||||||
|
}
|
||||||
|
void decodeStructBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
DynamicStruct::Builder output) const override final {
|
||||||
|
decode(codec, input, output.as<DynamicStruct>());
|
||||||
|
}
|
||||||
|
friend class JsonCodec;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class JsonCodec::Handler<T, Style::PRIMITIVE>: private JsonCodec::HandlerBase {
|
||||||
|
public:
|
||||||
|
virtual void encode(const JsonCodec& codec, T input, JsonValue::Builder output) const = 0;
|
||||||
|
virtual T decode(const JsonCodec& codec, JsonValue::Reader input) const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
|
||||||
|
JsonValue::Builder output) const override final {
|
||||||
|
encode(codec, input.as<T>(), output);
|
||||||
|
}
|
||||||
|
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Type type, Orphanage orphanage) const override final {
|
||||||
|
return decode(codec, input);
|
||||||
|
}
|
||||||
|
friend class JsonCodec;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class JsonCodec::Handler<T, Style::CAPABILITY>: private JsonCodec::HandlerBase {
|
||||||
|
public:
|
||||||
|
virtual void encode(const JsonCodec& codec, typename T::Client input,
|
||||||
|
JsonValue::Builder output) const = 0;
|
||||||
|
virtual typename T::Client decode(const JsonCodec& codec, JsonValue::Reader input) const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
|
||||||
|
JsonValue::Builder output) const override final {
|
||||||
|
encode(codec, input.as<T>(), output);
|
||||||
|
}
|
||||||
|
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
|
||||||
|
Type type, Orphanage orphanage) const override final {
|
||||||
|
return orphanage.newOrphanCopy(decode(codec, input));
|
||||||
|
}
|
||||||
|
friend class JsonCodec;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void JsonCodec::addTypeHandler(Handler<T>& handler) {
|
||||||
|
addTypeHandlerImpl(Type::from<T>(), handler);
|
||||||
|
}
|
||||||
|
inline void JsonCodec::addTypeHandler(Type type, Handler<DynamicValue>& handler) {
|
||||||
|
addTypeHandlerImpl(type, handler);
|
||||||
|
}
|
||||||
|
inline void JsonCodec::addTypeHandler(EnumSchema type, Handler<DynamicEnum>& handler) {
|
||||||
|
addTypeHandlerImpl(type, handler);
|
||||||
|
}
|
||||||
|
inline void JsonCodec::addTypeHandler(StructSchema type, Handler<DynamicStruct>& handler) {
|
||||||
|
addTypeHandlerImpl(type, handler);
|
||||||
|
}
|
||||||
|
inline void JsonCodec::addTypeHandler(ListSchema type, Handler<DynamicList>& handler) {
|
||||||
|
addTypeHandlerImpl(type, handler);
|
||||||
|
}
|
||||||
|
inline void JsonCodec::addTypeHandler(InterfaceSchema type, Handler<DynamicCapability>& handler) {
|
||||||
|
addTypeHandlerImpl(type, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void JsonCodec::addFieldHandler(StructSchema::Field field, Handler<T>& handler) {
|
||||||
|
addFieldHandlerImpl(field, Type::from<T>(), handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> void JsonCodec::addTypeHandler(Handler<DynamicValue>& handler)
|
||||||
|
KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; "
|
||||||
|
"try specifying a specific type schema as the first parameter");
|
||||||
|
template <> void JsonCodec::addTypeHandler(Handler<DynamicEnum>& handler)
|
||||||
|
KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; "
|
||||||
|
"try specifying a specific type schema as the first parameter");
|
||||||
|
template <> void JsonCodec::addTypeHandler(Handler<DynamicStruct>& handler)
|
||||||
|
KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; "
|
||||||
|
"try specifying a specific type schema as the first parameter");
|
||||||
|
template <> void JsonCodec::addTypeHandler(Handler<DynamicList>& handler)
|
||||||
|
KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; "
|
||||||
|
"try specifying a specific type schema as the first parameter");
|
||||||
|
template <> void JsonCodec::addTypeHandler(Handler<DynamicCapability>& handler)
|
||||||
|
KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; "
|
||||||
|
"try specifying a specific type schema as the first parameter");
|
||||||
|
// TODO(someday): Implement support for registering handlers that cover thinsg like "all structs"
|
||||||
|
// or "all lists". Currently you can only target a specific struct or list type.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void JsonCodec::handleByAnnotation() {
|
||||||
|
return handleByAnnotation(Schema::from<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace capnp
|
|
@ -0,0 +1,52 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# This is a one-off test rule.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo findProvider file:capnp
|
||||||
|
read CAPNP
|
||||||
|
export CAPNP
|
||||||
|
|
||||||
|
echo findProvider file:capnp/compiler/capnp-test.sh
|
||||||
|
read CAPNP_TEST
|
||||||
|
|
||||||
|
# Register our interest in the testdata files.
|
||||||
|
echo findProvider file:capnp/testdata/binary; read JUNK
|
||||||
|
echo findProvider file:capnp/testdata/flat; read JUNK
|
||||||
|
echo findProvider file:capnp/testdata/packed; read JUNK
|
||||||
|
echo findProvider file:capnp/testdata/segmented; read JUNK
|
||||||
|
echo findProvider file:capnp/testdata/segmented-packed; read JUNK
|
||||||
|
echo findProvider file:capnp/testdata/pretty.txt; read JUNK
|
||||||
|
echo findProvider file:capnp/testdata/short.txt; read JUNK
|
||||||
|
echo findProvider file:capnp/testdata/errors.capnp.nobuild; read JUNK
|
||||||
|
echo findProvider file:capnp/testdata/errors.txt; read JUNK
|
||||||
|
|
||||||
|
# Register our interest in the schema files.
|
||||||
|
echo findProvider file:capnp/c++.capnp
|
||||||
|
echo findProvider file:capnp/test.capnp
|
||||||
|
|
||||||
|
$CAPNP_TEST >&2
|
||||||
|
|
||||||
|
echo passed
|
|
@ -0,0 +1,118 @@
|
||||||
|
#! /bin/sh
|
||||||
|
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
# Licensed under the MIT License:
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# Tests the `capnp` tool's various commands, other than `compile`.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "FAILED: $@" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if test -f ./capnp; then
|
||||||
|
CAPNP=${CAPNP:-./capnp}
|
||||||
|
elif test -f ./capnp.exe; then
|
||||||
|
CAPNP=${CAPNP:-./capnp.exe}
|
||||||
|
else
|
||||||
|
CAPNP=${CAPNP:-capnp}
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCHEMA=`dirname "$0"`/../test.capnp
|
||||||
|
JSON_SCHEMA=`dirname "$0"`/../compat/json-test.capnp
|
||||||
|
TESTDATA=`dirname "$0"`/../testdata
|
||||||
|
SRCDIR=`dirname "$0"`/../..
|
||||||
|
|
||||||
|
SUFFIX=${TESTDATA#*/src/}
|
||||||
|
PREFIX=${TESTDATA%${SUFFIX}}
|
||||||
|
|
||||||
|
if [ "$PREFIX" = "" ]; then
|
||||||
|
PREFIX=.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ========================================================================================
|
||||||
|
# convert
|
||||||
|
|
||||||
|
$CAPNP convert text:binary $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/binary - || fail encode
|
||||||
|
$CAPNP convert text:flat $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/flat - || fail encode flat
|
||||||
|
$CAPNP convert text:packed $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/packed - || fail encode packed
|
||||||
|
$CAPNP convert text:flat-packed $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/packedflat - || fail encode packedflat
|
||||||
|
$CAPNP convert text:binary $SCHEMA TestAllTypes < $TESTDATA/pretty.txt | cmp $TESTDATA/binary - || fail parse pretty
|
||||||
|
|
||||||
|
$CAPNP convert binary:text $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/pretty.txt - || fail decode
|
||||||
|
$CAPNP convert flat:text $SCHEMA TestAllTypes < $TESTDATA/flat | cmp $TESTDATA/pretty.txt - || fail decode flat
|
||||||
|
$CAPNP convert packed:text $SCHEMA TestAllTypes < $TESTDATA/packed | cmp $TESTDATA/pretty.txt - || fail decode packed
|
||||||
|
$CAPNP convert flat-packed:text $SCHEMA TestAllTypes < $TESTDATA/packedflat | cmp $TESTDATA/pretty.txt - || fail decode packedflat
|
||||||
|
$CAPNP convert binary:text --short $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/short.txt - || fail decode short
|
||||||
|
|
||||||
|
$CAPNP convert binary:text $SCHEMA TestAllTypes < $TESTDATA/segmented | cmp $TESTDATA/pretty.txt - || fail decode segmented
|
||||||
|
$CAPNP convert packed:text $SCHEMA TestAllTypes < $TESTDATA/segmented-packed | cmp $TESTDATA/pretty.txt - || fail decode segmented-packed
|
||||||
|
|
||||||
|
$CAPNP convert binary:packed < $TESTDATA/binary | cmp $TESTDATA/packed - || fail binary to packed
|
||||||
|
$CAPNP convert packed:binary < $TESTDATA/packed | cmp $TESTDATA/binary - || fail packed to binary
|
||||||
|
|
||||||
|
$CAPNP convert binary:json $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/pretty.json - || fail binary to json
|
||||||
|
$CAPNP convert binary:json --short $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/short.json - || fail binary to short json
|
||||||
|
|
||||||
|
$CAPNP convert json:binary $SCHEMA TestAllTypes < $TESTDATA/pretty.json | cmp $TESTDATA/binary - || fail json to binary
|
||||||
|
$CAPNP convert json:binary $SCHEMA TestAllTypes < $TESTDATA/short.json | cmp $TESTDATA/binary - || fail short json to binary
|
||||||
|
|
||||||
|
$CAPNP convert json:binary $JSON_SCHEMA TestJsonAnnotations -I"$SRCDIR" < $TESTDATA/annotated.json | cmp $TESTDATA/annotated-json.binary || fail annotated json to binary
|
||||||
|
$CAPNP convert binary:json $JSON_SCHEMA TestJsonAnnotations -I"$SRCDIR" < $TESTDATA/annotated-json.binary | cmp $TESTDATA/annotated.json || fail annotated json to binary
|
||||||
|
|
||||||
|
# ========================================================================================
|
||||||
|
# DEPRECATED encode/decode
|
||||||
|
|
||||||
|
$CAPNP encode $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/binary - || fail encode
|
||||||
|
$CAPNP encode --flat $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/flat - || fail encode flat
|
||||||
|
$CAPNP encode --packed $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/packed - || fail encode packed
|
||||||
|
$CAPNP encode --packed --flat $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/packedflat - || fail encode packedflat
|
||||||
|
$CAPNP encode $SCHEMA TestAllTypes < $TESTDATA/pretty.txt | cmp $TESTDATA/binary - || fail parse pretty
|
||||||
|
|
||||||
|
$CAPNP decode $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/pretty.txt - || fail decode
|
||||||
|
$CAPNP decode --flat $SCHEMA TestAllTypes < $TESTDATA/flat | cmp $TESTDATA/pretty.txt - || fail decode flat
|
||||||
|
$CAPNP decode --packed $SCHEMA TestAllTypes < $TESTDATA/packed | cmp $TESTDATA/pretty.txt - || fail decode packed
|
||||||
|
$CAPNP decode --packed --flat $SCHEMA TestAllTypes < $TESTDATA/packedflat | cmp $TESTDATA/pretty.txt - || fail decode packedflat
|
||||||
|
$CAPNP decode --short $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/short.txt - || fail decode short
|
||||||
|
|
||||||
|
$CAPNP decode $SCHEMA TestAllTypes < $TESTDATA/segmented | cmp $TESTDATA/pretty.txt - || fail decode segmented
|
||||||
|
$CAPNP decode --packed $SCHEMA TestAllTypes < $TESTDATA/segmented-packed | cmp $TESTDATA/pretty.txt - || fail decode segmented-packed
|
||||||
|
|
||||||
|
# ========================================================================================
|
||||||
|
# eval
|
||||||
|
|
||||||
|
test_eval() {
|
||||||
|
test "x`$CAPNP eval $SCHEMA $1 | tr -d '\r'`" = "x$2" || fail eval "$1 == $2"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_eval TestDefaults.uInt32Field 3456789012
|
||||||
|
test_eval TestDefaults.structField.textField '"baz"'
|
||||||
|
test_eval TestDefaults.int8List "[111, -111]"
|
||||||
|
test_eval 'TestDefaults.structList[1].textField' '"structlist 2"'
|
||||||
|
test_eval globalPrintableStruct '(someText = "foo")'
|
||||||
|
test_eval TestConstants.enumConst corge
|
||||||
|
test_eval 'TestListDefaults.lists.int32ListList[2][0]' 12341234
|
||||||
|
|
||||||
|
test "x`$CAPNP eval $SCHEMA -ojson globalPrintableStruct | tr -d '\r'`" = "x{\"someText\": \"foo\"}" || fail eval json "globalPrintableStruct == {someText = \"foo\"}"
|
||||||
|
|
||||||
|
$CAPNP compile --src-prefix="$PREFIX" -ofoo $TESTDATA/errors.capnp.nobuild 2>&1 | sed -e "s,^.*errors[.]capnp[.]nobuild:,file:,g" | tr -d '\r' |
|
||||||
|
cmp $TESTDATA/errors.txt - || fail error output
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
||||||
|
capnp bin
|
||||||
|
capnpc-capnp bin
|
||||||
|
capnpc-c++ bin
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,685 @@
|
||||||
|
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||||
|
// Licensed under the MIT License:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// This program is a code generator plugin for `capnp compile` which writes the schema back to
|
||||||
|
// stdout in roughly capnpc format.
|
||||||
|
|
||||||
|
#ifndef _GNU_SOURCE
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <capnp/schema.capnp.h>
|
||||||
|
#include "../serialize.h"
|
||||||
|
#include <kj/debug.h>
|
||||||
|
#include <kj/io.h>
|
||||||
|
#include <kj/string-tree.h>
|
||||||
|
#include <kj/vector.h>
|
||||||
|
#include "../schema-loader.h"
|
||||||
|
#include "../dynamic.h"
|
||||||
|
#include <kj/miniposix.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <kj/main.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#if HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef VERSION
|
||||||
|
#define VERSION "(unknown)"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace capnp {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool hasDiscriminantValue(const schema::Field::Reader& reader) {
|
||||||
|
return reader.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Indent {
|
||||||
|
uint amount;
|
||||||
|
Indent() = default;
|
||||||
|
inline Indent(int amount): amount(amount) {}
|
||||||
|
|
||||||
|
Indent next() {
|
||||||
|
return Indent(amount + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Iterator {
|
||||||
|
uint i;
|
||||||
|
Iterator() = default;
|
||||||
|
inline Iterator(uint i): i(i) {}
|
||||||
|
inline char operator*() const { return ' '; }
|
||||||
|
inline Iterator& operator++() { ++i; return *this; }
|
||||||
|
inline Iterator operator++(int) { Iterator result = *this; ++i; return result; }
|
||||||
|
inline bool operator==(const Iterator& other) const { return i == other.i; }
|
||||||
|
inline bool operator!=(const Iterator& other) const { return i != other.i; }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline size_t size() const { return amount; }
|
||||||
|
|
||||||
|
inline Iterator begin() const { return Iterator(0); }
|
||||||
|
inline Iterator end() const { return Iterator(amount); }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Indent KJ_STRINGIFY(const Indent& indent) {
|
||||||
|
return indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================================
|
||||||
|
|
||||||
|
class CapnpcCapnpMain {
|
||||||
|
public:
|
||||||
|
CapnpcCapnpMain(kj::ProcessContext& context): context(context) {}
|
||||||
|
|
||||||
|
kj::MainFunc getMain() {
|
||||||
|
return kj::MainBuilder(context, "Cap'n Proto loopback plugin version " VERSION,
|
||||||
|
"This is a Cap'n Proto compiler plugin which \"de-compiles\" the schema back into "
|
||||||
|
"Cap'n Proto schema language format, with comments showing the offsets chosen by the "
|
||||||
|
"compiler. This is meant to be run using the Cap'n Proto compiler, e.g.:\n"
|
||||||
|
" capnp compile -ocapnp foo.capnp")
|
||||||
|
.callAfterParsing(KJ_BIND_METHOD(*this, run))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::ProcessContext& context;
|
||||||
|
SchemaLoader schemaLoader;
|
||||||
|
|
||||||
|
Text::Reader getUnqualifiedName(Schema schema) {
|
||||||
|
auto proto = schema.getProto();
|
||||||
|
KJ_CONTEXT(proto.getDisplayName());
|
||||||
|
auto parent = schemaLoader.get(proto.getScopeId());
|
||||||
|
for (auto nested: parent.getProto().getNestedNodes()) {
|
||||||
|
if (nested.getId() == proto.getId()) {
|
||||||
|
return nested.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KJ_FAIL_REQUIRE("A schema Node's supposed scope did not contain the node as a NestedNode.");
|
||||||
|
return "(?)";
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree nodeName(Schema target, Schema scope, schema::Brand::Reader brand,
|
||||||
|
kj::Maybe<InterfaceSchema::Method> method) {
|
||||||
|
kj::Vector<Schema> targetPath;
|
||||||
|
kj::Vector<Schema> scopeParts;
|
||||||
|
|
||||||
|
targetPath.add(target);
|
||||||
|
|
||||||
|
std::map<uint64_t, List<schema::Brand::Binding>::Reader> scopeBindings;
|
||||||
|
for (auto scopeBrand: brand.getScopes()) {
|
||||||
|
switch (scopeBrand.which()) {
|
||||||
|
case schema::Brand::Scope::BIND:
|
||||||
|
scopeBindings[scopeBrand.getScopeId()] = scopeBrand.getBind();
|
||||||
|
break;
|
||||||
|
case schema::Brand::Scope::INHERIT:
|
||||||
|
// TODO(someday): We need to pay attention to INHERIT and be sure to explicitly override
|
||||||
|
// any bindings that are not inherited. This requires a way to determine which of our
|
||||||
|
// parent scopes have a non-empty parameter list.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Schema parent = target;
|
||||||
|
while (parent.getProto().getScopeId() != 0) {
|
||||||
|
parent = schemaLoader.get(parent.getProto().getScopeId());
|
||||||
|
targetPath.add(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Schema parent = scope;
|
||||||
|
scopeParts.add(parent);
|
||||||
|
while (parent.getProto().getScopeId() != 0) {
|
||||||
|
parent = schemaLoader.get(parent.getProto().getScopeId());
|
||||||
|
scopeParts.add(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove common scope (unless it has been reparameterized).
|
||||||
|
// TODO(someday): This is broken in that we aren't checking for shadowing.
|
||||||
|
while (!scopeParts.empty() && targetPath.size() > 1 &&
|
||||||
|
scopeParts.back() == targetPath.back() &&
|
||||||
|
scopeBindings.count(scopeParts.back().getProto().getId()) == 0) {
|
||||||
|
scopeParts.removeLast();
|
||||||
|
targetPath.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parts = kj::heapArrayBuilder<kj::StringTree>(targetPath.size());
|
||||||
|
while (!targetPath.empty()) {
|
||||||
|
auto part = targetPath.back();
|
||||||
|
auto proto = part.getProto();
|
||||||
|
kj::StringTree partStr;
|
||||||
|
if (proto.getScopeId() == 0) {
|
||||||
|
partStr = kj::strTree("import \"/", proto.getDisplayName(), '\"');
|
||||||
|
} else {
|
||||||
|
partStr = kj::strTree(getUnqualifiedName(part));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto iter = scopeBindings.find(proto.getId());
|
||||||
|
if (iter != scopeBindings.end()) {
|
||||||
|
auto bindings = KJ_MAP(binding, iter->second) {
|
||||||
|
switch (binding.which()) {
|
||||||
|
case schema::Brand::Binding::UNBOUND:
|
||||||
|
return kj::strTree("AnyPointer");
|
||||||
|
case schema::Brand::Binding::TYPE:
|
||||||
|
return genType(binding.getType(), scope, method);
|
||||||
|
}
|
||||||
|
return kj::strTree("<unknown binding>");
|
||||||
|
};
|
||||||
|
partStr = kj::strTree(kj::mv(partStr), "(", kj::StringTree(kj::mv(bindings), ", "), ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.add(kj::mv(partStr));
|
||||||
|
targetPath.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
return kj::StringTree(parts.finish(), ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genType(schema::Type::Reader type, Schema scope,
|
||||||
|
kj::Maybe<InterfaceSchema::Method> method) {
|
||||||
|
switch (type.which()) {
|
||||||
|
case schema::Type::VOID: return kj::strTree("Void");
|
||||||
|
case schema::Type::BOOL: return kj::strTree("Bool");
|
||||||
|
case schema::Type::INT8: return kj::strTree("Int8");
|
||||||
|
case schema::Type::INT16: return kj::strTree("Int16");
|
||||||
|
case schema::Type::INT32: return kj::strTree("Int32");
|
||||||
|
case schema::Type::INT64: return kj::strTree("Int64");
|
||||||
|
case schema::Type::UINT8: return kj::strTree("UInt8");
|
||||||
|
case schema::Type::UINT16: return kj::strTree("UInt16");
|
||||||
|
case schema::Type::UINT32: return kj::strTree("UInt32");
|
||||||
|
case schema::Type::UINT64: return kj::strTree("UInt64");
|
||||||
|
case schema::Type::FLOAT32: return kj::strTree("Float32");
|
||||||
|
case schema::Type::FLOAT64: return kj::strTree("Float64");
|
||||||
|
case schema::Type::TEXT: return kj::strTree("Text");
|
||||||
|
case schema::Type::DATA: return kj::strTree("Data");
|
||||||
|
case schema::Type::LIST:
|
||||||
|
return kj::strTree("List(", genType(type.getList().getElementType(), scope, method), ")");
|
||||||
|
case schema::Type::ENUM:
|
||||||
|
return nodeName(schemaLoader.get(type.getEnum().getTypeId()), scope,
|
||||||
|
type.getEnum().getBrand(), method);
|
||||||
|
case schema::Type::STRUCT:
|
||||||
|
return nodeName(schemaLoader.get(type.getStruct().getTypeId()), scope,
|
||||||
|
type.getStruct().getBrand(), method);
|
||||||
|
case schema::Type::INTERFACE:
|
||||||
|
return nodeName(schemaLoader.get(type.getInterface().getTypeId()), scope,
|
||||||
|
type.getInterface().getBrand(), method);
|
||||||
|
case schema::Type::ANY_POINTER: {
|
||||||
|
auto anyPointer = type.getAnyPointer();
|
||||||
|
switch (anyPointer.which()) {
|
||||||
|
case schema::Type::AnyPointer::UNCONSTRAINED:
|
||||||
|
switch (anyPointer.getUnconstrained().which()) {
|
||||||
|
case schema::Type::AnyPointer::Unconstrained::ANY_KIND:
|
||||||
|
return kj::strTree("AnyPointer");
|
||||||
|
case schema::Type::AnyPointer::Unconstrained::STRUCT:
|
||||||
|
return kj::strTree("AnyStruct");
|
||||||
|
case schema::Type::AnyPointer::Unconstrained::LIST:
|
||||||
|
return kj::strTree("AnyList");
|
||||||
|
case schema::Type::AnyPointer::Unconstrained::CAPABILITY:
|
||||||
|
return kj::strTree("Capability");
|
||||||
|
}
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
case schema::Type::AnyPointer::PARAMETER: {
|
||||||
|
auto param = anyPointer.getParameter();
|
||||||
|
auto scopeProto = scope.getProto();
|
||||||
|
auto targetScopeId = param.getScopeId();
|
||||||
|
while (scopeProto.getId() != targetScopeId) {
|
||||||
|
scopeProto = schemaLoader.get(param.getScopeId()).getProto();
|
||||||
|
}
|
||||||
|
auto params = scopeProto.getParameters();
|
||||||
|
KJ_REQUIRE(param.getParameterIndex() < params.size());
|
||||||
|
return kj::strTree(params[param.getParameterIndex()].getName());
|
||||||
|
}
|
||||||
|
case schema::Type::AnyPointer::IMPLICIT_METHOD_PARAMETER: {
|
||||||
|
auto params = KJ_REQUIRE_NONNULL(method).getProto().getImplicitParameters();
|
||||||
|
uint index = anyPointer.getImplicitMethodParameter().getParameterIndex();
|
||||||
|
KJ_REQUIRE(index < params.size());
|
||||||
|
return kj::strTree(params[index].getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KJ_UNREACHABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kj::strTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
int typeSizeBits(schema::Type::Reader type) {
|
||||||
|
switch (type.which()) {
|
||||||
|
case schema::Type::VOID: return 0;
|
||||||
|
case schema::Type::BOOL: return 1;
|
||||||
|
case schema::Type::INT8: return 8;
|
||||||
|
case schema::Type::INT16: return 16;
|
||||||
|
case schema::Type::INT32: return 32;
|
||||||
|
case schema::Type::INT64: return 64;
|
||||||
|
case schema::Type::UINT8: return 8;
|
||||||
|
case schema::Type::UINT16: return 16;
|
||||||
|
case schema::Type::UINT32: return 32;
|
||||||
|
case schema::Type::UINT64: return 64;
|
||||||
|
case schema::Type::FLOAT32: return 32;
|
||||||
|
case schema::Type::FLOAT64: return 64;
|
||||||
|
case schema::Type::TEXT: return -1;
|
||||||
|
case schema::Type::DATA: return -1;
|
||||||
|
case schema::Type::LIST: return -1;
|
||||||
|
case schema::Type::ENUM: return 16;
|
||||||
|
case schema::Type::STRUCT: return -1;
|
||||||
|
case schema::Type::INTERFACE: return -1;
|
||||||
|
case schema::Type::ANY_POINTER: return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmptyValue(schema::Value::Reader value) {
|
||||||
|
switch (value.which()) {
|
||||||
|
case schema::Value::VOID: return true;
|
||||||
|
case schema::Value::BOOL: return value.getBool() == false;
|
||||||
|
case schema::Value::INT8: return value.getInt8() == 0;
|
||||||
|
case schema::Value::INT16: return value.getInt16() == 0;
|
||||||
|
case schema::Value::INT32: return value.getInt32() == 0;
|
||||||
|
case schema::Value::INT64: return value.getInt64() == 0;
|
||||||
|
case schema::Value::UINT8: return value.getUint8() == 0;
|
||||||
|
case schema::Value::UINT16: return value.getUint16() == 0;
|
||||||
|
case schema::Value::UINT32: return value.getUint32() == 0;
|
||||||
|
case schema::Value::UINT64: return value.getUint64() == 0;
|
||||||
|
case schema::Value::FLOAT32: return value.getFloat32() == 0;
|
||||||
|
case schema::Value::FLOAT64: return value.getFloat64() == 0;
|
||||||
|
case schema::Value::TEXT: return !value.hasText();
|
||||||
|
case schema::Value::DATA: return !value.hasData();
|
||||||
|
case schema::Value::LIST: return !value.hasList();
|
||||||
|
case schema::Value::ENUM: return value.getEnum() == 0;
|
||||||
|
case schema::Value::STRUCT: return !value.hasStruct();
|
||||||
|
case schema::Value::INTERFACE: return true;
|
||||||
|
case schema::Value::ANY_POINTER: return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genValue(Type type, schema::Value::Reader value) {
|
||||||
|
switch (value.which()) {
|
||||||
|
case schema::Value::VOID: return kj::strTree("void");
|
||||||
|
case schema::Value::BOOL:
|
||||||
|
return kj::strTree(value.getBool() ? "true" : "false");
|
||||||
|
case schema::Value::INT8: return kj::strTree((int)value.getInt8());
|
||||||
|
case schema::Value::INT16: return kj::strTree(value.getInt16());
|
||||||
|
case schema::Value::INT32: return kj::strTree(value.getInt32());
|
||||||
|
case schema::Value::INT64: return kj::strTree(value.getInt64());
|
||||||
|
case schema::Value::UINT8: return kj::strTree((uint)value.getUint8());
|
||||||
|
case schema::Value::UINT16: return kj::strTree(value.getUint16());
|
||||||
|
case schema::Value::UINT32: return kj::strTree(value.getUint32());
|
||||||
|
case schema::Value::UINT64: return kj::strTree(value.getUint64());
|
||||||
|
case schema::Value::FLOAT32: return kj::strTree(value.getFloat32());
|
||||||
|
case schema::Value::FLOAT64: return kj::strTree(value.getFloat64());
|
||||||
|
case schema::Value::TEXT:
|
||||||
|
return kj::strTree(DynamicValue::Reader(value.getText()));
|
||||||
|
case schema::Value::DATA:
|
||||||
|
return kj::strTree(DynamicValue::Reader(value.getData()));
|
||||||
|
case schema::Value::LIST: {
|
||||||
|
auto listValue = value.getList().getAs<DynamicList>(type.asList());
|
||||||
|
return kj::strTree(listValue);
|
||||||
|
}
|
||||||
|
case schema::Value::ENUM: {
|
||||||
|
auto enumNode = type.asEnum().getProto();
|
||||||
|
auto enumerants = enumNode.getEnum().getEnumerants();
|
||||||
|
KJ_REQUIRE(value.getEnum() < enumerants.size(),
|
||||||
|
"Enum value out-of-range.", value.getEnum(), enumNode.getDisplayName());
|
||||||
|
return kj::strTree(enumerants[value.getEnum()].getName());
|
||||||
|
}
|
||||||
|
case schema::Value::STRUCT: {
|
||||||
|
KJ_REQUIRE(type.which() == schema::Type::STRUCT, "type/value mismatch");
|
||||||
|
auto structValue = value.getStruct().getAs<DynamicStruct>(type.asStruct());
|
||||||
|
return kj::strTree(structValue);
|
||||||
|
}
|
||||||
|
case schema::Value::INTERFACE: {
|
||||||
|
return kj::strTree("");
|
||||||
|
}
|
||||||
|
case schema::Value::ANY_POINTER: {
|
||||||
|
return kj::strTree("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kj::strTree("");
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genGenericParams(List<schema::Node::Parameter>::Reader params, Schema scope) {
|
||||||
|
if (params.size() == 0) {
|
||||||
|
return kj::strTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
return kj::strTree(" (", kj::StringTree(
|
||||||
|
KJ_MAP(param, params) { return kj::strTree(param.getName()); }, ", "), ')');
|
||||||
|
}
|
||||||
|
kj::StringTree genGenericParams(Schema schema) {
|
||||||
|
auto proto = schema.getProto();
|
||||||
|
return genGenericParams(proto.getParameters(), schemaLoader.get(proto.getScopeId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genAnnotation(schema::Annotation::Reader annotation,
|
||||||
|
Schema scope,
|
||||||
|
const char* prefix = " ", const char* suffix = "") {
|
||||||
|
auto decl = schemaLoader.get(annotation.getId(), annotation.getBrand(), scope);
|
||||||
|
auto proto = decl.getProto();
|
||||||
|
KJ_REQUIRE(proto.isAnnotation());
|
||||||
|
auto annDecl = proto.getAnnotation();
|
||||||
|
|
||||||
|
auto value = genValue(schemaLoader.getType(annDecl.getType(), decl),
|
||||||
|
annotation.getValue()).flatten();
|
||||||
|
if (value.startsWith("(")) {
|
||||||
|
return kj::strTree(prefix, "$", nodeName(decl, scope, annotation.getBrand(), nullptr),
|
||||||
|
value, suffix);
|
||||||
|
} else {
|
||||||
|
return kj::strTree(prefix, "$", nodeName(decl, scope, annotation.getBrand(), nullptr),
|
||||||
|
"(", value, ")", suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genAnnotations(List<schema::Annotation>::Reader list, Schema scope) {
|
||||||
|
return kj::strTree(KJ_MAP(ann, list) { return genAnnotation(ann, scope); });
|
||||||
|
}
|
||||||
|
kj::StringTree genAnnotations(Schema schema) {
|
||||||
|
auto proto = schema.getProto();
|
||||||
|
return genAnnotations(proto.getAnnotations(), schemaLoader.get(proto.getScopeId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* elementSizeName(schema::ElementSize size) {
|
||||||
|
switch (size) {
|
||||||
|
case schema::ElementSize::EMPTY: return "void";
|
||||||
|
case schema::ElementSize::BIT: return "1-bit";
|
||||||
|
case schema::ElementSize::BYTE: return "8-bit";
|
||||||
|
case schema::ElementSize::TWO_BYTES: return "16-bit";
|
||||||
|
case schema::ElementSize::FOUR_BYTES: return "32-bit";
|
||||||
|
case schema::ElementSize::EIGHT_BYTES: return "64-bit";
|
||||||
|
case schema::ElementSize::POINTER: return "pointer";
|
||||||
|
case schema::ElementSize::INLINE_COMPOSITE: return "inline composite";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OrderByCodeOrder {
|
||||||
|
template <typename T>
|
||||||
|
inline bool operator()(const T& a, const T& b) const {
|
||||||
|
return a.getProto().getCodeOrder() < b.getProto().getCodeOrder();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename MemberList>
|
||||||
|
kj::Array<decltype(kj::instance<MemberList>()[0])> sortByCodeOrder(MemberList&& list) {
|
||||||
|
auto sorted = KJ_MAP(item, list) { return item; };
|
||||||
|
std::sort(sorted.begin(), sorted.end(), OrderByCodeOrder());
|
||||||
|
return kj::mv(sorted);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Array<kj::StringTree> genStructFields(StructSchema schema, Indent indent) {
|
||||||
|
// Slightly hacky: We want to print in code order, but we also need to print the union in one
|
||||||
|
// chunk. Its fields should be together in code order anyway, but it's easier to simply
|
||||||
|
// output the whole union in place of the first union field, and then output nothing for the
|
||||||
|
// subsequent fields.
|
||||||
|
|
||||||
|
bool seenUnion = false;
|
||||||
|
return KJ_MAP(field, sortByCodeOrder(schema.getFields())) {
|
||||||
|
if (hasDiscriminantValue(field.getProto())) {
|
||||||
|
if (seenUnion) {
|
||||||
|
return kj::strTree();
|
||||||
|
} else {
|
||||||
|
seenUnion = true;
|
||||||
|
uint offset = schema.getProto().getStruct().getDiscriminantOffset();
|
||||||
|
|
||||||
|
// GCC 4.7.3 crashes if you inline unionFields.
|
||||||
|
auto unionFields = sortByCodeOrder(schema.getUnionFields());
|
||||||
|
return kj::strTree(
|
||||||
|
indent, "union { # tag bits [", offset * 16, ", ", offset * 16 + 16, ")\n",
|
||||||
|
KJ_MAP(uField, unionFields) {
|
||||||
|
return genStructField(uField, schema, indent.next());
|
||||||
|
},
|
||||||
|
indent, "}\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return genStructField(field, schema, indent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genStructField(StructSchema::Field field, Schema scope, Indent indent) {
|
||||||
|
auto proto = field.getProto();
|
||||||
|
switch (proto.which()) {
|
||||||
|
case schema::Field::SLOT: {
|
||||||
|
auto slot = proto.getSlot();
|
||||||
|
int size = typeSizeBits(slot.getType());
|
||||||
|
return kj::strTree(
|
||||||
|
indent, proto.getName(), " @", proto.getOrdinal().getExplicit(),
|
||||||
|
" :", genType(slot.getType(), scope, nullptr),
|
||||||
|
isEmptyValue(slot.getDefaultValue()) ? kj::strTree("") :
|
||||||
|
kj::strTree(" = ", genValue(field.getType(), slot.getDefaultValue())),
|
||||||
|
genAnnotations(proto.getAnnotations(), scope),
|
||||||
|
"; # ", size == -1 ? kj::strTree("ptr[", slot.getOffset(), "]")
|
||||||
|
: kj::strTree("bits[", slot.getOffset() * size, ", ",
|
||||||
|
(slot.getOffset() + 1) * size, ")"),
|
||||||
|
hasDiscriminantValue(proto)
|
||||||
|
? kj::strTree(", union tag = ", proto.getDiscriminantValue()) : kj::strTree(),
|
||||||
|
"\n");
|
||||||
|
}
|
||||||
|
case schema::Field::GROUP: {
|
||||||
|
auto group = field.getType().asStruct();
|
||||||
|
return kj::strTree(
|
||||||
|
indent, proto.getName(),
|
||||||
|
" :group", genAnnotations(proto.getAnnotations(), scope), " {",
|
||||||
|
hasDiscriminantValue(proto)
|
||||||
|
? kj::strTree(" # union tag = ", proto.getDiscriminantValue()) : kj::strTree(),
|
||||||
|
"\n",
|
||||||
|
genStructFields(group, indent.next()),
|
||||||
|
indent, "}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kj::strTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genParamList(InterfaceSchema interface, StructSchema schema,
|
||||||
|
schema::Brand::Reader brand, InterfaceSchema::Method method) {
|
||||||
|
if (schema.getProto().getScopeId() == 0) {
|
||||||
|
// A named parameter list.
|
||||||
|
return kj::strTree("(", kj::StringTree(
|
||||||
|
KJ_MAP(field, schema.getFields()) {
|
||||||
|
auto proto = field.getProto();
|
||||||
|
auto slot = proto.getSlot();
|
||||||
|
|
||||||
|
return kj::strTree(
|
||||||
|
proto.getName(), " :", genType(slot.getType(), interface, nullptr),
|
||||||
|
isEmptyValue(slot.getDefaultValue()) ? kj::strTree("") :
|
||||||
|
kj::strTree(" = ", genValue(field.getType(), slot.getDefaultValue())),
|
||||||
|
genAnnotations(proto.getAnnotations(), interface));
|
||||||
|
}, ", "), ")");
|
||||||
|
} else {
|
||||||
|
return nodeName(schema, interface, brand, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genSuperclasses(InterfaceSchema interface) {
|
||||||
|
auto superclasses = interface.getProto().getInterface().getSuperclasses();
|
||||||
|
if (superclasses.size() == 0) {
|
||||||
|
return kj::strTree();
|
||||||
|
} else {
|
||||||
|
return kj::strTree(" superclasses(", kj::StringTree(
|
||||||
|
KJ_MAP(superclass, superclasses) {
|
||||||
|
return nodeName(schemaLoader.get(superclass.getId()), interface,
|
||||||
|
superclass.getBrand(), nullptr);
|
||||||
|
}, ", "), ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent indent) {
|
||||||
|
auto proto = schema.getProto();
|
||||||
|
if (proto.getScopeId() != scopeId) {
|
||||||
|
// This appears to be an alias for something declared elsewhere.
|
||||||
|
KJ_FAIL_REQUIRE("Aliases not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (proto.which()) {
|
||||||
|
case schema::Node::FILE:
|
||||||
|
KJ_FAIL_REQUIRE("Encountered nested file node.");
|
||||||
|
break;
|
||||||
|
case schema::Node::STRUCT: {
|
||||||
|
auto structProto = proto.getStruct();
|
||||||
|
return kj::strTree(
|
||||||
|
indent, "struct ", name,
|
||||||
|
" @0x", kj::hex(proto.getId()), genGenericParams(schema),
|
||||||
|
genAnnotations(schema), " { # ",
|
||||||
|
structProto.getDataWordCount() * 8, " bytes, ",
|
||||||
|
structProto.getPointerCount(), " ptrs",
|
||||||
|
structProto.getPreferredListEncoding() == schema::ElementSize::INLINE_COMPOSITE
|
||||||
|
? kj::strTree()
|
||||||
|
: kj::strTree(", packed as ",
|
||||||
|
elementSizeName(structProto.getPreferredListEncoding())),
|
||||||
|
"\n",
|
||||||
|
genStructFields(schema.asStruct(), indent.next()),
|
||||||
|
genNestedDecls(schema, indent.next()),
|
||||||
|
indent, "}\n");
|
||||||
|
}
|
||||||
|
case schema::Node::ENUM: {
|
||||||
|
return kj::strTree(
|
||||||
|
indent, "enum ", name, " @0x", kj::hex(proto.getId()), genAnnotations(schema), " {\n",
|
||||||
|
KJ_MAP(enumerant, sortByCodeOrder(schema.asEnum().getEnumerants())) {
|
||||||
|
return kj::strTree(indent.next(), enumerant.getProto().getName(), " @",
|
||||||
|
enumerant.getIndex(),
|
||||||
|
genAnnotations(enumerant.getProto().getAnnotations(), schema),
|
||||||
|
";\n");
|
||||||
|
},
|
||||||
|
genNestedDecls(schema, indent.next()),
|
||||||
|
indent, "}\n");
|
||||||
|
}
|
||||||
|
case schema::Node::INTERFACE: {
|
||||||
|
auto interface = schema.asInterface();
|
||||||
|
return kj::strTree(
|
||||||
|
indent, "interface ", name, " @0x", kj::hex(proto.getId()), genGenericParams(schema),
|
||||||
|
genSuperclasses(interface),
|
||||||
|
genAnnotations(schema), " {\n",
|
||||||
|
KJ_MAP(method, sortByCodeOrder(interface.getMethods())) {
|
||||||
|
auto methodProto = method.getProto();
|
||||||
|
|
||||||
|
auto implicits = methodProto.getImplicitParameters();
|
||||||
|
kj::StringTree implicitsStr;
|
||||||
|
if (implicits.size() > 0) {
|
||||||
|
implicitsStr = kj::strTree(
|
||||||
|
"[", kj::StringTree(KJ_MAP(implicit, implicits) {
|
||||||
|
return kj::strTree(implicit.getName());
|
||||||
|
}, ", "), "] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto params = schemaLoader.get(methodProto.getParamStructType()).asStruct();
|
||||||
|
auto results = schemaLoader.get(methodProto.getResultStructType()).asStruct();
|
||||||
|
return kj::strTree(
|
||||||
|
indent.next(), methodProto.getName(),
|
||||||
|
" @", method.getIndex(), " ", kj::mv(implicitsStr),
|
||||||
|
genParamList(interface, params, methodProto.getParamBrand(), method), " -> ",
|
||||||
|
genParamList(interface, results, methodProto.getResultBrand(), method),
|
||||||
|
genAnnotations(methodProto.getAnnotations(), interface), ";\n");
|
||||||
|
},
|
||||||
|
genNestedDecls(schema, indent.next()),
|
||||||
|
indent, "}\n");
|
||||||
|
}
|
||||||
|
case schema::Node::CONST: {
|
||||||
|
auto constProto = proto.getConst();
|
||||||
|
return kj::strTree(
|
||||||
|
indent, "const ", name, " @0x", kj::hex(proto.getId()), " :",
|
||||||
|
genType(constProto.getType(), schema, nullptr), " = ",
|
||||||
|
genValue(schema.asConst().getType(), constProto.getValue()),
|
||||||
|
genAnnotations(schema), ";\n");
|
||||||
|
}
|
||||||
|
case schema::Node::ANNOTATION: {
|
||||||
|
auto annotationProto = proto.getAnnotation();
|
||||||
|
|
||||||
|
kj::Vector<kj::String> targets(8);
|
||||||
|
bool targetsAll = true;
|
||||||
|
|
||||||
|
auto dynamic = toDynamic(annotationProto);
|
||||||
|
for (auto field: dynamic.getSchema().getFields()) {
|
||||||
|
auto fieldName = field.getProto().getName();
|
||||||
|
if (fieldName.startsWith("targets")) {
|
||||||
|
if (dynamic.get(field).as<bool>()) {
|
||||||
|
auto target = kj::str(fieldName.slice(strlen("targets")));
|
||||||
|
target[0] = target[0] - 'A' + 'a';
|
||||||
|
targets.add(kj::mv(target));
|
||||||
|
} else {
|
||||||
|
targetsAll = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetsAll) {
|
||||||
|
targets = kj::Vector<kj::String>(1);
|
||||||
|
targets.add(kj::heapString("*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return kj::strTree(
|
||||||
|
indent, "annotation ", name, " @0x", kj::hex(proto.getId()),
|
||||||
|
" (", strArray(targets, ", "), ") :",
|
||||||
|
genType(annotationProto.getType(), schema, nullptr), genAnnotations(schema), ";\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kj::strTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genNestedDecls(Schema schema, Indent indent) {
|
||||||
|
uint64_t id = schema.getProto().getId();
|
||||||
|
return kj::strTree(KJ_MAP(nested, schema.getProto().getNestedNodes()) {
|
||||||
|
return genDecl(schemaLoader.get(nested.getId()), nested.getName(), id, indent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::StringTree genFile(Schema file) {
|
||||||
|
auto proto = file.getProto();
|
||||||
|
KJ_REQUIRE(proto.isFile(), "Expected a file node.", (uint)proto.which());
|
||||||
|
|
||||||
|
return kj::strTree(
|
||||||
|
"# ", proto.getDisplayName(), "\n",
|
||||||
|
"@0x", kj::hex(proto.getId()), ";\n",
|
||||||
|
KJ_MAP(ann, proto.getAnnotations()) { return genAnnotation(ann, file, "", ";\n"); },
|
||||||
|
genNestedDecls(file, Indent(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::MainBuilder::Validity run() {
|
||||||
|
ReaderOptions options;
|
||||||
|
options.traversalLimitInWords = 1 << 30; // Don't limit.
|
||||||
|
StreamFdMessageReader reader(STDIN_FILENO, options);
|
||||||
|
auto request = reader.getRoot<schema::CodeGeneratorRequest>();
|
||||||
|
|
||||||
|
for (auto node: request.getNodes()) {
|
||||||
|
schemaLoader.load(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::FdOutputStream rawOut(STDOUT_FILENO);
|
||||||
|
kj::BufferedOutputStreamWrapper out(rawOut);
|
||||||
|
|
||||||
|
for (auto requestedFile: request.getRequestedFiles()) {
|
||||||
|
genFile(schemaLoader.get(requestedFile.getId())).visit(
|
||||||
|
[&](kj::ArrayPtr<const char> text) {
|
||||||
|
out.write(text.begin(), text.size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace capnp
|
||||||
|
|
||||||
|
KJ_MAIN(capnp::CapnpcCapnpMain);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue