Component packages

Structure

Component packages are ordinary zip files with a .vapkg file name extension. A component package can contain multiple components and a license. This makes it possible to distribute a fully functional component with all of its dependencies in a single file.

When component packages are created by the Builder, they include exactly one component. Packages that contain many components and licenses are only created by the cloud service. In the example below, the licenses/ directory is added by the cloud.

The structure of a component package is, for example, as follows:

com.visionappster.mvp/
com.visionappster.mvp/1/
com.visionappster.mvp/1/component.json
com.visionappster.mvp/1/component.json.crt
com.visionappster.mvp/1/windows-x86_64/plugin.dll
com.visionappster.mvp/1/linux-x86_64/libplugin.so
com.visionappster.mvp/1/macos-x86_64/libplugin.dylib
com.visionappster.mvp/1/linux-arm/libplugin.so
com.visionappster.mvp/1/Fancy.toolc
com.visionappster.mvp/1/model.onnx
licenses/
licenses/license.json
licenses/license.json.crt
licenses/config

Each component is stored under a path that is composed of the component ID and the major version number of the component. This means that multiple copies of the same component with different major versions can coexists. Different minor or patch versions don't need to because the requirement is that they are backwards compatible within the major version.

The file system structure of the component itself can be anything, but the example above shows which conventions should be followed for directory names. The component.json file describes how each file should be interpreted.

General configuration

Each version directory must contain a file called component.json. The contents of this file are as follows:

{
  "componentId": "com.visionappster.mvp",
  "version": "1.2.3",
  "name": "VisionAppster minimum viable product",
  "uuid": "c1624182-9ff6-44e4-a887-95d533fc78e4",
  "archs": [
    "windows-x86_64",
    "linux-x86_64",
    "linux-arm",
    "macos-x86_64"
  ],
  "files": [
    {
      "arch": "windows-x86_64",
      "filename": "windows-x86_64/plugin.dll",
      "checksum": "21258fdfd376ccce934f9364e55a47242e610101",
      "type": "toolplugin"
    },
    {
      "arch": "linux-x86_64",
      "filename": "linux-x86_64/libplugin.so",
      "checksum": "e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e",
      "type": "toolplugin"
    },
    {
      "arch": "macos-x86_64",
      "filename": "macos-x86_64/libplugin.dylib",
      "checksum": "7448d8798a4380162d4b56f9b452e2f6f9e24e7a",
      "type": "toolplugin"
    },
    {
      "arch": "linux-arm",
      "filename": "linux-arm/libplugin.so",
      "checksum": "5d9474c0309b7ca09a182d888f73b37a8fe1362c",
      "type": "toolplugin"
    },
    {
      "filename": "Fancy.toolc",
      "checksum": "a8fdc205a9f19cc1c7507a60c4f01b13d11d7fd0",
      "type": "tool"
    },
    {
      "filename", "model.onnx",
      "checksum", "39e342e32171661a2b44916687b0fc2514fe58bc"
    }
  ],
  "depends": [
    {
      "componentId": "com.l33tcoder.fancycomponent",
      "version": ">=1.2"
    },
    {
      "componentId": "system.engine",
      "version": ">=3.0"
    }
  ]
}

Note that the checksums are shortened for display. The actual sums are longer.

The file must contain the componentId and version keys. componentId must match the directory name. If a component contains binary plugins implemented using the tool interface, the COMPONENT_ID parameter of the VA_IMPLEMENT_PLUGIN or VA_IMPLEMENT_LICENSED_PLUGIN macro must also match the component ID. The version field contains the exact version number of the component.

The name and uuid fields are for the cloud service and only required if the component is uploaded to the store.

The archs fields provides a way to list the architectures the component supports. If the component contains no architecture-dependent machine code and therefore runs on any platform, archs can be omitted. The final list of supported architectures is the intersection of the archs lists of all dependent components and the component itself.

If the component requires other components to be functional, those components are listed in the depends array. The componentId field contains the component ID of the dependency and the version field specifies the compatible version numbers.

Compatible version numbers are specified either as a string that contains a single rule or an array of such rule strings. If an array of strings is given, all rules must be matched (logical and). Each rule contains an operator and a three-part version number in which trailing zeros may be omitted.

Recognized operators are >, <, >=, <= and =. For example, [">=2", "<=3"] states that the major version number must be between 2 and 3, inclusive. If version is "=2", the component is compatible with any version within major version 2, but nothing else. Special care must be taken with equality and less than checks. They should be used with major versions only because upgrades within the same major version are required to be compatible and thus always allowed.

In dependencies, components IDs starting with "system." refer to system components and let one to specify version requirements for the underlying VisionAppster runtime. In the example above, the component specifies it requires Engine version 3.0 or greater.

Finally, the files array lists all files contained in the package. Each must be accompanied with the filename and checksum keys. If type is omitted, the file is treated as a generic resource file with no special meaning.

Tools

Tools can be stored either as machine code plugins or as architecture-independent tool files. Any file that specifies toolplugin as its type must also contain an arch field that specifies the target processor architecture and operating system.

If the type of a file is tool, the file contains an architecture-independent tool saved by the Builder. The base name of the tool file will be used as the name of the tool. It should be noted that although the tool file itself is architecture-independent, it may refer to tools that are available on certain platforms only.

Resource files

A component may require external files to be functional. Typical examples include images and machine learning models. Sometimes, these can be baked into binaries, but this is not always possible or even useful. The type of a resource file is file and can be omitted.

Whenever a component refers to a file in itself or another component at run time, the path is relative to the component installation root directory. The Engine resolves the full path based on the component ID and version number of the referred component.

For example, if a component references res://com.example.foobar/1/model.onnx, the Engine determines where components are installed in the system and inserts the absolute root path to the beginning. resulting e.g. in /opt/visionappster/components/com.example.foobar/1/model.onnx.

Apps

The structure of a component package containing a single analysis app is like this:

com.visionappster.awesome.app/1/
com.visionappster.awesome.app/1/component.json
com.visionappster.awesome.app/1/component.json.crt
com.visionappster.awesome.app/1/appconfig.json
com.visionappster.awesome.app/1/main.qml
com.visionappster.awesome.app/1/engine.toolc

The contents of component.json would be as follows:

{
  "componentId": "com.visionappster.awesome.app",
  "version": "1.0.0",
  "name": "So cool app",
  "uuid": "eaec7b02-9ce0-4bbb-acde-50c26be0f35a",
  "files": [
    {
      "filename": "appconfig.json",
      "checksum": "7d97e98f8af710c7e7fe703abc8f639e0ee507c4",
      "type": "app"
    },
    {
      "filename": "main.qml",
      "checksum": "b8bd228ae7f3ff0934cfe36708053db68cabcaf7"
    },
    {
      "filename": "engine.toolc",
      "checksum": "75dffe083d16dee7f91404a1b66d37088da21379"
    }
  ],
  "depends": [
    {
      "componentId": "com.visionappster.mvp",
      "version": ">=1.0.0"
    },
    {
      "componentId": "system.engine",
      "version": ">=2.9"
    }
  ]
}

The type of appconfig.json is app, which tells the Engine to treat it as an executable application. The appconfig.json contains execution instructions such as a reference to the main component (main.qml).

Although there is a digital signature for the component.json file and a checksum for all of the app's files, this is only for integrity checking and keeping things consistent, not for enforcing the license of the app's script code (main.qml). There is no way to identify the script code, which means that nothing prevents users from copying the file and running the app elsewhere. The copied app will however fail if it tries to load the engine.toolc file.

An important thing to note is that the type of engine.toolc is not specified. If it was, the Engine would make com.visionappster.awesome.app/1/engine available as a tool everywhere. This may be wanted sometimes, but in a typical use case the tool is not generally usable and loaded directly from the file by the app's script code. Therefore, it is considered just another file in the package.

Web apps

For the VisionAppster engine, web apps are just files that are served through the built-in web server. In the simplest case, the structure of the component package can be for example like this:

com.visionappster.awesome.web/1/
com.visionappster.awesome.web/1/component.json
com.visionappster.awesome.web/1/component.json.crt
com.visionappster.awesome.web/1/index.html

The contents of component.json would be:

{
  "componentId": "com.visionappster.awesome.web",
  "version": "1.0.1",
  "name": "Cooler than ice web app",
  "uuid": "d816b989-8ebe-4701-9847-4d4658233484",
  "files": [
    {
      "filename": "index.html",
      "checksum": "244aa7266b3f5a08321b403b2c59baeba5539b19",
      "type": "webapp"
    }
  ]
}

Here, the type field is optional because any file in the component can be accessed through the Engine's web server under /components/com.visionappster.awesome.web/1/. The type however makes it possible for the Engine's front page to know that there actually is something that is intended to be used as a web user interface.

It is possible for a web app to depend on another web app. For example, it is possible to create a library component that only provides JavaScript files other apps depend on. Such library components just don't have an index.html that would work as the user interface. In other web apps, the files in the library will be accessible using URLs that are relative to the server's root, for example /components/com.visionappster.weblib/1/library.js.

It is also possible to bundle the web page with all of its resources into a single zip archive. In this case the zip works as if it was a directory. This approach has some performance benefits. By convention, webapp zips should have a .webapp file name extension.

Checksums

All files must be accompanied with a checksum, which is a 64-byte Blake2b hash of the file's contents, represented as a hexadecimal number with lowercase letters (128 characters in total). The checksum is used to make sure that a licensed file has not been modified after publication. It is also used to ensure the integrity of all other files and for detecting if a file has been changed by the user after installation. This information is useful when uninstalling a component. Loading a licensed file will fail if the checksum of a file doesn't match.

Signatures

When a component package is distributed through the VisionAppster Store, component.json and possible license files are digitally signed by the cloud service. The signature is stored in a .crt file alongside the signed file. If an installed component has a signature, its validity will be checked when loading the component. Licensed components distributed through the Store can only be loaded if they have a valid signature.

Licenses

A component package can optionally contain license files and their signatures in a directory called licenses. This makes it possible for the cloud service to craft self-contained packages that don't require extra licensing steps after installation.

In addition to the license file and its certificate, a component package can contain a configuration file (licenses/config) that contains the information required to fetch a new license file from the cloud service. The file format is such that it can be easily parsed in any programming language and the shell. Two variables are currently recognized: url specifies where a new license file can be obtained for the user and key is an API key for authorization.

url=https://va-engine.com/api/1/users/id-here/licenses.vapkg
key=6ee144ad-0bbb-4bf8-aeb4-f292febbe5d1

Copy protection

The VisionAppster engine provides protection against unauthorized copying of licensed files. Currently, tools (.tool) and tool plugins (.so, .dll, .dylib) can be protected.

To protect a binary tool plugin all one needs to do is to use the VA_IMPLEMENT_LICENSED_PLUGIN macro (details here) and pass it the component's ID and version. This embeds a component ID check to the auto-generated plugin code. The platform verifies the integrity of the plugin binary and the validity of its license.

To protect a tool saved in the Builder one does not need to do anything. The Builder automatically encrypts all .tool files when a component is being created for the cloud. This will transform the (about human-readable) .tool files into a .toolc files that can only be opened once they has been signed by the cloud service.