├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bin ├── capnpc-erl ├── ecapnp_test └── run_test ├── doc └── overview.edoc ├── include ├── ecapnp.hrl ├── ecapnp_records.hrl ├── ecapnp_runtime.hrl └── ecapnp_schema.hrl ├── priv └── samples │ ├── addressbook.capnp │ ├── addressbook.sh │ ├── addressbook_capnp.erl │ ├── calculator-client.erl │ ├── calculator-server.erl │ ├── calculator.capnp │ ├── calculator_capnp.erl │ └── run_samples.sh ├── src ├── c++.capnp ├── c++_capnp.erl ├── ecapnp.app.src ├── ecapnp.erl ├── ecapnp_capability.erl ├── ecapnp_capability_sup.erl ├── ecapnp_compiler.erl ├── ecapnp_data.erl ├── ecapnp_get.erl ├── ecapnp_message.erl ├── ecapnp_obj.erl ├── ecapnp_promise.erl ├── ecapnp_promise_sup.erl ├── ecapnp_ref.erl ├── ecapnp_rpc.erl ├── ecapnp_schema.erl ├── ecapnp_serialize.erl ├── ecapnp_set.erl ├── ecapnp_val.erl ├── ecapnp_vat.erl ├── ecapnpc.erl ├── rpc.capnp ├── rpc_capnp.erl ├── schema.capnp └── schema_capnp.erl └── test ├── ecapnp_capability_tests.erl ├── ecapnp_get_tests.erl ├── ecapnp_obj_tests.erl ├── ecapnp_props.erl ├── ecapnp_ref_tests.erl ├── ecapnp_rpc_tests.erl ├── ecapnp_set_tests.erl ├── ecapnp_test_utils.erl ├── ecapnp_val_tests.erl ├── ecapnp_vat_tests.erl ├── eunit_SUITE.erl ├── proper_SUITE.erl ├── test.capnp └── test_capnp.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.plt 6 | ebin/ 7 | *.dump 8 | erlang.mk 9 | bin/test.capnp.hrl 10 | logs/ 11 | .directory 12 | doc/*.html 13 | doc/edoc-info 14 | doc/erlang.png 15 | doc/stylesheet.css 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Options for erlang.mk 2 | PROJECT = ecapnp 3 | 4 | #test%: TEST_ERLC_OPTS += -DEUNIT_NOAUTO 5 | 6 | CT_SUITES = proper #eunit 7 | PLT_APPS = crypto 8 | EDOC_OPTS = preprocess, {dir, "doc/html"} 9 | 10 | EUNIT_OPTS = no_tty, {report, {eunit_progress, [colored]}} 11 | EUNIT_DIR = test 12 | 13 | # call `make tests TEST_DEPS=` after the first run in order to skip 14 | # the `make all` for all test deps.. (which for meck using rebar is 15 | # sloooow... :/ ) 16 | TEST_DEPS ?= meck proper eunit_formatters 17 | 18 | dep_meck = https://github.com/eproxus/meck.git master 19 | dep_proper = pkg://proper master 20 | dep_eunit_formatters = git git://github.com/seancribbs/eunit_formatters master 21 | 22 | include erlang.mk 23 | 24 | # erlang.mk bootstrapping 25 | # erlang.mk: erlang_mk_url ?= \ 26 | # http://raw.github.com/extend/erlang.mk/master/erlang.mk 27 | 28 | # erlang.mk: 29 | # @echo " GET " $@; wget -O $@ $(erlang_mk_url) 30 | 31 | # build rules for .capnp files 32 | %.capnp.hrl: %.capnp 33 | $(gen_verbose) capnpc -oerl $< 34 | 35 | %_capnp.erl: %.capnp | ebin 36 | $(gen_verbose) ECAPNP_TO_ERL=../$(dir $@) capnpc\ 37 | -oerl:ebin --src-prefix=$(dir $<) $< 38 | 39 | ebin: 40 | @mkdir -p ebin 41 | 42 | # make sure we rebuild on any header file change 43 | %.erl: include/*.hrl include/*/*.hrl ; @touch $@ 44 | 45 | # capnp_test integration 46 | dep_capnp_test = git://github.com/kaos/capnp_test.git 47 | $(eval $(call dep_target,capnp_test)) 48 | 49 | bin/test.capnp.hrl: $(DEPS_DIR)/capnp_test/test.capnp 50 | capnpc -oerl:$(dir $@) --src-prefix=$(dir $<) $< 51 | 52 | .PHONY: check 53 | check:: export CAPNP_TEST_APP = $(CURDIR)/bin/ecapnp_test 54 | check:: $(DEPS_DIR)/capnp_test bin/test.capnp.hrl 55 | $(MAKE) -C $< 56 | 57 | 58 | # DEV/TEST-only target.. 59 | # call it as `make dbg PROP=text_data LINE=117` 60 | # will dump you attached to a process running the text_data prop test, 61 | # on line 117 62 | # Currently we need ecapnp on the erlang lib path.. will fix that eventually.. 63 | .PHONY: bld dbg tst e p 64 | bld: TEST_DEPS= 65 | bld: TEST_ERLC_OPTS += -DEUNIT_NOAUTO 66 | bld: app build-tests 67 | 68 | dbg: bld 69 | erl -pa ebin test -eval \ 70 | "begin\ 71 | [i:ii(M) || M <- [ecapnp, ecapnp_obj, ecapnp_get, ecapnp_set,\ 72 | ecapnp_props, ecapnp_ref, ecapnp_data]],\ 73 | i:ib(ecapnp_props, $(LINE)),\ 74 | i:iaa([break]),\ 75 | proper:quickcheck(ecapnp_props:prop_$(PROP)())\ 76 | end" 77 | 78 | tst: e p 79 | 80 | erl: bld 81 | erl -pa ebin test priv/samples -eval \ 82 | "[i:ii(M) || M <- [ecapnp, ecapnp_obj, ecapnp_get, ecapnp_set,\ 83 | ecapnp_ref, ecapnp_data, ecapnp_schema, ecapnp_get_tests,\ 84 | ecapnp_set_tests, ecapnp_rpc, ecapnp_rpc_tests, ecapnp_vat,\ 85 | ecapnp_vat_tests, ecapnp_capability, ecapnp_capability_sup,\ 86 | ecapnp_ref_tests, 'calculator-server']]" 87 | 88 | e: bld 89 | erl -pa ebin test -pa deps/meck/ebin -noinput \ 90 | -eval "case eunit:test(\"test\", [no_tty, {report, {eunit_progress,\ 91 | [colored]}}]) of ok -> halt(0); _ -> halt(1) end" 92 | 93 | p: bld 94 | erl -pa ebin test -noinput \ 95 | -eval "proper:module(ecapnp_props), init:stop()" 96 | 97 | .PHONY: samples 98 | samples: app 99 | cd priv/samples && ./run_samples.sh 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ecapnp 2 | ====== 3 | 4 | [Cap'n Proto](http://capnproto.com) library for Erlang. 5 | 6 | NOTICE: This is work-in-progress. Feedback appreciated. 7 | 8 | 9 | ## Current Status 10 | 11 | * Compiler 12 | 13 | The compiler produces standalone schema modules to either compiled 14 | `.beam` or in `.erl` source form. 15 | 16 | * API 17 | 18 | The API are still quite verbose, and subject to change. 19 | 20 | * Serialization support 21 | 22 | Most constructs should work. There are a few corner cases I haven't 23 | tested yet. *Packed* messages are also supported. 24 | 25 | * RPC support 26 | 27 | The RPC support is shaping up, aiming for a level 1 implementation to be 28 | ready soon. The calculator sample from capnproto has been successfully 29 | ported to erlang. 30 | 31 | * Tests 32 | 33 | In addition to the eunit tests, there are now also 34 | [PropEr](https://github.com/manopapad/proper) tests to cover more 35 | corner cases. I find that they complement each other well, as unit 36 | tests are easier to write for a specific slightly contrived 37 | scenario, while the property tests are well suited for a general 38 | approach covering as many different inputs as possible. 39 | 40 | * Future work 41 | 42 | At this point, the goal is to get everything working properly. The 43 | next step will be to re-factor it into beautiful code/design, and 44 | after that, improvements for efficiency and performance. 45 | 46 | 47 | ### Note 48 | 49 | In order for `ecapnp` to work properly, `ecapnp` has to be on the 50 | Erlang lib path (i.e. `ERL_LIBS`), and `ecapnp/bin/capnpc-erl` needs 51 | to be on your `PATH`. 52 | 53 | 54 | Try it: 55 | 56 | cd ../path/to/ecapnp 57 | export ERL_LIBS=$(dirname $(pwd)) 58 | export PATH=$PATH:$(pwd)/bin 59 | make samples 60 | capnpc -oerl .../my_schema.capnp 61 | 62 | This will _(given that everything is working as intended)_ produce a 63 | `.../my_schema_capnp.beam` file. 64 | 65 | A few noteworthy options you can set in the environment are: 66 | 67 | * `ECAPNP_TO_ERL` - Save erlang source of compiled schema to path 68 | (relative to `outdir`). 69 | 70 | * `ECAPNP_NO_BEAM` - Do not compile to beam code. 71 | 72 | * `ECAPNP_LOAD_BEAM` - Load compiled beam code on the running 73 | node. Useful in case you compile files programatically, rather than 74 | using the capnp plugin. 75 | 76 | _(these options should be available as argument when compiling the code 77 | generator request.. alas, that is yet to be implemented)_ 78 | 79 | 80 | Web Site 81 | -------- 82 | 83 | Head over to [ecapnp.astekk.se](http://ecapnp.astekk.se) for documentation etc. 84 | -------------------------------------------------------------------------------- /bin/capnpc-erl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## 3 | ## Copyright 2013, Andreas Stenius 4 | ## 5 | ## Licensed under the Apache License, Version 2.0 (the "License"); 6 | ## you may not use this file except in compliance with the License. 7 | ## You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## 17 | 18 | tmpfile=`mktemp capnpc-erl.XXXXXXXXXX` 19 | cat > $tmpfile && erl -noshell -eval \ 20 | 'ok = ecapnpc:compile_file("'$tmpfile'")' -s init stop 21 | RET=$? 22 | rm $tmpfile 23 | exit $RET 24 | -------------------------------------------------------------------------------- /bin/ecapnp_test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The tmpfile hack is to avoid using stdin from erlang 4 | # as that is seriously bugged on windows 5 | 6 | case $1 in 7 | decode*) 8 | tmpfile=`mktemp ecapnp_test.XXXXXXXXXX` 9 | cat > $tmpfile && ${0%/*}/run_test $tmpfile $* 10 | ret=$? 11 | rm $tmpfile 12 | exit $ret;; 13 | *) ${0%/*}/run_test $* ;; 14 | esac 15 | -------------------------------------------------------------------------------- /bin/run_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- mode: erlang -*- 3 | %%! -pa /usr/local/include 4 | 5 | -include("test.capnp.hrl"). 6 | -define(SKIP, 127). 7 | 8 | main([File, Case, Test]) -> 9 | {ok, Message} = ecapnp_message:read_file(File), 10 | run_test(Case, Test, Message); 11 | main([Case, Test]) -> 12 | run_test(Case, Test, undefined); 13 | main(Args) -> 14 | io:format(standard_error, "ecapnp_test unknown args: ~p~n", [Args]), 15 | halt(?SKIP). 16 | 17 | run_test(Case, Test, Message) when is_list(Case) -> 18 | try 19 | run_test(list_to_atom(Case), list_to_atom(Test), Message) 20 | catch 21 | Class:Error -> 22 | io:format(standard_error, "ecapnp_test error: ~p:~p ~p~n", [Class, Error, erlang:get_stacktrace()]) 23 | end; 24 | 25 | run_test(decode, Test, Message) -> 26 | decode(Test, Message); 27 | run_test(encode, Test, undefined) -> 28 | io:setopts([{encoding, unicode}]), 29 | io:put_chars( ecapnp_message:write(encode(Test)) ); 30 | run_test(Action, Test, _) -> 31 | not_implemented(Action, Test). 32 | 33 | not_implemented(Action, Test) -> 34 | io:format(standard_error, "Unknown test: ~p ~p.~n", [Action, Test]), 35 | halt(?SKIP). 36 | 37 | 38 | decode(simpleTest, Msg) -> 39 | {ok, Root} = test(root, 'SimpleTest', Msg), 40 | io:format("(int = ~b, msg = \"~s\")~n", 41 | [test(get, int, Root), test(get, msg, Root)]); 42 | 43 | decode(textListTypeTest, Msg) -> 44 | {ok, Root} = test(root, 'ListTest', Msg), 45 | io:format("(textList = [~s])~n", 46 | [string:join( 47 | [io_lib:format("\"~s\"", [S]) 48 | || S <- test(get, textList, Root)], 49 | ", ")] 50 | ); 51 | 52 | decode(uInt8DefaultValueTest, Msg) -> 53 | {ok, Root} = test(root, 'TestDefaults', Msg), 54 | io:format("(uInt8Field = ~b)~n", 55 | [test(get, uInt8Field, Root)]); 56 | 57 | decode(constTest, _Msg) -> 58 | io:format("(msg = \"~s\")~n", [test(const, constTestValue)]); 59 | 60 | decode(Test, _) -> 61 | not_implemented(decode, Test). 62 | 63 | 64 | encode(simpleTest) -> 65 | {ok, Root} = test(root, 'SimpleTest'), 66 | ok = test(set, int, 1234567890, Root), 67 | ok = test(set, msg, <<"a short message...">>, Root), 68 | Root; 69 | 70 | encode(textListTypeTest) -> 71 | {ok, Root} = test(root, 'ListTest'), 72 | ok = test(set, textList, [<<"foo">>, <<"bar">>, <<"baz">>], Root), 73 | Root; 74 | 75 | encode(uInt8DefaultValueTest) -> 76 | {ok, Root} = test(root, 'TestDefaults'), 77 | ok = test(set, uInt8Field, 0, Root), 78 | Root; 79 | 80 | encode(constTest) -> 81 | {ok, Root} = test(root, 'SimpleTest'), 82 | ok = test(set, msg, test(const, constTestValue), Root), 83 | Root; 84 | 85 | encode(Test) -> 86 | not_implemented(encode, Test). 87 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @copyright 2013, Andreas Stenius 2 | @author Andreas Stenius [http://blog.astekk.se] 3 | @title Overview of ecapnp: the Cap'n Proto library for Erlang 4 | @version {@version} 5 | @doc 6 | 7 | == Description == 8 | 9 | The `ecapnp' library supports both Cap'n Proto serialization and RPC. The RPC 10 | support is currently a level 1 implementation. 11 | 12 | Cap'n Proto schemas are compiled with the capnpc-erl plugin, generating Erlang 13 | modules. 14 | 15 | To invoke the {@link ecapnpc. Cap'n Proto Erlang compiler plugin}, it is 16 | convenient to use the `capnpc-erl' script like this: 17 |
 18 |   capnpc -oerl my_schema.capnp
 19 | 
20 | 21 | This requires that `ecapnp/bin/ecapnpc-erl' is in your `$PATH', and that 22 | `ecapnp' is in your 23 | Erlang libs path 24 | (hint: use the `ERL_LIBS' environment variable). 25 | 26 | //TODO: See {@section Installation} for more details. 27 | 28 | 29 | == Schema modules == 30 | 31 | Once a `.capnp' schema file has been compiled to an Erlang module, all types 32 | defined in that schema is available to you from Erlang. 33 | 34 | There are several ways to get the schema for a given type, depending on what you 35 | use to find it. Each type is exported as a 0 arity function, both by its name 36 | and by its id, as well as a 1 arity function by its name taking a list nested 37 | type names. 38 | 39 | There's also a common `schema/1' function where you can look up the schema for 40 | any given type by name or id. Nested types are also supported by passing the 41 | type names in a list. 42 | 43 | See {@section Sample compiled schema}. 44 | 45 | 46 | Typically, this is `schema(root, ...)' for getting a root 48 | object, `schema(get, Field, Object)' for reading and `schema(set, 49 | Field, Value, Object)' for writing; where `schema' is the name of the 50 | schema file. 51 | 52 | There are also functions for type casting references to lists (or 53 | text/data) or other structs (useful when reading fields of type 54 | `object'). 55 | 56 | 57 | === Example === 58 | 59 | A practical example is best to show what it looks like. 60 | 61 | The `addressbook' example from Cap'n Proto has been ported and serves 62 | as an example for ecapnp as well; and is included here as a reference 63 | example. 64 | 65 | ==== Addressbook schema ==== 66 | 67 | The `addressbook.capnp' schema is defined thus: 68 |
``
 69 | @0x9eb32e19f86ee174;
 70 | 
 71 | using Cxx = import "/capnp/c++.capnp";
 72 | $Cxx.namespace("addressbook");
 73 | 
 74 | struct Person {
 75 |   id @0 :UInt32;
 76 |   name @1 :Text;
 77 |   email @2 :Text;
 78 |   phones @3 :List(PhoneNumber);
 79 | 
 80 |   struct PhoneNumber {
 81 |     number @0 :Text;
 82 |     type @1 :Type;
 83 | 
 84 |     enum Type {
 85 |       mobile @0;
 86 |       home @1;
 87 |       work @2;
 88 |     }
 89 |   }
 90 | 
 91 |   employment :union {
 92 |     unemployed @4 :Void;
 93 |     employer @5 :Text;
 94 |     school @6 :Text;
 95 |     selfEmployed @7 :Void;
 96 |     # We assume that a person is only one of these.
 97 |   }
 98 | }
 99 | 
100 | struct AddressBook {
101 |   people @0 :List(Person);
102 | }
103 | ''
104 | 
105 | 106 | To give a feel for how the compiled schema works, here's an excerpt of what it 107 | compiles to (with added comments): 108 |
``
109 | %% The schema file id
110 | -vsn(11435534567900897652).
111 | 
112 | %% functions for getting the schema for any given type
113 | -export([schema/1, 'Person'/0, 'Person'/1, '10988939875124296728'/0, '9317543775882349264'/0,
114 | 	 '10511609358742521391'/0, '13477914502553102653'/0, 'AddressBook'/0, 'AddressBook'/1,
115 | 	 '17957216978475721012'/0, root/0, root/1, '11435534567900897652'/0]).
116 | 
117 | %% a list mapping all type names with their corresponding id
118 | -types([{10988939875124296728, 'Person'}, {9317543775882349264, ['Person', 'PhoneNumber']},
119 | 	{10511609358742521391, ['Person', 'PhoneNumber', 'Type']},
120 | 	{13477914502553102653, ['Person', employment]}, {17957216978475721012, 'AddressBook'},
121 | 	{11435534567900897652, root}]).
122 | 
123 | %% any imported types can be seen in the source, but are not directly listed in
124 | %% the types above, but are accessible using the `schema/1' function
125 | -import('c++_capnp', ['13386661402618388268'/0]).
126 | ''
127 | 
128 | 129 | The `root' type is the file level schema. It can be used to find all top-level 130 | types as well as any annotations that applies to the schema file. 131 | 132 | 133 | ==== Writing an addressbook ==== 134 | 135 | To write an addressbook message, we first need an `AddressBook' root object: 136 | 137 | ``{ok, AddressBook} = ecapnp:set_root(addressbook_capnp:'AddressBook'().'' 138 | 139 | Now, we can fill in the details. Let's add two people, and call them 140 | `Alice' and `Bob': 141 | 142 | ``[Alice, Bob] = ecapnp:set(people, 2, AddressBook).'' 143 | 144 | Now, `Alice' has one phone number, while `Bob' has two: 145 | 146 | ``[ecapnp:set(phones, N, P) || {P, N} <- [{Alice, 1}, {Bob, 2}]].'' 147 | 148 | Ok, we're all set to fill in the blanks of the people and phone objects we have 149 | allocated. We're using a list comprehension to save on some typing: 150 | 151 |
152 | ``
153 | [ecapnp:set(Field, Value, Obj)
154 |  || {Obj, FieldValue} <-
155 |           [{Alice,
156 |                 [{id, 123},
157 |                  {name, <<"Alice">>},
158 |                  {email, <<"alice@example.com">>},
159 |                  {employment, {shool, <<"MIT">>}},
160 |                  {phones, {0, {number, <<"555-1212">>}}},
161 |                  {phones, {0, {type, mobile}}}]},
162 |            {Bob,
163 |                 [{id, 456},
164 |                  {name, <<"Bob">>},
165 |                  {email, <<"bob@example.com">>},
166 |                  {employment, unemployed},
167 |                  {phones, {0, {number, <<"555-4567">>}}},
168 |                  {phones, {0, {type, home}}},
169 |                  {phones, {1, {number, <<"555-7654">>}}},
170 |                  {phones, {1, {type, mobile}}}]}],
171 |     {Field, Value} <- FieldValues].
172 | ''
173 | 
174 | 175 | Note that we could have saved a reference to the phone objects 176 | directly and used those instead of embedding them in the calls to 177 | Alice and Bob, when we allocated the phone objects, the same way we 178 | saved the result when allocating people. 179 | 180 | All that is left now is to get the message out. Here's how to get a 181 | packed binary ready for dispatching: 182 | 183 | ``Data = ecapnp_serialize:pack(ecapnp_message:write(AddressBook)).'' 184 | 185 | If you intend to send it to `io', make sure it uses `unicode' encoding: 186 | 187 | ``io:setopts([{encoding, unicode}]).'' will take care of it. 188 | 189 | 190 | ==== Reading an addressbook message ==== 191 | 192 | Assuming you have the packed binary from the previous section in `Data', here's 193 | how you can read some stuff out of it. 194 | 195 | First, we need to unpack it and parse the message header (segment table) before 196 | getting at the root struct node: 197 | 198 |
199 | ``
200 | Unpacked = ecapnp_serialize:unpack(Data),
201 | {ok, Message, <<>>} = ecapnp_message:read(Unpacked),
202 | {ok, AddressBook} = ecapnp:get_root(addressbook_capnp:'AddressBook'(), Message)
203 | ''
204 | 
205 | 206 | Then we can start reading data out of it using `ecapnp:get': 207 | 208 |
209 | ``
210 | %% this will of course fail if the people list is empty
211 | [Person|People] = ecapnp:get(people, AddressBook),
212 | Name = ecapnp:get(name, Person),
213 | 
214 | [Phone|Phones] = ecapnp:get(phones, Person),
215 | Number = ecapnp:get(number, Phone),
216 | 
217 | {Employment, Value} = ecapnp:get(employment, Person),
218 | 
219 | %% `Value' can be the school name, employer or `void' depending on the value of
220 | %% Employment.
221 | ''
222 | 
223 | 224 | 225 | == RPC == 226 | 227 | The RPC support is at level 1 (almost). Still lacking is a bit of infrastructure 228 | for sockets, and the reference counting/releasing is still a bit of a moving 229 | target. 230 | 231 | The calculator sample application is the best source of examples, and I won't 232 | repeat everything here at this time, as things are still subject to change.. 233 | 234 | In short, there is: 235 | 236 | - `ecapnp:request/2' to invoke a call on a capability. 237 | - `ecapnp:send/1' to dispatch a call request. 238 | - `ecapnp:wait/1' to wait for the results of a call. 239 | - `ecapnp_vat:import_capability/3' to restore capabilities. 240 | 241 | For now, refer to the client and server samples for the glue code needed. 242 | 243 | 244 | == Project info == 245 |
246 |
Project page
[http://ecapnp.astekk.se]
247 |
Source code
[http://github.com/kaos/ecapnp]
248 |
Cap'n Proto
249 |
250 | Home: [http://capnproto.com]
251 | Code: [http://github.com/kentonv/capnproto] 252 |
253 |
License
254 |
Licensed under the Apache License, Version 2.0 (the "License");
255 | you may not use this file except in compliance with the License.
256 | You may obtain a copy of the License at
257 | 
258 |   http://www.apache.org/licenses/LICENSE-2.0
259 | 
260 | Unless required by applicable law or agreed to in writing, software
261 | distributed under the License is distributed on an "AS IS" BASIS,
262 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
263 | See the License for the specific language governing permissions and
264 | limitations under the License.
265 |
266 |
267 | -------------------------------------------------------------------------------- /include/ecapnp.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ecapnp_hrl). 2 | -define(ecapnp_hrl,1). 3 | 4 | -include("ecapnp_records.hrl"). 5 | 6 | -type annotation() :: ecapnp:annotation(). 7 | -type bit_count() :: ecapnp:bit_count(). 8 | -type const() :: ecapnp:const(). 9 | -type data() :: ecapnp:data(). 10 | -type element_size() :: ecapnp:element_size(). 11 | -type enum() :: ecapnp:enum(). 12 | -type enum_values() :: ecapnp:enum_values(). 13 | -type far_ref() :: ecapnp:far_ref(). 14 | -type field_name() :: ecapnp:field_name(). 15 | -type field_type() :: ecapnp:field_type(). 16 | -type field_value() :: ecapnp:field_value(). 17 | -type group() :: ecapnp:group(). 18 | -type interface() :: ecapnp:interface(). 19 | -type list_ref() :: ecapnp:list_ref(). 20 | -type message() :: ecapnp:message(). 21 | -type msg() :: ecapnp:msg(). 22 | -type node_type() :: ecapnp:node_type(). 23 | -type node_types() :: ecapnp:node_types(). 24 | -type object() :: ecapnp:object(). 25 | -type object_field() :: ecapnp:object_field(). 26 | -type object_fields() :: ecapnp:object_fields(). 27 | -type ptr() :: ecapnp:ptr(). 28 | -type ptr_count() :: ecapnp:ptr_count(). 29 | -type ptr_index() :: ecapnp:ptr_index(). 30 | -type ref() :: ecapnp:ref(). 31 | -type ref_kind() :: ecapnp:ref_kind(). 32 | -type schema() :: ecapnp:schema(). 33 | -type schema_kind() :: ecapnp:schema_kind(). 34 | -type schema_node() :: ecapnp:schema_node(). 35 | -type schema_nodes() :: ecapnp:schema_nodes(). 36 | -type schema_type() :: ecapnp:schema_type(). 37 | -type segment_id() :: ecapnp:segment_id(). 38 | -type segment_offset() :: ecapnp:segment_offset(). 39 | -type segment_pos() :: ecapnp:segment_pos(). 40 | -type struct() :: ecapnp:struct(). 41 | -type struct_fields() :: ecapnp:struct_fields(). 42 | -type struct_ref() :: ecapnp:struct_ref(). 43 | -type text() :: ecapnp:text(). 44 | -type type_id() :: ecapnp:type_id(). 45 | -type type_name() :: ecapnp:type_name(). 46 | -type value() :: ecapnp:value(). 47 | -type value_type() :: ecapnp:value_type(). 48 | -type word_count() :: ecapnp:word_count(). 49 | 50 | 51 | -ifdef(ECAPNP_TRACE). 52 | 53 | -ifndef(ECAPNP_GEN_OPTS). 54 | -define(ECAPNP_GEN_OPTS,[{debug, [trace]}]). 55 | -endif. 56 | 57 | -define(ECAPNP_DEBUG,1). 58 | 59 | -else. 60 | 61 | -ifndef(ECAPNP_GEN_OPTS). 62 | -define(ECAPNP_GEN_OPTS,[]). 63 | -endif. 64 | 65 | -endif. 66 | 67 | -ifndef(ECAPNP_DEBUG). 68 | -define(DBG(F,A),ok). 69 | -define(DUMP(D),). 70 | 71 | -else. 72 | -define(DBG(F,A), 73 | _ = io:format(%%standard_error, 74 | "~-10w ~20s:~-4w\t~s~n", 75 | [self(), ?MODULE, ?LINE, io_lib:format(F, A)]) 76 | ). 77 | -define(DUMP(D), ecapnp:dump(D)). 78 | 79 | -endif. 80 | 81 | -endif. 82 | -------------------------------------------------------------------------------- /include/ecapnp_records.hrl: -------------------------------------------------------------------------------- 1 | %% Schema records 2 | -include("ecapnp_schema.hrl"). 3 | 4 | %% Runtime records 5 | -include("ecapnp_runtime.hrl"). 6 | -------------------------------------------------------------------------------- /include/ecapnp_runtime.hrl: -------------------------------------------------------------------------------- 1 | -record(builder, { 2 | pid :: pid() 3 | }). 4 | 5 | -record(reader, { 6 | data :: list(binary()) | binary(), 7 | caps = [] :: list() 8 | }). 9 | 10 | -record(ref, { 11 | segment :: ecapnp:segment_id(), 12 | pos = -1 :: ecapnp:segment_pos(), 13 | offset = 0 :: ecapnp:segment_offset(), %% or capability index in CapTable for #interface_ref{}'s 14 | align = 0 :: ecapnp:bit_count(), 15 | kind = null :: ecapnp:ref_kind(), 16 | data :: #builder{} | #reader{} 17 | }). 18 | 19 | -record(struct_ref, { 20 | dsize = 0 :: ecapnp:word_count(), 21 | psize = 0 :: ecapnp:ptr_count() 22 | }). 23 | 24 | -record(list_ref, { 25 | size = 0 :: ecapnp:bit_count() | pointer | {inlineComposite, #struct_ref{}}, 26 | count = 0 :: non_neg_integer() %% ALWAYS number of elements in list 27 | }). 28 | 29 | -record(far_ref, { 30 | segment = 0 :: non_neg_integer(), 31 | double_far = false :: boolean() 32 | }). 33 | 34 | -record(interface_ref, { 35 | owner :: {atom(), pid()}, 36 | id :: term() 37 | }). 38 | 39 | -record(object, { 40 | ref = null :: #ref{}, 41 | schema :: atom() | ecapnp:schema_node() 42 | }). 43 | 44 | -record(rpc_call, { 45 | target :: term(), 46 | interface :: ecapnp:type_id(), 47 | method :: non_neg_integer(), 48 | params :: ecapnp:object(), 49 | results :: ecapnp:object(), 50 | resultSchema = object :: ecapnp:schema_node() | object 51 | }). 52 | 53 | -record(promise, { 54 | owner :: {atom(), pid()}, 55 | pid :: pid(), %% ecapnp_promise pid 56 | transform = [] :: list(), 57 | schema :: ecapnp:schema_node() 58 | }). 59 | -------------------------------------------------------------------------------- /include/ecapnp_schema.hrl: -------------------------------------------------------------------------------- 1 | %% ecapnp schema records 2 | %% 3 | %% Bump version number on ANY change in ANY of the records in this file. 4 | %% Must have the same version in both ecapnp runtime libs and compiled schemas. 5 | -ecapnp_schema_version(4). 6 | 7 | %% Common record for all schema nodes 8 | -record(schema_node, { 9 | module :: atom(), 10 | name :: ecapnp:type_name(), 11 | id = 0 :: ecapnp:type_id(), 12 | src = <<>> :: ecapnp:text(), 13 | kind = file :: ecapnp:schema_kind(), 14 | annotations = [] :: list(), 15 | nodes = [] :: ecapnp:schema_nodes(), 16 | scope = 0 :: ecapnp:type_id() 17 | }). 18 | 19 | %% Struct node 20 | -record(struct, { 21 | dsize = 0 :: ecapnp:word_count(), 22 | psize = 0 :: ecapnp:ptr_count(), 23 | esize = inlineComposite :: ecapnp:element_size(), 24 | union_field = none :: none | ecapnp:field_type(), 25 | fields = [] :: ecapnp:struct_fields() 26 | }). 27 | 28 | %% Enum node 29 | -record(enum, { 30 | values = [] :: ecapnp:enum_values() 31 | }). 32 | 33 | %% Interface node 34 | -record(interface, { 35 | extends = [] :: list(), 36 | methods = [] :: list() 37 | }). 38 | 39 | %% Const node 40 | -record(const, { 41 | field 42 | }). 43 | 44 | %% Annotation node 45 | -record(annotation, { 46 | type, 47 | targets = [] :: list(atom()) 48 | }). 49 | 50 | %% Struct field 51 | -record(field, { 52 | id, 53 | name, 54 | kind, 55 | annotations = [] 56 | }). 57 | 58 | %% Schema Field types 59 | -record(ptr, { 60 | type :: term(), 61 | idx = 0 :: ecapnp:ptr_index(), 62 | default = <<0:64/integer-little>> :: ecapnp:value() 63 | }). 64 | 65 | -record(data, { 66 | type :: term(), 67 | align = 0 :: ecapnp:bit_count(), 68 | default :: ecapnp:value() 69 | }). 70 | 71 | -record(group, { 72 | id = 0 :: ecapnp:type_id() 73 | }). 74 | 75 | %% Interface methods 76 | -record(method, { 77 | id, 78 | name, 79 | paramType, 80 | resultType 81 | }). 82 | -------------------------------------------------------------------------------- /priv/samples/addressbook.capnp: -------------------------------------------------------------------------------- 1 | @0x9eb32e19f86ee174; 2 | 3 | using Cxx = import "/capnp/c++.capnp"; 4 | $Cxx.namespace("addressbook"); 5 | 6 | struct Person { 7 | id @0 :UInt32; 8 | name @1 :Text; 9 | email @2 :Text; 10 | phones @3 :List(PhoneNumber); 11 | 12 | struct PhoneNumber { 13 | number @0 :Text; 14 | type @1 :Type; 15 | 16 | enum Type { 17 | mobile @0; 18 | home @1; 19 | work @2; 20 | } 21 | } 22 | 23 | employment :union { 24 | unemployed @4 :Void; 25 | employer @5 :Text; 26 | school @6 :Text; 27 | selfEmployed @7 :Void; 28 | # We assume that a person is only one of these. 29 | } 30 | } 31 | 32 | struct AddressBook { 33 | people @0 :List(Person); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /priv/samples/addressbook.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- mode: erlang -*- 3 | 4 | main(Args) -> 5 | try process(Args) 6 | catch 7 | C:E -> 8 | io:format(standard_error, 9 | "~s ~p: ~s:~p~n" 10 | "~p~n", 11 | [escript:script_name(), Args, C, E, 12 | erlang:get_stacktrace()]), 13 | halt(2) 14 | end. 15 | 16 | process(["read"|Args]) -> 17 | read(Args); 18 | process(["write"|Args]) -> 19 | write(Args); 20 | process(_) -> 21 | io:format(standard_error, 22 | "Usage: ~s {read [filename] | write}~n", 23 | [escript:script_name()]), 24 | halt(1). 25 | 26 | read([FileName]) -> 27 | {ok, Data} = file:read_file(FileName), 28 | dump_message(Data); 29 | read([]) -> 30 | io:format(standard_error, 31 | "Reading message from stdin~n" 32 | " (note, this will likely fail)~n" 33 | " (if you are on windows, )~n" 34 | " (see README. )~n~n", 35 | []), 36 | dump_message(read_stdin()). 37 | 38 | write([]) -> 39 | {ok, Root} = ecapnp:set_root(addressbook_capnp:'AddressBook'()), 40 | [Alice, Bob, Steve] = ecapnp:set(people, 3, Root), 41 | [AlicePhone] = ecapnp:set(phones, 1, Alice), 42 | [BobPhone1, BobPhone2] = ecapnp:set(phones, 2, Bob), 43 | [ecapnp:set(Field, Value, Obj) 44 | || {Obj, FieldValues} <- 45 | [{Alice, 46 | [{id, 123}, 47 | {name, <<"Alice">>}, 48 | {email, <<"alice@example.com">>}, 49 | {employment, {school, <<"MIT">>}} 50 | ]}, 51 | {AlicePhone, 52 | [{number, <<"555-1212">>}, 53 | {type, mobile}]}, 54 | {Bob, 55 | [{id, 456}, 56 | {name, <<"Bob">>}, 57 | {email, <<"bob@example.com">>}, 58 | {employment, unemployed} 59 | ]}, 60 | {BobPhone1, 61 | [{number, <<"555-4567">>}, 62 | {type, home}]}, 63 | {BobPhone2, 64 | [{number, <<"555-7654">>}, 65 | {type, work}]}, 66 | {Steve, 67 | [{id, 123456}, 68 | {name, <<"Steve">>}, 69 | {email, <<"steve@example.com">>}, 70 | {employment, selfEmployed}, 71 | {phones, 3}, 72 | {phones, {0, {number, <<"555-1234">>}}}, 73 | {phones, {0, {type, home}}}, 74 | {phones, {1, {number, <<"555-4321">>}}}, 75 | {phones, {1, {type, work}}}, 76 | {phones, {2, {number, <<"070-5555">>}}}, 77 | {phones, {2, {type, mobile}}} 78 | ]} 79 | ], 80 | {Field, Value} <- FieldValues], 81 | 82 | %% Get message data and pack it 83 | Data1 = ecapnp_serialize:pack( 84 | ecapnp_message:write(Root)), 85 | io:setopts([{encoding, unicode}]), 86 | io:put_chars(Data1). 87 | 88 | 89 | dump_message(Data) -> 90 | %% unpack and read message data 91 | {ok, Message, <<>>} = ecapnp_message:read( 92 | ecapnp_serialize:unpack(Data)), 93 | {ok, Root} = ecapnp:get_root( 94 | addressbook_capnp:'AddressBook'(), Message), 95 | People = ecapnp:get(people, Root), 96 | [dump_person(Person) || Person <- People]. 97 | 98 | dump_person(Person) -> 99 | io:format("#~p ", [ecapnp:get(id, Person)]), 100 | io:format("~s: ~s~n", [ecapnp:get(name, Person), 101 | ecapnp:get(email, Person)]), 102 | Phones = ecapnp:get(phones, Person), 103 | [io:format(" ~s phone: ~s~n", [ecapnp:get(type, P), 104 | ecapnp:get(number, P)]) 105 | || P <- Phones], 106 | case ecapnp:get(employment, Person) of 107 | {unemployed, void} -> io:format(" unemployed~n"); 108 | {employer, Employer} -> 109 | io:format(" employer: ~s~n", [Employer]); 110 | {school, School} -> 111 | io:format(" student at: ~s~n", [School]); 112 | {selfEmployed, void} -> io:format(" self-employed~n") 113 | end. 114 | 115 | read_stdin() -> 116 | read_stdin([]). 117 | 118 | read_stdin(Acc) 119 | when is_list(Acc) -> 120 | read_stdin(file:read(standard_io, 1024), Acc). 121 | 122 | read_stdin(eof, Acc) -> 123 | list_to_binary( 124 | lists:reverse(Acc)); 125 | read_stdin({ok, Data}, Acc) -> 126 | read_stdin([Data|Acc]). 127 | -------------------------------------------------------------------------------- /priv/samples/addressbook_capnp.erl: -------------------------------------------------------------------------------- 1 | -file("addressbook.capnp", 1). 2 | 3 | %% This file was generated 2014-05-27 10:20:43 UTC by ecapnp 0.2. 4 | %% http://github.com/kaos/ecapnp 5 | -module(addressbook_capnp). 6 | 7 | -vsn(11435534567900897652). 8 | 9 | -export([schema/1, 'Person'/0, 'Person'/1, '10988939875124296728'/0, '9317543775882349264'/0, 10 | '10511609358742521391'/0, '13477914502553102653'/0, 'AddressBook'/0, 'AddressBook'/1, 11 | '17957216978475721012'/0, root/0, root/1, '11435534567900897652'/0]). 12 | 13 | -types([{10988939875124296728, 'Person'}, {9317543775882349264, ['Person', 'PhoneNumber']}, 14 | {10511609358742521391, ['Person', 'PhoneNumber', 'Type']}, 15 | {13477914502553102653, ['Person', employment]}, {17957216978475721012, 'AddressBook'}, 16 | {11435534567900897652, root}]). 17 | 18 | -import('c++_capnp', ['13386661402618388268'/0]). 19 | 20 | -file("/home/kaos/src/erl/libs/ecapnp/include/ecapnp_schema.hrl", 1). 21 | 22 | -ecapnp_schema_version(3). 23 | 24 | -record(schema_node, 25 | {module, name, id = 0, src = <<>>, kind = file, annotations = [], nodes = [], scope = 0}). 26 | 27 | -type({{record, schema_node}, 28 | [{typed_record_field, {record_field, 9, {atom, 9, module}}, 29 | {type, 9, union, [{atom, 9, undefined}, {type, 9, atom, []}]}}, 30 | {typed_record_field, {record_field, 10, {atom, 10, name}}, 31 | {type, 10, union, 32 | [{atom, 10, undefined}, {remote_type, 10, [{atom, 10, ecapnp}, {atom, 10, type_name}, []]}]}}, 33 | {typed_record_field, {record_field, 11, {atom, 11, id}, {integer, 11, 0}}, 34 | {remote_type, 11, [{atom, 11, ecapnp}, {atom, 11, type_id}, []]}}, 35 | {typed_record_field, {record_field, 12, {atom, 12, src}, {bin, 12, []}}, 36 | {remote_type, 12, [{atom, 12, ecapnp}, {atom, 12, text}, []]}}, 37 | {typed_record_field, {record_field, 13, {atom, 13, kind}, {atom, 13, file}}, 38 | {remote_type, 13, [{atom, 13, ecapnp}, {atom, 13, schema_kind}, []]}}, 39 | {typed_record_field, {record_field, 14, {atom, 14, annotations}, {nil, 14}}, {type, 14, list, []}}, 40 | {typed_record_field, {record_field, 15, {atom, 15, nodes}, {nil, 15}}, 41 | {remote_type, 15, [{atom, 15, ecapnp}, {atom, 15, schema_nodes}, []]}}, 42 | {typed_record_field, {record_field, 16, {atom, 16, scope}, {integer, 16, 0}}, 43 | {remote_type, 16, [{atom, 16, ecapnp}, {atom, 16, type_id}, []]}}], 44 | []}). 45 | 46 | -record(struct, {dsize = 0, psize = 0, esize = inlineComposite, union_field = none, fields = []}). 47 | 48 | -type({{record, struct}, 49 | [{typed_record_field, {record_field, 21, {atom, 21, dsize}, {integer, 21, 0}}, 50 | {remote_type, 21, [{atom, 21, ecapnp}, {atom, 21, word_count}, []]}}, 51 | {typed_record_field, {record_field, 22, {atom, 22, psize}, {integer, 22, 0}}, 52 | {remote_type, 22, [{atom, 22, ecapnp}, {atom, 22, ptr_count}, []]}}, 53 | {typed_record_field, {record_field, 23, {atom, 23, esize}, {atom, 23, inlineComposite}}, 54 | {remote_type, 23, [{atom, 23, ecapnp}, {atom, 23, element_size}, []]}}, 55 | {typed_record_field, {record_field, 24, {atom, 24, union_field}, {atom, 24, none}}, 56 | {type, 24, union, 57 | [{atom, 24, none}, {remote_type, 24, [{atom, 24, ecapnp}, {atom, 24, field_type}, []]}]}}, 58 | {typed_record_field, {record_field, 25, {atom, 25, fields}, {nil, 25}}, 59 | {remote_type, 25, [{atom, 25, ecapnp}, {atom, 25, struct_fields}, []]}}], 60 | []}). 61 | 62 | -record(enum, {values = []}). 63 | 64 | -type({{record, enum}, 65 | [{typed_record_field, {record_field, 30, {atom, 30, values}, {nil, 30}}, 66 | {remote_type, 30, [{atom, 30, ecapnp}, {atom, 30, enum_values}, []]}}], 67 | []}). 68 | 69 | -record(interface, {extends = [], methods = []}). 70 | 71 | -type({{record, interface}, 72 | [{typed_record_field, {record_field, 35, {atom, 35, extends}, {nil, 35}}, {type, 35, list, []}}, 73 | {typed_record_field, {record_field, 36, {atom, 36, methods}, {nil, 36}}, {type, 36, list, []}}], 74 | []}). 75 | 76 | -record(const, {field}). 77 | 78 | -record(annotation, {type, targets = []}). 79 | 80 | -type({{record, annotation}, 81 | [{record_field, 46, {atom, 46, type}}, 82 | {typed_record_field, {record_field, 47, {atom, 47, targets}, {nil, 47}}, 83 | {type, 47, list, [{type, 47, atom, []}]}}], 84 | []}). 85 | 86 | -record(field, {id, name, kind, annotations = []}). 87 | 88 | -record(ptr, {type, idx = 0, default = null}). 89 | 90 | -type({{record, ptr}, 91 | [{typed_record_field, {record_field, 60, {atom, 60, type}}, 92 | {type, 60, union, [{atom, 60, undefined}, {type, 60, term, []}]}}, 93 | {typed_record_field, {record_field, 61, {atom, 61, idx}, {integer, 61, 0}}, 94 | {remote_type, 61, [{atom, 61, ecapnp}, {atom, 61, ptr_index}, []]}}, 95 | {typed_record_field, {record_field, 62, {atom, 62, default}, {atom, 62, null}}, 96 | {remote_type, 62, [{atom, 62, ecapnp}, {atom, 62, value}, []]}}], 97 | []}). 98 | 99 | -record(data, {type, align = 0, default}). 100 | 101 | -type({{record, data}, 102 | [{typed_record_field, {record_field, 66, {atom, 66, type}}, 103 | {type, 66, union, [{atom, 66, undefined}, {type, 66, term, []}]}}, 104 | {typed_record_field, {record_field, 67, {atom, 67, align}, {integer, 67, 0}}, 105 | {remote_type, 67, [{atom, 67, ecapnp}, {atom, 67, bit_count}, []]}}, 106 | {typed_record_field, {record_field, 68, {atom, 68, default}}, 107 | {type, 68, union, 108 | [{atom, 68, undefined}, {remote_type, 68, [{atom, 68, ecapnp}, {atom, 68, value}, []]}]}}], 109 | []}). 110 | 111 | -record(group, {id = 0}). 112 | 113 | -type({{record, group}, 114 | [{typed_record_field, {record_field, 72, {atom, 72, id}, {integer, 72, 0}}, 115 | {remote_type, 72, [{atom, 72, ecapnp}, {atom, 72, type_id}, []]}}], 116 | []}). 117 | 118 | -record(method, {id, name, paramType, resultType}). 119 | 120 | -file("addressbook.capnp", 1). 121 | 122 | schema(10988939875124296728) -> '10988939875124296728'(); 123 | schema('Person') -> '10988939875124296728'(); 124 | schema(['Person']) -> '10988939875124296728'(); 125 | schema(9317543775882349264) -> '9317543775882349264'(); 126 | schema(['Person', 'PhoneNumber']) -> '9317543775882349264'(); 127 | schema(10511609358742521391) -> '10511609358742521391'(); 128 | schema(['Person', 'PhoneNumber', 'Type']) -> '10511609358742521391'(); 129 | schema(13477914502553102653) -> '13477914502553102653'(); 130 | schema(['Person', employment]) -> '13477914502553102653'(); 131 | schema(17957216978475721012) -> '17957216978475721012'(); 132 | schema('AddressBook') -> '17957216978475721012'(); 133 | schema(['AddressBook']) -> '17957216978475721012'(); 134 | schema(11435534567900897652) -> '11435534567900897652'(); 135 | schema(root) -> '11435534567900897652'(); 136 | schema([root]) -> '11435534567900897652'(); 137 | %% Imported from c++_capnp 138 | schema(13386661402618388268) -> '13386661402618388268'(); 139 | schema(namespace) -> '13386661402618388268'(); 140 | schema([namespace]) -> '13386661402618388268'(); 141 | schema(_) -> undefined. 142 | 143 | root() -> '11435534567900897652'(). 144 | 145 | root([]) -> '11435534567900897652'(). 146 | 147 | '11435534567900897652'() -> 148 | #schema_node{module = addressbook_capnp, name = root, id = 11435534567900897652, scope = 0, 149 | src = <<"addressbook.capnp">>, annotations = [{13386661402618388268, <<"addressbook">>}], 150 | kind = file, 151 | nodes = 152 | [10988939875124296728, %% Person 153 | 17957216978475721012]}. %% AddressBook 154 | 155 | 'AddressBook'() -> '17957216978475721012'(). 156 | 157 | 'AddressBook'([]) -> '17957216978475721012'(). 158 | 159 | '17957216978475721012'() -> 160 | #schema_node{module = addressbook_capnp, name = 'AddressBook', id = 17957216978475721012, 161 | scope = 11435534567900897652, src = <<"addressbook.capnp:AddressBook">>, 162 | kind = 163 | #struct{dsize = 0, psize = 1, esize = pointer, union_field = none, 164 | fields = 165 | [#field{id = 0, name = people, 166 | kind = 167 | #ptr{type = {list, {struct, 10988939875124296728}}, idx = 0, 168 | default = <<0, 0, 0, 0, 0, 0, 0, 0>>}}]}}. 169 | 170 | 'Person'() -> '10988939875124296728'(). 171 | 172 | 'Person'(['PhoneNumber']) -> '9317543775882349264'(); 173 | 'Person'(['PhoneNumber', 'Type']) -> '10511609358742521391'(); 174 | 'Person'([employment]) -> '13477914502553102653'(); 175 | 'Person'([]) -> '10988939875124296728'(). 176 | 177 | '10988939875124296728'() -> 178 | #schema_node{module = addressbook_capnp, name = 'Person', id = 10988939875124296728, 179 | scope = 11435534567900897652, src = <<"addressbook.capnp:Person">>, 180 | kind = 181 | #struct{dsize = 1, psize = 4, esize = inlineComposite, union_field = none, 182 | fields = 183 | [#field{id = 0, name = id, kind = #data{type = uint32, align = 0, default = <<0, 0, 0, 0>>}}, 184 | #field{id = 1, name = name, kind = #ptr{type = text, idx = 0, default = <<"">>}}, 185 | #field{id = 2, name = email, kind = #ptr{type = text, idx = 1, default = <<"">>}}, 186 | #field{id = 3, name = phones, 187 | kind = 188 | #ptr{type = {list, {struct, 9317543775882349264}}, idx = 2, default = <<0, 0, 0, 0, 0, 0, 0, 0>>}}, 189 | #field{id = 4, name = employment, kind = #group{id = 13477914502553102653}}]}, 190 | nodes = 191 | [9317543775882349264]}. %% PhoneNumber 192 | 193 | '9317543775882349264'() -> 194 | #schema_node{module = addressbook_capnp, name = ['Person', 'PhoneNumber'], id = 9317543775882349264, 195 | scope = 10988939875124296728, src = <<"addressbook.capnp:Person.PhoneNumber">>, 196 | kind = 197 | #struct{dsize = 1, psize = 1, esize = inlineComposite, union_field = none, 198 | fields = 199 | [#field{id = 0, name = number, kind = #ptr{type = text, idx = 0, default = <<"">>}}, 200 | #field{id = 1, name = type, 201 | kind = #data{type = {enum, 10511609358742521391}, align = 0, default = <<0, 0>>}}]}, 202 | nodes = 203 | [10511609358742521391]}. %% Type 204 | 205 | '10511609358742521391'() -> 206 | #schema_node{module = addressbook_capnp, name = ['Person', 'PhoneNumber', 'Type'], 207 | id = 10511609358742521391, scope = 9317543775882349264, 208 | src = <<"addressbook.capnp:Person.PhoneNumber.Type">>, 209 | kind = #enum{values = [{0, mobile}, {1, home}, {2, work}]}}. 210 | 211 | '13477914502553102653'() -> 212 | #schema_node{module = addressbook_capnp, name = ['Person', employment], id = 13477914502553102653, 213 | scope = 10988939875124296728, src = <<"addressbook.capnp:Person.employment">>, 214 | kind = 215 | #struct{dsize = 1, psize = 4, esize = inlineComposite, 216 | union_field = 217 | #data{type = 218 | {union, 219 | [#field{id = 0, name = unemployed, kind = void}, 220 | #field{id = 1, name = employer, kind = #ptr{type = text, idx = 3, default = <<"">>}}, 221 | #field{id = 2, name = school, kind = #ptr{type = text, idx = 3, default = <<"">>}}, 222 | #field{id = 3, name = selfEmployed, kind = void}]}, 223 | align = 32, default = <<0, 0>>}, 224 | fields = []}}. -------------------------------------------------------------------------------- /priv/samples/calculator-client.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2014, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2014, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Calculator client sample application. 20 | %% 21 | %% The operations are based on the calculator client sample from the 22 | %% capnproto distribution. 23 | 24 | -module('calculator-client'). 25 | 26 | -export([run/0, handle_call/5]). 27 | 28 | run() -> 29 | ecapnp_promise_sup:start_link(), 30 | ecapnp_capability_sup:start_link(), 31 | Calculator = connect(), 32 | _ = 33 | [begin 34 | Ref = monitor(process, spawn(fun () -> exit(F(Calculator)) end)), 35 | receive 36 | {'DOWN', Ref, process, _Pid, Info} -> {T, Info} 37 | end 38 | end || {T, F} <- [{eval_literal, fun eval_literal/1}, 39 | {add_and_subtract, fun add_and_subtract/1}, 40 | {pipelining, fun pipelining/1}, 41 | {def_functions, fun def_functions/1}, 42 | {callback, fun callback/1} 43 | ] 44 | ], 45 | %% proper shutdown not yet implemented.. 46 | %% give some time to let the last finish messages get out 47 | receive after 500 -> 48 | io:format("~n~nAll done.~n"), 49 | halt(0) 50 | end. 51 | 52 | connect() -> 53 | connect({"localhost", 55000}). 54 | 55 | connect({Addr, Port}) -> 56 | {ok, Socket} = gen_tcp:connect(Addr, Port, [binary, {active, false}]), 57 | {ok, Vat} = ecapnp_vat:start_link({gen_tcp, Socket}), 58 | spawn_link(fun () -> read_socket(Socket, Vat) end), 59 | ecapnp:import_capability( 60 | Vat, {text, <<"calculator">>}, 61 | calculator_capnp:'Calculator'()). 62 | 63 | read_socket(Sock, Vat) -> 64 | case gen_tcp:recv(Sock, 0) of 65 | {ok, Data} -> 66 | %% useful to check where/when ecapnp is waiting on response data 67 | %% erlang:send_after(2000, Vat, {receive_message, Data}), 68 | Vat ! {receive_data, Data}, 69 | read_socket(Sock, Vat); 70 | {error, closed} -> 71 | io:format("~ntcp socket closed~n"); 72 | {error, Reason} -> 73 | io:format("~ntcp socket error: ~p~n", [Reason]), 74 | halt(1) 75 | end. 76 | 77 | eval_literal(C) -> 78 | io:format("~nEvaluating a literal... "), 79 | Req = ecapnp:request(evaluate, C), 80 | Expression = ecapnp:init(expression, Req), 81 | ok = ecapnp:set({literal, 123}, Expression), 82 | EvalPromise = ecapnp:send(Req), 83 | Read = ecapnp:request(read, ecapnp:get(value, EvalPromise)), 84 | ReadPromise = ecapnp:send(Read), 85 | {ok, Response} = ecapnp:wait(ReadPromise), 86 | case ecapnp:get(value, Response) of 87 | 123.0 -> 88 | io:format("PASS"); 89 | Other -> 90 | io:format("Error: ~p", [Other]), 91 | error 92 | end. 93 | 94 | add_and_subtract(C0) -> 95 | io:format("~nUsing add and subtract... "), 96 | %% resolve the Calculator promise to use the imported cap, rather 97 | %% than the promise 98 | {ok, C} = ecapnp:wait(C0), 99 | 100 | Add = get_operator(add, C), 101 | Sub = get_operator(subtract, C), 102 | 103 | Req = ecapnp:request(evaluate, C), 104 | Expr = ecapnp:init(expression, Req), 105 | 106 | SubCall = ecapnp:set(call, Expr), 107 | ok = ecapnp:set(function, Sub, SubCall), 108 | [Sub1, Sub2] = ecapnp:set(params, 2, SubCall), 109 | ecapnp:set({literal, 67}, Sub2), 110 | 111 | AddCall = ecapnp:set(call, Sub1), 112 | ok = ecapnp:set(function, Add, AddCall), 113 | [Add1, Add2] = ecapnp:set(params, 2, AddCall), 114 | ok = ecapnp:set({literal, 123}, Add1), 115 | ok = ecapnp:set({literal, 45}, Add2), 116 | 117 | Promise = ecapnp:send(Req), 118 | Read = ecapnp:request(read, ecapnp:get(value, Promise)), 119 | ReadPromise = ecapnp:send(Read), 120 | {ok, Response} = ecapnp:wait(ReadPromise), 121 | case ecapnp:get(value, Response) of 122 | 101.0 -> 123 | io:format("PASS"); 124 | Other -> 125 | io:format("Error: ~p", [Other]), 126 | error 127 | end. 128 | 129 | pipelining(C) -> 130 | io:format("~nPipelining eval() calls... "), 131 | Add = get_operator(add, C), 132 | Mul = get_operator(multiply, C), 133 | 134 | Req = ecapnp:request(evaluate, C), 135 | Expr = ecapnp:init(expression, Req), 136 | [Mul1, Mul2] = call_expression(Mul, 2, Expr), 137 | 138 | ok = ecapnp:set({literal, 4}, Mul1), 139 | ok = ecapnp:set({literal, 6}, Mul2), 140 | 141 | MulPromise = ecapnp:send(Req), 142 | MulValue = ecapnp:get(value, MulPromise), 143 | 144 | Add3Req = ecapnp:request(evaluate, C), 145 | [Add3P1, Add3P2] = call_expression( 146 | Add, 2, ecapnp:init( 147 | expression, Add3Req)), 148 | ok = ecapnp:set({previousResult, MulValue}, Add3P1), 149 | ok = ecapnp:set({literal, 3}, Add3P2), 150 | Add3Promise = ecapnp:send(Add3Req), 151 | Add3Value = ecapnp:send( 152 | ecapnp:request( 153 | read, ecapnp:get(value, Add3Promise))), 154 | 155 | Add5Req = ecapnp:request(evaluate, C), 156 | [Add5P1, Add5P2] = call_expression( 157 | Add, 2, ecapnp:init( 158 | expression, Add5Req)), 159 | ok = ecapnp:set({previousResult, MulValue}, Add5P1), 160 | ok = ecapnp:set({literal, 5}, Add5P2), 161 | Add5Promise = ecapnp:send(Add5Req), 162 | Add5Value = ecapnp:send( 163 | ecapnp:request( 164 | read, ecapnp:get(value, Add5Promise))), 165 | 166 | case ecapnp:get(value, Add3Value) of 167 | 27.0 -> 168 | case ecapnp:get(value, Add5Value) of 169 | 29.0 -> 170 | io:format("PASS"); 171 | Other -> 172 | io:format("Add 5 value: ~p", [Other]), 173 | error 174 | end; 175 | Other -> 176 | io:format("Add 3 value: ~p", [Other]), 177 | error 178 | end. 179 | 180 | def_functions(C0) -> 181 | %% The calculator interface supports defining functions. Here we 182 | %% use it to define two functions and then make calls to them as 183 | %% follows: 184 | %% 185 | %% f(x, y) = x * 100 + y 186 | %% g(x) = f(x, x + 1) * 2; 187 | %% f(12, 34) 188 | %% g(21) 189 | %% 190 | %% Once again, the whole thing takes only one network round trip. 191 | 192 | io:format("~nDefining functions... "), 193 | 194 | %% this doesn't really wait, as the result is already there, but 195 | %% merely retreives the result from the local vat's question 196 | %% table. 197 | {ok, C} = ecapnp:wait(C0), 198 | 199 | %% get operators from server 200 | Add = get_operator(add, C), 201 | Mul = get_operator(multiply, C), 202 | 203 | %% define f 204 | F = (fun () -> 205 | Req = ecapnp:request(defFunction, C), 206 | ok = ecapnp:set(paramCount, 2, Req), 207 | Body = ecapnp:init(body, Req), 208 | [Add1, Add2] = call_expression(Add, 2, Body), 209 | [Mul1, Mul2] = call_expression(Mul, 2, Add1), 210 | ok = ecapnp:set({parameter, 0}, Mul1), %% x 211 | ok = ecapnp:set({literal, 100}, Mul2), %% * 100 212 | ok = ecapnp:set({parameter, 1}, Add2), %% + y 213 | Promise = ecapnp:send(Req), 214 | ecapnp:get(func, Promise) 215 | end)(), 216 | %% define g 217 | G = (fun () -> 218 | Req = ecapnp:request(defFunction, C), 219 | ok = ecapnp:set(paramCount, 1, Req), 220 | Body = ecapnp:init(body, Req), 221 | [Mul1, Mul2] = call_expression(Mul, 2, Body), 222 | [Call1, Call2] = call_expression(F, 2, Mul1), 223 | [Add1, Add2] = call_expression(Add, 2, Call2), 224 | ok = ecapnp:set({parameter, 0}, Call1), %% x 225 | ok = ecapnp:set({parameter, 0}, Add1), %% x 226 | ok = ecapnp:set({literal, 1}, Add2), %% + 1 227 | ok = ecapnp:set({literal, 2}, Mul2), %% * 2 228 | Promise = ecapnp:send(Req), 229 | ecapnp:get(func, Promise) 230 | end)(), 231 | %% f(12, 34) 232 | Freq = ecapnp:request(evaluate, C), 233 | Fexpr = ecapnp:init(expression, Freq), 234 | [F1, F2] = call_expression(F, 2, Fexpr), 235 | ok = ecapnp:set({literal, 12}, F1), 236 | ok = ecapnp:set({literal, 34}, F2), 237 | Fvalue = req_value(Freq), 238 | 239 | %% g(21) 240 | Greq = ecapnp:request(evaluate, C), 241 | Gexpr = ecapnp:init(expression, Greq), 242 | [G1] = call_expression(G, 1, Gexpr), 243 | ok = ecapnp:set({literal, 21}, G1), 244 | Gvalue = req_value(Greq), 245 | 246 | case ecapnp:get(value, Fvalue) of 247 | 1234.0 -> 248 | case ecapnp:get(value, Gvalue) of 249 | 4244.0 -> 250 | io:format("PASS"); 251 | Other -> 252 | io:format("g(21): ~p", [Other]), 253 | error 254 | end; 255 | Other -> 256 | io:format("f(12, 34): ~p", [Other]), 257 | error 258 | end. 259 | 260 | callback(C) -> 261 | io:format("~nUsing a callback... "), 262 | 263 | {ok, PowerFunction} = ecapnp_capability_sup:start_capability( 264 | ?MODULE, calculator_capnp:'Calculator'(['Function']), 265 | [{monitor, self()}]), 266 | 267 | Add = get_operator(add, C), 268 | 269 | %% expr: 2^(4+5) 270 | Req = ecapnp:request(evaluate, C), 271 | Exp = ecapnp:init(expression, Req), 272 | 273 | [Arg1, Arg2] = call_expression(PowerFunction, 2, Exp), 274 | [Add1, Add2] = call_expression(Add, 2, Arg2), 275 | ok = ecapnp:set({literal, 2}, Arg1), 276 | ok = ecapnp:set({literal, 4}, Add1), 277 | ok = ecapnp:set({literal, 5}, Add2), 278 | 279 | Value = req_value(Req), 280 | case ecapnp:get(value, Value) of 281 | 512.0 -> 282 | io:format("PASS"); 283 | Other -> 284 | io:format("Error: ~p", [Other]), 285 | error 286 | end. 287 | 288 | %%% utils 289 | 290 | get_operator(Op, C) -> 291 | Req = ecapnp:request(getOperator, C), 292 | ok = ecapnp:set(op, Op, Req), 293 | ecapnp:get(func, ecapnp:send(Req)). 294 | 295 | call_expression(Fun, ParamCount, Expr) -> 296 | Call = ecapnp:set(call, Expr), 297 | ok = ecapnp:set(function, Fun, Call), 298 | ecapnp:set(params, ParamCount, Call). 299 | 300 | req_value(Req) -> 301 | ecapnp:send( 302 | ecapnp:request( 303 | read, ecapnp:get(value, ecapnp:send(Req)) 304 | )). 305 | 306 | %% implement pow() function 307 | handle_call(['Calculator', 'Function'], 'call', Params, Results, State) -> 308 | [Arg1, Arg2] = ecapnp:get(params, Params), 309 | {ecapnp:set(value, math:pow(Arg1, Arg2), Results), State}. 310 | -------------------------------------------------------------------------------- /priv/samples/calculator-server.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2014, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2014, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Calculator server sample application. 20 | %% 21 | %% The operations are based on the calculator server sample from the 22 | %% capnproto distribution. 23 | 24 | -module('calculator-server'). 25 | 26 | -export([dbg/0, run/0, init/1, handle_call/5]). 27 | 28 | -include_lib ("ecapnp/include/ecapnp.hrl"). 29 | 30 | dbg() -> 31 | io:format( 32 | "dbg: ~p~n", 33 | [[dbg:tracer(), 34 | dbg:p(new, [c]), 35 | %dbg:tpl(?MODULE, []), 36 | dbg:tpl(ecapnp_capability, []) 37 | %dbg:tp(ecapnp_rpc, []), 38 | %dbg:tpl(ecapnp_vat, []), 39 | %dbg:tp(ecapnp, []) 40 | ]]). 41 | 42 | run() -> 43 | spawn_link( 44 | fun () -> 45 | ecapnp_promise_sup:start_link(), 46 | ecapnp_capability_sup:start_link(), 47 | CapRestorer = fun (ObjectId, Vat) -> 48 | case ecapnp_obj:to_text(ObjectId) of 49 | <<"calculator">> -> 50 | ecapnp_capability_sup:start_capability( 51 | ?MODULE, calculator_capnp:'Calculator'(), 52 | [{monitor, Vat}]) 53 | end 54 | end, 55 | listen({localhost, 55000}, CapRestorer) 56 | end). 57 | 58 | listen({_Addr, Port}, CapRestorer) -> 59 | {ok, Socket} = gen_tcp:listen(Port, [binary, {active, false}, {reuseaddr, true}]), 60 | accept(Socket, CapRestorer). 61 | 62 | accept(Socket, CapRestorer) -> 63 | case gen_tcp:accept(Socket) of 64 | {ok, Client} -> 65 | spawn_link(fun () -> accept(Socket, CapRestorer) end), 66 | {ok, Vat} = ecapnp_vat:start_link({gen_tcp, Client}, CapRestorer), 67 | %%sys:trace(Vat, true), 68 | read_socket(Client, Vat), 69 | accept(Socket, CapRestorer); 70 | {error, closed} -> 71 | io:format("~ntcp server socket closed~n"); 72 | %%halt(0); 73 | {error, Reason} -> 74 | io:format("~ntcp server socket error: ~p~n", [Reason]) 75 | %%halt(1) 76 | end. 77 | 78 | read_socket(Sock, Vat) -> 79 | case gen_tcp:recv(Sock, 0) of 80 | {ok, Data} -> 81 | Vat ! {receive_data, Data}, 82 | read_socket(Sock, Vat); 83 | {error, closed} -> 84 | io:format("~ntcp client socket closed~n"); 85 | {error, Reason} -> 86 | io:format("~ntcp client socket error: ~p~n", [Reason]) 87 | end. 88 | 89 | 90 | %% Capability callbacks 91 | 92 | %% since I'm lazy, all calculator capabilities call back to this 93 | %% module, so it's kind of messy here.. 94 | 95 | init({defFunction, Params}) -> 96 | ParamCount = ecapnp:get(paramCount, Params), 97 | Body = ecapnp:get(body, Params), 98 | {def, ParamCount, Body}; 99 | init(State) -> State. 100 | 101 | handle_call('Calculator', evaluate, Params, Results, State) -> 102 | Expr = ecapnp:get(expression, Params), 103 | Value = evaluate(Expr), 104 | {ecapnp:set(value, cap('Value', Value), Results), State}; 105 | handle_call('Calculator', getOperator, Params, Results, State) -> 106 | {ecapnp:set(func, cap('Function', {op, ecapnp:get(op, Params)}), Results), State}; 107 | handle_call(['Calculator', 'Value'], read, _Params, Results, Value) -> 108 | {ecapnp:set(value, ecapnp:wait(Value), Results), Value}; 109 | handle_call(['Calculator', 'Function'], call, Params, Results, {op, Operator}=State) -> 110 | [Op1, Op2] = ecapnp:get(params, Params), 111 | Value = 112 | case Operator of 113 | add -> Op1 + Op2; 114 | subtract -> Op1 - Op2; 115 | multiply -> Op1 * Op2; 116 | divide -> Op1 / Op2 117 | end, 118 | {ecapnp:set(value, Value, Results), State}; 119 | handle_call(['Calculator', 'Function'], call, Params, Results, {def, ParamCount, Body}=State) -> 120 | CallParams = ecapnp:get(params, Params), 121 | if length(CallParams) == ParamCount -> 122 | {ecapnp:set(value, ecapnp:wait(evaluate(Body, CallParams)), Results), State} 123 | end; 124 | handle_call('Calculator', defFunction, Params, Results, State) -> 125 | {ecapnp:set(func, cap('Function', {defFunction, Params}), Results), State}. 126 | 127 | 128 | %% Cap utils 129 | 130 | evaluate(Expr) -> evaluate(Expr, []). 131 | evaluate(Expr, EvalParams) -> 132 | {ok, Promise} = 133 | ecapnp_promise_sup:start_promise( 134 | [{fullfiller, 135 | fun () -> 136 | Result = 137 | case ecapnp:get(Expr) of 138 | {literal, Literal} -> Literal; 139 | {previousResult, Value} -> 140 | ReadReq = ecapnp:request(read, Value), 141 | ecapnp:get(value, ecapnp:send(ReadReq)); 142 | {parameter, Idx} -> 143 | lists:nth(Idx + 1, EvalParams); 144 | {call, Call} -> 145 | Func = ecapnp:get(function, Call), 146 | CallParams = [evaluate(E, EvalParams) 147 | || E <- ecapnp:get(params, Call)], 148 | CallReq = ecapnp:request(call, Func), 149 | ecapnp:set(params, [ecapnp:wait(P) || P <- CallParams], CallReq), 150 | ecapnp:get(value, ecapnp:send(CallReq)) 151 | end, 152 | {ok, Result} 153 | end} 154 | ]), 155 | #promise{ pid = Promise }. 156 | 157 | cap(Type, Init) -> 158 | {ok, Cap} = ecapnp_capability_sup:start_capability( 159 | ?MODULE, calculator_capnp:schema(['Calculator', Type]), 160 | [{monitor, self()}, {init, Init}]), 161 | Cap. 162 | -------------------------------------------------------------------------------- /priv/samples/calculator.capnp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013, Kenton Varda 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | @0x85150b117366d14b; 25 | 26 | interface Calculator { 27 | # A "simple" mathematical calculator, callable via RPC. 28 | # 29 | # But, to show off Cap'n Proto, we add some twists: 30 | # 31 | # - You can use the result from one call as the input to the next 32 | # without a network round trip. To accomplish this, evaluate() 33 | # returns a `Value` object wrapping the actual numeric value. 34 | # This object may be used in a subsequent expression. With 35 | # promise pipelining, the Value can actually be used before 36 | # the evaluate() call that creates it returns! 37 | # 38 | # - You can define new functions, and then call them. This again 39 | # shows off pipelining, but it also gives the client the 40 | # opportunity to define a function on the client side and have 41 | # the server call back to it. 42 | # 43 | # - The basic arithmetic operators are exposed as Functions, and 44 | # you have to call getOperator() to obtain them from the server. 45 | # This again demonstrates pipelining -- using getOperator() to 46 | # get each operator and then using them in evaluate() still 47 | # only takes one network round trip. 48 | 49 | evaluate @0 (expression :Expression) -> (value :Value); 50 | # Evaluate the given expression and return the result. The 51 | # result is returned wrapped in a Value interface so that you 52 | # may pass it back to the server in a pipelined request. To 53 | # actually get the numeric value, you must call read() on the 54 | # Value -- but again, this can be pipelined so that it incurs 55 | # no additional latency. 56 | 57 | struct Expression { 58 | # A numeric expression. 59 | 60 | union { 61 | literal @0 :Float64; 62 | # A literal numeric value. 63 | 64 | previousResult @1 :Value; 65 | # A value that was (or, will be) returned by a previous 66 | # evaluate(). 67 | 68 | parameter @2 :UInt32; 69 | # A parameter to the function (only valid in function bodies; 70 | # see defFunction). 71 | 72 | call :group { 73 | # Call a function on a list of parameters. 74 | function @3 :Function; 75 | params @4 :List(Expression); 76 | } 77 | } 78 | } 79 | 80 | interface Value { 81 | # Wraps a numeric value in an RPC object. This allows the value 82 | # to be used in subsequent evaluate() requests without the client 83 | # waiting for the evaluate() that returns the Value to finish. 84 | 85 | read @0 () -> (value :Float64); 86 | # Read back the raw numeric value. 87 | } 88 | 89 | defFunction @1 (paramCount :Int32, body :Expression) 90 | -> (func :Function); 91 | # Define a function that takes `paramCount` parameters and returns the 92 | # evaluation of `body` after substituting these parameters. 93 | 94 | interface Function { 95 | # An algebraic function. Can be called directly, or can be used inside 96 | # an Expression. 97 | # 98 | # A client can create a Function that runs on the server side using 99 | # `defFunction()` or `getOperator()`. Alternatively, a client can 100 | # implement a Function on the client side and the server will call back 101 | # to it. However, a function defined on the client side will require a 102 | # network round trip whenever the server needs to call it, whereas 103 | # functions defined on the server and then passed back to it are called 104 | # locally. 105 | 106 | call @0 (params :List(Float64)) -> (value :Float64); 107 | # Call the function on the given parameters. 108 | } 109 | 110 | getOperator @2 (op :Operator) -> (func :Function); 111 | # Get a Function representing an arithmetic operator, which can then be 112 | # used in Expressions. 113 | 114 | enum Operator { 115 | add @0; 116 | subtract @1; 117 | multiply @2; 118 | divide @3; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /priv/samples/run_samples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -exuo pipefail 4 | 5 | erlc +debug_info addressbook_capnp.erl calculator_capnp.erl \ 6 | calculator-client.erl calculator-server.erl 7 | 8 | ./addressbook.sh write | ./addressbook.sh read 9 | 10 | erl -eval "'calculator-server':run()" -noinput -noshell & 11 | sleep 0.1 12 | erl -eval "'calculator-client':run()" -noinput -noshell 13 | kill %+ 14 | -------------------------------------------------------------------------------- /src/c++.capnp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013, Kenton Varda 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | @0xbdf87d7bb8304e81; 25 | $namespace("capnp::annotations"); 26 | 27 | annotation namespace(file): Text; 28 | -------------------------------------------------------------------------------- /src/c++_capnp.erl: -------------------------------------------------------------------------------- 1 | -file("c++.capnp", 1). 2 | 3 | %% This file was generated 2016-08-18 19:40:29 UTC by ecapnp 0.2. 4 | %% http://github.com/kaos/ecapnp 5 | -module('c++_capnp'). 6 | 7 | -vsn(13688829037717245569). 8 | 9 | -export([schema/1, namespace/0, namespace/1, '13386661402618388268'/0, root/0, root/1, 10 | '13688829037717245569'/0]). 11 | 12 | -types([{13386661402618388268, namespace}, {13688829037717245569, root}]). 13 | 14 | -file("/Users/aadt/lib/erl/global/ecapnp/include/ecapnp_schema.hrl", 1). 15 | 16 | -ecapnp_schema_version(4). 17 | 18 | -record(schema_node, 19 | {module :: atom(), name :: ecapnp:type_name(), id = 0 :: ecapnp:type_id(), 20 | src = <<>> :: ecapnp:text(), kind = file :: ecapnp:schema_kind(), annotations = [] :: list(), 21 | nodes = [] :: ecapnp:schema_nodes(), scope = 0 :: ecapnp:type_id()}). 22 | 23 | -record(struct, 24 | {dsize = 0 :: ecapnp:word_count(), psize = 0 :: ecapnp:ptr_count(), 25 | esize = inlineComposite :: ecapnp:element_size(), 26 | union_field = none :: none | ecapnp:field_type(), fields = [] :: ecapnp:struct_fields()}). 27 | 28 | -record(enum, {values = [] :: ecapnp:enum_values()}). 29 | 30 | -record(interface, {extends = [] :: list(), methods = [] :: list()}). 31 | 32 | -record(const, {field}). 33 | 34 | -record(annotation, {type, targets = [] :: [atom()]}). 35 | 36 | -record(field, {id, name, kind, annotations = []}). 37 | 38 | -record(ptr, 39 | {type :: term(), idx = 0 :: ecapnp:ptr_index(), 40 | default = <<0:64/integer-little>> :: ecapnp:value()}). 41 | 42 | -record(data, {type :: term(), align = 0 :: ecapnp:bit_count(), default :: ecapnp:value()}). 43 | 44 | -record(group, {id = 0 :: ecapnp:type_id()}). 45 | 46 | -record(method, {id, name, paramType, resultType}). 47 | 48 | -file("c++.capnp", 1). 49 | 50 | schema(13386661402618388268) -> '13386661402618388268'(); 51 | schema(namespace) -> '13386661402618388268'(); 52 | schema([namespace]) -> '13386661402618388268'(); 53 | schema(13688829037717245569) -> '13688829037717245569'(); 54 | schema(root) -> '13688829037717245569'(); 55 | schema([root]) -> '13688829037717245569'(); 56 | schema(_) -> undefined. 57 | 58 | root() -> '13688829037717245569'(). 59 | 60 | root([]) -> '13688829037717245569'(). 61 | 62 | '13688829037717245569'() -> 63 | #schema_node{module = 'c++_capnp', name = root, id = 13688829037717245569, scope = 0, 64 | src = <<"c++.capnp">>, annotations = [{13386661402618388268, <<"capnp::annotations">>}], 65 | kind = file, 66 | nodes = 67 | [13386661402618388268]}. %% namespace 68 | 69 | namespace() -> '13386661402618388268'(). 70 | 71 | namespace([]) -> '13386661402618388268'(). 72 | 73 | '13386661402618388268'() -> 74 | #schema_node{module = 'c++_capnp', name = namespace, id = 13386661402618388268, 75 | scope = 13688829037717245569, src = <<"c++.capnp:namespace">>, 76 | kind = #annotation{type = #ptr{type = text, idx = 0, default = <<>>}, targets = [targetsFile]}}. -------------------------------------------------------------------------------- /src/ecapnp.app.src: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | {application, ecapnp, 3 | [{description, "Cap'n Proto library for Erlang"}, 4 | {vsn, "0.2"}, 5 | {registered, []}, 6 | {applications, [ kernel, stdlib, crypto ]}, 7 | {modules, [ecapnp_capability, ecapnpc, ecapnp_val, ecapnp_obj, ecapnp_schema, ecapnp_compiler, ecapnp_get, ecapnp_ref, ecapnp_serialize, ecapnp, ecapnp_message, ecapnp_data, ecapnp_set, 'c++_capnp', schema_capnp]}, 8 | {env, []} 9 | ]}. 10 | -------------------------------------------------------------------------------- /src/ecapnp.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc The highlevel Cap'n Proto API. 20 | %% 21 | %% This module doesn't implement any functionality, it is simply 22 | %% exposing the highlevel functions from the other modules. 23 | 24 | -module(ecapnp). 25 | -author("Andreas Stenius "). 26 | -include("ecapnp_records.hrl"). 27 | 28 | %% =================================================================== 29 | %% Public API 30 | %% =================================================================== 31 | 32 | -export([get_root/2, get_root/3, get/1, get/2, set_root/1, set_root/2, 33 | set/2, set/3, init/2, init/3, const/2, request/2, send/1, 34 | wait/1, wait/2, import_capability/3, dump/1]). 35 | 36 | %% =================================================================== 37 | %% Public Types 38 | %% =================================================================== 39 | 40 | -export_type([annotation/0, bit_count/0, const/0, data/0, 41 | element_size/0, enum/0, enum_values/0, far_ref/0, 42 | field_type/0, field_value/0, group/0, interface/0, 43 | list_ref/0, message/0, object/0, ptr/0, ptr_count/0, 44 | ptr_index/0, ref/0, ref_kind/0, schema/0, schema_kind/0, 45 | schema_node/0, schema_nodes/0, schema_type/0, 46 | segment_id/0, segment_offset/0, segment_pos/0, struct/0, 47 | struct_fields/0, struct_ref/0, text/0, type_id/0, 48 | type_name/0, value/0, value_type/0, word_count/0 ]). 49 | 50 | 51 | -type annotation() :: #annotation{}. 52 | %% Describes an annotation type. 53 | 54 | -type bit_count() :: non_neg_integer(). 55 | -type const() :: #const{}. 56 | %% A schema const value. 57 | 58 | -type data() :: #data{}. 59 | %% Describes a data field within a struct. 60 | 61 | -type element_size() :: empty | bit | byte | twoBytes | fourBytes 62 | | eightBytes | pointer | inlineComposite. 63 | %% The data size for the values in a list. 64 | %% 65 | %% In case of `inlineComposite' the list data is prefixed with a `tag' 66 | %% word describing the layout of the element data. The `tag' is in the 67 | %% same format as a struct ref, except the `offset' field indicates 68 | %% the number of elements in the list. 69 | 70 | -type enum() :: #enum{}. 71 | %% Describes the schema for a enum type. 72 | 73 | -type enum_values() :: list({integer(), atom()}). 74 | %% A list of tuples, pairing the enumerants ordinal value with its name. 75 | 76 | -type far_ref() :: #far_ref{}. 77 | 78 | -type field_name() :: atom(). 79 | -type field_type() :: data() | ptr() | group(). 80 | -type field_value() :: any(). 81 | %% @todo improve type spec. 82 | 83 | -type group() :: #group{}. 84 | %% Declares the type of group for a struct field. 85 | 86 | -type interface() :: #interface{}. 87 | %% Describes the schema for a interface type. 88 | 89 | -type list_ref() :: #list_ref{}. 90 | %% The reference is a pointer to a list. 91 | 92 | -type message() :: list(binary()). 93 | %% Holds all the segments in a Cap'n Proto message. 94 | %% 95 | %% This is the raw segment data, no segment headers or other 96 | %% information is present. 97 | 98 | -type object() :: #object{}. 99 | %% A reference paired with schema type information. 100 | 101 | %% -type object_field() :: #data{} | #ptr{}. 102 | %% -type object_fields() :: list({field_name(), object_field()}). 103 | 104 | -type ptr() :: #ptr{}. 105 | %% Describes a pointer field within a struct. 106 | 107 | -type ptr_count() :: non_neg_integer(). 108 | -type ptr_index() :: non_neg_integer(). 109 | -type ref() :: #ref{}. 110 | %% A reference instance. 111 | %% 112 | %% If `pos' is `-1', then the instance has no allocated space in the 113 | %% message, but may still point to a valid location within a 114 | %% segment. Note, the value of `pos' should still be used even when `pos' is `-1'. 115 | %% 116 | %% To get the position of the data the reference points to: 117 | %% ``DataPos = R#ref.pos + R#ref.offset + 1.'' 118 | 119 | -type ref_kind() :: null | struct_ref() | list_ref() | far_ref(). 120 | 121 | -type schema() :: #schema_node{ kind::file }. 122 | %% The top-level schema node (for the .capnp-file). 123 | 124 | -type schema_kind() :: file | struct() 125 | | enum() | interface() 126 | | const() | annotation(). 127 | 128 | 129 | -type schema_node() :: #schema_node{}. 130 | %% Each schema node within a file. 131 | 132 | -type schema_nodes() :: list(schema_node()). 133 | -type schema_type() :: type_name() | type_id(). 134 | -type segment_id() :: integer(). 135 | -type segment_offset() :: integer(). 136 | -type segment_pos() :: -1 | non_neg_integer(). 137 | -type struct() :: #struct{}. 138 | %% Describes the schema for a struct type. 139 | %% 140 | %%
141 | %%
`dsize'
142 | %%
The size of the struct's data section, in words.
143 | %%
`psize'
144 | %%
The number of pointers in the struct.
145 | %%
`esize'
146 | %%
The list {@link element_size(). element size} for the struct.
147 | %%
`union_field'
148 | %%
Describes the unnamed union in the struct, or `none' if there 149 | %% is no unnamed union in the struct.
150 | %%
`fields'
151 | %%
Describes all the fields in the struct.
152 | %%
153 | 154 | -type struct_fields() :: list(field_type()). 155 | 156 | -type struct_ref() :: #struct_ref{}. 157 | %% The reference is a pointer to a struct. 158 | %% 159 | %% `dsize' and `psize' specifies the data size and pointer count 160 | %% actually allocated in the message. This may not match those 161 | %% expected by the schema. Any data in the message that is outside of 162 | %% what the schema expects will be unreachable by application code, 163 | %% while any reads outside of the allocated data will result in a 164 | %% default value back. Thus the missmatch is transparent in 165 | %% application code. 166 | 167 | -type text() :: binary(). 168 | %% The required NULL byte suffix is automatically taken care of by 169 | %% {@link ecapnp_ref} for `text' values. 170 | 171 | -type type_id() :: integer(). 172 | -type type_name() :: atom(). 173 | -type value() :: number() | boolean() | list(value()) | binary() | null. 174 | -type value_type() :: void | bool | float32 | float63 175 | | uint8 | uint16 | uint32 | uint64 176 | | int8 | int16 | int32 | int64. 177 | -type word_count() :: non_neg_integer(). 178 | %% Note that in Cap'n Proto, a word is 8 bytes (64 bits). 179 | 180 | 181 | %% =================================================================== 182 | %% API Implementation 183 | %% =================================================================== 184 | 185 | -spec get_root(type_name(), schema(), message()) -> {ok, Root::object()}. 186 | %% @doc Get the root object for a message. 187 | %% The message should already have been unpacked and parsed. 188 | %% @see ecapnp_get:root/3 189 | %% @see ecapnp_serialize:unpack/1 190 | %% @see ecapnp_message:read/1 191 | get_root(Type, Schema, Segments) -> 192 | ecapnp_get:root(Type, Schema, Segments). 193 | 194 | get_root(Schema, Segments) -> 195 | ecapnp_get:root(Schema, Segments). 196 | 197 | -spec set_root(type_name(), schema()) -> {ok, Root::object()}. 198 | %% @doc Set the root object for a new message. 199 | %% This creates a new empty message, ready to be filled with data. 200 | %% 201 | %% To get the segment data out, call {@link ecapnp_message:write/1}. 202 | %% @see ecapnp_set:root/2 203 | set_root(Type, Schema) -> 204 | ecapnp_set:root(Type, Schema). 205 | 206 | set_root(Schema) -> 207 | ecapnp_set:root(Schema). 208 | 209 | -spec get(object()) -> {field_name(), field_value()} | field_name(). 210 | %% @doc Read the unnamed union value of object. 211 | %% The result value is either a tuple, describing which union tag it 212 | %% is, and its associated value, or just the tag name, if the value is 213 | %% void. 214 | %% @see ecapnp_get:union/1 215 | get(Object) -> 216 | ecapnp_get:union(Object). 217 | 218 | -spec get(field_name(), object()) -> field_value(). 219 | %% @doc Read the field value of object. 220 | %% @see ecapnp_get:field/2 221 | get(Field, Object) -> 222 | ecapnp_get:field(Field, Object). 223 | 224 | -spec set({field_name(), field_value()}|field_name(), object()) -> ok. 225 | %% @doc Write union value to the unnamed union of object. 226 | %% @see ecapnp_set:union/2 227 | set(Values, Object) when is_list(Values) -> 228 | ecapnp_set:fields(Values, Object); 229 | set(Value, Object) -> 230 | ecapnp_set:union(Value, Object). 231 | 232 | -spec set(field_name(), field_value(), object()) -> ok. 233 | %% @doc Write value to a field of object. 234 | %% @see ecapnp_set:field/3 235 | set(Field, Value, Object) -> 236 | ecapnp_set:field(Field, Value, Object). 237 | 238 | init(Value, Object) -> 239 | ecapnp_obj:init(ecapnp_set:field(Value, Object)). 240 | 241 | init(Field, Type, Object) -> 242 | ecapnp_obj:init( 243 | ecapnp_obj:to_struct( 244 | Type, ecapnp_set:field(Field, Object))). 245 | 246 | -spec const(type_name()|type_id(), schema()) -> value(). 247 | %% @doc Get const value from schema. 248 | const(Name, Schema) -> 249 | case ecapnp_schema:lookup(Name, Schema) of 250 | #schema_node{ kind=#const{ field=Field } } -> 251 | case Field of 252 | #data{ type=Type, default=Value } -> 253 | ecapnp_val:get(Type, Value); 254 | #ptr{}=Ptr -> 255 | ecapnp_get:ref_data(Ptr, #ref{ data=not_yet_implemented }) 256 | end 257 | end. 258 | 259 | import_capability(Vat, ObjectId, Schema) -> 260 | ecapnp_rpc:import_capability(Vat, ObjectId, Schema). 261 | 262 | request(Name, Target) -> 263 | ecapnp_rpc:request(Name, Target). 264 | 265 | send(Req) -> 266 | ecapnp_rpc:send(Req). 267 | 268 | wait(Promise) -> 269 | ecapnp_rpc:wait(Promise, infinity). 270 | 271 | wait(Promise, Time) -> 272 | ecapnp_rpc:wait(Promise, Time). 273 | 274 | dump(Object) when is_record(Object, object) -> ecapnp_obj:dump(Object); 275 | dump(Request) when is_record(Request, rpc_call) -> ecapnp_rpc:dump(Request); 276 | dump(Schema) when is_record(Schema, schema_node) -> ecapnp_schema:dump(Schema); 277 | dump(Cap) when is_record(Cap, interface_ref) -> dump_cap(Cap); 278 | dump(Tuple) when is_tuple(Tuple) -> ["{", dump_list(tuple_to_list(Tuple)), "}"]; 279 | dump(List) when is_list(List) -> ["[", dump_list(List), "]"]; 280 | dump(Other) -> io_lib:format("~W", [Other, 5]). 281 | 282 | %% =================================================================== 283 | %% Internal functions 284 | %% =================================================================== 285 | 286 | dump_list(Xs) -> 287 | string:join([dump(X) || X <- Xs], ", "). 288 | 289 | dump_cap(#interface_ref{ owner = Owner, id = Id }) -> 290 | io_lib:format("cap(~W, ~W)", [Owner, 5, Id, 5]). 291 | -------------------------------------------------------------------------------- /src/ecapnp_capability.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Capability support. 20 | %% 21 | %% Everything capability. 22 | 23 | -module(ecapnp_capability). 24 | -author("Andreas Stenius "). 25 | -behaviour(gen_server). 26 | 27 | -export([start/1, start_link/1, stop/1, send/2]). 28 | 29 | %% gen_server callbacks 30 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 31 | terminate/2, code_change/3]). 32 | 33 | %%-define(ECAPNP_DEBUG,1). %% un-comment to enable debug messages, or 34 | %%-define(ECAPNP_TRACE,1). %% un-comment to enable debug messages and trace the gen_server 35 | 36 | -include("ecapnp.hrl"). 37 | 38 | -record(state, { impl, cap_state, interfaces = [] }). 39 | 40 | 41 | %% =================================================================== 42 | %% API functions 43 | %% =================================================================== 44 | 45 | %%-------------------------------------------------------------------- 46 | %% @doc 47 | %% Starts server for a capability described by the schema interface node. 48 | %% 49 | %% @spec start(list()) -> {ok, Pid} | ignore | {error, Error} 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | start(Args) -> 53 | gen_server:start(?MODULE, Args, ?ECAPNP_GEN_OPTS). 54 | 55 | start_link(Args) -> 56 | gen_server:start_link(?MODULE, Args, ?ECAPNP_GEN_OPTS). 57 | 58 | stop(Cap) -> 59 | gen_server:call(Cap, stop). 60 | 61 | send(Cap, #rpc_call{ 62 | interface = IntfId, method = MethId, 63 | params = Params, results = Results }) -> 64 | {ok, Promise} = ecapnp_promise_sup:start_promise(), 65 | ok = gen_server:cast(Cap, {call, Promise, IntfId, MethId, {Params, Results}}), 66 | Promise. 67 | 68 | 69 | %%-------------------------------------------------------------------- 70 | %% @private 71 | %% @doc 72 | %% Initializes the server 73 | %% 74 | %% @spec init(Args) -> {ok, State} | 75 | %% {ok, State, Timeout} | 76 | %% ignore | 77 | %% {stop, Reason} 78 | %% @end 79 | %%-------------------------------------------------------------------- 80 | init([Mod, Interfaces|Opts]) -> 81 | State0 = #state{ impl = Mod, interfaces = list_interfaces(Interfaces) }, 82 | State = init_opts(Opts, State0), 83 | ?DBG("++ New capability: ~p, opts: ~p", [State, Opts]), 84 | {ok, State}. 85 | 86 | init_opts([], State) -> State; 87 | init_opts([{init, CapArgs}|Opts], #state{ impl = Mod }=State) -> 88 | init_opts(Opts, State#state{ cap_state = Mod:init(CapArgs) }); 89 | init_opts([{monitor, Pid}|Opts], State) -> 90 | monitor(process, Pid), 91 | init_opts(Opts, State). 92 | 93 | %%-------------------------------------------------------------------- 94 | %% @private 95 | %% @doc 96 | %% Handling call messages 97 | %% 98 | %% @spec handle_call(Request, From, State) -> 99 | %% {reply, Reply, State} | 100 | %% {reply, Reply, State, Timeout} | 101 | %% {noreply, State} | 102 | %% {noreply, State, Timeout} | 103 | %% {stop, Reason, Reply, State} | 104 | %% {stop, Reason, State} 105 | %% @end 106 | %%-------------------------------------------------------------------- 107 | handle_call(stop, _From, State) -> 108 | {stop, normal, ok, State}. 109 | 110 | %%-------------------------------------------------------------------- 111 | %% @private 112 | %% @doc 113 | %% Handling cast messages 114 | %% 115 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 116 | %% {noreply, State, Timeout} | 117 | %% {stop, Reason, State} 118 | %% @end 119 | %%-------------------------------------------------------------------- 120 | handle_cast({call, Promise, Intf, Meth, Args}, State) -> 121 | {Result, State1} = dispatch_call(Intf, Meth, Args, State), 122 | ok = ecapnp_promise:fullfill(Promise, Result), 123 | {noreply, State1}; 124 | handle_cast(_Msg, State) -> 125 | {noreply, State}. 126 | 127 | %%-------------------------------------------------------------------- 128 | %% @private 129 | %% @doc 130 | %% Handling all non call/cast messages 131 | %% 132 | %% @spec handle_info(Info, State) -> {noreply, State} | 133 | %% {noreply, State, Timeout} | 134 | %% {stop, Reason, State} 135 | %% @end 136 | %%-------------------------------------------------------------------- 137 | handle_info({'DOWN', _Ref, process, _Pid, _Info}, State) -> 138 | {stop, normal, State}; 139 | handle_info(_Info, State) -> 140 | {noreply, State}. 141 | 142 | %%-------------------------------------------------------------------- 143 | %% @private 144 | %% @doc 145 | %% This function is called by a gen_server when it is about to 146 | %% terminate. It should be the opposite of Module:init/1 and do any 147 | %% necessary cleaning up. When it returns, the gen_server terminates 148 | %% with Reason. The return value is ignored. 149 | %% 150 | %% @spec terminate(Reason, State) -> void() 151 | %% @end 152 | %%-------------------------------------------------------------------- 153 | terminate(_Reason, _State) -> 154 | ok. 155 | 156 | %%-------------------------------------------------------------------- 157 | %% @private 158 | %% @doc 159 | %% Convert process state when code is changed 160 | %% 161 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 162 | %% @end 163 | %%-------------------------------------------------------------------- 164 | code_change(_OldVsn, State, _Extra) -> 165 | {ok, State}. 166 | 167 | %%%=================================================================== 168 | %%% Internal functions 169 | %%%=================================================================== 170 | 171 | dispatch_call(Intf, Meth, Args, State) -> 172 | case find_interface(Intf, State) of 173 | false -> {{error, {unsupported_interface, Intf}}, State}; 174 | Interface -> 175 | case find_method(Meth, Interface) of 176 | false -> {{error, {unknown_method, Intf, Meth}}, State}; 177 | Method -> 178 | do_dispatch(Interface, Method, Args, State) 179 | end 180 | end. 181 | 182 | do_dispatch(#schema_node{ name = InterfaceName }=Interface, 183 | #method{ name = MethodName, 184 | paramType = ParamType, 185 | resultType = ResultType }, 186 | Args, #state{ impl = Mod, cap_state = CapState0 }=State) -> 187 | 188 | ParamSchema = ecapnp_schema:get(ParamType, Interface), 189 | ResultSchema = ecapnp_schema:get(ResultType, Interface), 190 | 191 | {Params, Results} = 192 | case Args of 193 | {Ps, undefined} -> 194 | {ok, Rs} = ecapnp_set:root(ResultSchema), 195 | {Ps, Rs}; 196 | {Ps, Payload} -> 197 | Content = ecapnp:init(content, ResultSchema, Payload), 198 | {Ps, Content} 199 | end, 200 | 201 | ?DBG("!! DISPATCH ~p:handle_call\n" 202 | "\t~p::~p(~s, ~s)\n" 203 | "\twith state ~s", 204 | [Mod, InterfaceName, MethodName, 205 | ?DUMP(Params), ?DUMP(Results), ?DUMP(CapState0)]), 206 | 207 | {ok, CapState1} = apply(Mod, handle_call, 208 | [InterfaceName, MethodName, 209 | ecapnp_obj:to_struct(ParamSchema, Params), 210 | Results, CapState0]), 211 | 212 | ?DBG("!! Dispath Results: ~s, new state: ~s", [?DUMP(Results), ?DUMP(CapState1)]), 213 | {{ok, Results}, State#state{ cap_state = CapState1 }}. 214 | 215 | find_interface(IntfId, #state{ interfaces = Ns }) -> 216 | lists:keyfind(IntfId, #schema_node.id, Ns). 217 | 218 | find_method(MethId, #schema_node{ kind = #interface{ methods = Ms } }) -> 219 | lists:keyfind(MethId, #method.id, Ms). 220 | 221 | list_interfaces(Interfaces) -> 222 | list_interfaces(Interfaces, []). 223 | 224 | list_interfaces([], Acc) -> Acc; 225 | list_interfaces([N|Ns], Acc) -> 226 | #schema_node{ kind = #interface{ extends = Es } } = N, 227 | list_interfaces( 228 | Ns, list_interfaces( 229 | [ecapnp_schema:get(E, N) || E <- Es], 230 | [N|Acc])). 231 | -------------------------------------------------------------------------------- /src/ecapnp_capability_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Andreas Stenius 3 | %%% @copyright (C) 2014, Andreas Stenius 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 12 May 2014 by Andreas Stenius 8 | %%%------------------------------------------------------------------- 9 | -module(ecapnp_capability_sup). 10 | 11 | -behaviour(supervisor). 12 | 13 | %% API 14 | -export([start_link/0, start_capability/2, start_capability/3]). 15 | 16 | %% Supervisor callbacks 17 | -export([init/1]). 18 | 19 | -define(SERVER, ?MODULE). 20 | 21 | -include("ecapnp.hrl"). 22 | 23 | %%%=================================================================== 24 | %%% API functions 25 | %%%=================================================================== 26 | 27 | %%-------------------------------------------------------------------- 28 | %% @doc 29 | %% Starts the supervisor 30 | %% 31 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 32 | %% @end 33 | %%-------------------------------------------------------------------- 34 | start_link() -> 35 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 36 | 37 | %%-------------------------------------------------------------------- 38 | start_capability(Module, Interfaces) -> 39 | start_capability(Module, Interfaces, []). 40 | 41 | %%-------------------------------------------------------------------- 42 | start_capability(Module, Interfaces, Opts) 43 | when is_list(Interfaces), is_list(Opts) -> 44 | StartArgs = [Module, Interfaces | Opts], 45 | case supervisor:start_child(?SERVER, [StartArgs]) of 46 | {ok, Pid} -> 47 | Cap = #interface_ref{ owner = {ecapnp_capability, Pid} }, 48 | {ok, #object{ ref = #ref{ kind = Cap }, 49 | schema = Interfaces 50 | }}; 51 | Err -> Err 52 | end; 53 | start_capability(Module, Interface, Opts) when is_list(Opts) -> 54 | start_capability(Module, [Interface], Opts). 55 | 56 | 57 | %%%=================================================================== 58 | %%% Supervisor callbacks 59 | %%%=================================================================== 60 | 61 | %%-------------------------------------------------------------------- 62 | %% @private 63 | %% @doc 64 | %% Whenever a supervisor is started using supervisor:start_link/[2,3], 65 | %% this function is called by the new process to find out about 66 | %% restart strategy, maximum restart frequency and child 67 | %% specifications. 68 | %% 69 | %% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} | 70 | %% ignore | 71 | %% {error, Reason} 72 | %% @end 73 | %%-------------------------------------------------------------------- 74 | init([]) -> 75 | RestartStrategy = simple_one_for_one, 76 | MaxRestarts = 10, 77 | MaxSecondsBetweenRestarts = 3600, 78 | 79 | SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, 80 | 81 | Restart = transient, 82 | Shutdown = 2000, 83 | Type = worker, 84 | 85 | AChild = {capability, {ecapnp_capability, start_link, []}, 86 | Restart, Shutdown, Type, [ecapnp_capability]}, 87 | 88 | {ok, {SupFlags, [AChild]}}. 89 | 90 | %%%=================================================================== 91 | %%% Internal functions 92 | %%%=================================================================== 93 | -------------------------------------------------------------------------------- /src/ecapnp_get.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Read support. 20 | %% 21 | %% Everything for reading data out of a message. 22 | 23 | -module(ecapnp_get). 24 | -author("Andreas Stenius "). 25 | 26 | -export([root/2, root/3, field/2, union/1, ref_data/2, ref_data/3]). 27 | 28 | -include("ecapnp.hrl"). 29 | 30 | 31 | %% =================================================================== 32 | %% API functions 33 | %% =================================================================== 34 | 35 | %% @doc Get the root object for a message. 36 | %% @see ecapnp:get_root/3 37 | -spec root(schema_node(), message()) -> {ok, Root::object()}. 38 | root(Node, Segments) -> 39 | {ok, ecapnp_obj:from_data(Segments, Node)}. 40 | 41 | -spec root(type_name(), schema(), message()) -> {ok, Root::object()}. 42 | root(Type, Schema, Segments) -> 43 | root(Schema:schema(Type), Segments). 44 | 45 | %% @doc Read the field value of object. 46 | %% @see ecapnp:get/2 47 | -spec field(field_name(), object()) -> field_value(). 48 | %%field(FieldName, #object{ ref=Ref }=Object) 49 | field(FieldName, #rpc_call{ params = Object }) -> field(FieldName, Object); 50 | field(FieldName, #promise{ schema = Schema }=Promise) -> 51 | case ecapnp_schema:find_field(FieldName, Schema) of 52 | #field{ id = Id, kind = #ptr{ type = {struct, Type} } } -> 53 | transform_promise(Promise, Type, {getPointerField, Id}); 54 | #field{ kind = Kind } -> 55 | case ecapnp:wait(Promise, 5000) of 56 | {ok, Res} -> read_field(Kind, Res); 57 | timeout -> throw(timeout) 58 | end 59 | end; 60 | field(FieldName, Object) 61 | when is_record(Object, object) -> 62 | case ecapnp_obj:field(FieldName, Object) of 63 | false -> throw({unknown_field, FieldName, Object}); 64 | Field -> read_field(Field, Object) 65 | end. 66 | 67 | %% @doc Read the unnamed union value of object. 68 | %% @see ecapnp:get/1 69 | -spec union(object()) -> {field_name(), field_value()} | field_name(). 70 | union(#rpc_call{ params = Object }) -> union(Object); 71 | union(#object{ schema=#schema_node{ 72 | kind=#struct{ union_field=Union } 73 | }}=Object) -> 74 | if Union /= none -> read_field(Union, Object); 75 | true -> throw({no_unnamed_union_in_object, Object}) 76 | end. 77 | 78 | %% @doc internal function not intended for client code. 79 | ref_data(Ptr, Obj) -> 80 | read_ptr(Ptr, Obj). 81 | 82 | %% @doc Read data of object reference as type. 83 | %% This is a Low-level function. 84 | ref_data(Type, Obj, Default) -> 85 | read_ptr(#ptr{ type=Type, default=Default }, Obj). 86 | 87 | 88 | %% =================================================================== 89 | %% internal functions 90 | %% =================================================================== 91 | 92 | transform_promise(#promise{ transform = Ts, schema = S } = P, Type, T) -> 93 | P#promise{ transform = [T|Ts], schema = ecapnp_schema:get(Type, S) }. 94 | 95 | read_field(#field{ kind = void }, _) -> void; 96 | read_field(#field{ kind=Kind }, Object) -> read_field(Kind, Object); 97 | read_field(#data{ type=Type, align=Align, default=Default }=D, Object) -> 98 | case Type of 99 | {enum, EnumType} -> 100 | Tag = read_field(D#data{ type=uint16 }, Object), 101 | get_enum_value(EnumType, Tag, Object); 102 | {union, Fields} -> 103 | Tag = read_field(D#data{ type=uint16 }, Object), 104 | case lists:keyfind(Tag, #field.id, Fields) of 105 | #field{ name=FieldName }=Field -> 106 | {FieldName, read_field(Field, Object)} 107 | end; 108 | Type -> 109 | case ecapnp_val:size(Type) of 110 | 0 -> void; 111 | Size -> 112 | ecapnp_val:get( 113 | Type, ecapnp_ref:read_struct_data( 114 | Align, Size, Object#object.ref), 115 | Default) 116 | end 117 | end; 118 | read_field(#ptr{ idx=Idx }=Ptr, #object{ ref = Ref }=Object) -> 119 | Obj = ecapnp_obj:init( 120 | ecapnp_ref:read_struct_ptr(Idx, Ref), 121 | Object), 122 | read_ptr(Ptr, Obj); 123 | read_field(#group{ id=Type }, Object) -> 124 | case ecapnp_obj:to_struct(Type, Object) of 125 | #object{ 126 | schema = #schema_node{ 127 | kind = #struct{ 128 | union_field = Union, 129 | fields = [] 130 | }}}=Group 131 | when Union =/= none -> 132 | %% when we read a named union, we want to get the union 133 | %% value directly, but a named union is represented as a 134 | %% unnamed union wrapped in a group 135 | read_field(Union, Group); 136 | Group -> 137 | Group 138 | end. 139 | 140 | read_ptr(#ptr{ type=Type, default=Default }, #object{ ref = Ref }=Obj) -> 141 | case Type of 142 | text -> ecapnp_ref:read_text(Ref, Default); 143 | data -> ecapnp_ref:read_data(Ref, Default); 144 | object -> read_obj(object, Obj, Default); 145 | {struct, StructType} -> read_obj(StructType, Obj, Default); 146 | {interface, InterfaceType} -> read_obj(InterfaceType, Obj, Default); 147 | {list, ElementType} -> read_list(ElementType, Default, Obj) 148 | end. 149 | 150 | read_obj(Type, #object{ ref = #ref{ kind=null } }=Obj, Default) -> 151 | if is_binary(Default) -> 152 | ecapnp_obj:from_data(Default, Type, Obj) 153 | end; 154 | read_obj(Type, Obj, _) -> 155 | ecapnp_obj:to_struct(Type, Obj). 156 | 157 | read_list({struct, StructType}, Default, #object{ ref = Ref }=Obj) -> 158 | Schema = ecapnp_schema:get(StructType, Obj), 159 | case ecapnp_ref:read_list_refs( 160 | Ref, ecapnp_schema:get_ref_kind(Schema), undefined) 161 | of 162 | [] -> []; 163 | undefined -> 164 | if is_binary(Default) -> 165 | ecapnp_obj:from_data(Default, {list, {struct, StructType}}, Obj); 166 | true -> Default 167 | end; 168 | Refs when is_record(hd(Refs), ref) -> 169 | [read_ptr(#ptr{ type = {struct, StructType} }, 170 | ecapnp_obj:init(R, Obj)) 171 | || R <- Refs] 172 | end; 173 | read_list(ElementType, Default, #object{ ref = Ref }=Obj) -> 174 | case ecapnp_ref:read_list(Ref, undefined) of 175 | undefined -> 176 | if is_binary(Default) -> 177 | ecapnp_obj:from_data( 178 | Default, {list, ElementType}, Obj); 179 | true -> 180 | Default 181 | end; 182 | Refs when is_record(hd(Refs), ref) -> 183 | [read_ptr( 184 | #ptr{ type=ElementType }, 185 | ecapnp_obj:init(R, Obj)) 186 | || R <- Refs]; 187 | Values -> 188 | case ElementType of 189 | {enum, EnumType} -> 190 | [get_enum_value( 191 | EnumType, 192 | ecapnp_val:get(uint16, Data), 193 | Obj) 194 | || Data <- Values]; 195 | _ when is_atom(ElementType) -> 196 | [ecapnp_val:get(ElementType, Data) 197 | || Data <- Values] 198 | end 199 | end. 200 | 201 | get_enum_value(Type, Tag, Obj) -> 202 | #schema_node{ kind=#enum{ values=Values } } 203 | = ecapnp_schema:lookup(Type, Obj), 204 | case lists:keyfind(Tag, 1, Values) of 205 | {Tag, Value} -> Value; 206 | false -> Tag 207 | end. 208 | -------------------------------------------------------------------------------- /src/ecapnp_message.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Message framing. 20 | %% 21 | %% Takes care of reading a framed message, getting the segments data 22 | %% out, and writing the message header with the segment table. 23 | 24 | -module(ecapnp_message). 25 | -author("Andreas Stenius "). 26 | 27 | -export([read/1, read/2, write/1, read_file/1]). 28 | 29 | -include("ecapnp.hrl"). 30 | 31 | -type continuation() :: term(). 32 | -type read_result() :: {ok, message(), Rest::binary()} | {cont, continuation()}. 33 | 34 | %% =================================================================== 35 | %% API functions 36 | %% =================================================================== 37 | 38 | %% @doc Parse segment table in the (unpacked, but otherwise framed) 39 | %% message. 40 | -spec read(binary()) -> read_result(). 41 | read(Data) when is_binary(Data) -> 42 | read_message(Data). 43 | 44 | -spec read(binary(), continuation() | undefined) -> read_result(). 45 | read(Data, <<>>) when is_binary(Data) -> read_message(Data); 46 | read(Data, {SegSizes, Rest, Segments}) when is_binary(Data) -> 47 | read_message(SegSizes, <>, Segments); 48 | read(Data, {SegCount, Rest}) when is_binary(Data) -> 49 | read_message(SegCount, <>); 50 | read(Data, Rest) when is_binary(Data), is_binary(Rest) -> 51 | read_message(<>). 52 | 53 | 54 | %% @doc Write segment table for message and return it along with the 55 | %% segments data. 56 | %% 57 | %% Any non-default object may be passed to this function. 58 | -spec write(object()) -> binary(). 59 | write(#object{ ref=#ref{ data=#builder{ pid=Data } } }) -> 60 | write_message(ecapnp_data:get_segments(Data)); 61 | write(#object{ ref=#ref{ data=#reader{ data = Data } } }) -> 62 | Segments = if is_binary(Data) -> [Data]; 63 | true -> Data 64 | end, 65 | write_message(Segments). 66 | 67 | %% @doc Read binary message from file. 68 | %% @see read/1 69 | -spec read_file(string()) -> read_result(). 70 | read_file(Filename) -> 71 | {ok, Data} = file:read_file(Filename), 72 | read_message(Data). 73 | 74 | 75 | %% =================================================================== 76 | %% internal functions 77 | %% =================================================================== 78 | 79 | read_message(<>) -> 80 | read_message({SegCount + 1, SegCount rem 2}, Data); 81 | read_message(Data) -> {cont, Data}. 82 | 83 | read_message({SegCount, Pad}, Data) 84 | when is_integer(SegCount), SegCount > 0, 85 | size(Data) >= (4 * (SegCount + Pad)) -> 86 | <> = Data, 89 | read_message(SegSizes, Rest, []); 90 | read_message(SegCount, Data) -> 91 | {cont, {SegCount, Data}}. 92 | 93 | read_message(<>, Data, Segments) 94 | when size(Data) >= (SegSize * 8) -> 95 | <> = Data, 96 | read_message(SegSizes, Rest, [Segment|Segments]); 97 | read_message(<<>>, Rest, Segments) -> 98 | {ok, lists:reverse(Segments), Rest}; 99 | read_message(SegSizes, Rest, Segments) -> 100 | {cont, {SegSizes, Rest, Segments}}. 101 | 102 | write_message(Segments) -> 103 | SegCount = length(Segments) - 1, 104 | Pad = SegCount rem 2, 105 | Padding = <<0:Pad/integer-unit:32>>, 106 | SegSizes = << <<(size(S) div 8):32/integer-little>> || S <- Segments >>, 107 | iolist_to_binary([<>, SegSizes, Padding | Segments]). 108 | -------------------------------------------------------------------------------- /src/ecapnp_obj.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Everything object. 20 | %% 21 | %% Structs.. structs.. and more structs. 22 | 23 | -module(ecapnp_obj). 24 | -author("Andreas Stenius "). 25 | 26 | -export([init/1, init/2, alloc/3, from_ref/3, from_data/2, 27 | from_data/3, field/2, copy/1, refresh/1, to_struct/2, 28 | to_list/2, to_text/1, to_data/1, set_cap_table/2, 29 | get_cap_table/1, add_ref/2, discard_ref/2, dump/1]). 30 | 31 | -include("ecapnp.hrl"). 32 | 33 | 34 | %% =================================================================== 35 | %% API functions 36 | %% =================================================================== 37 | 38 | init(#object{ ref = #ref{ kind = null } = Ref, schema = Schema }=Obj) -> 39 | Obj#object{ 40 | ref = ecapnp_ref:paste( 41 | ecapnp_ref:create_ptr( 42 | 0, ecapnp_schema:get_ref_kind(Schema)), 43 | Ref) 44 | }. 45 | 46 | init(Ref, Type) -> 47 | #object{ ref = Ref, schema = init_schema(Type) }. 48 | 49 | %% @doc Allocate data for a new object. 50 | -spec alloc(schema_node(), segment_id(), pid()) -> object(). 51 | alloc(Node, SegmentId, Data) when is_pid(Data) -> 52 | init( 53 | ecapnp_ref:alloc( 54 | ecapnp_schema:get_ref_kind(Node), 55 | SegmentId, 1 + ecapnp_schema:size_of(Node), 56 | #builder{ pid = Data }), 57 | Node). 58 | 59 | %% @doc Get object (or list) from reference. 60 | %% from ref doesn't work well as it doesn't preserve/keep the schema module name.. 61 | -spec from_ref(#ref{}, type_name(), term()) -> object() | list(). 62 | from_ref(Ref, object, Schema) when is_record(Ref, ref) -> 63 | init(Ref, Schema); 64 | from_ref(#ref{ kind=Kind }=Ref, {list, _}=Type, Schema) 65 | when is_record(Kind, list_ref); Kind == null -> 66 | ecapnp_get:ref_data(Type, init(Ref, Schema), []); 67 | from_ref(Ref, Type, Schema) -> 68 | to_struct(Type, init(Ref, Schema)). 69 | 70 | %% @doc Get object (or list) from data. 71 | %% 72 | %% Returns a reader object (i.e. a read-only version). 73 | %% @see from_ref/2 74 | -spec from_data(binary() | list(binary()), type_name(), term()) -> object() | list(). 75 | from_data(Data, Type, Schema) -> 76 | Ref = ecapnp_ref:get(0, 0, #reader{ data = Data }), 77 | from_ref(Ref, Type, Schema). 78 | 79 | from_data(Data, Type) -> 80 | from_data(Data, Type, undefined). 81 | 82 | %% @doc Lookup field definition by name for object. 83 | -spec field(field_name() | non_neg_integer() | {ptr, non_neg_integer()}, object()) -> field_type(). 84 | field({ptr, Idx}, _) when is_number(Idx) -> 85 | #field{ kind = #ptr{ type = object, idx = Idx } }; 86 | field(Field, #object{ schema = Schema }) -> 87 | ecapnp_schema:find_field(Field, Schema). 88 | 89 | %% @doc Copy object recursively. 90 | -spec copy(object()) -> binary(). 91 | copy(#object{ ref=Ref }) -> 92 | ecapnp_ref:copy(Ref). 93 | 94 | -spec refresh(object()) -> object(). 95 | %% @doc Reread object reference. 96 | %% @see ecapnp_ref:refresh/1 97 | refresh(#object{ ref=Ref }=Object) -> 98 | Object#object{ ref=ecapnp_ref:refresh(Ref) }. 99 | 100 | %% @doc Type cast object to another type of object. 101 | -spec to_struct(type_name(), object()) -> object(). 102 | to_struct(object, Object) -> 103 | Object#object{ schema = init_schema(Object) }; 104 | to_struct(Type, #object{ ref=#ref{ kind=Kind } }=Object) 105 | when Kind == null; 106 | is_record(Kind, struct_ref); 107 | is_record(Kind, interface_ref) -> 108 | T = ecapnp_schema:lookup(Type, Object), 109 | Object#object{ schema=T }. 110 | 111 | %% @doc Type cast object to list of type. 112 | %% Object must be a reference to a list. 113 | -spec to_list(type_name(), object()) -> list(). 114 | to_list(Type, #object{ ref=#ref{ kind=Kind }}=Obj) 115 | when is_record(Kind, list_ref); Kind == null -> 116 | ecapnp_get:ref_data({list, Type}, Obj, []). 117 | 118 | %% @doc Type cast object to text. 119 | %% Object must be a reference to text. 120 | -spec to_text(object()) -> binary(). 121 | to_text(#object{ ref=#ref{ kind=Kind }}=Obj) 122 | when is_record(Kind, list_ref); Kind == null -> 123 | ecapnp_get:ref_data(text, Obj, <<>>). 124 | 125 | %% @doc Type cast object to binary data. 126 | %% Object must be a reference to data. 127 | -spec to_data(object()) -> binary(). 128 | to_data(#object{ ref=#ref{ kind=Kind }}=Obj) 129 | when is_record(Kind, list_ref); Kind == null -> 130 | ecapnp_get:ref_data(data, Obj, <<>>). 131 | 132 | set_cap_table(CapTable, #object{ ref = Ref }=Object) -> 133 | Object#object{ ref = set_cap_table(CapTable, Ref) }; 134 | set_cap_table(CapTable, #ref{ data = Data }=Ref) -> 135 | Ref#ref{ data = set_cap_table(CapTable, Data) }; 136 | set_cap_table(CapTable, Reader) when is_record(Reader, reader) -> 137 | Reader#reader{ caps = CapTable }; 138 | set_cap_table(CapTable, #builder{ pid = Pid }=Data) -> 139 | ok = ecapnp_data:set_cap_table(CapTable, Pid), 140 | Data. 141 | 142 | get_cap_table(#object{ ref = Ref }) -> 143 | get_cap_table(Ref); 144 | get_cap_table(#ref{ data = Data }) -> 145 | get_cap_table(Data); 146 | get_cap_table(#reader{ caps = CapTable }) -> 147 | {ok, CapTable}; 148 | get_cap_table(#builder{ pid = Pid }) -> 149 | ecapnp_data:get_cap_table(Pid). 150 | 151 | add_ref(Ref, #object{ ref = #ref{ data = #builder{ pid = Pid } }}) 152 | when is_pid(Ref) -> ecapnp_data:add_ref(Ref, Pid); 153 | add_ref(_, #object{ ref = #ref{ data = #reader{} }}) -> ok. 154 | 155 | discard_ref(Ref, #object{ ref = #ref{ data = #builder{ pid = Pid } }}) 156 | when is_pid(Ref) -> ecapnp_data:discard_ref(Ref, Pid); 157 | discard_ref(_, #object{ ref = #ref{ data = #reader{} }}) -> ok. 158 | 159 | dump(Object) when is_record(Object, object) -> dump(kind, Object). 160 | 161 | 162 | %% =================================================================== 163 | %% internal functions 164 | %% =================================================================== 165 | 166 | init_schema(#schema_node{}=N) -> N; 167 | init_schema(#object{ schema = #schema_node{ module = Module } }) -> 168 | Module; 169 | init_schema(#object{ schema = Schema }) -> 170 | Schema; 171 | init_schema(Module) when is_atom(Module) -> 172 | Module. 173 | 174 | dump(#struct{ fields = Fields }, Object) -> 175 | ["(", dump_fields(Fields, Object), ")"]; 176 | dump(kind, #object{ schema = #schema_node{ kind = Kind } } = Object) -> 177 | dump(Kind, Object); 178 | dump(kind, #object{ ref = #ref{ kind = Kind } }) -> 179 | ecapnp:dump(Kind); 180 | dump(Kind, Value) -> 181 | io_lib:format("((~W, ~W))", [Kind, 5, Value, 5]). 182 | 183 | dump_fields(_, #object{ ref = #ref{ kind = null }}) -> "null"; 184 | dump_fields(Fields, Object) -> 185 | string:join([dump_field(F, Object) || F <- Fields] 186 | ++ dump_field(union, Object), ", "). 187 | 188 | dump_field(#field{ name = Name, kind = Kind }, Object) -> 189 | Raw = ecapnp:get(Name, Object), 190 | Value = case Kind of 191 | #ptr{ type = text } when is_binary(Raw) -> Raw; 192 | _ -> ecapnp:dump(Raw) 193 | end, 194 | io_lib:format("~p = ~s", [Name, Value]); 195 | dump_field(union, #object{ schema = #schema_node{ 196 | kind = #struct{ union_field = none } 197 | } }) -> []; 198 | dump_field(union, Object) -> 199 | {Tag, Value} = ecapnp:get(Object), 200 | [io_lib:format("~p = ~s", [Tag, ecapnp:dump(Value)])]. 201 | -------------------------------------------------------------------------------- /src/ecapnp_promise.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2014, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | %%%------------------------------------------------------------------- 17 | %% @copyright 2014, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Promise support. 20 | %% 21 | %% Everything promise. 22 | %%%------------------------------------------------------------------- 23 | -module(ecapnp_promise). 24 | -author("Andreas Stenius "). 25 | -behaviour(gen_fsm). 26 | 27 | %% API 28 | -export([start_link/1, stop/1, wait/2, notify/3, fullfill/2, chain/2]). 29 | 30 | %% gen_fsm callbacks 31 | -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, 32 | terminate/3, code_change/4]). 33 | 34 | %% gen_fsm state callbacks 35 | -export([ 36 | pending/2, pending/3, 37 | fullfilled/2, fullfilled/3 38 | ]). 39 | 40 | %%-define(ECAPNP_DEBUG,1). %% un-comment to enable debug messages, or 41 | %%-define(ECAPNP_TRACE,1). %% un-comment to enable debug messages and trace the gen_server 42 | 43 | -include("ecapnp.hrl"). 44 | 45 | -record(state, { 46 | waiting = [], 47 | result 48 | }). 49 | 50 | %%%=================================================================== 51 | %%% API 52 | %%%=================================================================== 53 | 54 | %%-------------------------------------------------------------------- 55 | %% @doc 56 | %% Creates a gen_fsm process which calls Module:init/1 to 57 | %% initialize. To ensure a synchronized start-up procedure, this 58 | %% function does not return until Module:init/1 has returned. 59 | %% 60 | %% @spec start_link(list()) -> {ok, Pid} | ignore | {error, Error} 61 | %% @end 62 | %%-------------------------------------------------------------------- 63 | start_link(Args) -> 64 | gen_fsm:start_link(?MODULE, Args, ?ECAPNP_GEN_OPTS). 65 | 66 | %%-------------------------------------------------------------------- 67 | stop(Promise) -> 68 | gen_fsm:sync_send_all_state_event(Promise, stop). 69 | 70 | %%-------------------------------------------------------------------- 71 | wait(Promise, Time) -> 72 | gen_fsm:sync_send_event(Promise, wait, Time). 73 | 74 | %%-------------------------------------------------------------------- 75 | notify(Promise, Pid, Tag) -> 76 | gen_fsm:send_event(Promise, {notify, Pid, Tag}). 77 | 78 | %%-------------------------------------------------------------------- 79 | fullfill(Promise, Result) -> 80 | gen_fsm:send_event(Promise, {fullfill, Result}). 81 | 82 | %%-------------------------------------------------------------------- 83 | chain(Promise, OuterPromise) -> 84 | gen_fsm:send_event(Promise, {chain, OuterPromise}). 85 | 86 | 87 | %%%=================================================================== 88 | %%% gen_fsm callbacks 89 | %%%=================================================================== 90 | 91 | %%-------------------------------------------------------------------- 92 | %% @private 93 | %% @doc 94 | %% Whenever a gen_fsm is started using gen_fsm:start/[3,4] or 95 | %% gen_fsm:start_link/[3,4], this function is called by the new 96 | %% process to initialize. 97 | %% 98 | %% @spec init(Args) -> {ok, StateName, State} | 99 | %% {ok, StateName, State, Timeout} | 100 | %% ignore | 101 | %% {stop, StopReason} 102 | %% @end 103 | %%-------------------------------------------------------------------- 104 | init(Opts) -> 105 | State = init_state(Opts, #state{}), 106 | ?DBG("++ New promise: ~p, opts: ~p", [State, Opts]), 107 | {ok, pending, State}. 108 | 109 | init_state([], State) -> State; 110 | init_state([{monitor, Pid}|Opts], State) -> 111 | monitor(process, Pid), 112 | init_state(Opts, State); 113 | init_state([{fullfiller, F}|Opts], State) -> 114 | State1 = init_fullfiller(F, State), 115 | init_state(Opts, State1). 116 | 117 | init_fullfiller(F, State) when is_function(F, 0) -> 118 | Promise = self(), 119 | spawn_link(fun() -> fullfill(Promise, F()) end), 120 | State. 121 | 122 | 123 | %%-------------------------------------------------------------------- 124 | %% @private 125 | %% @doc 126 | %% There should be one instance of this function for each possible 127 | %% state name. Whenever a gen_fsm receives an event sent using 128 | %% gen_fsm:send_event/2, the instance of this function with the same 129 | %% name as the current state name StateName is called to handle 130 | %% the event. It is also called if a timeout occurs. 131 | %% 132 | %% @spec state_name(Event, State) -> 133 | %% {next_state, NextStateName, NextState} | 134 | %% {next_state, NextStateName, NextState, Timeout} | 135 | %% {stop, Reason, NewState} 136 | %% @end 137 | %%-------------------------------------------------------------------- 138 | pending({notify, Pid, Tag}, State) -> 139 | ?DBG(".. NOTIFY ~p, ~s", [Pid, ?DUMP(Tag)]), 140 | {next_state, pending, enlist({message_to, Pid, Tag}, State)}; 141 | pending({chain, Promise}, State) -> 142 | ?DBG(".. CHAIN ~s", [?DUMP(Promise)]), 143 | {next_state, pending, enlist({chained_with, Promise}, State)}; 144 | pending({fullfill, Result}, State) -> 145 | {next_state, fullfilled, set_result(Result, State)}. 146 | 147 | %%-------------------------------------------------------------------- 148 | fullfilled({notify, Pid, Tag}, State) -> 149 | ?DBG(".. NOTIFY NOW ~p, ~s: ~s", [Pid, ?DUMP(Tag), ?DUMP(State#state.result)]), 150 | Pid ! {Tag, State#state.result}, 151 | {next_state, fullfilled, State}; 152 | fullfilled({chain, Promise}, State) -> 153 | ?DBG(".. CHAIN NOW ~s", [?DUMP(Promise)]), 154 | fullfill(Promise, State#state.result), 155 | {next_state, fullfilled, State}; 156 | fullfilled({fullfill, _}, State) -> 157 | {stop, already_fullfilled, State}. 158 | 159 | 160 | %%-------------------------------------------------------------------- 161 | %% @private 162 | %% @doc 163 | %% There should be one instance of this function for each possible 164 | %% state name. Whenever a gen_fsm receives an event sent using 165 | %% gen_fsm:sync_send_event/[2,3], the instance of this function with 166 | %% the same name as the current state name StateName is called to 167 | %% handle the event. 168 | %% 169 | %% @spec state_name(Event, From, State) -> 170 | %% {next_state, NextStateName, NextState} | 171 | %% {next_state, NextStateName, NextState, Timeout} | 172 | %% {reply, Reply, NextStateName, NextState} | 173 | %% {reply, Reply, NextStateName, NextState, Timeout} | 174 | %% {stop, Reason, NewState} | 175 | %% {stop, Reason, Reply, NewState} 176 | %% @end 177 | %%-------------------------------------------------------------------- 178 | pending(wait, From, State) -> 179 | ?DBG(".. WAIT ~p", [From]), 180 | {next_state, pending, enlist({reply_to, From}, State)}. 181 | 182 | %%-------------------------------------------------------------------- 183 | fullfilled(wait, _From, State) -> 184 | ?DBG(".. WAIT ~p, result: ~s", [_From, ?DUMP(State#state.result)]), 185 | {reply, State#state.result, fullfilled, State}. 186 | 187 | 188 | %%-------------------------------------------------------------------- 189 | %% @private 190 | %% @doc 191 | %% Whenever a gen_fsm receives an event sent using 192 | %% gen_fsm:send_all_state_event/2, this function is called to handle 193 | %% the event. 194 | %% 195 | %% @spec handle_event(Event, StateName, State) -> 196 | %% {next_state, NextStateName, NextState} | 197 | %% {next_state, NextStateName, NextState, Timeout} | 198 | %% {stop, Reason, NewState} 199 | %% @end 200 | %%-------------------------------------------------------------------- 201 | handle_event(_Event, StateName, State) -> 202 | {next_state, StateName, State}. 203 | 204 | %%-------------------------------------------------------------------- 205 | %% @private 206 | %% @doc 207 | %% Whenever a gen_fsm receives an event sent using 208 | %% gen_fsm:sync_send_all_state_event/[2,3], this function is called 209 | %% to handle the event. 210 | %% 211 | %% @spec handle_sync_event(Event, From, StateName, State) -> 212 | %% {next_state, NextStateName, NextState} | 213 | %% {next_state, NextStateName, NextState, Timeout} | 214 | %% {reply, Reply, NextStateName, NextState} | 215 | %% {reply, Reply, NextStateName, NextState, Timeout} | 216 | %% {stop, Reason, NewState} | 217 | %% {stop, Reason, Reply, NewState} 218 | %% @end 219 | %%-------------------------------------------------------------------- 220 | handle_sync_event(stop, _From, pending, State) -> 221 | {stop, canceled, ok, set_result(canceled, State)}; 222 | handle_sync_event(stop, _From, fullfilled, State) -> 223 | {stop, normal, ok, State}. 224 | 225 | %%-------------------------------------------------------------------- 226 | %% @private 227 | %% @doc 228 | %% This function is called by a gen_fsm when it receives any 229 | %% message other than a synchronous or asynchronous event 230 | %% (or a system message). 231 | %% 232 | %% @spec handle_info(Info,StateName,State)-> 233 | %% {next_state, NextStateName, NextState} | 234 | %% {next_state, NextStateName, NextState, Timeout} | 235 | %% {stop, Reason, NewState} 236 | %% @end 237 | %%-------------------------------------------------------------------- 238 | handle_info({'DOWN', _Ref, process, _Pid, Info}, pending, State) -> 239 | {stop, Info, State}; 240 | handle_info({'DOWN', _Ref, process, _Pid, _Info}, fullfilled, State) -> 241 | {stop, normal, State}; 242 | handle_info(_Info, StateName, State) -> 243 | {next_state, StateName, State}. 244 | 245 | %%-------------------------------------------------------------------- 246 | %% @private 247 | %% @doc 248 | %% This function is called by a gen_fsm when it is about to 249 | %% terminate. It should be the opposite of Module:init/1 and do any 250 | %% necessary cleaning up. When it returns, the gen_fsm terminates with 251 | %% Reason. The return value is ignored. 252 | %% 253 | %% @spec terminate(Reason, StateName, State) -> void() 254 | %% @end 255 | %%-------------------------------------------------------------------- 256 | terminate(Reason, pending, State) -> 257 | _ = set_result({error, Reason}, State), 258 | ok; 259 | terminate(_Reason, fullfilled, _State) -> 260 | ok. 261 | 262 | %%-------------------------------------------------------------------- 263 | %% @private 264 | %% @doc 265 | %% Convert process state when code is changed 266 | %% 267 | %% @spec code_change(OldVsn, StateName, State, Extra) -> 268 | %% {ok, StateName, NewState} 269 | %% @end 270 | %%-------------------------------------------------------------------- 271 | code_change(_OldVsn, StateName, State, _Extra) -> 272 | {ok, StateName, State}. 273 | 274 | %%%=================================================================== 275 | %%% Internal functions 276 | %%%=================================================================== 277 | 278 | %%-------------------------------------------------------------------- 279 | enlist(Tag, #state{ waiting = Ws }=State) -> 280 | State#state{ waiting = [Tag|Ws] }. 281 | 282 | %%-------------------------------------------------------------------- 283 | set_result(Result, #state{ waiting = Ws }=State) -> 284 | ?DBG(".. FULLFILLED ~s", [?DUMP(Result)]), 285 | [case W of 286 | {reply_to, From} -> 287 | gen_fsm:reply(From, Result); 288 | {message_to, Pid, Tag} -> 289 | Pid ! {Tag, Result}; 290 | {chained_with, Promise} -> 291 | fullfill(Promise, Result) 292 | end || W <- Ws], 293 | State#state{ waiting = [], result = Result }. 294 | -------------------------------------------------------------------------------- /src/ecapnp_promise_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Andreas Stenius 3 | %%% @copyright (C) 2014, Andreas Stenius 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 31 May 2014 by Andreas Stenius 8 | %%%------------------------------------------------------------------- 9 | -module(ecapnp_promise_sup). 10 | 11 | -behaviour(supervisor). 12 | 13 | %% API 14 | -export([start_link/0, start_promise/0, start_promise/1]). 15 | 16 | %% Supervisor callbacks 17 | -export([init/1]). 18 | 19 | -define(SERVER, ?MODULE). 20 | 21 | %%%=================================================================== 22 | %%% API functions 23 | %%%=================================================================== 24 | 25 | %%-------------------------------------------------------------------- 26 | %% @doc 27 | %% Starts the supervisor 28 | %% 29 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 30 | %% @end 31 | %%-------------------------------------------------------------------- 32 | start_link() -> 33 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 34 | 35 | %%-------------------------------------------------------------------- 36 | start_promise() -> 37 | start_promise([]). 38 | 39 | start_promise(Opts) -> 40 | supervisor:start_child(?SERVER, [Opts]). 41 | 42 | 43 | %%%=================================================================== 44 | %%% Supervisor callbacks 45 | %%%=================================================================== 46 | 47 | %%-------------------------------------------------------------------- 48 | %% @private 49 | %% @doc 50 | %% Whenever a supervisor is started using supervisor:start_link/[2,3], 51 | %% this function is called by the new process to find out about 52 | %% restart strategy, maximum restart frequency and child 53 | %% specifications. 54 | %% 55 | %% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} | 56 | %% ignore | 57 | %% {error, Reason} 58 | %% @end 59 | %%-------------------------------------------------------------------- 60 | init([]) -> 61 | RestartStrategy = simple_one_for_one, 62 | MaxRestarts = 10, 63 | MaxSecondsBetweenRestarts = 3600, 64 | 65 | SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, 66 | 67 | Restart = transient, 68 | Shutdown = 2000, 69 | Type = worker, 70 | 71 | AChild = {promise, {ecapnp_promise, start_link, []}, 72 | Restart, Shutdown, Type, [ecapnp_promise]}, 73 | 74 | {ok, {SupFlags, [AChild]}}. 75 | 76 | %%%=================================================================== 77 | %%% Internal functions 78 | %%%=================================================================== 79 | -------------------------------------------------------------------------------- /src/ecapnp_rpc.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2014, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2014, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Everything rpc. 20 | %% 21 | 22 | -module(ecapnp_rpc). 23 | -author("Andreas Stenius "). 24 | 25 | %% API 26 | -export([import_capability/3, request/2, send/1, wait/2]). 27 | 28 | %% Internal helper functions 29 | -export([get_target/1, wait/3, dump/1]). 30 | 31 | %%-define(ECAPNP_DEBUG,1). %% un-comment to enable debug messages, or 32 | %%-define(ECAPNP_TRACE,1). %% un-comment to enable debug messages and trace the gen_server 33 | 34 | -include("ecapnp.hrl"). 35 | 36 | 37 | %% =================================================================== 38 | %% API functions 39 | %% =================================================================== 40 | 41 | %% =================================================================== 42 | import_capability(Vat, ObjectId, Schema) -> 43 | #promise{ 44 | owner = {ecapnp_vat, Vat}, 45 | pid = ecapnp_vat:import_capability(Vat, ObjectId), 46 | schema = Schema 47 | }. 48 | 49 | %% =================================================================== 50 | request(MethodName, Target) -> 51 | do_request(MethodName, get_target(Target)). 52 | 53 | %% =================================================================== 54 | send(Req0) when is_record(Req0, rpc_call) -> 55 | case decode_target(Req0) of 56 | {#promise{ owner = {Mod, Pid} } = Promise, Req} -> 57 | ?DBG("== CALL~n~s", [?DUMP(Req)]), 58 | Promise#promise{ pid = Mod:send(Pid, Req) }; 59 | Promise when is_record(Promise, promise) -> Promise 60 | end. 61 | 62 | %% =================================================================== 63 | wait(#promise{ pid = Pid }=Promise, Time) -> 64 | {ok, Res} = ecapnp_promise:wait(Pid, Time), 65 | wait_result(Promise, transform(Promise, Res)). 66 | 67 | %%-------------------------------------------------------------------- 68 | wait(Pid, Ts, Time) -> 69 | {ok, Res} = ecapnp_promise:wait(Pid, Time), 70 | wait_result(Pid, transform(Ts, Res)). 71 | 72 | %% =================================================================== 73 | get_target(#object{ ref = #ref{ kind = Target }, schema = Schema }) 74 | when is_record(Target, interface_ref) -> 75 | {Target, Schema}; 76 | get_target(#promise{ schema = Schema } = Target) -> 77 | {Target, Schema}. 78 | 79 | 80 | %% =================================================================== 81 | dump(#rpc_call{ target = T, interface = I, method = M, params = P, 82 | results = R, resultSchema = S }) -> 83 | io_lib:format("#rpc_call{\n" 84 | "\ttarget = ~s,\n" 85 | "\tinterface = ~p,\n" 86 | "\tmethod = ~p,\n" 87 | "\tparams = ~s,\n" 88 | "\tresults = ~s,\n" 89 | "\tresultSchema = ~s\n" 90 | "}", 91 | [ecapnp:dump(T), I, M, ecapnp:dump(P), 92 | ecapnp:dump(R), ecapnp:dump(S)]). 93 | 94 | %% =================================================================== 95 | %% Internal functions 96 | %% =================================================================== 97 | 98 | do_request(MethodName, {Target, Schema}) -> 99 | {ok, Interface, Method} = ecapnp_schema:find_method_by_name( 100 | MethodName, Schema), 101 | {ok, Msg} = ecapnp_set:root('Message', rpc_capnp), 102 | Call = ecapnp:init(call, Msg), 103 | Payload = ecapnp:init(params, Call), 104 | Content = ecapnp:init( 105 | content, ecapnp_schema:lookup( 106 | Method#method.paramType, Interface), 107 | Payload), 108 | ResultSchema = ecapnp_schema:get(Method#method.resultType, Interface), 109 | #rpc_call{ 110 | target = Target, 111 | interface = Interface#schema_node.id, 112 | method = Method#method.id, 113 | params = Content, 114 | resultSchema = ResultSchema 115 | }. 116 | 117 | decode_target(#rpc_call{ target = Target, resultSchema = Schema } = Req) -> 118 | case Target of 119 | #interface_ref{ owner = promise, id = {Pid, Ts} } -> 120 | %% we need the target to resolve before dispatching the call, but we're not supposed to block 121 | %% the sender, but give a promise for the calls' result back, so we have to create this promise 122 | %% here, now. 123 | {ok, Promise} = ecapnp_promise_sup:start_promise( 124 | [{fullfiller, 125 | fun () -> 126 | {ok, Obj} = wait(Pid, Ts, infinity), 127 | {T, _S} = get_target(Obj), 128 | wait(send(Req#rpc_call{ target = T }), infinity) 129 | end}]), 130 | #promise{ pid = Promise, schema = Schema }; 131 | #interface_ref{ owner = O, id = I } -> 132 | {#promise{ owner = O, schema = Schema }, 133 | Req#rpc_call{ target = I }}; 134 | #promise{ owner = O, pid = P, transform = Ts } -> 135 | {#promise{ owner = O, schema = Schema }, 136 | Req#rpc_call{ target = {P, Ts} }} 137 | end. 138 | 139 | transform(#promise{ transform = []}, Res) -> Res; 140 | transform(#promise{ transform = Ts}, Obj) -> transform(Ts, Obj); 141 | transform([], Obj) -> Obj; 142 | transform([{getPointerField, Idx}|Ts], Obj) -> 143 | transform(Ts, ecapnp:get({ptr, Idx}, Obj)). 144 | 145 | wait_result(#promise{ schema = Schema }, Res) -> 146 | wait_result(Schema, Res); 147 | wait_result(undefined, Res) -> 148 | ?DBG(".. RESULT ~s", [?DUMP(Res)]), 149 | Res; 150 | wait_result(Schema, Res) -> 151 | Obj = ecapnp_obj:to_struct(Schema, Res), 152 | ?DBG(".. RESULT {ok, ~s}", [?DUMP(Obj)]), 153 | {ok, Obj}. 154 | -------------------------------------------------------------------------------- /src/ecapnp_schema.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Schema functions. 20 | %% 21 | %% This module exports functions for interacting with a compiled 22 | %% schema. 23 | 24 | -module(ecapnp_schema). 25 | -author("Andreas Stenius "). 26 | 27 | -export([type_of/1, get/2, lookup/2, lookup/3, size_of/1, size_of/2, 28 | data_size/1, ptrs_size/1, get_ref_kind/1, get_ref_kind/2, 29 | set_ref_to/2, find_method_by_name/2, find_field/2, dump/1]). 30 | 31 | -include("ecapnp.hrl"). 32 | 33 | -type lookup_type() :: type_id() | type_name() | object | schema_node(). 34 | %% The various types that can be looked up. 35 | -type lookup_search() :: object() | ref() | pid() 36 | | schema_nodes() | schema_node(). 37 | %% Where to search for the type being looked up. 38 | 39 | %% =================================================================== 40 | %% API functions 41 | %% =================================================================== 42 | 43 | get(Type, Schema) -> 44 | case lookup(Type, Schema) of 45 | undefined -> throw({schema_not_found, Type, Schema}); 46 | Node -> Node 47 | end. 48 | 49 | lookup(Type, Schema, Default) -> 50 | case lookup(Type, Schema) of 51 | undefined -> Default; 52 | Node -> Node 53 | end. 54 | 55 | -spec lookup(lookup_type(), lookup_search()) -> schema_node() | undefined. 56 | %% @doc Find schema node for type. 57 | lookup(N, _) when is_record(N, schema_node) -> N; 58 | lookup(Type, Schema) when is_atom(Schema) -> 59 | if Type =:= object -> Schema; 60 | true -> Schema:schema(Type) 61 | end; 62 | lookup(Id, #schema_node{ id=Id }=N) -> N; 63 | lookup(Name, #schema_node{ name=Name }=N) -> N; 64 | lookup(Type, #schema_node{ module=Module }) -> lookup(Type, Module); 65 | lookup(Type, #object{ schema = Schema }) -> lookup(Type, Schema); 66 | lookup(Type, [N|Ns]) -> 67 | case {lookup(Type, N), Ns} of 68 | {undefined, []} -> undefined; 69 | {undefined, _} -> lookup(Type, Ns); 70 | Node -> Node 71 | end; 72 | lookup(_Type, _Schema) -> 73 | %%io:format("type not found: ~p (in schema ~p)~n", [Type, Schema]), 74 | undefined. 75 | 76 | -spec type_of(object()) -> schema_node(). 77 | %% @doc Get type of object. 78 | %% @todo Doesn't this belong in ecapnp_obj? 79 | type_of(#object{ schema=Type }) -> Type. 80 | 81 | -spec size_of(lookup_type(), lookup_search()) -> non_neg_integer(). 82 | %% @doc Lookup struct type and query it's size. 83 | size_of(Type, Store) -> 84 | size_of(lookup(Type, Store)). 85 | 86 | -spec size_of(Node::schema_node()) -> non_neg_integer(). 87 | %% @doc Query size of a struct type. 88 | %% 89 | %% Will crash with `function_clause' if `Node' is not a struct or 90 | %% interface node. 91 | size_of(#schema_node{ kind=Kind }) -> size_of(Kind); 92 | size_of(#struct{ dsize=DSize, psize=PSize }) -> DSize + PSize; 93 | %% Size in message data, which simply is a Capability pointer, the 94 | %% CapDescriptor is stored out-of-band. 95 | size_of(#interface{}) -> 1. 96 | 97 | -spec data_size(schema_node()) -> non_neg_integer(). 98 | %% @doc Get data size of a struct type. 99 | data_size(#struct{ dsize=DSize }) -> DSize. 100 | 101 | -spec ptrs_size(schema_node()) -> non_neg_integer(). 102 | %% @doc Get pointer count for a struct type. 103 | ptrs_size(#struct{ psize=PSize }) -> PSize. 104 | 105 | get_ref_kind(#struct{ dsize=DSize, psize=PSize }) -> 106 | #struct_ref{ dsize=DSize, psize=PSize }; 107 | get_ref_kind(#schema_node{ kind=Kind }) -> 108 | get_ref_kind(Kind); 109 | get_ref_kind(#interface{}) -> 110 | #interface_ref{}. 111 | 112 | get_ref_kind(Type, Ref) when is_atom(Type); is_number(Type) -> 113 | get_ref_kind(lookup(Type, Ref)); 114 | get_ref_kind(Type, _) -> 115 | get_ref_kind(Type). 116 | 117 | 118 | -spec set_ref_to(lookup_type(), ref()) -> ref(). 119 | %% @doc Set reference kind. 120 | %% 121 | %% Lookup struct `Type' and return an updated {@link ref(). ref}. 122 | %% 123 | %% Note: it is only the record that is updated, the change is not 124 | %% committed to the message. 125 | set_ref_to(Type, Ref) -> 126 | Ref#ref{ kind=get_ref_kind(Type, Ref) }. 127 | 128 | %% @doc Find Interface and Method. 129 | find_method_by_name(MethodName, #schema_node{ 130 | kind = #interface{ methods = Ms } 131 | }=S) -> 132 | case lists:keyfind(MethodName, #method.name, Ms) of 133 | false -> undefined; 134 | Method -> {ok, S, Method} 135 | end; 136 | find_method_by_name(MethodName, [S|Ss]) -> 137 | case find_method_by_name(MethodName, S) of 138 | undefined -> 139 | find_method_by_name(MethodName, Ss); 140 | Result -> 141 | Result 142 | end; 143 | find_method_by_name(_MethodName, []) -> undefined. 144 | 145 | %% @doc Find struct field from schema definition by name or index. 146 | find_field(Field, #schema_node{ kind = #struct{ fields = Fields } }) -> 147 | Idx = if is_atom(Field) -> #field.name; 148 | is_number(Field) -> #field.id 149 | end, 150 | lists:keyfind(Field, Idx, Fields). 151 | 152 | dump(#schema_node{ src = Source, id = Id }) -> 153 | io_lib:format("~s(~p)", [Source, Id]). 154 | 155 | 156 | %% =================================================================== 157 | %% internal functions 158 | %% =================================================================== 159 | -------------------------------------------------------------------------------- /src/ecapnp_serialize.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @reference Cap'n 20 | %% Proto packing. 21 | %% @doc Serialize messages. 22 | %% 23 | %% Currently, only the packing scheme is implemented. 24 | 25 | -module(ecapnp_serialize). 26 | -author("Andreas Stenius "). 27 | 28 | -export([pack/1, unpack/1]). 29 | 30 | %% =================================================================== 31 | %% API functions 32 | %% =================================================================== 33 | 34 | -spec pack(binary()) -> binary(). 35 | %% @doc Pack message data. 36 | %% 37 | %% Pack the final message data using the Cap'n 39 | %% Proto packing scheme. 40 | pack(Data) 41 | when is_binary(Data) -> 42 | pack_data(Data). 43 | 44 | -spec unpack(binary()) -> binary(). 45 | %% @doc Unpack message data. 46 | %% 47 | %% Unpack data using the Cap'n 49 | %% Proto packing scheme. 50 | unpack(Data) 51 | when is_binary(Data) -> 52 | unpack_data(Data). 53 | 54 | 55 | %% =================================================================== 56 | %% internal functions 57 | %% =================================================================== 58 | 59 | pack_data(Data) -> pack_data(Data, <<>>). 60 | 61 | pack_data(<<0:1/integer-unit:64, Rest/binary>>, Acc) -> 62 | {Count, Rest2} = pack_nulls(Rest), 63 | pack_data(Rest2, <>); 64 | pack_data(<>, Acc) -> 65 | case pack_tag(Word, <<>>) of 66 | {{8, 255}, TagData} -> 67 | Count = pack_blob(Rest), 68 | <> = Rest, 69 | pack_data(Rest2, <>); 70 | {{_, TagByte}, TagData} -> 71 | pack_data(Rest, <>) 72 | end; 73 | pack_data(<<>>, Acc) -> Acc. 74 | 75 | pack_tag(Word, Acc) -> pack_tag([], Word, Acc). 76 | 77 | pack_tag(Tag, <<0, Rest/binary>>, Acc) -> 78 | pack_tag([0|Tag], Rest, Acc); 79 | pack_tag(Tag, <>, Acc) -> 80 | pack_tag([1|Tag], Rest, <>); 81 | pack_tag(Tag, <<>>, Acc) -> 82 | TagByte = lists:foldl( 83 | fun(B, {C, V}) -> 84 | {C + B, (V bsl 1) bor B} 85 | end, 86 | {0, 0}, 87 | Tag), 88 | {TagByte, Acc}. 89 | 90 | pack_blob(Data) -> pack_blob(0, Data). 91 | 92 | pack_blob(255, _) -> 93 | 255; % individual blob size can only be stored in 1 byte 94 | pack_blob(Count, <>) -> 95 | case pack_tag(Word, <<>>) of 96 | {{C, _V}, _Tag} when C >= 6 -> 97 | %% keep packing blob as long as we only have 2 or fewer zero bytes in each word 98 | pack_blob(Count + 1, Rest); 99 | _ -> Count 100 | end; 101 | pack_blob(Count, <<>>) -> Count. 102 | 103 | pack_nulls(Data) -> pack_nulls(0, Data). 104 | 105 | pack_nulls(Count, <<0:1/binary-unit:64, Rest/binary>>) -> 106 | pack_nulls(Count + 1, Rest); 107 | pack_nulls(Count, Rest) -> 108 | {Count, Rest}. 109 | 110 | 111 | unpack_data(Data) -> unpack_data(Data, <<>>). 112 | 113 | unpack_data(<<0, Count:8/integer, Rest/binary>>, Acc) -> 114 | Size = Count + 1, 115 | unpack_data(Rest, <>); 116 | unpack_data(<<16#ff, TagData:1/binary-unit:64, Count:8/integer, Rest0/binary>>, Acc) -> 117 | <> = Rest0, 118 | unpack_data(Rest, <>); 119 | unpack_data(<>, Acc) -> 120 | unpack_tag([(Tag bsr B) band 1 || B <- lists:seq(0, 7)], Rest, Acc); 121 | unpack_data(<<>>, Acc) -> Acc. 122 | 123 | unpack_tag([0|Bs], Rest, Acc) -> 124 | unpack_tag(Bs, Rest, <>); 125 | unpack_tag([1|Bs], <>, Acc) -> 126 | unpack_tag(Bs, Rest, <>); 127 | unpack_tag([], Rest, Acc) -> 128 | unpack_data(Rest, Acc). 129 | -------------------------------------------------------------------------------- /src/ecapnp_val.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc Cap'n Proto value support. 20 | %% 21 | %% Everything value. 22 | 23 | -module(ecapnp_val). 24 | -author("Andreas Stenius "). 25 | 26 | -export([set/2, set/3, get/2, get/3, size/1]). 27 | 28 | -include("ecapnp.hrl"). 29 | 30 | 31 | %% =================================================================== 32 | %% API functions 33 | %% =================================================================== 34 | 35 | -spec set(value_type(), number() | boolean()) -> binary(). 36 | %% @doc Encode value to Cap'n Proto format. 37 | set(ValueType, Value) -> 38 | value(set, {ValueType, to_value(ValueType, Value)}). 39 | 40 | -spec set(value_type(), number() | boolean(), binary()) -> binary(). 41 | %% @doc Encode value to Cap'n Proto format. 42 | %% 43 | %% The result is XOR'ed with `Default'. 44 | set(ValueType, Value, Default) when is_bitstring(Default) -> 45 | value(set, {ValueType, to_value(ValueType, Value), Default}). 46 | 47 | -spec get(value_type(), binary()) -> number() | boolean(). 48 | %% @doc Decode data from Cap'n Proto format. 49 | get(ValueType, Data) when is_bitstring(Data) -> 50 | from_value(ValueType, value(get, {ValueType, Data})). 51 | 52 | -spec get(value_type(), binary(), binary()) -> number() | boolean(). 53 | %% @doc Decode data from Cap'n Proto format. 54 | %% 55 | %% The `Data' is XOR'ed with `Default' prior to decoding. 56 | get(ValueType, Data, Default) 57 | when is_bitstring(Data), 58 | is_bitstring(Default) -> 59 | Value = value(get, {ValueType, Data, Default}), 60 | from_value(ValueType, Value). 61 | 62 | -spec size(value_type()) -> non_neg_integer(). 63 | %% @doc Get number of bits for `ValueType'. 64 | size(ValueType) -> 65 | value(size, ValueType). 66 | 67 | 68 | %% =================================================================== 69 | %% internal functions 70 | %% =================================================================== 71 | 72 | -define(DEFINE_TYPE(ValueType, Size, TypeSpec), 73 | value(size, ValueType) -> Size; 74 | value(get, {ValueType, Data}) -> 75 | <> = Data, Value; 76 | value(get, {ValueType, Data, Default}) -> 77 | PadSize = 7 - ((Size + 7) rem 8), 78 | <> 79 | = apply_default( 80 | <>, 81 | <>), 82 | Value; 83 | 84 | value(set, {ValueType, Value}) -> 85 | <>; 86 | value(set, {ValueType, Value, Default}) -> 87 | PadSize = 7 - ((Size + 7) rem 8), 88 | <> 89 | = apply_default( 90 | <>, 91 | <>), 92 | Data 93 | ). 94 | 95 | ?DEFINE_TYPE(uint64, 64, integer-unsigned-little); 96 | ?DEFINE_TYPE(uint32, 32, integer-unsigned-little); 97 | ?DEFINE_TYPE(uint16, 16, integer-unsigned-little); 98 | ?DEFINE_TYPE(uint8, 8, integer-unsigned-little); 99 | ?DEFINE_TYPE(int64, 64, integer-signed-little); 100 | ?DEFINE_TYPE(int32, 32, integer-signed-little); 101 | ?DEFINE_TYPE(int16, 16, integer-signed-little); 102 | ?DEFINE_TYPE(int8, 8, integer-signed-little); 103 | ?DEFINE_TYPE(bool, 1, bits); 104 | ?DEFINE_TYPE(float32, 32, bits); %% actual float conversion is done in the 105 | ?DEFINE_TYPE(float64, 64, bits); %% to_value/from_value functions. 106 | value(size, void) -> 0; 107 | value(_, {void, _}) -> void. 108 | 109 | -define(INF_NAN_32(N,S), <<0:16,1:1,N:1,0:6,S:1,127:7>>). 110 | -define(INF_NAN_64(N,S), <<0:48,15:4,N:1,0:3,S:1,127:7>>). 111 | 112 | from_value(bool, <<0:1>>) -> false; 113 | from_value(bool, <<1:1>>) -> true; 114 | from_value(float32, ?INF_NAN_32(1, _)) -> nan; 115 | from_value(float32, ?INF_NAN_32(0, 0)) -> inf; 116 | from_value(float32, ?INF_NAN_32(0, 1)) -> '-inf'; 117 | from_value(float32, <>) -> Value; 118 | from_value(float64, ?INF_NAN_64(1, _)) -> nan; 119 | from_value(float64, ?INF_NAN_64(0, 0)) -> inf; 120 | from_value(float64, ?INF_NAN_64(0, 1)) -> '-inf'; 121 | from_value(float64, <>) -> Value; 122 | from_value(_, Value) when is_number(Value) -> Value; 123 | from_value(_, void) -> void. 124 | 125 | to_value(bool, true) -> <<1:1>>; 126 | to_value(bool, false) -> <<0:1>>; 127 | to_value(float32, inf) -> ?INF_NAN_32(0, 0); 128 | to_value(float32, '-inf') -> ?INF_NAN_32(0, 1); 129 | to_value(float32, nan) -> ?INF_NAN_32(1, 0); 130 | to_value(float32, Value) -> <>; 131 | to_value(float64, inf) -> ?INF_NAN_64(0, 0); 132 | to_value(float64, '-inf') -> ?INF_NAN_64(0, 1); 133 | to_value(float64, nan) -> ?INF_NAN_64(1, 0); 134 | to_value(float64, Value) -> <>; 135 | to_value(_, Value) when is_number(Value) -> Value; 136 | to_value(_, void) -> void. 137 | 138 | apply_default(Value, Default) -> 139 | crypto:exor(Value, Default). 140 | -------------------------------------------------------------------------------- /src/ecapnpc.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | %% @copyright 2013, Andreas Stenius 18 | %% @author Andreas Stenius 19 | %% @doc The Erlang Cap'n Proto Compiler plugin. 20 | %% 21 | %% This module takes a `CodeGeneratorRequest' 23 | %% message and compiles the requested schema files into Erlang header 24 | %% files. 25 | %% 26 | %% Thus, a `my_schema.capnp' will be compiled to 27 | %% `my_schema.capnp.hrl', implementing `my_schema/N' functions for 28 | %% reading and writing Cap'n Proto messages using `my_schema'. 29 | %% 30 | %% 31 | %% == Schema functions == 32 | %% 33 | %% The schema functions implemented in a compiled schema header file are: 34 | %%
35 | %%
{@type fun((root, schema_type(), message()) -> {ok, object()@})}
36 | %%
Get a reference to the root object in message.
37 | %% 38 | %%
{@type fun((root, schema_type()) -> {ok, object()@})}
39 | %%
Set root object type for a new message.
40 | %% 41 | %%
{@type fun((get, field_name(), object()) -> field_value())}
42 | %%
Read object field value.
43 | %% 44 | %%
{@type fun((get, object()) -> (field_name() | {field_name(), field_value()@}))}
45 | %%
Read unnamed union value of object.
46 | %% 47 | %%
{@type fun((set, field_name(), field_value(), object()) -> ok)}
48 | %%
Write value to object field.
49 | %% 50 | %%
{@type fun((set, {field_name(), field_value()@}|field_name(), object()) -> ok)}
51 | %%
Write unnamed union value.
52 | %% 53 | %%
{@type fun((to_struct, schema_type(), object()) -> object())}
54 | %%
Type cast object to another struct type.
55 | %% 56 | %%
{@type fun((to_list, schema_type(), object()) -> list())}
57 | %%
Type cast object to list.
58 | %% 59 | %%
{@type fun((to_text | to_data, object()) -> binary())}
60 | %%
Type cast object to text or data.
61 | %% 62 | %%
{@type fun((schema) -> schema())}
63 | %%
Get the compiled schema definition.
64 | %%
65 | %% 66 | %% Where `fun' is named after the basename of the schema file (refer 67 | %% to the `my_schema.capnp' example in the {@section Description} section). 68 | 69 | -module(ecapnpc). 70 | -author("Andreas Stenius "). 71 | 72 | -export([compile_file/1, 73 | compile_data/1, 74 | compile_message/1 75 | ]). 76 | 77 | -include("ecapnp.hrl"). 78 | 79 | %% TODO: add options argument, so os environment only acts as override.. 80 | 81 | %% =================================================================== 82 | %% API functions 83 | %% =================================================================== 84 | 85 | %% @doc Read a `CodeGeneratorRequest' message (unpacked) from 86 | %% `FileName' and compile it. 87 | -spec compile_file( file:name_all() ) -> ok. 88 | compile_file(FileName) -> 89 | {ok, Data} = file:read_file(FileName), 90 | compile_data(Data). 91 | 92 | %% @doc Compile the `CodeGeneratorRequest' message (unpacked) in 93 | %% `Data'. 94 | -spec compile_data( binary() ) -> ok. 95 | compile_data(Data) 96 | when is_binary(Data) -> 97 | {ok, Message, <<>>} = ecapnp_message:read(Data), 98 | compile_message(Message). 99 | 100 | %% @doc Compile the `CodeGeneratorRequest' message. The `Message' 101 | %% argument holds the raw segments data to process, no futher 102 | %% processing on the message itself will be carried out prior to the 103 | %% compilation step. 104 | -spec compile_message( message() ) -> ok. 105 | compile_message(Message) 106 | when is_list(Message), is_binary(hd(Message)) -> 107 | {ok, Compiled} = ecapnp_compiler:compile(Message), 108 | ok = maybe_save_sources(Compiled), 109 | ok = maybe_compile_ast(Compiled). 110 | 111 | 112 | %% =================================================================== 113 | %% Internal functions 114 | %% =================================================================== 115 | 116 | maybe_save_sources(Compiled) -> 117 | case os:getenv("ECAPNP_TO_ERL") of 118 | false -> ok; 119 | "" -> save_sources(Compiled, "."); 120 | Path -> save_sources(Compiled, Path) 121 | end. 122 | 123 | maybe_compile_ast(Compiled) -> 124 | case os:getenv("ECAPNP_NO_BEAM") of 125 | false -> compile_ast(Compiled); 126 | _ -> ok 127 | end. 128 | 129 | save_sources([], _Path) -> ok; 130 | save_sources([{File, Ast}|Compiled], Path) -> 131 | Filename = filename:join(Path, <>), 132 | case write_file( 133 | Filename, 134 | erl_prettypr:format(Ast, [{ribbon, 100}, {paper, 200}])) 135 | of 136 | ok -> save_sources(Compiled, Path); 137 | error -> halt(2) 138 | end. 139 | 140 | compile_ast([]) -> ok; 141 | compile_ast([{_File, Ast}|Compiled]) -> 142 | Forms = erl_syntax:revert_forms(Ast), 143 | case compile:forms( 144 | Forms, 145 | [verbose, report, debug_info]) of 146 | {ok, Module, Bin} -> 147 | Filename = lists:concat([Module, ".beam"]), 148 | case write_file(Filename, Bin) of 149 | ok -> maybe_load(Module, Filename, Bin); 150 | error -> halt(2) 151 | end, 152 | compile_ast(Compiled); 153 | error -> halt(1) 154 | end. 155 | 156 | maybe_load(Module, Filename, Bin) -> 157 | case os:getenv("ECAPNP_LOAD_BEAM") of 158 | false -> ok; 159 | _ -> 160 | case code:load_binary(Module, Filename, Bin) of 161 | {module, Module} -> ok; 162 | {error, Reason} -> 163 | io:format(standard_error, 164 | "~s: warning: failed to load: ~p~n", 165 | [Filename, Reason]) 166 | end 167 | end. 168 | 169 | write_file(Filename, Data) -> 170 | case file:write_file(Filename, Data) of 171 | ok -> ok; 172 | {error, Reason} -> 173 | io:format(standard_error, 174 | "~s: failed to write file: ~p~n", 175 | [Filename, Reason]), 176 | error 177 | end. 178 | -------------------------------------------------------------------------------- /test/ecapnp_capability_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(ecapnp_capability_tests). 18 | -ifdef(TEST). 19 | -export([basicCap_funs/0]). 20 | -import(ecapnp_test_utils, [meck/3, setup_meck/2, teardown_meck/1]). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | -include("include/ecapnp.hrl"). 24 | 25 | 26 | basicCap_test_() -> 27 | {setup, 28 | fun () -> 29 | {ok, _} = ecapnp_promise_sup:start_link(), 30 | setup_meck(basicCap, basicCap_funs()) 31 | end, 32 | fun (Mod) -> 33 | teardown_meck(Mod) 34 | end, 35 | [fun () -> 36 | {ok, Pid} = ecapnp_capability:start([basicCap, [test_capnp:'BasicCap'()]]), 37 | ?assert(is_process_alive(Pid)), 38 | ok = ecapnp_capability:stop(Pid), 39 | receive after 100 -> ok end, %% ugly hack, I know.. 40 | ?assert(not is_process_alive(Pid)) 41 | end, 42 | fun () -> 43 | S = test_capnp:'BasicCap'(), 44 | {ok, Pid} = ecapnp_capability:start_link([basicCap, [S]]), 45 | test_basicCap_add(Pid, S, 123, 456) 46 | end 47 | ]}. 48 | 49 | thirdCap_test_() -> 50 | meck(thirdCap, thirdCap_funs(), 51 | [fun () -> 52 | S = test_capnp:'ThirdCap'(), 53 | {ok, Pid} = ecapnp_capability:start_link([thirdCap, [S], {init, third}]), 54 | test_basicCap_add(Pid, test_capnp:'BasicCap'(), 333, 666), 55 | test_otherCap_sqroot(Pid, test_capnp:'OtherCap'(), 4), 56 | test_thirdCap_square(Pid, S, 5) 57 | end 58 | ]). 59 | 60 | basicCap_funs() -> 61 | [{handle_call, fun ('BasicCap', add, Params, Result, undefined) -> {basicCap_add(Params, Result), undefined}; 62 | ('BasicCap', sub, Params, Result, undefined) -> {basicCap_sub(Params, Result), undefined} 63 | end}]. 64 | 65 | basicCap_add(Params, Results) -> 66 | ecapnp:set(result, 67 | ecapnp:get(a, Params) 68 | + ecapnp:get(b, Params), 69 | Results). 70 | 71 | basicCap_sub(Params, Results) -> 72 | ecapnp:set(result, 73 | ecapnp:get(a, Params) 74 | - ecapnp:get(b, Params), 75 | Results). 76 | 77 | test_basicCap_add(Pid, S, A, B) -> 78 | {ok, Params} = ecapnp:set_root(['BasicCap', [add, '$Params']], test_capnp), 79 | ok = ecapnp:set(a, A, Params), 80 | ok = ecapnp:set(b, B, Params), 81 | Req = #rpc_call{ 82 | interface = S#schema_node.id, 83 | method = 0, 84 | params = Params 85 | }, 86 | Promise = ecapnp_capability:send(Pid, Req), 87 | {ok, Result} = ecapnp_promise:wait(Promise, 1000), 88 | ?assertEqual(A+B, ecapnp:get(result, Result)). 89 | 90 | otherCap_sqroot(Params, Results) -> 91 | Sqrt = math:sqrt(ecapnp:get(a, Params)), 92 | ecapnp:set(root1, Sqrt, Results), 93 | ecapnp:set(root2, -Sqrt, Results). 94 | 95 | test_otherCap_sqroot(Pid, S, A) -> 96 | {ok, Params} = ecapnp:set_root(['OtherCap', [sqroot, '$Params']], test_capnp), 97 | ok = ecapnp:set(a, A, Params), 98 | {ok, Result} = ecapnp_promise:wait( 99 | ecapnp_capability:send( 100 | Pid, #rpc_call{ interface = S#schema_node.id, 101 | method = 0, params = Params }), 102 | 1000), 103 | R1 = ecapnp:get(root1, Result), 104 | R2 = ecapnp:get(root2, Result), 105 | ?assertEqual(float(-A), R1*R2). 106 | 107 | thirdCap_funs() -> 108 | [{init, fun (third) -> {state, []} end}, 109 | {handle_call, 110 | fun ('BasicCap', add, Params, Result, {state, []}) -> {basicCap_add(Params, Result), {state, []}}; 111 | ('OtherCap', sqroot, Params, Result, {state, []}) -> {otherCap_sqroot(Params, Result), {state, []}}; 112 | ('ThirdCap', square, Params, Result, {state, []}) -> {thirdCap_square(Params, Result), {state, []}} 113 | end}]. 114 | 115 | thirdCap_square(Params, Results) -> 116 | A = ecapnp:get(a, Params), 117 | ecapnp:set(sq, A * A, Results). 118 | 119 | test_thirdCap_square(Pid, S, A) -> 120 | {ok, Params} = ecapnp:set_root(['ThirdCap', [square, '$Params']], test_capnp), 121 | ok = ecapnp:set(a, A, Params), 122 | {ok, Result} = ecapnp_promise:wait( 123 | ecapnp_capability:send( 124 | Pid, #rpc_call{ interface = S#schema_node.id, 125 | method = 0, params = Params }), 126 | 1000), 127 | ?assertEqual(A*A, ecapnp:get(sq, Result)). 128 | 129 | -endif. 130 | -------------------------------------------------------------------------------- /test/ecapnp_get_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(ecapnp_get_tests). 18 | -ifdef(TEST). 19 | -include_lib("eunit/include/eunit.hrl"). 20 | -include("include/ecapnp.hrl"). 21 | 22 | root_test() -> 23 | Msg = [<<0,0,0,0, 2,0,3,0>>], 24 | {ok, Root} = ecapnp_get:root('Test', test_capnp, Msg), 25 | T = ecapnp_schema:lookup('Test', test_capnp), 26 | Data = (Root#object.ref)#ref.data, 27 | ?assertEqual( 28 | #object{ 29 | ref=#ref{ segment=0, pos=0, offset=0, data=Data, 30 | kind=#struct_ref{ dsize=2, psize=3 } }, 31 | schema=T 32 | }, Root). 33 | 34 | field_defaults_test() -> 35 | Msg= [<<0,0,0,0, 2,0,6,0, %% struct, 2 word data, 6 pointers 36 | %% data 37 | 0:64/integer-unit:2, 38 | %% pointers 39 | 0:64/integer-unit:6 40 | >>], 41 | check_default_values(Msg), 42 | check_default_values([<<0:64/integer>>]). 43 | 44 | check_default_values(Msg) -> 45 | {ok, Root} = ecapnp:get_root('Test', test_capnp, Msg), 46 | ?assertEqual(33, ecapnp:get(intField, Root)), 47 | ?assertEqual(<<"test">>, ecapnp:get(textField, Root)), 48 | ?assertEqual({boolField, false}, ecapnp:get(Root)), 49 | ?assertEqual({bool, true}, ecapnp:get(opts, Root)), 50 | Meta = ecapnp:get(meta, Root), 51 | ?assertEqual(0, ecapnp:get(id, Meta)), 52 | ?assertEqual(<<>>, ecapnp:get(tag, Meta)), 53 | ?assertEqual(<<"1234">>, ecapnp:get(data, Meta)), 54 | Struct = ecapnp:get(structField, Root), 55 | ?assertEqual(<<"simple message">>, ecapnp:get(simpleMessage, Struct)), 56 | ?assertEqual(<<"default message">>, ecapnp:get(message, Struct)), 57 | ?assertEqual(222, ecapnp:get(value, Struct)), 58 | ?assertEqual(333, ecapnp:get(defaultValue, Struct)). 59 | 60 | field_values_test() -> 61 | Msg = [<<0,0,0,0, 2,0,6,0, %% struct, 2 word data, 6 pointers 62 | %% data 63 | 33: 8/integer-little, %% intField 64 | 0: 8/integer-little, %% tag 0: boolField, 1: groupField.a 65 | 1:16/integer-little, %% union tag 66 | %% 32 67 | 55: 8/integer-little, %% tag 1: groupField.b 68 | 22: 8/integer-little, %% tag 1: groupField.c 69 | 70 | 0: 1/integer-little, %% opts tag 0: bool 71 | 0:15/integer-little, %% padding 72 | %% 64 73 | 3:16/integer-little, %% opts union tag 74 | 1234:16/integer-little, %% meta.id 75 | %% 96 76 | 0:32/integer-little, %% padding 77 | %% 128 78 | 79 | %% pointers 80 | 1:32/integer-little, 2:32/integer-little, %% textField 81 | %% opts tag 1: text, 2: data, 3: object 82 | 16:32/integer-little, 83 | 1:16/integer-little, 84 | 2:16/integer-little, 85 | 86 | 0:64/integer-little, %% meta.tag 87 | 0:64/integer-little, %% meta.data 88 | %% structField (offset -1, 0 data 0 ptrs), effectively a null ptr 89 | %% but doesn't use the default values defined for this field 90 | -4:32/integer-little, 0:32/integer-little, 91 | 0:64/integer-little, %% meta.struct 92 | 93 | %% opts.object data (Simple struct) 94 | 222:32/integer-little-unsigned, %% value 95 | 0:32/integer-little-unsigned, %% defaultValue 96 | 0:64/integer-little, %% message 97 | 1:32/integer-little, 106:32/integer-little, %% simpleMessage 98 | "Hello World!", 0, 0:24/integer-little 99 | >>], 100 | {ok, Root} = ecapnp:get_root('Test', test_capnp, Msg), 101 | ?assertEqual(0, ecapnp:get(intField, Root)), 102 | ?assertEqual(<<>>, ecapnp:get(textField, Root)), 103 | {groupField, Union} = ecapnp:get(Root), 104 | ?assertEqual(-44, ecapnp:get(a, Union)), 105 | ?assertEqual(0, ecapnp:get(b, Union)), 106 | ?assertEqual(22, ecapnp:get(c, Union)), 107 | {object, Obj} = ecapnp:get(opts, Root), 108 | Simple = ecapnp_obj:to_struct('Simple', Obj), 109 | ?assertEqual(<<"Hello World!">>, ecapnp:get(simpleMessage, Simple)), 110 | ?assertEqual(<<"default message">>, ecapnp:get(message, Simple)), 111 | ?assertEqual(0, ecapnp:get(value, Simple)), 112 | ?assertEqual(333, ecapnp:get(defaultValue, Simple)), 113 | Meta = ecapnp:get(meta, Root), 114 | ?assertEqual(1234, ecapnp:get(id, Meta)), 115 | ?assertEqual(<<>>, ecapnp:get(tag, Meta)), 116 | ?assertEqual(<<"1234">>, ecapnp:get(data, Meta)), 117 | Struct = ecapnp:get(structField, Root), 118 | ?assertEqual(<<"simple message">>, ecapnp:get(simpleMessage, Struct)), 119 | ?assertEqual(<<"default message">>, ecapnp:get(message, Struct)), 120 | ?assertEqual(222, ecapnp:get(value, Struct)), 121 | ?assertEqual(333, ecapnp:get(defaultValue, Struct)). 122 | 123 | 124 | default_list_test() -> 125 | {ok, Root} = ecapnp:get_root('ListTest', test_capnp, [<<0:64/integer>>]), 126 | ?assertEqual([456, 789, -123], ecapnp:get(listInts, Root)), 127 | ?assertEqual([], ecapnp_obj:to_list(bool, ecapnp:get(listAny, Root))), 128 | [Obj1, Obj2] = ecapnp:get(listSimples, Root), 129 | [?assertEqual(E, ecapnp:get(F, O)) 130 | || {O, T} <- [{Obj1, [{value, 1}, {message, <<"first">>}]}, 131 | {Obj2, [{value, 2}, {message, <<"second">>}]}], 132 | {F, E} <- T]. 133 | 134 | text_list_test() -> 135 | Text1 = <<"abcdefghijklmnopqrstuvwxyz">>, 136 | Text2 = <<"0123456789">>, 137 | Text3 = <<"The end">>, 138 | Msg = <<0,0,0,0, 0,0,4,0, 139 | 0:3/integer-little-unit:64, 140 | 1,0,0,0, 30,0,0,0, %% listText: off 0, 3 ptrs 141 | 9,0,0,0, 218,0,0,0, %% text 1, 26 bytes+NULL, 4 words 142 | 21,0,0,0, 90,0,0,0, %% text 2, 10 bytes+NULL, 2 words 143 | 25,0,0,0, 66,0,0,0, %% text 3, 7+NULL, 1 word 144 | Text1/binary, 0, 145 | 0:5/integer-little-unit:8, %% padding 146 | Text2/binary, 0, 147 | 0:5/integer-little-unit:8, %% padding 148 | Text3/binary, 0>>, 149 | {ok, Root} = ecapnp:get_root('ListTest', test_capnp, [Msg]), 150 | [?assertEqual(Expect, Actual) 151 | || {Expect, Actual} <- 152 | lists:zip( 153 | [Text1, Text2, Text3], 154 | ecapnp:get(listText, Root))]. 155 | 156 | get_cap_test() -> 157 | Msg = [<<0,0,0,0, 0,0,2,0, %% root struct w 2 ptrs 158 | 3,0,0,0, 1,0,0,0, %% cap ptr 11 (basic) 159 | 3,0,0,0, 2,0,0,0 %% cap ptr 22 (obj) 160 | >>], 161 | {ok, R} = ecapnp:get_root('CapTest', test_capnp, Msg), 162 | Cap1 = #interface_ref{ id = 1 }, 163 | Cap2 = #interface_ref{ id = 2 }, 164 | Root = ecapnp_obj:set_cap_table([dummy, Cap1, Cap2], R), 165 | Basic = ecapnp:get(basic, Root), 166 | Obj = ecapnp:get(obj, Root), 167 | ?assertEqual(Cap1, Basic#object.ref#ref.kind), 168 | ?assertEqual(Cap2, Obj#object.ref#ref.kind). 169 | 170 | 171 | -endif. 172 | -------------------------------------------------------------------------------- /test/ecapnp_obj_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(ecapnp_obj_tests). 18 | -ifdef(TEST). 19 | -include_lib("eunit/include/eunit.hrl"). 20 | -include("include/ecapnp.hrl"). 21 | 22 | -import(ecapnp_test_utils, [data/1]). 23 | 24 | from_ref_test() -> 25 | Data = data([<<0,0,0,0, 2,0,3,0>>]), 26 | Ref = ecapnp_ref:get(0, 0, Data), 27 | ?assertEqual( 28 | #object{ ref=Ref, schema=test_capnp }, 29 | ecapnp_obj:from_ref(Ref, object, test_capnp)). 30 | 31 | field_test() -> 32 | T = test_capnp:'Test'(), 33 | ?assertEqual( 34 | #field{ id = 0, name=intField, 35 | kind=#data{ type=uint8, align=0, default= <<33>> } }, 36 | ecapnp_obj:field(intField, #object{ schema=T })). 37 | 38 | copy_test() -> 39 | Bin = <<0,0,0,0, 2,0,2,0, 40 | 1234:32/integer, 5678:32/integer, 41 | 8765:32/integer, 4321:32/integer, 42 | 0:64/integer, 43 | 1,0,0,0, 106,0,0,0, 44 | "Hello World!", 0, 45 | 0:24/integer 46 | >>, 47 | Data = data([Bin]), 48 | Ref = ecapnp_ref:get(0, 0, Data), 49 | Obj = ecapnp_obj:from_ref(Ref, object, test_capnp), 50 | ?assertEqual(#object{ ref=Ref, schema=test_capnp }, Obj), 51 | ?assertEqual(Bin, ecapnp_obj:copy(Obj)). 52 | 53 | object_test() -> 54 | Data = data([]), 55 | 56 | NullRef = #ref{ data=Data }, 57 | NullObj = ecapnp_obj:from_ref(NullRef, object, test_capnp), 58 | 59 | ListRef = #ref{ kind=#list_ref{ size=8 }, data=Data }, 60 | ListObj = ecapnp_obj:from_ref(ListRef, object, test_capnp), 61 | 62 | T = test_capnp:'Test'(), 63 | ?assertEqual( 64 | #object{ schema=T, ref=NullRef }, 65 | ecapnp_obj:to_struct('Test', NullObj)), 66 | 67 | ?assertEqual([], ecapnp_obj:to_list('Simple', ListObj)), 68 | ?assertEqual(<<>>, ecapnp_obj:to_text(ListObj)), 69 | ?assertEqual(<<>>, ecapnp_obj:to_data(ListObj)). 70 | 71 | -endif. 72 | -------------------------------------------------------------------------------- /test/ecapnp_ref_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(ecapnp_ref_tests). 18 | -ifdef(TEST). 19 | -include_lib("eunit/include/eunit.hrl"). 20 | -include("include/ecapnp.hrl"). 21 | 22 | -import(ecapnp_test_utils, [data/1]). 23 | 24 | get_test() -> 25 | Data = data([<<4,0,0,0,2,0,3,0>>]), 26 | ?assertEqual( 27 | #ref{ segment=0, pos=0, offset=1, data=Data, 28 | kind=#struct_ref{ dsize=2, psize=3 } }, 29 | ecapnp_ref:get(0, 0, Data)). 30 | 31 | read_struct_ptr_test() -> 32 | Data = data([<<0,0,0,0, 0,0, 1,0, 41,0,0,0, 119,0,0,0, 33 | 0:10/integer-unit:64, 8,0,0,0, 3,0, 4,0 34 | >>]), 35 | Ref = ecapnp_ref:get(0, 0, Data), 36 | ?assertEqual( 37 | #ref{ segment=0, pos=1, offset=10, data=Data, 38 | kind=#list_ref{ size={inlineComposite, #struct_ref{ dsize=3, psize=4 }}, 39 | count=2 } }, 40 | ecapnp_ref:read_struct_ptr(0, Ref)). 41 | 42 | read_struct_data_test() -> 43 | Data = data([<<0,0,0,0, 1,0, 0,0, 1,2,3,4, 5,6,7,8>>]), 44 | Ref = ecapnp_ref:get(0, 0, Data), 45 | ?assertEqual( 46 | <<5, 6>>, 47 | ecapnp_ref:read_struct_data(32, 16, Ref)). 48 | 49 | read_composite_list_test() -> 50 | Data = data(<<1,0,0,0, 55,0,0,0, %% 6 words inline composite data 51 | 8,0,0,0, 1,0, 2,0, %% tag, count 2, 1 word data + 2 ptrs / element 52 | 0:6/integer-unit:64 53 | >>), 54 | Ref = ecapnp_ref:get(0, 0, Data), 55 | ?assertEqual( 56 | #ref{ segment=0, pos=0, offset=0, data=Data, 57 | kind = #list_ref{ 58 | size = {inlineComposite, #struct_ref{ dsize=1, psize=2 }}, 59 | count = 2 } 60 | }, Ref), 61 | ?assertEqual( 62 | [#ref{ segment=0, pos=-1, offset=2, data=Data, 63 | kind=#struct_ref{ dsize=1, psize=2 } }, 64 | #ref{ segment=0, pos=-1, offset=5, data=Data, 65 | kind=#struct_ref{ dsize=1, psize=2 } }], 66 | ecapnp_ref:read_list(Ref)). 67 | 68 | read_pointer_list_test() -> 69 | Data = data(<<1,0,0,0, 22,0,0,0, 70 | 5,0,0,0, 34,0,0,0, 71 | 5,0,0,0, 34,0,0,0, 72 | 102,111,111,0,0,0,0,0, 73 | 98,97,114,0,0,0,0,0 74 | >>), 75 | ListRef = ecapnp_ref:get(0, 0, Data), 76 | ?assertEqual( 77 | #ref{ segment=0, pos=0, offset=0, data=Data, 78 | kind=#list_ref{ size=pointer, count=2 } }, 79 | ListRef), 80 | List = ecapnp_ref:read_list(ListRef), 81 | ?assertEqual( 82 | [#ref{ segment=0, pos=1, offset=1, data=Data, 83 | kind=#list_ref{ size=8, count=4 } }, 84 | #ref{ segment=0, pos=2, offset=1, data=Data, 85 | kind=#list_ref{ size=8, count=4 } }], 86 | List), 87 | ?assertEqual( 88 | [<<"foo">>, <<"bar">>], 89 | [ecapnp_ref:read_text(R) || R <- List]). 90 | 91 | read_bool_list_test() -> 92 | Data = data(<<1,0,0,0, 81,0,0,0, 93 | 129,3,0,0, 0,0,0,0 94 | >>), 95 | Ref = ecapnp_ref:get(0, 0, Data), 96 | ?assertEqual( 97 | [<<1:1>>, <<0:1>>, <<0:1>>, <<0:1>>, 98 | <<0:1>>, <<0:1>>, <<0:1>>, <<1:1>>, 99 | <<1:1>>, <<1:1>>], 100 | ecapnp_ref:read_list(Ref)). 101 | 102 | read_text_test() -> 103 | Ref = ecapnp_ref:get( 104 | 0, 0, data( 105 | [<<1,0,0,0, 106,0,0,0, 106 | "Hello World!", 0, 107 | 0:24/integer>>] %% <- padding to get whole words 108 | )), 109 | ?assertEqual(<<"Hello World!">>, 110 | ecapnp_ref:read_text(Ref)). 111 | 112 | read_data_test() -> 113 | Data = <<123456789:64/integer-little,9876544321:64/integer>>, 114 | Ref = ecapnp_ref:get( 115 | 0, 0, data( 116 | [<<1,0,0,0, 130,0,0,0, Data/binary>>] 117 | )), 118 | ?assertEqual(Data, ecapnp_ref:read_data(Ref)). 119 | 120 | follow_far_test() -> 121 | Data = data([%% segment 0 122 | <<2,0,0,0, 1,0,0,0, 123 | 0,0,0,0, 0,0,1,0>>, 124 | %% segment 1 125 | <<0,0,0,0, 0,0,0,0>>, 126 | %% segment 2 127 | <<6,0,0,0, 0,0,0,0>> 128 | ]), 129 | Ref = ecapnp_ref:get(0, 0, Data), 130 | ?assertEqual( 131 | #ref{ segment=1, pos=0, offset=0, data=Data, kind=null}, 132 | Ref), 133 | ?assertEqual( 134 | #ref{ segment=1, pos=-1, offset=0, data=Data, 135 | kind=#struct_ref{ dsize=0, psize=1 } }, 136 | ecapnp_ref:get(2, 0, Data)). 137 | 138 | copy_test() -> 139 | Bin = <<0,0,0,0, 2,0,2,0, 140 | 1234:32/integer, 5678:32/integer, 141 | 8765:32/integer, 4321:32/integer, 142 | 0:64/integer, 143 | 1,0,0,0, 106,0,0,0, 144 | "Hello World!", 0, 145 | 0:24/integer 146 | >>, 147 | Data = data([Bin]), 148 | Ref = ecapnp_ref:get(0, 0, Data), 149 | ?assertEqual(Bin, ecapnp_ref:copy(Ref)). 150 | 151 | copy_struct_list_test() -> 152 | Bin = <<1,0,0,0, 39,0,0,0, 153 | 8,0,0,0, 1,0,1,0, 154 | 1,2,3,4, 5,6,7,8, 155 | 9,0,0,0, 34,0,0,0, 156 | 0:64/integer-little-unit:2, 157 | "foo", 0,0:32/integer-little 158 | >>, 159 | Data = data([Bin]), 160 | Ref = ecapnp_ref:get(0, 0, Data), 161 | ?assertEqual(Bin, ecapnp_ref:copy(Ref)). 162 | 163 | copy_struct_list2_test() -> 164 | Bin = <<1,0,0,0, 55,0,0,0, 165 | 8,0,0,0, 1,0,2,0, 166 | 167 | 1,2,3,4, 5,6,7,8, 168 | 17,0,0,0, 34,0,0,0, 169 | 0:64/integer-little, 170 | 171 | 8,7,6,5, 4,3,2,1, 172 | 9,0,0,0, 34,0,0,0, 173 | 0:64/integer-little, 174 | 175 | "foo", 0,0:32/integer-little, 176 | "bar", 0,0:32/integer-little 177 | >>, 178 | Data = data([Bin]), 179 | Ref = ecapnp_ref:get(0, 0, Data), 180 | ?assertEqual(Bin, ecapnp_ref:copy(Ref)). 181 | 182 | alloc_test() -> 183 | Data = data(10), 184 | Ref = ecapnp_ref:alloc(0, 5, Data), 185 | ?assertEqual( 186 | #ref{ segment=0, pos=0, offset=0, data=Data, kind=null}, 187 | Ref), 188 | D = ecapnp_data:get_segments(Data#builder.pid), 189 | ?assertEqual([<<0:5/integer-unit:64>>], D). 190 | 191 | set_test() -> 192 | Data = data(10), 193 | Kind = #struct_ref{ dsize=3, psize=4 }, 194 | Ref = ecapnp_ref:set(Kind, ecapnp_ref:alloc(0, 5, Data)), 195 | ?assertEqual( 196 | #ref{ segment=0, pos=0, offset=0, data=Data, 197 | kind=Kind}, 198 | Ref), 199 | D = ecapnp_data:get_segments(Data#builder.pid), 200 | ?assertEqual([<<0:32/integer, 201 | 3:16/integer-little, 202 | 4:16/integer-little, 203 | 0:4/integer-unit:64>>], 204 | D). 205 | 206 | write_struct_data_test() -> 207 | Data = data([<<0,0,0,0, 1,0, 0,0, 0:64/integer>>]), 208 | Ref = ecapnp_ref:get(0, 0, Data), 209 | ok = ecapnp_ref:write_struct_data(32, 16, <<5, 6>>, Ref), 210 | Bin = ecapnp_data:get_segments(Data#builder.pid), 211 | ?assertEqual( 212 | [<<0:32/integer, 1:32/integer-little, 213 | 0:32/integer, 5, 6, 0, 0>>], 214 | Bin). 215 | 216 | write_struct_ptr_test() -> 217 | Data = data([<<0,0,0,0, 0,0, 1,0, 1,2,3,4, 5,6,7,8>>]), 218 | Ref = ecapnp_ref:get(0, 0, Data), 219 | Ptr = ecapnp_ref:read_struct_ptr(0, Ref), 220 | ok = ecapnp_ref:write_struct_ptr(Ptr#ref{ kind=null }, Ref), 221 | Bin = ecapnp_data:get_segments(Data#builder.pid), 222 | ?assertEqual( 223 | [<<0:32/integer, 0, 0, 1, 0, 224 | 0:32/integer, 0, 0, 0, 0>>], 225 | Bin). 226 | 227 | write_struct_list_test() -> 228 | Data = data(10), 229 | Kind = #struct_ref{ dsize=1, psize=2 }, 230 | Ref = ecapnp_ref:alloc(Kind, 0, 4, Data), 231 | _ListRef = ecapnp_ref:alloc_list(0, #list_ref{ size=1, count=8 }, Ref), 232 | ok = ecapnp_ref:write_list(0, 0, <<1:1>>, Ref), 233 | ok = ecapnp_ref:write_list(0, 2, <<0:1>>, Ref), 234 | ok = ecapnp_ref:write_list(0, 1, <<1:1>>, Ref), 235 | ok = ecapnp_ref:write_list(0, 4, <<1:1>>, Ref), 236 | Bin = ecapnp_data:get_segments(Data#builder.pid), 237 | ?assertEqual( 238 | [<<0,0,0,0, 1,0, 2,0, 239 | 0:64/integer, %% data 240 | 5,0,0,0, 65,0,0,0, %% list ptr (0) 241 | 0:64/integer, %% ptr (1) 242 | 2#00010011, %% 8 bits 243 | 0:56/integer %% padding 244 | >>], Bin). 245 | 246 | write_composite_list_test() -> 247 | Data = data(20), 248 | Kind = #struct_ref{ dsize=1, psize=2 }, 249 | Ref = ecapnp_ref:alloc(Kind, 0, 4, Data), 250 | ListRef = ecapnp_ref:alloc_list( 251 | 0, #list_ref{ size={inlineComposite, #struct_ref{ dsize=2, psize=0 }}, 252 | count=2 }, 253 | Ref), 254 | [Ref1, Ref2] = ecapnp_ref:read_list(ListRef), 255 | ok = ecapnp_ref:write_struct_data(16, 32, <<1,2,3,4>>, Ref1), 256 | ok = ecapnp_ref:write_struct_data(80, 32, <<5,6,7,8>>, Ref2), 257 | Bin = ecapnp_data:get_segments(Data#builder.pid), 258 | ?assertEqual( 259 | [<<0,0,0,0, 1,0, 2,0, 260 | 0:64/integer, %% data 261 | 5,0,0,0, 39,0,0,0, %% list, off 1, size 7, count 4 262 | 0:64/integer, %% ptr (1) 263 | 8,0,0,0, 2,0,0,0, %% tag, 2 elems a 2 words data 0 ptrs 264 | 0,0,1,2, 3,4,0,0, 265 | 0,0,0,0, 0,0,0,0, 266 | 0,0,0,0, 0,0,0,0, 267 | 0,0,5,6, 7,8,0,0 268 | >>], Bin). 269 | 270 | -endif. 271 | -------------------------------------------------------------------------------- /test/ecapnp_rpc_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2014, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(ecapnp_rpc_tests). 18 | 19 | -ifdef(TEST). 20 | -include_lib("eunit/include/eunit.hrl"). 21 | -include("include/ecapnp.hrl"). 22 | 23 | -import(ecapnp_test_utils, [meck/2, setup_meck/2, teardown_meck/1]). 24 | -import(ecapnp_capability_tests, [basicCap_funs/0]). 25 | 26 | -record(test, { 27 | sup, basic, pipelines, mods, bridge, vat 28 | }). 29 | 30 | %%-define(RUN,1). 31 | -ifdef(RUN). 32 | %% code to easily run test in debugger.. 33 | -export([run/0]). 34 | run() -> 35 | {setup, S, T, {with, Ts}} = rpc_local_test_(), 36 | %%{setup, S, T, {with, Ts}} = rpc_remote_test_(), 37 | X = S(), 38 | [case P of 39 | #capability{ id = {local, Pid} } -> 40 | io:format("trace cap ~p~n", [Pid]), 41 | sys:trace(Pid, true); 42 | Pid when is_pid(Pid) -> 43 | io:format("trace pid ~p~n", [Pid]), 44 | sys:trace(Pid, true); 45 | _ -> nop 46 | end || P <- lists:flatten(erlang:tuple_to_list(X)), P /= X#test.bridge], 47 | (lists:nth(?RUN, Ts))(X), 48 | T(X). 49 | -endif. 50 | 51 | rpc_local_test_() -> 52 | {setup, 53 | fun () -> 54 | Mods = [setup_meck(Mod, Funs) 55 | || {Mod, Funs} <- 56 | [{basicCap, basicCap_funs()}, 57 | {pipelines, pipelines_funs()} 58 | ] 59 | ], 60 | {ok, CapS} = ecapnp_capability_sup:start_link(), 61 | {ok, ProS} = ecapnp_promise_sup:start_link(), 62 | {ok, BasicCap} = ecapnp_capability_sup:start_capability(basicCap, test_capnp:'BasicCap'()), 63 | {ok, PipelinesCap} = ecapnp_capability_sup:start_capability(pipelines, test_capnp:'Pipelines'(), [{init, BasicCap}]), 64 | #test{ sup = [CapS, ProS], basic = BasicCap, pipelines = PipelinesCap, mods = Mods } 65 | end, 66 | fun (#test{ sup = Sups, mods = Mods }) -> 67 | [exit(S, normal) || S <- Sups], 68 | [teardown_meck(Mod) || Mod <- Mods] 69 | end, 70 | {with, 71 | [fun test_request/1, 72 | fun test_send/1, 73 | fun test_pipeline/1 74 | ]}}. 75 | 76 | rpc_remote_test_() -> 77 | %% the remote test really is a local test, using two bridged 78 | %% client vat processes, so works exactly the same as for a real 79 | %% remote one, except there's no TCP/IP stack involved here.. 80 | {setup, 81 | fun () -> 82 | Mods = [setup_meck(Mod, Funs) 83 | || {Mod, Funs} <- 84 | [{basicCap, basicCap_funs()}, 85 | {pipelines, pipelines_funs()}, 86 | {bridge_echo, echo_funs()} 87 | ] 88 | ], 89 | {ok, CapS} = ecapnp_capability_sup:start_link(), 90 | {ok, ProS} = ecapnp_promise_sup:start_link(), 91 | {ok, BasicCap} = ecapnp_capability_sup:start_capability(basicCap, test_capnp:'BasicCap'()), 92 | {ok, PipelinesCap} = ecapnp_capability_sup:start_capability(pipelines, test_capnp:'Pipelines'(), [{init, BasicCap}]), 93 | Bridge = spawn_link( 94 | fun () -> 95 | Loop = fun (F, VatA, VatB) -> 96 | receive 97 | {From, stop} -> 98 | [ecapnp_vat:stop(Vat) || Vat <- [VatA, VatB]], 99 | From ! self(); 100 | {VatA, Data} -> 101 | VatB ! {receive_data, Data}, 102 | F(F, VatA, VatB); 103 | {VatB, Data} -> 104 | VatA ! {receive_data, Data}, 105 | F(F, VatA, VatB); 106 | Other -> 107 | %% doesn't expect anything here.. 108 | io:format("bridge: ~p~n", [Other]), 109 | F(F, VatA, VatB) 110 | end 111 | end, 112 | receive 113 | {VatA, VatB} -> 114 | Loop(Loop, VatA, VatB) 115 | end 116 | end), 117 | {ok, Client} = ecapnp_vat:start_link({bridge_echo, Bridge}), 118 | {ok, Server} = ecapnp_vat:start_link({bridge_echo, Bridge}, 119 | cap_restorer([{<<"basic">>, BasicCap}, 120 | {<<"pipelines">>, PipelinesCap}])), 121 | Bridge ! {Client, Server}, 122 | io:format("VAT A: ~p~nVAT B: ~p~n", [Client, Server]), 123 | sys:trace(Server, true), 124 | #test{ sup = [CapS, ProS], basic = BasicCap, pipelines = PipelinesCap, 125 | mods = Mods, bridge = Bridge, vat = Client } 126 | end, 127 | fun (#test{ sup = Sups, mods = Mods, bridge = Bridge }) -> 128 | Bridge ! {self(), stop}, 129 | receive Bridge -> ok end, 130 | [exit(S, normal) || S <- Sups], 131 | [teardown_meck(Mod) || Mod <- Mods] 132 | end, 133 | {with, 134 | [fun test_remote_basic/1 135 | %%fun test_pipeline/1 136 | ]}}. 137 | 138 | echo_funs() -> 139 | [{send, fun (Pid, Data) -> Pid ! {self(), Data}, ok end}]. 140 | 141 | pipelines_funs() -> 142 | [{init, fun (State) -> State end}, 143 | {handle_call, fun ('Pipelines', getBasic, _Params, Result, BasicCap) -> 144 | ecapnp:set(basic, BasicCap, Result), 145 | {ok, BasicCap} 146 | end}]. 147 | 148 | cap_restorer(Caps) -> 149 | fun (ObjectId, _Vat) -> 150 | case lists:keyfind(ecapnp_obj:to_text(ObjectId), 1, Caps) of 151 | false -> undefined; 152 | {_, Cap} -> {ok, Cap} 153 | end 154 | end. 155 | 156 | test_request(#test{ basic=Cap }) -> 157 | Req = ecapnp:request(add, Cap), 158 | Params = Req#rpc_call.params, 159 | Schema = test_capnp:'BasicCap'(), 160 | Method = hd(Schema#schema_node.kind#interface.methods), 161 | ?assertEqual( 162 | #rpc_call{ 163 | target = Cap#object.ref#ref.kind, 164 | interface = Schema#schema_node.id, 165 | method = Method#method.id, 166 | params = Params, 167 | resultSchema = test_capnp:schema(Method#method.resultType) 168 | }, Req), 169 | ?assertEqual( 170 | ['BasicCap', [add, '$Params']], 171 | Params#object.schema#schema_node.name). 172 | 173 | test_send(#test{ basic=Cap }) -> 174 | Req = ecapnp:request(sub, Cap), 175 | Params = Req#rpc_call.params, 176 | 177 | %% test set of both request object and the params directly 178 | ok = ecapnp:set(a, 333, Req), 179 | ok = ecapnp:set(b, 222, Params), 180 | %% test get of both params directly as well as from the request 181 | ?assertEqual(333, ecapnp:get(a, Params)), 182 | ?assertEqual(222, ecapnp:get(b, Req)), 183 | 184 | Promise = ecapnp:send(Req), 185 | {ok, Res} = ecapnp:wait(Promise), 186 | ?assertEqual(111, ecapnp:get(result, Res)). 187 | 188 | test_pipeline(#test{ pipelines = Cap }) -> 189 | BasicReq = ecapnp:request(getBasic, Cap), 190 | BasicPromise = ecapnp:send(BasicReq), 191 | PromisedBasicCap = ecapnp:get(basic, BasicPromise), 192 | AddReq = ecapnp:request(add, PromisedBasicCap), 193 | ok = ecapnp:set(a, 123, AddReq), 194 | ok = ecapnp:set(b, 321, AddReq), 195 | AddPromise = ecapnp:send(AddReq), 196 | %% get data on a promise will wait until fulfilled, then proceed 197 | ?assertEqual(444, ecapnp:get(result, AddPromise)). 198 | 199 | test_remote_basic(#test{ vat = Vat }) -> 200 | Promise = ecapnp:import_capability(Vat, {text, <<"basic">>}, test_capnp:'BasicCap'()), 201 | test_send(#test{ basic=Promise }). 202 | 203 | -endif. 204 | -------------------------------------------------------------------------------- /test/ecapnp_test_utils.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(ecapnp_test_utils). 18 | -ifdef(TEST). 19 | -include_lib("eunit/include/eunit.hrl"). 20 | -include("include/ecapnp.hrl"). 21 | -export([data/1, meck/2, meck/3, setup_meck/2, teardown_meck/1]). 22 | 23 | %% ---------------------------------------- 24 | meck_test_() -> 25 | meck( 26 | foo, [{bar, fun() -> baz end}], 27 | [?_assertEqual(baz, foo:bar()) 28 | ]). 29 | 30 | %% ---------------------------------------- 31 | data(Data) -> 32 | {ok, Pid} = ecapnp_data:start_link(Data), 33 | #builder{ pid = Pid }. 34 | 35 | %% ---------------------------------------- 36 | meck(Mod, Funs, Tests) -> 37 | {setup, fun() -> setup_meck(Mod, Funs) end, fun teardown_meck/1, Tests}. 38 | 39 | meck(Desc, Tests) -> 40 | {setup, 41 | fun() -> [setup_meck(Mod, Funs) || {Mod, Funs} <- Desc] end, 42 | fun(Mods) -> [teardown_meck(Mod) || Mod <- Mods] end, 43 | Tests}. 44 | 45 | %% ---------------------------------------- 46 | setup_meck(Mod, Funs) -> 47 | ?assertEqual(ok, meck:new(Mod, [non_strict])), 48 | [?assertEqual(ok, meck:expect(Mod, Fun, Impl)) 49 | || {Fun, Impl} <- Funs], 50 | Mod. 51 | 52 | teardown_meck(Mod) -> 53 | ?assert(meck:validate(Mod)), 54 | ?assertEqual(ok, meck:unload(Mod)). 55 | 56 | -endif. 57 | -------------------------------------------------------------------------------- /test/ecapnp_val_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(ecapnp_val_tests). 18 | -ifdef(TEST). 19 | -include_lib("eunit/include/eunit.hrl"). 20 | 21 | get_test() -> 22 | ?assertEqual(false, ecapnp_val:get(bool, <<0:1>>, <<0:1>>)), 23 | ?assertEqual(true, ecapnp_val:get(bool, <<1:1>>, <<0:1>>)), 24 | ?assertEqual(true, ecapnp_val:get(bool, <<0:1>>, <<1:1>>)), 25 | ?assertEqual(false, ecapnp_val:get(bool, <<1:1>>, <<1:1>>)). 26 | 27 | set_test() -> 28 | ?assertEqual(<<0:1>>, ecapnp_val:set(bool, false, <<0:1>>)), 29 | ?assertEqual(<<1:1>>, ecapnp_val:set(bool, true, <<0:1>>)), 30 | ?assertEqual(<<1:1>>, ecapnp_val:set(bool, false, <<1:1>>)), 31 | ?assertEqual(<<0:1>>, ecapnp_val:set(bool, true, <<1:1>>)). 32 | 33 | inf_nan_32_test() -> 34 | Pinf = <<0,0,128,127>>, 35 | Ninf = <<0,0,128,255>>, 36 | NaN = <<0,0,192,127>>, 37 | ?assertEqual(Pinf, ecapnp_val:set(float32, inf)), 38 | ?assertEqual(Ninf, ecapnp_val:set(float32, '-inf')), 39 | ?assertEqual(NaN, ecapnp_val:set(float32, nan)), 40 | ?assertEqual(inf, ecapnp_val:get(float32, Pinf)), 41 | ?assertEqual('-inf', ecapnp_val:get(float32, Ninf)), 42 | ?assertEqual(nan, ecapnp_val:get(float32, NaN)). 43 | 44 | inf_nan_64_test() -> 45 | Pinf = <<0:32, 0,0,240,127>>, 46 | Ninf = <<0:32, 0,0,240,255>>, 47 | NaN = <<0:32, 0,0,248,127>>, 48 | ?assertEqual(Pinf, ecapnp_val:set(float64, inf)), 49 | ?assertEqual(Ninf, ecapnp_val:set(float64, '-inf')), 50 | ?assertEqual(NaN, ecapnp_val:set(float64, nan)), 51 | ?assertEqual(inf, ecapnp_val:get(float64, Pinf)), 52 | ?assertEqual('-inf', ecapnp_val:get(float64, Ninf)), 53 | ?assertEqual(nan, ecapnp_val:get(float64, NaN)). 54 | 55 | float_test() -> 56 | F = 1234.5, 57 | F32 = ecapnp_val:set(float32, F), 58 | F64 = ecapnp_val:set(float64, F), 59 | ?assertEqual(4, size(F32)), 60 | ?assertEqual(8, size(F64)), 61 | ?assertEqual(F, ecapnp_val:get(float32, F32)), 62 | ?assertEqual(F, ecapnp_val:get(float64, F64)). 63 | 64 | -endif. 65 | -------------------------------------------------------------------------------- /test/ecapnp_vat_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2014, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(ecapnp_vat_tests). 18 | -ifdef(TEST). 19 | -include_lib("eunit/include/eunit.hrl"). 20 | -include("include/ecapnp.hrl"). 21 | -import(ecapnp_test_utils, [meck/3]). 22 | 23 | %% export_capability_test() -> 24 | %% {ok, Vat} = ecapnp_vat:start_link(), 25 | %% Cap = #capability{}, 26 | %% {ok, ExportId} = ecapnp_vat:export_capability(Cap, Vat), 27 | %% ?assertEqual(0, ExportId), 28 | %% ?assertEqual({ok, Cap}, ecapnp_vat:find_capability(ExportId, Vat)). 29 | 30 | %% restore_capability_test__() -> 31 | %% meck(echo, transport_funs(), 32 | %% [fun test_restore_capability/0 33 | %% %%,fun test_process_restore_message/0 34 | %% ]). 35 | 36 | %% transport_funs() -> 37 | %% [{send, fun (Tester, Data) -> Tester ! {captured, Data}, ok end}]. 38 | 39 | %% test_restore_capability() -> 40 | %% {ok, Vat} = ecapnp_vat:start_link({echo, self()}), 41 | %% {ok, _Promise} = ecapnp_vat:import_capability(<<"test-cap">>, Vat), 42 | %% {ok, Req} = receive {captured, Data} -> {ok, Data} after 10 -> missing_data end, 43 | %% {ok, Msg} = ecapnp_get:root('Message', rpc_capnp, ecapnp_message:read(Req)), 44 | %% {restore, Res} = ecapnp:get(Msg), 45 | %% Obj = ecapnp_obj:to_text(ecapnp:get(objectId, Res)), 46 | %% ?assertEqual(<<"test-cap">>, Obj). 47 | 48 | %% test_process_restore_message() -> 49 | %% {ok, Vat} = ecapnp_vat:start_link({echo, self()}), 50 | %% {ok, ExportId} = ecapnp_capability:start_link(basicCap, test_capnp:'BasicCap'()), 51 | %% {ok, ReqMsg} = ecapnp:set_root('Message', rpc_capnp), 52 | %% Res = ecapnp_obj:init(ecapnp:set(restore, ReqMsg)), 53 | %% ok = ecapnp:set(objectId, {text, <<"test-cap">>}, Res), 54 | %% Vat ! {message, ecapnp_message:write(ReqMsg)}, 55 | %% {ok, Rsp} = receive {captured, Data} -> {ok, Data} after 10 -> missing_data end, 56 | %% {ok, RspMsg} = ecapnp_get:root('Message', rpc_capnp, ecapnp_message:read(Rsp)), 57 | %% {return, Ret} = ecapnp:get(RspMsg), 58 | %% {results, Payload} = ecapnp:get(Ret), 59 | %% _Content = ecapnp:get(content, Payload), 60 | %% [Cap] = ecapnp:get(capTable, Payload), 61 | %% ?assertEqual({senderHosted, ExportId}, ecapnp:get(Cap)). 62 | 63 | 64 | %% sturdy_capability_test() -> 65 | %% ecapnp_vat:register_capability("Basic", basicCap, test_capnp:'BasicCap'()). 66 | 67 | 68 | %% basic_server_test() -> 69 | %% %% setup expectations 70 | %% setup_meck(basicCap, [{add, 71 | %% fun(Params, Results) -> 72 | %% ecapnp:set(result, 73 | %% ecapnp:get(a, Params) 74 | %% + ecapnp:get(b, Params), 75 | %% Results) 76 | %% end} 77 | %% ]), 78 | %% %% start server for capability 79 | %% {ok, Cap} = ecapnp_capability:start('BasicCap', basicCap, test_capnp), 80 | %% %% prepare request 81 | %% {ok, Request} = ecapnp_capability:request(add, Cap), 82 | %% %%check_request('BasicCap', add, Request), 83 | %% Params = ecapnp_capability:params(Request), 84 | %% ok = ecapnp:set(a, 123, Params), 85 | %% ok = ecapnp:set(b, 456, Params), 86 | %% %% send request 87 | %% {ok, Result} = ecapnp_capability:send(Request), 88 | %% %% check_promise(...), 89 | %% %% wait for then verify response 90 | %% ok = ecapnp_capability:wait(Result), 91 | %% ?assertEqual(579, ecapnp:get(result, Result)), 92 | %% %% clean up 93 | %% ecapnp_capability:stop(Cap), 94 | %% teardown_meck(basicCap). 95 | 96 | %% pipeline_test() -> 97 | %% %% setup expectations 98 | %% setup_meck(basicCap, [{add, 99 | %% fun(Params, Results) -> 100 | %% ecapnp:set(result, 101 | %% ecapnp:get(a, Params) 102 | %% + ecapnp:get(b, Params), 103 | %% Results) 104 | %% end} 105 | %% ]), 106 | %% setup_meck(pipelines, [{getBasic, 107 | %% fun(_Params, Results) -> 108 | %% {ok, Basic} = ecapnp_capability:start('BasicCap', basicCap, test_capnp), 109 | %% ecapnp:set(basic, Basic, Results), ok 110 | %% end} 111 | %% ]), 112 | %% %% start server for capabilities 113 | %% {ok, Pipe} = ecapnp_capability:start('Pipelines', pipelines, test_capnp), 114 | %% %% prepare request 115 | %% {ok, Request} = ecapnp_capability:request(getBasic, Pipe), 116 | %% check_request('Pipelines', getBasic, Request), 117 | %% %% send request 118 | %% {ok, Result} = ecapnp_capability:send(Request), 119 | %% %% check_promise(...), 120 | %% %% pipeline request 121 | %% Basic = ecapnp:get(basic, Result), 122 | %% {ok, PipeRequest} = ecapnp_capability:request(add, Basic), 123 | %% PipeParam = ecapnp_capability:param(PipeRequest), 124 | %% ok = ecapnp:set(a, 111, PipeParam), 125 | %% ok = ecapnp:set(b, 222, PipeParam), 126 | %% {ok, PipeResult} = ecapnp_capability:send(PipeRequest), 127 | %% %% wait for then verify response 128 | %% ok = ecapnp_capability:wait(Result), 129 | %% ok = ecapnp_capability:wait(PipeResult), 130 | %% ?assertEqual(333, ecapnp:get(result, PipeResult)), 131 | %% %% clean up 132 | %% ecapnp_capability:stop(Pipe), 133 | %% teardown_meck(basicCap), 134 | %% teardown_meck(pipelines). 135 | 136 | 137 | %% check_request(Cap, Method, Req) -> 138 | %% #request{ method=ActualMethod, param=Object } = Req, 139 | %% ?assertEqual(Method, ActualMethod), 140 | %% {ok, Node} = ecapnp_schema:lookup(Cap, test_capnp), 141 | %% #method{ paramType=ParamType } 142 | %% = lists:keyfind(Method, #method.name, 143 | %% (Node#schema_node.kind)#interface.methods), 144 | %% {ok, Params} = ecapnp_schema:lookup(ParamType, test_capnp), 145 | %% #object{ schema=ParamsNode, ref=ParamsRef } = Object, 146 | %% ?assertEqual(Params, ParamsNode), 147 | %% #schema_node{ kind=#struct{ dsize=DSize, psize=PSize } } = Params, 148 | %% ?assertEqual( 149 | %% #ref{ segment=0, pos=0, offset=0, 150 | %% data=ParamsRef#ref.data, %% don't care (it's a new pid every time) 151 | %% kind=#struct_ref{ dsize=DSize, psize=PSize } }, 152 | %% ParamsRef). 153 | 154 | 155 | -endif. 156 | -------------------------------------------------------------------------------- /test/eunit_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2013, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(eunit_SUITE). 18 | -ifdef(TEST). 19 | -export([all/0, run_eunit/1]). 20 | 21 | all() -> 22 | [run_eunit]. 23 | 24 | run_eunit(_Config) -> 25 | case eunit:test( 26 | [ecapnp_val, ecapnp_ref, ecapnp_obj, ecapnp_get, 27 | ecapnp_set, ecapnp_capability 28 | ]) 29 | of 30 | ok -> ok; 31 | Error -> ct:fail("eunit:test(...) == ~p.", [Error]) 32 | end. 33 | 34 | -endif. 35 | -------------------------------------------------------------------------------- /test/proper_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright 2014, Andreas Stenius 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | 17 | -module(proper_SUITE). 18 | 19 | -export([all/0, run_proper/1]). 20 | 21 | all() -> 22 | [run_proper]. 23 | 24 | run_proper(_Config) -> 25 | Options = [], %%quiet], 26 | [begin 27 | [] = proper:check_specs(Module, Options), 28 | [] = proper:module(Module, Options) 29 | end || Module <- [ecapnp_props] 30 | ], 31 | ok. 32 | -------------------------------------------------------------------------------- /test/test.capnp: -------------------------------------------------------------------------------- 1 | # test.capnp 2 | @0xe87e0317861d75a1; 3 | $testAnno("file anno 2013"); 4 | annotation testAnno @0xf0fabdffa4323aca (*) :Text; 5 | struct Test @0xfa556038e27b336d $testAnno("Test struct anno 2013 too") { # 16 bytes, 6 ptrs 6 | intField @0 :UInt8 = 33; # bits[0, 8) 7 | textField @1 :Text = "test"; # ptr[0] 8 | structField @13 :Simple; # ptr[4] 9 | union { # tag bits [16, 32) 10 | boolField @2 :Bool; # bits[8, 9), union tag = 0 11 | groupField :group { # union tag = 1 12 | a @3 :Int8 = -44; # bits[8, 16) 13 | b @4 :Int8 = 55; # bits[32, 40) 14 | c @5 :Int8; # bits[40, 48) 15 | } 16 | } 17 | opts :group { 18 | union { # tag bits [64, 80) 19 | bool @6 :Bool = true; # bits[48, 49), union tag = 0 20 | text @7 :Text; # ptr[1], union tag = 1 21 | data @8 :Data; # ptr[1], union tag = 2 22 | object @9 :AnyPointer; # ptr[1], union tag = 3 23 | } 24 | } 25 | meta :group { 26 | id @10 :UInt16; # bits[80, 96) 27 | tag @11 :Text; # ptr[2] 28 | data @12 :Data = "1234"; # ptr[3] 29 | struct @14 :Simple = (message = "overriden default message", value = 222, defaultValue = 321); # ptr[5] 30 | } 31 | } 32 | struct Simple @0xd16f318851f71be8 { # 8 bytes, 2 ptrs 33 | simpleMessage @2 :Text = "simple message"; # ptr[1] 34 | message @0 :Text = "default message"; # ptr[0] 35 | value @1 :UInt32 = 222; # bits[0, 32) 36 | defaultValue @3 :UInt32 = 333; # bits[32, 64) 37 | } 38 | struct ListTest @0xed15f6a91b7977a6 { # 0 bytes, 4 ptrs 39 | listInts @0 :List(Int32) = [456, 789, -123]; # ptr[0] 40 | listAny @1 :AnyPointer; # ptr[1] 41 | listSimples @2 :List(Simple) = [(message = "first", value = 1, defaultValue = 333), (message = "second", value = 2, defaultValue = 333)]; # ptr[2] 42 | listText @3 :List(Text); # ptr[3] 43 | } 44 | interface BasicCap @0xf329462caa09f38f { 45 | add @0 (a :Int64, b :Int64) -> (result :Int64); 46 | sub @1 (a :Int64, b :Int64) -> (result :Int64); 47 | } 48 | interface Pipelines @0xde7af08d2279ac69 { 49 | getBasic @0 () -> (basic :BasicCap); 50 | } 51 | interface OtherCap @0x9000899726987e7f { 52 | sqroot @0 (a :Int64) -> (root1 :Float64, root2 :Float64); 53 | } 54 | interface ThirdCap @0xfb84e23a9ca71e0e extends(BasicCap, OtherCap) { 55 | square @0 (a :Int64) -> (sq :Int64); 56 | } 57 | struct CapTest @0xf21107d21522cfc8 { # 0 bytes, 2 ptrs 58 | basic @0 :BasicCap; # ptr[0] 59 | obj @1 :AnyPointer; # ptr[1] 60 | } 61 | struct UnionTest @0xeb197971909ff76e { # 8 bytes, 1 ptrs 62 | union { # tag bits [16, 32) 63 | foo @0 :Bool; # bits[0, 1), union tag = 0 64 | test @1 :Test; # ptr[0], union tag = 1 65 | any @2 :AnyPointer; # ptr[0], union tag = 2 66 | } 67 | } 68 | struct PackedListTest @0xcd8b31ac54a1e6a6 { # 0 bytes, 1 ptrs, packed as pointer 69 | packedList @0 :List(Opts); # ptr[0] 70 | struct Opts @0xf21da93a2b273687 { # 8 bytes, 0 ptrs, packed as 16-bit 71 | flag @0 :Bool; # bits[0, 1) 72 | value @1 :UInt8; # bits[8, 16) 73 | toggle @2 :Bool; # bits[1, 2) 74 | } 75 | } 76 | --------------------------------------------------------------------------------