Runtime Requirements

The magic-script-cli tool was written to make development of MagicScript applications easier and more like existing JavaScript workflows, but it's useful to understand the actual runtime requirements of the platform independent of this tool.

This information can be used to better understand the internals of the platform, to know how to better contribute to the CLI tool, or to write your own personal workflow if that suits you.

Security Model

Magic Leap takes security seriously and has designed MagicScript a little differently than popular JavaScripts runtimes such as the web or node.js.

All executed code must be signed. For native C++ applications this is done by mabu build... when the binary is created as part of the compilation step. The native elf format used by linux allows arbitrary binary data to be appended to the executible and it is typically ignored by the operating system.

In LuminOS applications, this space is used to append metadata and cryptographic signatures. At runtime the kernel will verify these signatures and read this metadata before executing binary files to fine-tune it's security profile.

JavaScript compiles code at runtime instead of at build time so things need to work differently.

Digest File

Appending binary metadata and signature to JavaScript source files is a not a good solution so another technique is used to verify the source code is exactly as authored.

MagicScript applications must include a digest.sha512.signed file in the mpk that contains the sha512 checksums of all JavaScript source files that are to be run on the device. This file is then signed using the same method used on native elf binaries.

Normally digest file is automatically created by the magic-script build... command. The CLI tool searches for all bin/**/*js files as it's input, writes the file and signs it using a script inside the mlsdk.

NOTE: There are no restrictions in the runtime to '.js files being under the bin folder, this is a convention of the CLI tool. The module system only requires that any module loaded at runtime be present in the digest with a correct checksum.

The digest file consists of 5 parts:

  • A fixed header containing "#\xc2\xa1\x7fsha512sum -c\n"
  • The result of sha512sum src/main.js other-file.js ... with all the source files needed at runtime.
  • A fixed footer containing "#\0"
  • Tail data created by ${MLSDK}/tools/mabu/src/ ....
  • A digital signature created by ${MLSDK}/tools/signer/sign-file -f sha512 ....

The JavaScript implementation is the official way to generate this file, but a Makefile rule can also be written to illustrate the algorithm with less code noise.

# Guess current mlsdk root folder based on path to mabu
MLSDK := $(shell dirname $(shell which mabu))
# Generate the digest, append taildata and sign it.
digest.sha512.signed: $(shell find src -name '*.js')
/bin/echo -ne '#\xc2\xa1\x7fsha512sum -c\n' > $@
sha512sum $^ >> $@
/bin/echo -ne '#\0' >> $@
${MLSDK}/tools/python3/bin/python3.5 \
${MLSDK}/tools/mabu/src/ \
--sbox USER --debuggable $@
${MLSDK}/tools/signer/sign-file \
-f sha512 \
$(MLCERT:cert=privkey) \

No Eval

In addition to the digest file restricting loading of modules at runtime to source files with signed digests, MagicScript also disables all forms of eval in the V8 context. This means you can not use eval(someCode) or new Function("code") or anything considered eval by the runtime. This restriction ensures that the code authored at time of app submission is the only code run at runtime.

Application Layout

Consider the following minimal example app:

├── app.mabu
├── app.package
├── digest.sha512.signed
├── manifest.xml
├── res
│   └── DamagedHelmet.glb
└── src
├── App.js
├── Controller.js
└── main.js

The app.mabu file is very small; it contains:

KIND = program

The app.package file tells mabu which files to include in the mpk. For this sample, we use:

DATAS = "digest.sha512.signed" : "." \
"src/" : "src/" \
"res/" : "res/"
OPTIONS = package/minApiLevel/2

Module System

The module system in MagicScript is very modern and minimal. In particular, it cannot load scripts or common-js modules, only ECMA modules using import/export syntax.

The entry point is defined in manifest.xml which contains the following section pointing to src/main.js.

<component ml:name=".universe"
ml:visible_name="Minimal Sample"

The src/main.js file itself must be marked as executible and start with #!/system/bin/script/mxs to tell the kernel at runtime how to run.

import { App } from './App.js';
new App();

Notice that the include path for src/App.js is a relative path from src/Main.js including the .js extension.

In src/App.js we have the following:

import { LandscapeApp } from 'lumin';
import { Controller } from './Controller.js';
export class App extends LandscapeApp {
onAppStart() {
let prism = this.requestNewPrism([0.5, 0.5, 0.5]);
prism.setPrismController(new Controller());

Notice that the lumin module is just the name. This is how built-in native modules are used. Currently there are 3 of them:

  • lumin - Exposes the Lumin Runtime C++ APIs to JavaScript
  • uv - Exposes libuv to JavaScript to enable network access, file access and timers.
  • ssl - Exposes a tiny subset of openssl to enable TLS streams to be implemented in JS.

Debug Mode

TODO: write more...