├── Makefile
├── sandstorm-pkgdef.capnp
├── client
└── index.html
├── README.md
└── server.c++
/Makefile:
--------------------------------------------------------------------------------
1 | CXX=g++
2 | CXXFLAGS=-O2 -Wall
3 | CXXFLAGS2=-std=c++1y -Itmp $(CXXFLAGS)
4 | SANDSTORM_CAPNP_DIR=/opt/sandstorm/latest/usr/include
5 |
6 | .PHONEY: all clean dev
7 |
8 | package.spk: server sandstorm-pkgdef.capnp
9 | spk pack package.spk
10 |
11 | dev: server sandstorm-pkgdef.capnp
12 | spk dev
13 |
14 | clean:
15 | rm -rf tmp server package.spk
16 |
17 | tmp/genfiles:
18 | @mkdir -p tmp
19 | capnp compile --src-prefix=$(SANDSTORM_CAPNP_DIR) -oc++:tmp $(SANDSTORM_CAPNP_DIR)/sandstorm/*.capnp
20 | @touch tmp/genfiles
21 |
22 | server: tmp/genfiles server.c++
23 | $(CXX) -static server.c++ tmp/sandstorm/*.capnp.c++ -o server $(CXXFLAGS2) `pkg-config capnp-rpc --cflags --libs`
24 |
--------------------------------------------------------------------------------
/sandstorm-pkgdef.capnp:
--------------------------------------------------------------------------------
1 | @0xeef286f78b0168e0;
2 | # When cloning the example, you'll want to replace the above file ID with a new
3 | # one generated using the `capnp id` command.
4 |
5 | using Spk = import "/sandstorm/package.capnp";
6 | # This imports:
7 | # $SANDSTORM_HOME/latest/usr/include/sandstorm/package.capnp
8 | # Check out that file to see the full, documented package definition format.
9 |
10 | const pkgdef :Spk.PackageDefinition = (
11 | # The package definition. Note that the spk tool looks specifically for the
12 | # "pkgdef" constant.
13 |
14 | id = "8e4wpgf664gqek74uy6cmq3kyw1xu0ztfj8ktwwa9yxv90ew24z0",
15 | # The app ID is actually the public key used to sign the app package.
16 | # All packages with the same ID are versions of the same app.
17 | #
18 | # If you are working from the example, you'll need to replace the above
19 | # public key with one of your own. Use the `spk keygen` command to generate
20 | # a new one.
21 |
22 | manifest = (
23 | # This manifest is included in your app package to tell Sandstorm
24 | # about your app.
25 |
26 | appTitle = (defaultText = "Raw API Example"),
27 |
28 | appVersion = 0, # Increment this for every release.
29 | appMarketingVersion = (defaultText = "0.0.0"),
30 |
31 | actions = [
32 | # Define your "new document" handlers here.
33 | ( title = (defaultText = "New Raw API Example Instance"),
34 | nounPhrase = (defaultText = "instance"),
35 | command = .myCommand
36 | # The command to run when starting for the first time. (".myCommand"
37 | # is just a constant defined at the bottom of the file.)
38 | )
39 | ],
40 |
41 | continueCommand = .myCommand
42 | # This is the command called to start your app back up after it has been
43 | # shut down for inactivity. Here we're using the same command as for
44 | # starting a new instance, but you could use different commands for each
45 | # case.
46 | ),
47 |
48 | sourceMap = (
49 | # Here we define where to look for files to copy into your package.
50 | searchPath = [
51 | ( packagePath = "server", sourcePath = "server" ),
52 | # Map server binary at "/server".
53 |
54 | ( packagePath = "client", sourcePath = "client" ),
55 | # Map client directory at "/client".
56 | ]
57 | ),
58 |
59 | alwaysInclude = [ "." ]
60 | # Always include all mapped files, whether or not they are opened during
61 | # "spk dev".
62 | );
63 |
64 | const myCommand :Spk.Manifest.Command = (
65 | # Here we define the command used to start up your server.
66 | argv = ["/server"],
67 | environ = [
68 | # Note that this defines the *entire* environment seen by your app.
69 | (key = "PATH", value = "/usr/local/bin:/usr/bin:/bin")
70 | ]
71 | );
72 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sandstorm Raw API sample app
5 |
6 |
82 |
83 |
84 | Hello World!
85 | This is client/index.html. Go edit it and refresh!
86 |
87 | The contents of the following textarea are automatically saved to the server
88 | via GET/PUT on the URL /var/content.
89 | It will be restored on change.
90 |
91 | The contents of the following textarea are loaded via GET on the URL
92 | /var/content. You do not have edit
93 | permission, so you can't edit it (a PUT would return 403), but you can
94 | click here to fetch the latest edits
95 | made by others.
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sandstorm Raw API Example App
2 |
3 | This is an example [Sandstorm](https://sandstorm.io) application which uses the raw [Cap'n Proto](https://capnproto.org)-based Sandstorm API to serve a web UI without an HTTP server. Most apps instead use `sandstorm-http-bridge` wrapping a traditional server, but this one does't.
4 |
5 | ## Why do that?
6 |
7 | You might want to write a Sandstorm app using the raw API if:
8 |
9 | * You want to be as efficient as possible. Raw API apps -- especially if written in C++ or Rust -- take drastically less RAM, start much faster, and produce much smaller distributable packages.
10 | * You want to avoid legacy cruft. A traditional HTTP server is a huge, complicated piece of code much of which is not important for Sandstorm.
11 | * You want to use a language that has poor HTTP support, but good Cap'n Proto RPC support, such as C++ or Rust.
12 |
13 | That said, you should prefer `sandstorm-http-bridge` around a traditional HTTP server if:
14 |
15 | * You are porting an existing HTTP-based app.
16 | * You want to build on top of a standard HTTP server framework.
17 | * You want to use a programming language that doesn't have good Cap'n Proto RPC support.
18 |
19 | ## Instructions
20 |
21 | 1. Get a Linux machine.
22 | 2. Install [Cap'n Proto](https://capnproto.org). You'll need to use the latest code from git, not a release version, because Sandstorm and Cap'n Proto are developed together and Sandstorm uses unreleased features from Cap'n Proto.
23 | 3. Install [Sandstorm](https://github.com/sandstorm-io/sandstorm) and make sure it is successfully running locally.
24 | 4. Clone this git repo.
25 | 5. CD to the repo and `make dev`.
26 | 6. Open your local Sandstorm. The app should be visible there.
27 | 7. Ctrl+C in the terminal running `make dev` to disconnect.
28 |
29 | _Note: You can ignore the bogus warning about `getaddrinfo` and static linking -- the app will never actually call `getaddrinfo`. We statically link the binary so that there's no need to include shared libs in the package, making it smaller and simpler._
30 |
31 | _Note: Due to an ongoing disagreement between [GCC's](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58074) and [Clang's](https://llvm.org/bugs/show_bug.cgi?id=23764) interpretations of the C++ ABI, it is important that you use the same compiler to compile this example program as you use to compile Cap'n Proto. By default both will use GCC. You can run `make CXX=clang++` to build with Clang instead._
32 |
33 | ## Building your own
34 |
35 | You can use this code as a starting point for your own app.
36 |
37 | 1. Edit `sandstorm-pkgdef.capnp`, read the comments, and edit. At the very least you want to rename the app and set a new App ID.
38 | 2. Place your own client code (HTML, CSS, Javascript, etc.) in the `client` directory.
39 | 3. Have your client code use HTTP GET, PUT, and DELETE requests to store user data under the HTTP path `/var`. You can create files under `/var` with any name, but you cannot create sub-directories (for now).
40 | 4. Use `make dev` to test.
41 | 5. Type just `make` to build a distributable package `package.spk`.
42 |
43 | Note that `server.c++` has some "features" that you may feel inclined to modify:
44 |
45 | * Only the owner of an app instance is allowed to write (i.e. issue PUT or DELETE requests under `/var`). Anyone can read (though they must of course first receive the secret URL from the owner). For many apps, it's more appropriate for everyone to have write access (again, provided they've received the URL). Search for `canWrite` in the code, and `getViewInfo`.
46 | * `Content-Type` headers are derived from file extensions, with only a small number of types supported. `index.html` is the default file for a directory. You can probably do better.
47 | * Various useful Sandstorm API features are not exposed. You could implement an HTTP API to get access to those features from your client app.
48 |
49 | ## Questions?
50 |
51 | [Ask on the dev group.](https://groups.google.com/group/sandstorm-dev)
52 |
--------------------------------------------------------------------------------
/server.c++:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2014 Sandstorm Development Group, Inc.
2 | // Licensed under the MIT License:
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | // Hack around stdlib bug with C++14.
23 | #include // force libstdc++ to include its config
24 | #undef _GLIBCXX_HAVE_GETS // correct broken config
25 | // End hack.
26 |
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 |
42 | #include
43 | #include
44 | #include
45 |
46 | namespace {
47 |
48 | #if __QTCREATOR
49 | #define KJ_MVCAP(var) var
50 | // QtCreator dosen't understand C++14 syntax yet.
51 | #else
52 | #define KJ_MVCAP(var) var = ::kj::mv(var)
53 | // Capture the given variable by move. Place this in a lambda capture list. Requires C++14.
54 | //
55 | // TODO(cleanup): Move to libkj.
56 | #endif
57 |
58 | typedef unsigned int uint;
59 | typedef unsigned char byte;
60 |
61 | // =======================================================================================
62 | // Utility functions
63 | //
64 | // Most of these should be moved to the KJ library someday.
65 |
66 | kj::AutoCloseFd createFile(kj::StringPtr name, int flags, mode_t mode = 0666) {
67 | // Create a file, returning an RAII wrapper around the file descriptor. Errors throw exceptinos.
68 |
69 | int fd;
70 | KJ_SYSCALL(fd = open(name.cStr(), O_CREAT | flags, mode), name);
71 | return kj::AutoCloseFd(fd);
72 | }
73 |
74 | size_t getFileSize(int fd, kj::StringPtr filename) {
75 | struct stat stats;
76 | KJ_SYSCALL(fstat(fd, &stats));
77 | KJ_REQUIRE(S_ISREG(stats.st_mode), "Not a regular file.", filename);
78 | return stats.st_size;
79 | }
80 |
81 | kj::Maybe tryOpen(kj::StringPtr name, int flags, mode_t mode = 0666) {
82 | // Try to open a file, returning an RAII wrapper around the file descriptor, or null if the
83 | // file doesn't exist. All other errors throw exceptions.
84 |
85 | int fd;
86 |
87 | while ((fd = open(name.cStr(), flags, mode)) < 0) {
88 | int error = errno;
89 | if (error == ENOENT) {
90 | return nullptr;
91 | } else if (error != EINTR) {
92 | KJ_FAIL_SYSCALL("open(name)", error, name);
93 | }
94 | }
95 |
96 | return kj::AutoCloseFd(fd);
97 | }
98 |
99 | bool isDirectory(kj::StringPtr filename) {
100 | // Return true if the parameter names a directory, false if it's any other kind of node or
101 | // doesn't exist.
102 |
103 | struct stat stats;
104 | while (stat(filename.cStr(), &stats) < 0) {
105 | if (errno != EINTR) {
106 | return false;
107 | }
108 | }
109 | return S_ISDIR(stats.st_mode);
110 | }
111 |
112 | kj::Vector listDirectory(kj::StringPtr dirname) {
113 | // Return a list of all filenames in the given directory, except "." and "..".
114 |
115 | kj::Vector entries;
116 |
117 | DIR* dir = opendir(dirname.cStr());
118 | if (dir == nullptr) {
119 | KJ_FAIL_SYSCALL("opendir", errno, dirname);
120 | }
121 | KJ_DEFER(closedir(dir));
122 |
123 | for (;;) {
124 | errno = 0;
125 | struct dirent* entry = readdir(dir);
126 | if (entry == nullptr) {
127 | int error = errno;
128 | if (error == 0) {
129 | break;
130 | } else {
131 | KJ_FAIL_SYSCALL("readdir", error, dirname);
132 | }
133 | }
134 |
135 | kj::StringPtr name = entry->d_name;
136 | if (name != "." && name != "..") {
137 | entries.add(kj::heapString(entry->d_name));
138 | }
139 | }
140 |
141 | return entries;
142 | }
143 |
144 | // =======================================================================================
145 | // WebSession implementation (interface declared in sandstorm/web-session.capnp)
146 |
147 | class WebSessionImpl final: public sandstorm::WebSession::Server {
148 | public:
149 | WebSessionImpl(sandstorm::UserInfo::Reader userInfo,
150 | sandstorm::SessionContext::Client context,
151 | sandstorm::WebSession::Params::Reader params) {
152 | // Permission #0 is "write". Check if bit 0 in the PermissionSet is set.
153 | auto permissions = userInfo.getPermissions();
154 | canWrite = permissions.size() > 0 && (permissions[0] & 1);
155 |
156 | // `UserInfo` is defined in `sandstorm/grain.capnp` and contains info like:
157 | // - A stable ID for the user, so you can correlate sessions from the same user.
158 | // - The user's display name, e.g. "Mark Miller", useful for identifying the user to other
159 | // users.
160 | // - The user's permissions (seen above).
161 |
162 | // `WebSession::Params` is defined in `sandstorm/web-session.capnp` and contains info like:
163 | // - The hostname where the grain was mapped for this user. Every time a user opens a grain,
164 | // it is mapped at a new random hostname for security reasons.
165 | // - The user's User-Agent and Accept-Languages headers.
166 |
167 | // `SessionContext` is defined in `sandstorm/grain.capnp` and implements callbacks for
168 | // sharing/access control and service publishing/discovery.
169 | }
170 |
171 | kj::Promise get(GetContext context) override {
172 | // HTTP GET request.
173 |
174 | auto path = context.getParams().getPath();
175 | requireCanonicalPath(path);
176 |
177 | if (path == "var" || path == "var/") {
178 | // Return a listing of the directory contents, one per line.
179 | auto text = kj::strArray(listDirectory("var"), "\n");
180 | auto response = context.getResults().initContent();
181 | response.setMimeType("text/plain");
182 | response.getBody().setBytes(
183 | kj::arrayPtr(reinterpret_cast(text.begin()), text.size()));
184 | return kj::READY_NOW;
185 | } else if (path.startsWith("var/")) {
186 | // Serve all files under /var with type application/octet-stream since it comes from the
187 | // user. E.g. serving as "text/html" here would allow someone to trivially XSS other users
188 | // of the grain by PUTing malicious HTML content. (Such an attack wouldn't be a huge deal:
189 | // it would only allow the attacker to hijack another user's access to this grain, not to
190 | // Sandstorm in general, and if they attacker already has write access to upload the
191 | // malicious content, they have little to gain from hijacking another session.)
192 | return readFile(path, context, "application/octet-stream");
193 | } else if (path == ".can-write") {
194 | // Fetch "/.can-write" to determine if the user has write permission, so you can show them
195 | // a different UI if not.
196 | auto response = context.getResults().initContent();
197 | response.setMimeType("text/plain");
198 | response.getBody().setBytes(kj::str(canWrite).asBytes());
199 | return kj::READY_NOW;
200 | } else if (path == "" || path.endsWith("/")) {
201 | // A directory. Serve "index.html".
202 | return readFile(kj::str("client/", path, "index.html"), context, "text/html; charset=UTF-8");
203 | } else {
204 | // Request for a static file. Look for it under "client/".
205 | auto filename = kj::str("client/", path);
206 |
207 | // Check if it's a directory.
208 | if (isDirectory(filename)) {
209 | // It is. Return redirect to add '/'.
210 | auto redirect = context.getResults().initRedirect();
211 | redirect.setIsPermanent(true);
212 | redirect.setSwitchToGet(true);
213 | redirect.setLocation(kj::str(path, '/'));
214 | return kj::READY_NOW;
215 | }
216 |
217 | // Regular file (or non-existent).
218 | return readFile(kj::mv(filename), context, inferContentType(path));
219 | }
220 | }
221 |
222 | kj::Promise put(PutContext context) override {
223 | // HTTP PUT request.
224 |
225 | auto params = context.getParams();
226 | auto path = params.getPath();
227 | requireCanonicalPath(path);
228 |
229 | KJ_REQUIRE(path.startsWith("var/"), "PUT only supported under /var.");
230 |
231 | if (!canWrite) {
232 | context.getResults().initClientError()
233 | .setStatusCode(sandstorm::WebSession::Response::ClientErrorCode::FORBIDDEN);
234 | } else {
235 | auto tempPath = kj::str(path, ".uploading");
236 | auto data = params.getContent().getContent();
237 |
238 | kj::FdOutputStream(createFile(tempPath, O_WRONLY | O_TRUNC))
239 | .write(data.begin(), data.size());
240 |
241 | KJ_SYSCALL(rename(tempPath.cStr(), path.cStr()));
242 | context.getResults().initNoContent();
243 | }
244 |
245 | return kj::READY_NOW;
246 | }
247 |
248 | kj::Promise delete_(DeleteContext context) override {
249 | // HTTP DELETE request.
250 |
251 | auto path = context.getParams().getPath();
252 | requireCanonicalPath(path);
253 |
254 | KJ_REQUIRE(path.startsWith("var/"), "DELETE only supported under /var.");
255 |
256 | if (!canWrite) {
257 | context.getResults().initClientError()
258 | .setStatusCode(sandstorm::WebSession::Response::ClientErrorCode::FORBIDDEN);
259 | } else {
260 | while (unlink(path.cStr()) != 0) {
261 | int error = errno;
262 | if (error == ENOENT) {
263 | // Ignore file-not-found for idempotency.
264 | break;
265 | } else if (error != EINTR) {
266 | KJ_FAIL_SYSCALL("unlink", error);
267 | }
268 | }
269 | }
270 |
271 | return kj::READY_NOW;
272 | }
273 |
274 | private:
275 | bool canWrite;
276 | // True if the user has write permission.
277 |
278 | void requireCanonicalPath(kj::StringPtr path) {
279 | // Require that the path doesn't contain "." or ".." or consecutive slashes, to prevent path
280 | // injection attacks.
281 | //
282 | // Note that such attacks wouldn't actually accomplish much since everything outside /var
283 | // is a read-only filesystem anyway, containing the app package contents which are non-secret.
284 |
285 | KJ_REQUIRE(!path.startsWith("/"));
286 | KJ_REQUIRE(!path.startsWith("./") && path != ".");
287 | KJ_REQUIRE(!path.startsWith("../") && path != "..");
288 |
289 | KJ_IF_MAYBE(slashPos, path.findFirst('/')) {
290 | requireCanonicalPath(path.slice(*slashPos + 1));
291 | }
292 | }
293 |
294 | kj::StringPtr inferContentType(kj::StringPtr filename) {
295 | if (filename.endsWith(".html")) {
296 | return "text/html; charset=UTF-8";
297 | } else if (filename.endsWith(".js")) {
298 | return "text/javascript; charset=UTF-8";
299 | } else if (filename.endsWith(".css")) {
300 | return "text/css; charset=UTF-8";
301 | } else if (filename.endsWith(".png")) {
302 | return "image/png";
303 | } else if (filename.endsWith(".gif")) {
304 | return "image/gif";
305 | } else if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
306 | return "image/jpeg";
307 | } else if (filename.endsWith(".svg")) {
308 | return "image/svg+xml; charset=UTF-8";
309 | } else if (filename.endsWith(".txt")) {
310 | return "text/plain; charset=UTF-8";
311 | } else {
312 | return "application/octet-stream";
313 | }
314 | }
315 |
316 | kj::Promise readFile(
317 | kj::StringPtr filename, GetContext context, kj::StringPtr contentType) {
318 | KJ_IF_MAYBE(fd, tryOpen(filename, O_RDONLY)) {
319 | auto size = getFileSize(*fd, filename);
320 | kj::FdInputStream stream(kj::mv(*fd));
321 | auto response = context.getResults(capnp::MessageSize { size / sizeof(capnp::word) + 32, 0 });
322 | auto content = response.initContent();
323 | content.setStatusCode(sandstorm::WebSession::Response::SuccessCode::OK);
324 | content.setMimeType(contentType);
325 | stream.read(content.getBody().initBytes(size).begin(), size);
326 | return kj::READY_NOW;
327 | } else {
328 | auto error = context.getResults().initClientError();
329 | error.setStatusCode(sandstorm::WebSession::Response::ClientErrorCode::NOT_FOUND);
330 | return kj::READY_NOW;
331 | }
332 | }
333 | };
334 |
335 | // =======================================================================================
336 | // UiView implementation (interface declared in sandstorm/grain.capnp)
337 |
338 | class UiViewImpl final: public sandstorm::UiView::Server {
339 | public:
340 | kj::Promise getViewInfo(GetViewInfoContext context) override {
341 | auto viewInfo = context.initResults();
342 |
343 | // Define a "write" permission, and then define roles "editor" and "viewer" where only "editor"
344 | // has the "write" permission. This will allow people to share read-only.
345 | auto perms = viewInfo.initPermissions(1);
346 | auto write = perms[0];
347 | write.setName("write");
348 | write.initTitle().setDefaultText("write");
349 |
350 | auto roles = viewInfo.initRoles(2);
351 | auto editor = roles[0];
352 | editor.initTitle().setDefaultText("editor");
353 | editor.initVerbPhrase().setDefaultText("can edit");
354 | editor.initPermissions(1).set(0, true); // has "write" permission
355 | auto viewer = roles[1];
356 | viewer.initTitle().setDefaultText("viewer");
357 | viewer.initVerbPhrase().setDefaultText("can view");
358 | viewer.initPermissions(1).set(0, false); // does not have "write" permission
359 |
360 | return kj::READY_NOW;
361 | }
362 |
363 | kj::Promise newSession(NewSessionContext context) override {
364 | auto params = context.getParams();
365 |
366 | KJ_REQUIRE(params.getSessionType() == capnp::typeId(),
367 | "Unsupported session type.");
368 |
369 | context.getResults().setSession(
370 | kj::heap(params.getUserInfo(), params.getContext(),
371 | params.getSessionParams().getAs()));
372 |
373 | return kj::READY_NOW;
374 | }
375 | };
376 |
377 | // =======================================================================================
378 | // Program main
379 |
380 | class ServerMain {
381 | public:
382 | ServerMain(kj::ProcessContext& context): context(context), ioContext(kj::setupAsyncIo()) {}
383 |
384 | kj::MainFunc getMain() {
385 | return kj::MainBuilder(context, "Sandstorm Thin Server",
386 | "Intended to be run as the root process of a Sandstorm app.")
387 | .callAfterParsing(KJ_BIND_METHOD(*this, run))
388 | .build();
389 | }
390 |
391 | kj::MainBuilder::Validity run() {
392 | // Set up RPC on file descriptor 3.
393 | auto stream = ioContext.lowLevelProvider->wrapSocketFd(3);
394 | capnp::TwoPartyVatNetwork network(*stream, capnp::rpc::twoparty::Side::CLIENT);
395 | auto rpcSystem = capnp::makeRpcServer(network, kj::heap());
396 |
397 | // The `CLIENT` side of a `capnp::TwoPartyVatNetwork` does not serve its bootstrap capability
398 | // until it has initiated a request for the bootstrap capability of the `SERVER` side.
399 | // Therefore, we need to restore the supervisor's `SandstormApi` capability, even if we are not
400 | // going to use it.
401 | {
402 | capnp::MallocMessageBuilder message;
403 | auto vatId = message.getRoot();
404 | vatId.setSide(capnp::rpc::twoparty::Side::SERVER);
405 | sandstorm::SandstormApi<>::Client api =
406 | rpcSystem.bootstrap(vatId).castAs>();
407 | }
408 |
409 | kj::NEVER_DONE.wait(ioContext.waitScope);
410 | }
411 |
412 | private:
413 | kj::ProcessContext& context;
414 | kj::AsyncIoContext ioContext;
415 | };
416 |
417 | } // anonymous namespace
418 |
419 | KJ_MAIN(ServerMain)
420 |
--------------------------------------------------------------------------------