KasperskyOS Community Edition 1.0
[Topic kos_api_reference]

Description of entities, components, and interfaces (EDL, CDL, IDL)

All entities, components and interfaces used in the solution must be statically described using the EDL, CDL and IDL languages. The corresponding descriptions are saved in the *.edl, *.cdl and *.idl files, respectively.

Description files are used when building a solution for the following purposes:

In this Help section

Entity-Component-Interface model

EDL

CDL

IDL

IDL data types

Managing errors in IDL

Composite names of entities, components and interfaces

Page top
[Topic static_definitions]

Entity-Component-Interface model

Each entity can provide one or more interaction interfaces. Multiple interfaces can be combined into a component. Components can be embedded into other components. All entities, components and interfaces used in the solution must be statically described.

In KasperskyOS, there are three types of static description files:

  • An EDL file contains a description of the entity in the Entity Definition Language (EDL): its name, components and interfaces, and other information.
  • A CDL file contains a description of the component in the Component Definition Language,hereinafter referred to as CDL. The component description includes its name, set of included components and implemented interfaces.
  • An IDL file contains a description of the package containing one interface in the Interface Definition Language (IDL). The description includes the package name, a declaration of the interface included in it, and a declaration of the types and named constants.

To ensure flexibility, an entity can contain several instances of one component. Multiple entities can include instances of the same component.

An entity does not have to contain any component and does not have to implement an interface. If this is the case, it does not provide functionality that is available to other entities.

The Server entity includes instances of the Terminal and Serial components containing various implementations of the Console interface.

Page top
[Topic entity_component_interface_model]

EDL

Each entity in KasperskyOS must be described in the Entity Definition Language (EDL), in a separate file named <entity name>.edl.

The name of an EDL file must match the name of the entity that it describes.

An EDL file contains the following sections – the order is important:

  1. Name of the entity. Required section beginning with the reserved word entity, which is followed by the entity name.

    The entity name must begin with an uppercase letter and must not contain an underscore (_).

  2. Security interface used by the entity. This section is optional. It must be added only if the entity uses a security interface. It is declared with the reserved word security, which should be followed by the full name of the interface.
  3. List of implementations of interfaces that are provided by the entity. This is optional, and is described in the interfaces section. Each interface implementation is specified with a separate string in the following format:

    interfaces {

    <interface implementation name>:<interface name>

    }

    An entity can contain several implementations of one interface. All implemented interfaces must be described in the IDL language in IDL files.

    The interface implementation name must not contain an underscore (_).

  4. List of instances of components included in the entity. This is optional, and is described in the components section. Each component instance is specified with a separate string in the following format:

    components {

    <component instance name>:<component name>

    }

    For each specified component, a separate file named <component name>.cdl needs to be created, containing a description of the component in the CDL language. Multiple instances of the same component can be added to an entity, and each instance can have a separate state (see the example of UartDriver entity below).

    The component instance name must not contain an underscore (_).

The interfaces and components sections are optional and are added only for server entities. Implementations of interfaces declared in the interfaces section and implementations that are included in components from the components section can be used by client entities.

EDL supports single-line comments and multi-line C++-style comments:

/* This is a comment

And this, too */

// Another comment

Examples of EDL files

At its simplest, the entity does not use a security interface and does not provide functionality to other entities, like the hello entity from the hello example. An EDL description for such an entity contains only the reserved word entity and the entity name.

Hello.edl

// Entity name: Hello

entity Hello

In the following example, the EFoo entity contains a single interface implementation and does not use a security interface.

Efoo.edl

// Entity name: Efoo

entity Efoo

// The entity contains a named implementation of the IFool interface. Implementation name: foo.

interfaces {

foo : IFoo

}

In the following example, the UartDriver entity contains two instances of the UartComp component: one for each UART device.

The UartDriver entity does not use a security interface.

UartDriver.edl

// Entity name: UartDriver

entity UartDriver

// uart0 and uart1 are the names of instances of the UartComp component

// that are responsible for two different devices

components {

uart0: UartComp

uart1: UartComp

}

Page top
[Topic edl]

CDL

Each component used in the solution must be described in the CDL language, in a separate <component name>.cdl file.

The name of a CDL file must match the name of the component that it describes.

A CDL file includes the following sections:

  1. The name of the component. The reserved word component is placed before the component name.

    The component name must begin with an uppercase letter and must not contain an underscore (_).

  2. Security interface within this component. This section is optional. It must be added only if the component contains a security interface. It is declared with the reserved word security, which should be followed by the full name of the interface.
  3. A list of interface implementations included in this component. Interfaces are declared in the interfaces section in which each interface implementation is specified with a separate string in the following format:

    interfaces {

    <interface implementation name>:<interface name>

    }

    A component can contain several implementations of one interface. All implemented interfaces must be described in the IDL language in IDL files.

    The interface implementation name must not contain an underscore (_).

  4. The list of instances of components embedded in this component. This section is optional. It should be added if the component contains embedded components. Components are declared in the components section in which each component instance is specified with a separate string in the following format:

    components {

    <component instance name>:<component name>

    }

    For each specified component, a separate file named <component name>.cdl needs to be created, containing a description of the component in the CDL language. Multiple instances of the same component can be added to a component, and each instance can have a separate state.

    The component instance name must not contain an underscore (_).

CDL supports single-line comments and multi-line C++-style comments.

Examples of CDL files

At its simplest, the component contains a single interface implementation similar to the ping component from the echo example.

Ping.cdl

/* Component name: Ping */

component Ping

/* The component contains a named implementation of the IPing interface. Implementation name: pingimpl.*/

components {

pingimpl: IPing

}

In the following example, the CoFoo component contains implementations of two interfaces declared in two different packages named Foo and Baz (i.e. in the Foo.idl and Bar.idl files):

CoFoo.cdl

/* Component name: CoFoo */

component CoFoo

interfaces {

/* The component contains an implementation of the Foo interface. Implementation name: foo.*/

foo: Foo

/* The component contains three different implementations of the Bar interface. Names of the implementations: bar1, bar2 and bar3.*/

bar1: Bar

bar2: Bar

bar3: Bar

}

In the following example, the CoFoo component contains a single interface implementation and an embedded component.

CoFoo.cdl

/* Component name: CoFoo */

component CoFoo

interfaces {

/* The component contains an implementation of the Foo interface. Implementation name: foo.*/

foo: Foo

}

components {

/* The component contains an instance of the CoBar component. Instance name: bar.*/

bar: CoBar

}

Page top
[Topic cdl]

IDL

All interfaces implemented in a solution must be described in IDL files.

Only one interface can be declared in each IDL file.

The name of an IDL file must match the name of the interface that it describes.

An IDL file contains the following sections:

  1. Interface name.

    The only mandatory section.

    The interface name must begin with an uppercase letter and must not contain an underscore (_).

    A name declaration has the following format:

    package <name of the described interface>

    An interface name can be composite. In this case, it is used to generate a path to the IDL file, for example:

    /* Module.idl located at: a/b/c/Module.idl */

    package a.b.c.Module

  2. Import external packages.

    A package import statement has the following format:

    import <name of external package>

    It enables you to enter the following elements of an external package into the scope of an IDL file: named constants and user-defined types, including structures and variant types.

  3. Declaring structures, variant types, named constants, and synonyms.
  4. Declaration of interface methods.

    An interface methods declaration has the following syntax:

    interface {

    <Declaration of methods>

    }

    The curly brackets contain declarations of methods that have the following syntax:

    <Method name> (<arguments>)

    The method name must not contain an underscore (_). It is recommended to begin the names of methods with an uppercase letter.

    Arguments are divided into input (in) and output (out) and are separated with a comma. Example interface declaration:

    interface {

    // Declaration of Ping method

    Ping(in UInt32 value, out UInt32 result);

    }

    It is not recommended to use out arguments to return codes of errors that occur when a request is being handled by the server entity. Instead, it is recommended to use specialized error arguments. For more details, refer to Managing errors in IDL.

IDL supports single-line comments and multi-line C++-style comments.

Examples of IDL files

In the simplest case, an IDL description contains only a name and interface declaration:

Ping.idl

/* Ping is the interface name */

package Ping

/* Declaration of interface methods */

interface {

// Declaration of Ping method

Ping(in UInt32 value, out UInt32 result);

}

Example of a more complex IDL description:

Foo.idl

// Name declaration

package Foo

// Import statements

import Bar1

import Bar2

// Declaration of named constant

const UInt32 MaxStringSize = 1024;

// Declaration of synonym for user-defined type

typedef sequence <Char, MaxStringSize> String;

// Declaration of structure

struct BazInfo {

Char x;

array <String, 100> y;

sequence <sequence <UInt32, 100>, 200> y;

}

// Declaration of interface methods

interface {

Baz(in BazInfo a, out UInt32 b);

}

Page top
[Topic idl]

IDL data types

Primitive IDL data types

IDL supports the following primitive data types:

  • SInt8, SInt16, SInt32, SInt64 (signed integer);
  • UInt8, UInt16, UInt32, UInt64 (unsigned integer);
  • Handle (handle).

Example declarations of primitive variable types:

SInt32 value;

UInt8 ch;

Handle ResID;

For primitive types (except the Handle type), you can declare a named constant using the reserved word const:

const UInt32 c0 = 0;

const UInt32 c1 = 1;

Let us now examine complex (composite) data types: union (variant type), struct (structure), array and sequence.

union type

The union variant type, similar to the normal union in C, allows storing different types of values within one memory area. However, when transmitting an IPC message, the union type is provided with an additional tag field that lets you define which union field is used in the transmitted value. For this reason, the union type is also called a union with a tag. Example declaration of a union variable type:

union foo {

UInt32 bar;

UInt8 baz;

}

A variant type declaration is a top-level declaration.

struct type

Structures in the IDL language are analogous to structures in C. Example structure declaration:

struct BazInfo {

UInt64 x;

UInt64 y;

}

A structure declaration is a top-level declaration.

array type

An array has a constant size that is defined during declaration:

array <ElementType, ElementsCount>

In IDL, arrays are anonymous and may be declared directly when declaring other complex types, and when declaring interfaces.

The reserved word typedef is used to assign a name to a declared array type, for example:

typedef array <SInt8, 1024> String;

sequence type

A sequence resembles a variable-sized array. When declaring a sequence, the maximum number of elements is specified. However, you can actually transmit fewer elements. In this case, only an amount of memory necessary for the transmitted elements will be used.

Similarly to arrays, sequences are anonymous and may be declared directly when declaring interfaces, and when declaring other complex types:

sequence <ElementType, ElementsCount>

Example declaration of a named sequence:

typedef sequence <SInt8, 1024> String;

Embedded complex types

Complex types can be used when declaring other complex types. Arrays and sequences can be declared directly inside another composite type:

struct BazInfo {

UInt64 x;

array <UInt8, 100> y;

sequence <sequence <UInt32, 100>, 200> z;

}

The union or struct types must be declared prior to use:

union foo {

UInt32 value1;

UInt8 value2;

}

struct bar {

UInt32 a;

UInt8 b;

}

struct BazInfo {

foo x;

bar y;

}

Type synonym declaration (typedef)

The reserved word typedef declares a name that, within its scope, is a synonym for the specified type. For example:

typedef array <UInt32, 20> value;

The typedef struct and typedef union constructs are invalid. A new name for a structure or variant type can be entered only if they were previously declared:

union foo {

UInt32 value1;

UInt8 value2;

}

typedef foo bar;

A typedef declaration does not introduce a new data type; it declares new names for already existing types.

Page top
[Topic idl_data_types]

Managing errors in IDL

It is not recommended to use out arguments in an IDL description to return codes of logical errors occurring when a request is handled by the server entity to the client entity. Instead, it is recommended to use error arguments.

A logical error is an error that occurs when the server entity is handling a request and must be returned to the client entity. For example, if a client entity attempts to open a non-existent file, the file system server returns a logical error. Internal errors of the server entity such as memory shortage or invalid statement execution are not logical errors.

Use of error arguments enables the following:

  • No need to forward all out arguments of methods in a response message from the server entity to the client entity if an error is returned. Instead, a response message will contain only error arguments if an error code needs to be returned.
  • Bind methods of security models not only to response forwarding events but also to error return events.

The IDL language and NK compiler provide two ways to use error arguments:

  • Simplified error handling

    This is employed by the NK compiler by default.

    When using simplified error handling, the methods of interfaces can have no more than one error argument of the UInt16 type. The values of the returned error argument of the method are packaged into a result code of the corresponding client interface method.

  • Extended error handling

    Extended error handling requires the use of the --extended-errors parameter of the NK compiler.

    When this is employed, methods of an interface can have multiple error arguments of any type. The values of returned error arguments of a method are provided in the internal fields of the response message structure.

The types and macros for handling errors are provided in the file named /opt/KasperskyOS-Community-Edition-<version>/toolchain/include/nk/types.h

Simplified error handling

When employing simplified error handling, the methods of interfaces can have no more than one error argument of the UInt16 type named status.

In case of an error:

  • Implementations of methods on the server entity side must use the NK_ELOGIC_MAKE() macro to package the error code and return its result.
  • The corresponding client interface methods return the error code (logical or transport) packaged into a value of the nk_err_t type. The NK_ELOGIC_CHECK() and NK_ETRANSPORT_CHECK() macros are used to check it for logical errors and transport errors, respectively.

If the request is successfully handled:

  • Implementations of methods on the server entity side must put the results into the response message fields corresponding to the out arguments and return NK_EOK.
  • The corresponding client interface methods also return NK_EOK.

For example, let's examine an IDL description of an interface containing one method:

Connection.idl

typedef UInt16 ErrorCode

// error code example

const ErrorCode ESendFailed = 10;

interface {

Send(in Handle handle,

in Packet packet,

out UInt32 sentLen,

// the Send method has one UInt16-type error argument named status

error ErrorCode status

);

}

Implementation of the server entity:

server.c

...

// Send method implementation

nk_err_t Send_impl(struct Connection *self,

const struct Connection_Send_req *req,

const struct nk_arena* req_arena,

struct Connection_Send_res* res,

struct nk_arena* res_arena)

{

if (...) {

// if the request is successfully handled, put the result into an out argument and return NK_EOK

res->sentLen = value;

return NK_EOK;

} else {

// if an error occurs, return the result of the NK_ELOGIC_MAKE macro

return NK_ELOGIC_MAKE(Connection_ESendFailed);

}

Implementation of the client entity:

client.c

// the values of the returned error argument of the method are packaged into a result code of the corresponding client interface method

nk_err_t ret = Connection_Send(ctx,

&req, &req_arena,

&res, NK_NULL);

if (ret == NK_EOK) {

// if there are no errors, res contains the values of out arguments

return res.sentLen;

} else {

if (NK_ELOGIC_CHECK(ret)) {

// handle the logical error

} else {

// handle the transport error

}

}

Extended error handling

When employing extended error handling, the methods of interfaces can have more than one error argument of any type.

In case of an error:

  • Implementations of methods on the server entity side must do the following:
    • Use the nk_err_reset() macro to set the error flag in a response message.
    • Put error codes into the response message fields corresponding to error arguments.
    • return NK_EOK.
  • The corresponding client interface methods return the code of the transport error or NK_EOK if there is no transport error. The nk_msg_check_err() macro is used to check for logical errors in a response message. The values of returned error arguments of a method are provided in the internal fields of the response message structure.

If the request is successfully handled:

  • Implementations of methods on the server entity side must put the results into the response message fields corresponding to the out arguments and return NK_EOK.
  • The corresponding client interface methods also return NK_EOK.

For example, let's examine an IDL description of an interface containing one method:

Connection.idl

typedef UInt16 ErrorCode

interface {

Send(in Handle handle,

in Packet packet,

out UInt32 sentLen,

// the Send method has one UInt16-type error argument named status

error ErrorCode status

);

}

The union type generated by the NK compiler for the constant part of a response message for this method contains out arguments and error arguments:

Connection.idl.h

...

struct Connection_Send_res {

union {

struct {

struct nk_message base_;

nk_uint32_t sentLen;

};

struct {

struct nk_message base_;

nk_uint32_t sentLen;

} res_;

struct kl_Connection_Send_err {

struct nk_message base_;

nk_uint16_t status

} err_;

};

};

...

Implementation of the server entity:

server.c

...

// Send method implementation

nk_err_t Send_impl(struct Connection *self,

const struct Connection_Send_req *req,

const struct nk_arena* req_arena,

struct Connection_Send_res* res,

struct nk_arena* res_arena)

{

if (...) {

// if the request is successfully handled, put the result into an out argument and return NK_EOK

res->sentLen = value;

return NK_EOK;

} else {

// if an error occurs, call the nk_err_reset() macro to set the error flag in the response message

nk_err_reset(res)

// complete the fields of err arguments in the res->err_ structure

res->err_.status = 10;

// return NK_EOK

return NK_EOK;

}

Implementation of the client entity:

client.c

nk_err_t ret = Connection_Send(ctx,

&req, &req_arena,

&res, NK_NULL);

if (ret == NK_EOK) {

if (nk_msg_check_err(res)) {

// handle the logical error

// the values of returned error arguments of a method are provided in the internal fields of the response message structure

return res.err_.status;

} else {

// if there are no errors, res contains the values of out arguments

return res.sentLen;

}

} else {

// handle the transport error

}

Page top

[Topic idl_errors]

Composite names of entities, components and interfaces

For entities, components and interfaces, you can use composite names containing one or more dots. In this case, the part of the name that comes after the last dot is called the short name.

For example, if the full name of an entity is kl.VfsEntity, its short name is VfsEntity. An interface with the full name main.security.Verify has the short name Verify.

If a name is not composite (for example, server) – it is considered to be both the full name and the short name.

Names and paths of description files

The name of an EDL file must match the short name of the entity that it describes. In this case, the composite name is used to indicate the path to the EDL file. For example, kl.VfsEntity must be described in the kl/VfsEntity.edl file.

This same rule applies to components and interfaces: the name of a CDL file must match the short name of the component, and the name of an IDL file must match the short name of the interface. However, the composite name is used to indicate the path to a CDL file or IDL file. For example, the net.Updater component must be described in the net/Updater.cdl file, and the interface with the full name main.security.Verify must be described in the main/security/Verify.idl file.

The short names of entities, components, and interfaces must not contain an underscore (_). The underscore character is allowed in segments of a composite name.

Names of executable files for starting entities

When a solution is started, the Einit entity starts other entities as follows: in ROMFS (in the solution image), it runs the executable file with the name that matches the short name of the entity being started. For example, the Client and net.Client entities will be started from the executable file named Client by default. To start an entity from an executable file with a different name when the solution is started, use the reserved word path in the init description.

Full names in descriptions

Only the full names of entities, components and interfaces are used in EDL-, CDL- and IDL descriptions, and in the init description and security configuration. As an example, let us examine the already mentioned kl.VfsEntity, net.Updater component and main.security.Verify interface.

Example EDL description of kl.VfsEntity:

VfsEntity.edl

entity kl.VfsEntity

...

Example CDL description of the net.Updater component:

Updater.cdl

component net.Updater

...

Example init description:

init.yaml

entities:

- name: kl.VfsEntity

...

Example security configuration:

security.psl

...

/* Declaration of "kl.VfsEntity" entity. */

use EDL kl.VfsEntity;

...

/* Configuration of requests for calling the Check method of any "Verify" interface implementations. */

request interface=main.security.Verify, method=Check { grant () }

...

Names of NK-generated methods and types

The names of generated types and methods are based on the full names of entities, components and interfaces. For example, the net.Updater component will have the net_Updater_component structure, the net_Updater_component_init function, the net_Updater_component_dispatch dispatcher, as well as the net_Updater_component_req request structure and net_Updater_component_res response structure.

Page top

[Topic composite_names]

Entity startup

In this Help section

Einit entity

init.yaml file

Page top
[Topic entities_start]

Einit entity

One of the most important entities in KasperskyOS is the entity named Einit, which is the first entity started by the operating system kernel when the image is loaded. In most solutions built on KasperskyOS, the Einit entity starts all other entities included in the solution, which means that it serves as the initializing entity.

The toolkit provided in KasperskyOS Community Edition includes the einit tool, which lets you generate the code of the Einit entity in C based on the init.yaml file (also known as the init description). The Einit entity created using the einit script performs the following initializing functions:

  • creates all entities included in the solution;
  • creates required connections (IPC channels) between entities;
  • copies information about the entity's connections into the environment of each entity;
  • starts entities.

The standard way of using the einit tool is to integrate an einit call into one of the steps of the build script. As a result, the einit tool uses the init.yaml file to generate the einit.c file containing the Einit entity code. In one of the following steps of the build script, compile the einit.c file into the executable file of the Einit entity and include it into the solution image.

You are not required to create static description files for the Einit entity. These files are included in the KasperskyOS Community Edition toolkit and are automatically connected during a solution build. However, the Einit entity must be described in the security.psl file.

Page top
[Topic einit_entity]

init.yaml file

The init.yaml file (init description) is used by the einit tool to generate source code of the Einit initializing entity. This file contains data in YAML format. This data identifies the following:

  • Entities that are started when KasperskyOS is loaded.
  • IPC channels that are used by entities to interact with each other.

This data consists of a dictionary with the entities key containing a list of dictionaries of entities. Entity dictionary keys are presented in the table below.

Entity dictionary keys in an init description

Key

Required

Description

name

Yes

Name of the entity

task

No

Entity ID, which coincides with the entity name by default. Each entity must have a unique ID.

You can start multiple entities with the same name but different IDs.

path

No

Name of the executable file in ROMFS (in the solution image) from which the entity will be started. By default, the entity will be started from a file in ROMFS with a name that matches the short name of the entity. For example, the Client and net.Client entities will be started from the Client file by default.

You can start multiple entities with the same name from different executable files. However, the IDs of these entities must be different.

connections

No

Dictionary key containing a list of dictionaries of the IPC channels of the entity. This list defines the statically created IPC channels whose client handles will be owned by the entity. The list is empty by default. (In addition to statically created IPC channels, entities may use dynamically created IPC channels.)

Entity IPC channel dictionary keys are presented in the table below.

Entity IPC channel dictionary keys in an init description

Key

Required

Description

id

Yes

IPC channel ID, which can be defined as a specific value or as a link such as

{var: <constant name>, include: <path to header file>}.

Each IPC channel must have a unique ID.

(The IPC channel ID is used by entities to receive an IPC handle.)

target

Yes

ID of the entity that will own the server handle of the IPC channel.

Example init descriptions

In the provided examples, the file containing the init description is named init.yaml, but it can have any name.

init.yaml

# init description of the solution containing the client entity and server entity

entities:

# The Client entity will send requests to the Server entity.

- name: Client

connections:

# ID of the server entity to which the Client entity will

# send requests

- target: Server

# ID of the IPC channel for exchanging IPC messages

# between entities

id: server_connection

# The Server entity will perform the server role

# (will respond to requests from the Client entity).

- name: Server

init.yaml

# init description in which the IPC channel ID is defined by a link

entities:

- name: Client

connections:

- target: Server

# IPC channel ID is in the SERVER_CONN constant

# in the src/example.h file

id: {var: SERVER_CONN, include: src/example.h}

- name: Server

init.yaml

# init description that defines the names of executable files that

# will be used to start the entities named Client, ClientServer and

# MainServer

entities:

- name: Client

path: cl

connections:

- target: ClientServer

id: server_connection_cs

- name: ClientServer

path: csr

connections:

- target: MainServer

id: server_connection_ms

- name: MainServer

path: msr

init.yaml

# init description in which the MainServer and BkServer entities

# will be started from one executable file

entities:

- name: Client

connections:

- id: server_connection_ms

target: MainServer

- id: server_connection_bs

target: BkServer

- name: MainServer

path: srv

- name: BkServer

path: srv

init.yaml

# init description that will start two

# Server entities with different IDs from the same executable

# file

entities:

- name: Client

connections:

- id: server_connection_us

# Server entity ID

target: UserServer

- id: server_connection_ps

# Server entity ID

target: PrivilegedServer

- task: UserServer

name: Server

- task: PrivilegedServer

name: Server

Page top
[Topic init_yaml_file]

IPC and transport

In this Help section

IPC implementation

Transport

Page top
[Topic ipc_and_transport][Topic ipc_realisation]

IPC basics in KasperskyOS

In KasperskyOS, the only means of interprocess communication (IPC) is exchange of messages.

Types of messages and roles of entities

Messaging in KasperskyOS is built on a client-server model.

When two entities interact, one of them is the client (client entity), and the other is the server (server entity). The client initiates the interaction by sending a request message (or simply "request"). The server receives the request and responds by sending a response message (or simply "response") to the client.

IPC channels

To enable two entities to exchange messages, an IPC channel, also referred to as "channel" or "connection", must be established between them. Each entity can have multiple connections, acting as a client for some entities while acting as a server for others.

System calls

KasperskyOS provides three system calls for IPC message exchange: Call, Recv and Reply. The corresponding functions are declared in the syscalls.h file:

  • Call() is used by the client to send a request and receive a response. It receives an IPC handle, buffer containing the request, and buffer for the response.
  • Recv() is used by the server to receive a request. It receives an IPC handle and buffer for the request.
  • Reply() is used by the server to send a response. It receives an IPC handle and buffer for the response.

The Call(), Recv() and Reply() functions return rcOk or an error code.

The Call() and Recv() system calls are locking calls, meaning that messages are exchanged according to the rendezvous principle:

  • The server thread that executes the Recv() call remains locked until a request is received.
  • The client thread that executes the Call() call remains locked until a response is received from the server.

System calls are rarely used in entity code. It is recommended that you instead use more convenient NK-generated methods that execute system calls.

Page top
[Topic ipc_basics]

IPC channels

IPC channels connect entities with each other and are used for IPC messaging.

The channel is provided by a pair of IPC handles (client and server), which are linked to each other.

Two entities linked by an IPC channel

A channel is directional. The entity associated with the client handle of the channel (client entity) can send requests and receive responses over this channel. The entity associated with the server handle of the channel (server entity) can receive requests and send responses over this channel.

An entity can have multiple connections (channels) with other entities, acting as a client in some connections while acting as a server in others.

Entity A is a client for the C entity and a server for the B entity. Designations: cl_1, cl_2 – client IPC handles; srv_1, srv_2 – server IPC handles.

Creating and using channels

IPC channels can be created statically or dynamically.

Statically created IPC channels are created by the Einit entity when the solution is started. The code of the Einit entity is generated based on the init description that specifies all channels (connections) that need to be created.

In addition to statically created IPC channels, entities may use dynamically created IPC channels.

To send and receive an IPC message over a specific channel, the corresponding values of IPC handles (client and server) must be known. Service Locator is used to obtain the values of IPC handles.

In this section

Dynamically created IPC channels

Page top
[Topic ipc_channels]

Dynamically created IPC channels

The capability to dynamically create IPC channels allows you to change the topology of interaction between entities on the fly. This is necessary if it is unknown which specific server entity will become the resource provider required by a client entity. For example, you may not know which specific drive you will need to write data to.

In contrast to a statically created IPC channel, a dynamically created IPC channel has the following characteristics:

  • It is created between entities that have been started.
  • It is created jointly by the client entity and server entity.
  • It can be deleted.
  • It involves the use of a name server (kl.core.NameServer entity), which facilitates the transfer of information about available interface implementations from server entities to client entities.

A dynamically created IPC channel uses the following functions:

  • Name Server interface
  • Connection Manager

The Name Server interface is provided for the user in the following files:

  • coresrv/ns/ns_api.h is a header file of the libkos library;
  • coresrv/ns/NameServer.idl is an IDL description of the IPC interface of the Name Server.

The Connection Manager is provided for the user in the following files:

  • coresrv/cm/cm_api.h is a header file of the libkos library;
  • services/cm/CM.idl is an IDL description of the Connection Manager's IPC interface.

An IPC channel is dynamically created according to the following scenario:

  1. A client entity, server entity and name server are started.
  2. The server entity connects to the name server by using the NsCreate() function and publishes the server entity name, interface name, and interface implementation name by using the NsPublishService() function.
  3. The client entity uses the NsCreate() function to connect to the name server and then uses the NsEnumServices() function to search for the name of the server entity and the name of the interface implementation based on the interface name.
  4. The client entity uses the KnCmConnect() function to request access to the interface implementation and passes the found server entity name and interface implementation name as arguments to the function.
  5. The server entity calls the KnCmListen() function to check if the client entity sent a request to access the provided interface implementation.
  6. The server entity accepts the request to access the provided interface implementation by using the KnCmAccept() function, to which it passes the client entity name and interface implementation name received from the KnCmListen() function as arguments.

To use a name server, the solution security policy must allow the kl.core.NameServer entity to interact with entities for which IPC channels can be dynamically created.

The server entity can use the NsUnPublishService() function to unpublish interface implementations that were previously published on the name server.

The server entity can use the KnCmDrop() function to reject requests to access interface implementations.

ns_api.h (fragment)

/**

* Attempts to connect to name server "name"

* for the specified number of msecs. If the "name" parameter has the value

* RTL_NULL, the function attempts to connect to name server "ns"

* (default name server). Output parameter "ns" contains the handle

* of the connection with the name server.

* If successful, the function returns rcOk, otherwise it returns an error code.

*/

Retcode NsCreate(const char *name, rtl_uint32_t msecs, NsHandle *ns);

/**

* Publishes the interface implementation on the name server.

* The "ns" parameter defines the handle for the connection with the name server.

* The "server" parameter defines the name of the server entity (for example, ata).

* The "type" parameter defines the name of the published interface (for example, IBlkDev).

* The "service" parameter defines the name of the published interface implementation

* (for example, blkdev.blkdev).

* If successful, the function returns rcOk, otherwise it returns an error code.

*/

Retcode NsPublishService(NsHandle ns, const char *type, const char *server,

const char *service);

/**

* Unpublishes the interface implementation on the name server.

* The "ns" parameter defines the handle for the connection with the name server.

* The "server" parameter defines the name of the server entity. The "type" parameter defines the name

* of the published interface. The "service" parameter defines the name of the implementation

* of the published interface.

* If successful, the function returns rcOk, otherwise it returns an error code.

*/

Retcode NsUnPublishService( NsHandle ns, const char *type, const char *server,

const char *service);

/**

* Enumerates the interface implementations published on the

* name server. The "ns" parameter defines the handle for the connection with the name server.

* The "type" parameter defines the name of the required interface. The "index" parameter

* defines the index for enumerating the interface implementations.

* Output parameter "server" contains the name of the server entity providing the

* interface implementation. Output parameter "service" contains the name of the

* interface implementation.

* For example, IBlkDev interface implementations are enumerated

* as follows.

* rc = NsEnumServices(ns, "IBlkDev", 0, outServerName, outServiceName);

* rc = NsEnumServices(ns, "IBlkDev", 1, outServerName, outServiceName);

* ...

* rc = NsEnumServices(ns, "IBlkDev", N, outServerName, outServiceName);

* Function calls with index incrementation continue until

* the function returns rcResourceNotFound.

* If successful, the function returns rcOk, otherwise it returns an error code.

*/

Retcode NsEnumServices(NsHandle ns, const char *type, unsigned index,

char *server, char *service);

cm_api.h (fragment)

/**

* Requests access to the interface implementation named "service"

* provided by server entity "server". The "msecs" parameter defines the

* timeout period (in milliseconds) for the server entity to accept the request. Output

* parameter "endpoint" contains the client IPC handle. Output parameter

* "rsid" contains the identifier of the interface implementation.

* If successful, the function returns rcOk, otherwise it returns an error code.

*/

Retcode KnCmConnect(const char *server, const char *service,

rtl_uint32_t msecs, Handle *endpoint,

rtl_uint32_t *rsid);

/**

* Checks if there is a request to access the interface implementation

* named "filter". If the "filter" parameter has the value RTL_NULL,

* it checks if there is a request to access any

* interface implementation. The "msecs" parameter defines the request timeout period

* in milliseconds. Output parameter "client" contains the name

* of the client entity. Output parameter "service" contains the name of the

* interface implementation.

* If successful, the function returns rcOk, otherwise it returns an error code.

*/

Retcode KnCmListen(const char *filter, rtl_uint32_t msecs, char *client,

char *service);

/**

* Rejects the request to access the interface implementation

* named "service" by client entity "client".

* If successful, the function returns rcOk, otherwise it returns an error code.

*/

Retcode KnCmDrop(const char *client, const char *service);

/**

* Accepts the request to access the interface implementation

* named "service" by client entity "client". The "rsid" parameter defines

/* interface implementation identifier. The "listener" parameter defines

* the listener handle. If the "listener" parameter has the value

* INVALID_HANDLE, a new listener handle is created.

* Output parameter "endpoint" contains the server IPC handle.

* If successful, the function returns rcOk, otherwise it returns an error code.

*/

Retcode KnCmAccept(const char *client, const char *service, rtl_uint32_t rsid,

Handle listener, Handle *endpoint);

The filter parameter of the KnCmListen() function is reserved and does not affect the behavior of the function. The behavior of the function corresponds to the RTL_NULL value of the filter parameter.

The listener handle is a server IPC handle with expanded rights. It is created when the KnCmAccept() function is called with the INVALID_HANDLE argument in the listener parameter. If a listener handle is passed to the KnCmAccept() function when it is called, the created server IPC handle will provide the capability to receive requests over all IPC channels associated with this listener handle. (The first IPC channel associated with the listener handle is created when the KnCmAccept() function is called with the INVALID_HANDLE argument in the listener parameter. The second and subsequent IPC channels associated with the listener handle are created during the second and subsequent calls of the KnCmAccept() function, to which the listener handle obtained during the first call is passed.)

The listener handle is also used when statically creating IPC channels. It is created when the KnHandleConnectEx() function is called with the INVALID_HANDLE argument in the srListener parameter. If a listener handle is passed to the KnHandleConnectEx() function when it is called, the created server IPC handle will provide the capability to receive requests over all IPC channels associated with this listener handle. A listener handle is associated with multiple IPC channels if multiple IPC channels with identical names are created for one server entity.

If the dynamically created IPC channel is no longer required, its client and server handles should be deleted. The IPC channel can be created again if necessary.

Page top
[Topic dynamic_ipc_channels]

Messaging overview

Let us examine two entities (client and server) that have an IPC channel established between them. Let cl be the client IPC handle of this channel while sr is the server IPC handle of this channel.

The code provided below is intended to demonstrate the IPC mechanism. System calls are not normally used directly in entity code. To allow a convenient exchange of messages, special NK-generated methods are provided, which use system calls.

Client entity code:

client.c

// Get the client IPC handle cl using Service Locator

// Send request

Call(cl, &RequestBuffer, &ResponseBuffer);

Server entity code:

server.c

// Get the server IPC handle sr using Service Locator

// Receive request

Recv(sr, &RequestBuffer);

// Process request

// Send response

Reply(sr, &ResponseBuffer);

Messages are exchanged as follows:

  1. One of the client threads executes the Call() system call, passing as arguments the cl handle (client handle of the utilized channel), the pointer to the buffer containing the request message, and the pointer to the buffer for the response.
  2. The request message is sent to Kaspersky Security System to be checked. If Kaspersky Security System returns an "allowed" decision, we proceed to step 3. Otherwise, the Call() is terminated with the rcSecurityDisallow error code, and we proceed to step 9.
  3. If the server is waiting for a request from this client—i.e. the server has executed the Recv() call, passing sr as the first argument—we proceed to step 4. Otherwise, the client thread remains locked until one of the server threads executes a Recv() system call with the first sr argument.
  4. The request message is copied to the address space of the server. The server thread is unlocked, and the Recv() call is terminated with an rcOk code.
  5. The server processes the received message. The client thread remains locked.
  6. The server executes the Reply() system call, passing as arguments the sr handle and the pointer to the buffer with the response message.
  7. The response message is sent to Kaspersky Security System to be checked. If Kaspersky Security System returns an "allowed" decision, we proceed to step 8. Otherwise, the Call() and Reply() calls are terminated with an rcSecurityDisallow error code (see step 3).
  8. The response message is copied to the address space of the client. The server thread is unlocked, and the Reply() call is terminated with an rcOk code. The client thread is unlocked, and the Call() is terminated with an rcOk code.
  9. The exchange is complete.

If an error occurs when sending the request (insufficient memory, invalid message format, etc.), the threads are unlocked and the Call() and Reply() calls return an error code.

Page top
[Topic general_messaging_scheme]

Service Locator

Service Locator is a small library that lets you:

  • find out the value of an IPC handle based on the connection name;
  • find out the value of the interface ID (RIID) based on the name of the interface implementation.

The values of the IPC handle and RIID are required for calling a specific interface of a specific server entity. These values are used when initializing a transport.

To use Service Locator, you need to include the sl_api.h file in the entity code:

#include <coresrv/sl/sl_api.h>

Main functions of Service Locator

Client-side functions:

  • ServiceLocatorConnect() receives the connection name and returns the client IPC handle corresponding to this connection (channel).
  • ServiceLocatorGetRiid() receives the IPC handle and interface implementation name in the format <component instance name>.<interface implementation name>. Returns a corresponding RIID (sequence number of the interface implementation).

The connection name is specified in the init.yaml file, the component instance name is specified in the EDL file, and the interface implementation name is specified in the CDL file.

Server-side functions:

  • ServiceLocatorRegister() receives the connection name and returns the server IPC handle corresponding to this connection (channel). If the channel is part of a group, the function returns the listener handle of this group.

Example use of Service Locator

Examine the next solution consisting of two entities: Client and Server.

The client entity does not implement any interface.

Client.edl

entity Client

The server entity contains an instance of the Ping component. Instance name: ping_comp.

Server.edl

entity Server

components {

ping_comp: Ping

}

The Ping component contains a named implementation of the Ping interface declared in the Ping.idl file. Implementation name: ping_impl.

Ping.cdl

component Ping

interfaces {

ping_impl: Ping

}

(For brevity, the Ping.idl file is not provided.)

In the init description, we indicate that the Client and Server entities must be connected through a channel named server_connection:

init.yaml

entities:

- name: Client

connections:

- target: Server

id: server_connection

- name: Server

Use of Service Locator on the client side:

client.c

/* Connect Service Locator */

#include <coresrv/sl/sl_api.h>

/* Get client IPC handle "server_connection" channel */

Handle handle = ServiceLocatorConnect("server_connection");

/* Get ID (RIID) of ping_impl implementation contained in ping_comp instance */

nk_iid_t riid = ServiceLocatorGetRiid(handle, "ping_comp.ping_impl");

Use Service Locator on the server side:

server.c

/* Connect Service Locator */

#include <coresrv/sl/sl_api.h>

nk_iid_t iid;

/* Get server IPC handle of "server_connection" channel */

Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &iid);

Page top
[Topic service_locator]

Channel groups

The channels in which an entity serves as a server may be combined into one or more groups.

Each group of channels has its own listener handle.

Two IPC channels have the same name, "second_connection_to_A", and are combined into a group. Another channel, named "first_connection_to_A", is not included in this group. Designations: cl_1, cl_2, cl_3 – client IPC handles; srv_1 – server IPC handle; ls_1 – listener IPC handle.

The listener handle allows the server to receive requests immediately over all channels in the group. There is no need to create a separate thread for each channel. It is enough to execute one Recv() system call in one server thread by specifying a listener handle.

Creating a group of channels using an init description

For channels to form a group, they must connect to the same server entity and have identical names. For example, to produce the system of channels depicted above, the following init description can be used:

init.yaml

entities:

# Entity A acts as a server, so its list of connections is empty.

- name: A

# Entity B will be connected with entity A via two different channels.

- name: B

connections:

- target: A

id: first_connection_to_A

- target: A

id: second_connection_to_A

# Entity C will be connected with entity A via a channel named

# "second_connection_to_A". Two channels with identical names will be combined

# into a group: on the entity A side, they will have the same

# IPC handle (listener handle ls_1).

- name: C

connections:

- target: A

id: second_connection_to_A

Messaging within a channel group

Let us look at how messages are exchanged for the group of channels described above.

The client entities B and C receive a client handle value based on the name of the first_connection_to_A connection and send a request:

entity_B.c, entity_C.c

// Receive client IPC handle cl corresponding to "second_connection_to_A".

Handle cl = ServiceLocatorConnect("second_connection_to_A");

// Send request

Call(cl, &RequestBuffer, &ResponseBuffer);

Both channels being used have the same name: second_connection_to_A. However, by this name the entities B and C will receive different handle values for that name: entity B will receive the value cl_2, whereas entity C will receive the value cl_3.

Server entity A receives the listener handle value ls_1. Entity A awaits requests over two channels at the same time (from B and from C), using the ls_1 handle. After receiving and processing the request, entity A sends a response using the ls_1 handle. The response will be sent to the client that initiated the request:

entity_A.c

nk_iid_t iid;

// Receive listener handle ls_1 corresponding to

"second_connection_to_A"

Handle ls_1 = ServiceLocatorRegister("second_connection_to_A", NULL, 0, &iid);

// Wait for the request from entity B or C

Recv(ls_1, &RequestBuffer);

// Process received request

// Send response to client from which request was received

Reply(ls_1, &ResponseBuffer);

Page top
[Topic channel_groups][Topic transport]

IPC message structure

In KasperskyOS, all interactions between entities have statically defined types. The permissible structures of an IPC message are defined in the IDL description of the interfaces of the entity that receives the message (server).

A correct IPC message (request and response) contains a constant part and an arena.

Constant part of a message

The constant part of a message contains arguments of a fixed size, and the RIID and MID.

Fixed-size arguments can be arguments of any IDL types except the sequence type.

The RIID and MID identify the interface and method being called:

  • The RIID (Runtime Implementation ID) is the number of the entity interface implementation being called, starting at zero.
  • The MID (Method ID) is the number of the method within the interface that contains it, starting at zero.

The type of the constant part of the message is generated by the NK compiler based on the IDL description of the interface. A separate structure is generated for each interface method. Union types are also generated for storing any request to a server, component or interface.

For example, for the Ping method of the Ping interface (the Ping component of the Server entity in the echo example), the NK compiler will create the Ping_Ping_req type for the constant part of the request and the Ping_Ping_res type for the constant part of the response. The following union types will also be generated:

  • Ping_req and Ping_res are constant parts of the request and response for any method of the Ping interface.
  • Ping_component_req and Ping_component_res are constant parts of the request and response for any method of any interface whose implementation is included in the Ping component.

    If embedded components are present, these types also contain structures of the constant part of a message for any method of any interface whose implementations are included in all embedded components. For more details, refer to Generated methods and types.

  • Server_entity_req and Server_entity_res are the constant parts of the request and response for any method of any interface whose implementation is included in any component whose instance is included in the Server entity.

Arena

The arena is a buffer for storing variable-size arguments (sequence IDL type).

Validating a message in Kaspersky Security System

The Kaspersky Security Module verifies that the structure of the message being sent is correct. Requests and responses are both validated. If the message has an incorrect structure, it will be rejected without calling the security model methods associated with it.

Forming a message structure

KasperskyOS Community Edition includes the following tools that make it easier for the developer to create and package an IPC message:

The echo example shows the creation of a simple IPC message.

Page top
[Topic ipc_message_structure]

NkKosTransport

NkKosTransport is a convenient wrapper for the Call, Recv and Reply system calls. It lets you work separately with messages' constant parts and arenas.

The NkKosTransport structure and methods for working with it are declared in the transport-kos.h file.

To initialize the transport, it is sufficient to call the NkKosTransport_Init() function by specifying the IPC handle of the channel that should be used to transmit messages (handle):

#include <coresrv/nk/transport-kos.h>

NkKosTransport transport;

NkKosTransport_Init(&transport, handle, NK_NULL, 0);

A channel has two IPC handles: client and server. Therefore, when initializing the transport, you need to specify the client handle for the utilized channel on the client side and the server handle on the server side.

The functions nk_transport_call(), nk_transport_recv() and nk_transport_reply() declared in transport.h (included in the transport-kos.h file) are used to call the transport.

The nk_transport_call() function is intended for sending a request and receiving a response:

/* The constant part (req) and arena of the request (req_arena) must be initialized

* with the actual input arguments of the method being called. The constant part (req)

* must also contain the RIID and MID values.

* The nk_transport_call() function generates a request and executes the Call system call.

* The response received from the server is inserted into res (constant part of the response) and

* res_arena (response arena). */

nk_transport_call(&transport.base, (struct nk_message *)&req, &req_arena, (struct nk_message *)&res, &res_arena);

When using a generated interface method (for example, the IPing_Ping method from the echo example) on the client side, the corresponding values of the RIID and MID are automatically inserted into the request, after which the nk_transport_call() function is called.

The nk_transport_recv() function is intended for receiving a request:

/* The nk_transport_recv () function executes the Recv system call.

* The request received from the client is inserted into req (constant part of the response) and

* req_arena (response arena). */

nk_transport_recv(&transport.base, (struct nk_message *)&req, &req_arena);

The nk_transport_reply() function is intended for sending a response:

/* The constant part (res) and request arena (res_arena) must be initialized

* with the actual output arguments of the server method being called.

* The nk_transport_reply() function generates a response and executes a Reply system call. */

nk_transport_reply(&transport.base, (struct nk_message *)&res, &res_arena);

Page top

[Topic nk_kos_transport]

Generated methods and types

When building a solution, the NK compiler uses the EDL-, CDL- and IDL descriptions to generate a set of special methods and types that simplify the creation, forwarding, receipt and processing of IPC messages.

Examine the static description of the Server entity from the echo example. This description consists of three files: Server.edl, Ping.cdl and Ping.idl:

Server.edl

/* Description of the Server entity. */

entity Server

/* pingComp is a named instance of the Ping component. */

components {

pingComp: Ping

}

Ping.cdl

/* Description of the Ping component. */

component Ping

/* pingImpl is the Ping interface implementation. */

interfaces {

pingImpl: Ping

}

Ping.idl

/* Description of the Ping interface. */

package Ping

interface {

Ping(in UInt32 value, out UInt32 result);

}

These files will be used to generate the files named Server.edl.h, Ping.cdl.h, and Ping.idl.h, which contain the following methods and types:

Methods and types that are common to the client and server

  • Abstract interfaces containing the pointers to the implementations of the methods included in them.

    In our example, one abstract interface (Ping) will be generated:

    struct Ping_ops {

    nk_err_t (*Ping)(struct Ping *,

    const struct Ping_req *,

    const struct nk_arena *,

    struct Ping_res *,

    struct nk_arena *); };

    struct Ping {

    const struct Ping_ops *ops;

    };

  • Set of interface methods.

    When calling an interface method, corresponding values of the RIID and MID are automatically inserted into the request, after which the nk_transport_call() function is called.

    In our example, a single Ping_Ping interface method will be generated:

    nk_err_t Ping_Ping(struct Ping *,

    const struct Ping_Ping_req *,

    const struct nk_arena *,

    struct Ping_Ping_res *,

    struct nk_arena *);

Methods and types used only on the client

  • Types of proxy objects.

    A proxy object is used as an argument in an interface method. In our example, a single Ping_proxy proxy object type will be generated:

    struct Ping_proxy {

    struct Ping base;

    struct nk_transport *transport;

    nk_iid_t iid;

    };

  • Functions for initializing proxy objects.

    In our example, the single initializing function Ping_proxy_init will be generated:

    void Ping_proxy_init(struct Ping_proxy *, struct nk_transport *, nk_iid_t);

  • Types that define the structure of the constant part of a message for each specific method.

    In our example, two such types will be generated: Ping_Ping_req (for a request) and Ping_Ping_res (for a response).

    struct Ping_Ping_req {

    struct nk_message base_;

    nk_uint32_t value;

    };

    struct Ping_Ping_res {

    struct nk_message base_;

    nk_uint32_t result;

    };

Methods and types used only on the server

  • Type containing implementations of all component interfaces, and the initializing function. (For each server component.)

    If there are embedded components, this type also contains their instances, and the initializing function takes their corresponding initialized structures. Therefore, if embedded components are present, their initialization must begin with the most deeply embedded component.

    In our example, the Ping_component structure and Ping_component_init function will be generated:

    struct Ping_component {

    struct Ping *pingImpl;

    };

    void Ping_component_init(struct Ping_component *, struct Ping *);

  • Type containing implementations of all interfaces provided directly by the server entity; all instances of components included in the server entity; and the initializing function.

    In our example, the Server_entity structure and Server_entity_init function will be generated:

    struct Server_entity {

    struct Ping_component *pingComp;

    };

    void Server_entity_init(struct Server_entity *, struct Ping_component *);

  • Types that define the structure of the constant part of a message for any method of a specific interface.

    In our example, two such types will be generated: Ping_req (for a request) and Ping_res (for a response).

    union Ping_req {

    struct nk_message base_;

    struct Ping_Ping_req Ping;

    };

    union Ping_res {

    struct nk_message base_;

    struct Ping_Ping_res Ping;

    };

  • Types that define the structure of the constant part of a message for any method of any interface whose implementation is included in the specific component.

    If embedded components are present, these types also contain structures of the constant part of a message for any method of any interface whose implementations are included in all embedded components.

    In our example, two such types will be generated: Ping_component_req (for a request) and Ping_component_res (for a response).

    union Ping_component_req {

    struct nk_message base_;

    union Ping_req pingImpl;

    };

    union Ping_component_res {

    struct nk_message base_;

    union Ping_res pingImpl;

    };

  • Types that define the structure of the constant part of a message for any method of any interface whose implementation is included in any component whose instance is included in the server entity.

    If embedded components are present, these types also contain structures of the constant part of a message for any method of any interface whose implementations are included in all embedded components.

    In our example, two such types will be generated: Server_entity_req (for a request) and Server_entity_res (for a response).

    union Server_entity_req {

    struct nk_message base_;

    union Ping_req pingComp_pingImpl;

    };

    union Server_entity_res {

    struct nk_message base_;

    union Ping_res pingComp_pingImpl;

    };

  • Dispatch methods (dispatchers) for a separate interface, component, or the entity.

    Dispatchers analyze the received request (the RIID and MID values), call the implementation of the corresponding method, and then save the response in the buffer. In our example, three dispatchers will be generated: Ping_dispatch, Ping_component_dispatch, and Server_entity_dispatch.

    The entity dispatcher handles the request and calls the methods implemented by this entity. If the request contains an incorrect RIID (for example, an RIID for a different interface implementation that this entity does not have) or an incorrect MID, the dispatcher returns NK_EOK or NK_ENOENT.

    nk_err_t Server_entity_dispatch(struct Server_entity *,

    const union Server_entity_req *,

    const struct nk_arena *,

    union Server_entity_res *,

    struct nk_arena *);

    In special cases, you can use dispatchers of the interface and the component. They take an additional argument: interface implementation ID (nk_iid_t). The request will be handled only if the passed argument and RIID from the request match, and if the MID is correct. Otherwise, the dispatchers return NK_EOK or NK_ENOENT.

    nk_err_t Ping_dispatch(struct Ping *,

    nk_iid_t,

    const union Ping_req *,

    const struct nk_arena *,

    union Ping_res *,

    struct nk_arena *);

    nk_err_t Ping_component_dispatch(struct Ping_component *,

    nk_iid_t,

    const union Ping_component_req *,

    const struct nk_arena *,

    union Ping_component_res *,

    struct nk_arena *);

Page top
[Topic generated_methods_and_types]

Tools for building a solution

This section contains a description of the scripts, tools, compilers and build templates provided in KasperskyOS Community Edition.

In this section

Scripts and compilers

Preparing the solution's boot image

Deploying the solution's boot image on target devices

Page top
[Topic solution_build_tools]

Scripts and compilers

This section contains a description of the scripts, tools and compilers provided in KasperskyOS Community Edition.

In this section

Build scripts and tools

Cross compilers

Page top
[Topic scripts_and_compliers]

Build scripts and tools

KasperskyOS Community Edition includes the following build scripts and tools:

  • nk-gen-c

    The NK compiler (nk-gen-c) generates the set of transport methods and types based on the EDL, CDL and IDL descriptions of applications, components and interfaces. The transport methods and types are needed for generating, sending, receiving and processing IPC messages.

  • nk-psl-gen-c

    The nk-psl-gen-c compiler generates the source code of the Kaspersky Security System security module based on the solution security policy file (security.psl) and the EDL descriptions of applications included in the solution.

  • einit

    The einit tool lets you automate the creation of code of the Einit initializing application. This application is the first to start when KasperskyOS is loaded. Then it starts the other applications and creates channels (connections) between them.

  • makekss

    The makekss script creates the Kaspersky Security System security module for the KasperskyOS kernel.

  • makeimg

    The makeimg script creates the final boot image of the KasperskyOS-based solution with all applications to be started and the Kaspersky Security System module.

In this section

nk-gen-c

nk-psl-gen-c

einit

makekss

makeimg

Page top
[Topic build_utilities_and_scripts]

nk-gen-c

The NK compiler (nk-gen-c) generates the set of transport methods and types based on the EDL, CDL and IDL descriptions of the process classes, components and interfaces. The transport methods and types are needed for generating, sending, receiving and processing IPC messages.

Transport methods and types are generated with fully qualified names. The full name of the process class/component/interface is used as prefixes in names (declared in the corresponding EDL-, CDL- or IDL file) by replacing dots with underscores (_).

The NK compiler receives the EDL, CDL or IDL file and creates the following files:

  • H file containing a declaration and implementation of transport methods and types.
  • D file that lists the dependencies of the created C file. This file can be used for building automation using the make tool.

Syntax for using the NK compiler:

nk-gen-c [-I PATH][-o PATH][--types][--interface][--client][--server][--extended-errors][--enforce-alignment-check][--help][--version] FILE

Parameters:

  • FILE

    Path to the EDL-, CDL- or IDL description of the process class, component or interface for which you need to generate transport methods and types.

  • -I PATH

    Path to the folder containing auxiliary files required for generating transport methods and types. By default, these files are located in the folder: /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/include.

    It may also be used for adding other folders to search for the files required for generating the methods and types.

    To indicate more than one folder. you can use several -I switches.

  • -o PATH

    Path to an existing folder where files containing transport methods and types will be created.

  • -h, --help

    Displays the Help text.

  • --version

    Displays the nk-gen-c version.

  • --enforce-alignment-check

    Enables mandatory alignment checks for queries to memory, even if this check is disabled for the target platform. If these checks are enabled, the NK compiler adds additional alignment checks to the code of the IPC message validators.

    By default, memory query alignment check settings are defined for each platform in the file named system.platform.

  • --extended-errors

    Enables extended error handling in the code of client and server methods.

Selective generation

To reduce the amount of code generated by the NK compiler, you can use selective generation flags. For example, it is convenient to use the --server flag for applications that implement interfaces, and to use the --client flag for applications that are clients of the interfaces.

If no selective generation flag is specified, the NK compiler will create all transport types and methods that are possible for the specified file.

Selective generation flags for descriptions of interfaces (IDL files):

  • --types

    The compiler will create only files that contain all constants and types, including redefined (typedef), from the input IDL file, and the types from imported IDL files that are used in the types of the input file.

    However, constants and redefined types from imported IDL files will not be explicitly included in the generated files. If you need to use types from imported files in code, you need to separately generate H-files for each such IDL file.

  • --interface

    The compiler will generate files created with the --types flag, and the structures of request and response messages for all methods of this interface.

  • --client

    The compiler will generate files created with the --interface flag, and the client proxy objects and functions of their initialization for all methods of this interface.

  • --server

    The compiler will generate files created with the --interface flag, and the types and methods of the dispatcher of this interface.

Selective generation flags for descriptions of components (CDL files) and process classes (EDL files):

  • --types

    The compiler will generate files created with the --types flag for all interfaces used in this component/process class.

    However, only the types that are used in arguments of interface methods will be explicitly included in the generated files.

  • --interface

    The compiler will generate files created with the --types flag for this component/process class, and files generated with the --interface flag for all interfaces used in this component/process class.

  • --client

    The compiler will generate files created with the --interface flag, and the client proxy objects and functions of their initialization for all interfaces used in this component/process class.

  • --server

    The compiler will generate files created with the --interface flag, and the types and methods of the dispatcher of this component/process class and the types and methods of dispatchers for all interfaces used in this component/process class.

Page top
[Topic nkgenc]

nk-psl-gen-c

The nk-psl-gen-c compiler generates the source code of the Kaspersky Security System security module based on the solution security policy file (security.psl) and the EDL descriptions of process classes included in the solution. This code is used by the makekss script.

The nk-psl-gen-c compiler also lets you generate and run code of test scenarios written in the PAL language for the solution security policy.

Syntax for using the nk-psl-gen-c compiler:

nk-psl-gen-c [-I PATH][-o PATH][--audit PATH][--tests ARG][--help][--version] FILE

Parameters:

  • FILE

    Path to the PSL description of the solution security policy (security.psl)

  • -I,--include-dir PATH

    Path to the folder containing auxiliary files required for generating transport methods and types. By default, these files are located in the folder: /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/include.

    The nk-psl-gen-c compiler will require access to all EDL descriptions of process classes listed in the security configuration, and will require access to the CDL- or IDL descriptions of their components and interfaces. To enable the nk-psl-gen-c compiler to find these descriptions, you need to pass the paths to these descriptions using the -I switch.

    To indicate more than one folder. you can use several -I switches.

  • -o,--output PATH

    Path to the created file containing the security module code.

  • -t, --tests ARG

    Flag for controlling code generation and starting test scenarios for the solution security policy. Possible values:

    • skip – code of test scenarios is not generated. This value is used by default if the --tests flag is not indicated.
    • generate – code of test scenarios is generated but is not compiled and is not executed.
    • run – code of test scenarios is generated, compiled using the gcc compiler, and executed.
  • -a, --audit PATH

    Path to the created file containing the code of the audit decoder.

  • -h, --help

    Displays the Help text.

  • --version

    Displays the nk-psl-gen-c version.

Page top
[Topic nkpslgenc]

einit

The einit tool lets you automate the creation of code of the Einit initializing application. This application is the first to start when KasperskyOS is loaded. Then it starts the other applications and creates channels (connections) between them.

The einit tool receives the init description file (init.yaml by default) and creates a .c file containing the code of the Einit initializing application. Then the Einit application must be built using the C compiler that is provided in KasperskyOS Community Edition.

Syntax for using the einit tool:

einit -I PATH -o PATH [--help] FILE

Parameters:

  • FILE

    Path to the init.yaml file containing descriptions of process classes and connections.

  • -I PATH

    Path to the folder containing auxiliary files required for generating the initializing application. By default, these files are located in the folder: /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/include.

  • -o, --out-file PATH

    Path to the created .c file containing the code of the initializing application.

  • -h, --help

    Displays the Help text.

Page top
[Topic einit_tool]

makekss

The makekss script creates the security module of Kaspersky Security System.

The script calls the nk-psl-gen-c compiler to generate the source code of the security module, then compiles the resulting code by calling the C compiler that is provided in KasperskyOS Community Edition.

The script obtains the file containing a description of the solution security policy (security.psl by default) and creates the ksm.module security module file.

Syntax for using the makekss script:

makekss --target=ARCH --module=PATH --with-nk="PATH" --with-nktype="TYPE" --with-nkflags="FLAGS" [--output="PATH"][--help][--with-cc="PATH"][--with-cflags="FLAGS"] FILE

Parameters:

  • FILE

    Path to the security configuration file (.psl).

  • --target=ARCH

    Architecture for which the build is intended.

  • --module=-lPATH

    Path to the ksm_kss library. This key is passed to the C compiler for linking to this library.

  • --with-nk=PATH

    Path to the nk-psl-gen-c compiler that will be used to generate the source code of the security module. By default, the compiler is located in /opt/KasperskyOS-Community-Edition-<version>/toolchain/bin/nk-psl-gen-c.

  • --with-nktype="TYPE"

    Indicates the type of NK compiler that will be used. To use the nk-psl-gen-c compiler, indicate the psl type.

  • --with-nkflags="FLAGS"

    Parameters used when calling the nk-psl-gen-c compiler.

    The nk-psl-gen-c compiler will require access to all EDL descriptions of process classes listed in the security configuration, and will require access to the CDL- or IDL descriptions of their components and interfaces. To enable the nk-psl-gen-c compiler to find these descriptions, you need to pass the paths to these descriptions in the --with-nkflags parameter by using the -I switch of the nk-psl-gen-c compiler.

  • --output=PATH

    Path to the created security module file.

  • --with-cc=PATH

    Path to the C compiler that will be used to build the security module. The compiler provided in KasperskyOS Community Edition is used by default.

  • --with-cflags=FLAGS

    Parameters used when calling the C compiler.

  • -h, --help

    Displays the Help text.

Page top
[Topic makekss]

makeimg

The makeimg script creates the final boot image of the KasperskyOS-based solution with all applications to be started and the Kaspersky Security System module.

The script receives a list of files, including the executable files of all applications that need to be added to ROMFS of the loaded image, and creates the following files:

  • Solution image
  • Solution image without character tables (.stripped)
  • Solution image with debug character tables (.dbg.syms)

Syntax for using the makeimg script:

makeimg --target=ARCH --sys-root=PATH --with-toolchain=PATH --ldscript=PATH --img-src=PATH --img-dst=PATH --with-init=PATH [--with-extra-asflags=FLAGS][--with-extra-ldflags=FLAGS][--help] FILES

Parameters:

  • FILES

    List of paths to files, including the executable files of all applications that need to be added to romfs.

    The security module (ksm.module) must be explicitly specified, or else it will not be included in the solution image. The Einit application does not need to be indicated because it will be automatically included in the solution image.

  • --target=ARCH

    Architecture for which the build is intended.

  • --sys-root=PATH

    Path to the root directory sysroot. By default, this directory is located in /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/.

  • --with-toolchain=PATH

    Path to the set of auxiliary tools required for the solution build. By default, these tools are located in /opt/KasperskyOS-Community-Edition-<version>/toolchain/.

  • --ldscript=PATH

    Path to the linker script required for the solution build. By default, this script is located in /opt/KasperskyOS-Community-Edition-<version>/libexec/arm-kos/.

  • --img-src=PATH

    Path to the precompiled KasperskyOS kernel not containing the romfs. By default, the kernel is located in /opt/KasperskyOS-Community-Edition-<version>/libexec/arm-kos/.

  • --img-dst=PATH

    Path to the created image file.

  • --with-init=PATH

    Path to the executable file of the Einit initializing application.

  • --with-extra-asflags=FLAGS

    Additional flags for the AS Assembler.

  • --with-extra-ldflags=FLAGS

    Additional flags for the LD Linker.

  • -h, --help

    Displays the Help text.

Page top
[Topic makeimg]

Cross compilers

Properties of KasperskyOS cross compilers

The cross compilers included in KasperskyOS Community Edition support processors that have the arm architecture.

The KasperskyOS Community Edition toolchain includes the following tools for cross compilation:

  • GCC:
    • arm-kos-gcc
    • arm-kos-g++
  • Binutils:
    • AS Assembler: arm-kos-as
    • LD Linker: arm-kos-ld

In addition to standard macros, an additional macro __KOS__=1 is defined in GCC. Using this macro lets you simplify porting of the software code to KasperskyOS, and also simplifies development of platform-independent applications.

To view the list of standard macros of GCC, run the following command:

echo '' | arm-kos-gcc -dM -E -

Linker operation specifics

When building the executable file of an application, by default the linker links the following libraries in the specified order:

  1. libc – standard C library.
  2. libm – library that implements the mathematical functions of the standard C language library.
  3. libvfs_stubs – library that contains stubs of I/O functions (for example, open, socket, read, write).
  4. libkos – library consisting of two parts. The first part provides the C interface for accessing KasperskyOS kernel functions. It is available through the header files in the coresrv folder, for example: #include <coresrv/vmm/vmm_api.h>. The second part of the libkos library is a wrapper over the first part and contains additional synchronization functions: mutex, semaphore, event. Other libraries (including libc) interact with the kernel through the libkos library.
  5. libenv – client library of the subsystem for configuring the environment of applications (environmental variables, arguments of the main function, and custom configurations).
  6. libsrvtransport-u – internal library with the implementation of transport of interprocess communication between KasperskyOS kernel services.
Page top
[Topic crosscompliers]

Preparing the solution's boot image

To automate the process of preparing the solution's boot image, you need to configure a build system. You can base this system on the build system implemented in the examples.

This section describes various methods for configuring a build system to prepare a solution's boot image.

In this section

Using a Makefile template from the contents of KasperskyOS Community Edition

Using CMake from the contents of KasperskyOS Community Edition

Using your own build system

Page top
[Topic build_automation_tools]

Using a Makefile template from the contents of KasperskyOS Community Edition

To simplify the process of preparing the solution's boot image using the make build system, you can use the build.mk template from KasperskyOS Community Edition. The template file is located at the following path:

/opt/KasperskyOS-Community-Edition-<version>/common/build.mk

To prepare a make build system using the build.mk template, do the following in the Makefile build script:

  1. Specify the value of the targets variable. In the variable value, list all applications included in the solution, separating them with a space. You are not required to specify the Einit or kl.core.Core applications because they are processed separately.
  2. For each application specified in the targets variable, specify the values for the following variables:
    • <application-name>-objects – list of object files of the application. You must list all object files.

      The names of object files are taken from the names of the entity source files according to the following rules:

      • *.c → *.o
      • *.idl → *.idl.o
      • *.cdl → *.cdl.o
      • *.edl → *.edl.o
    • <application-name>-ldflags – list of flags passed to the linker. If the entity uses the virtual file system, you must pass the flags specified in the LIBVFS_REMOTE variable.
    • <application-name>-base – application load address in hexadecimal. If this variable is not specified, the address is assigned automatically. Use this variable for debugging the application.

    This same address is passed to the debugger using the following command, which can be added to the .gdbinit file:

    add-symbol-file <application-name> <application-load-address>

  3. If the ROMFS partition must contain additional files, define the value for the ROMFS-FILES variable. In the variable value, list the files while separating them with blank spaces. If you are using the virtual file system, you must pass the file specified in the VFS_ENTITY variable.
  4. Add a statement for importing the build.mk template using the following command:

    include /opt/KasperskyOS-Community-Edition-<version>/common/build.mk

The build.mk template file has the following build targets:

  • sim (default target) – start the solution's boot image using the QEMU emulator.

    When started, the QEMU emulator may capture the mouse and will indicate this in the title bar of the emulator window.

  • Kos-image – build the solution's boot image to be started on the target hardware platform.
  • gdbsim – start the solution's boot image with the capability for debugging. After QEMU emulation is started, it awaits the start of the debugger. For this, call the gdb target in a different command line.

    Make sure that TCP/IP port 1234 is open by using the netstat -anput command, for example.

    Port 1234 is monitored by the gdbserver program, which is used for remote debugging of applications and is part of the QEMU emulator.

    When using the gdb debugger, you must use hardware breakpoints (hbreak). The QEMU emulator used in the examples is started with the -enable-kvm key, which makes it impossible to use regular breakpoints.

  • gdb – start the debugger. After starting the debugger for applications that require debugging, run the following commands:

    add-symbol-file <application-name> <application-load-address>

    target remote localhost:1234

Example

This example uses the make build system. In addition to the actions executed in the build.mk template, in the build script you must specify the Hello application and the list of object files of this application. The load address is specified for solution debugging purposes.

Makefile

# List of applications for the build.

targets = hello

# List of object files of the Hello application

hello-objects = hello.o hello.edl.o

# Load address of the Hello entity (in hex)

hello-base = 800000

# Include template with general build rules.

include ../common/build.mk

To start the make build system, run the make hello command.

To run the hello example while in the folder /opt/KasperskyOS-Community-Edition-<version>/examples/hello, run the make command.

Page top
[Topic using_makefiles]

Using CMake from the contents of KasperskyOS Community Edition

The CMake build automation system supports cross compilation of applications. To perform cross compilation using CMake, specify the path to a file with the build system extension (toolchain.cmake).

To cross compile an application for KasperskyOS, define the value of the variable: DCMAKE_TOOLCHAIN_FILE=/opt/KasperskyOS-Community-Edition-<version>/toolchain/share/toolchain.cmake

KasperskyOS Community Edition includes the platform library containing a set of ready-to-use scripts for the CMake system.

To prepare an application for debugging:

  1. In the CMakeLists.txt file, define the LINK_FLAGS parameter value for the application that you want to debug as follows:

    set_target_properties (<application-name> PROPERTIES LINK_FLAGS "-Ttext <text-section-address>")

    The script automatically creates .gdbinit files. The set of commands for the GDB debugger are contained within .gdbinit files. This set of commands determines which address the GDB debugger will use to load entities for debugging.

  2. Build the application.
Page top
[Topic using_cmake]

Using your own build system

You can use other build systems or implement your own build system to prepare the solution's boot image.

To prepare the solution's boot image, the build system must include the following actions:

  1. Generate code of the transport methods and types used to create, send, receive and process IPC messages between entities that are included in the solution.

    Use the NK compiler for this purpose. In command arguments, relay the path to the files containing EDL-, CDL- and IDL descriptions of entities, components and interfaces.

  2. Build all entities included in the solution.

    To do so, use the cross compilers that are included in KasperskyOS Community Edition.

  3. Build an Einit initializing entity.

    Use the einit tool to generate the code of the Einit entity. In the command arguments, pass the path to the init description file (init.yaml by default).

    Then the Einit entity must be built using the C compiler that is provided in KasperskyOS Community Edition.

  4. Build the kernel module with Kaspersky Security System.

    To do so, use the makekss script. In the command arguments, relay the path to the security configuration file (security.psl by default).

  5. Create an image of the solution.

    To do so, use the makeimg script. In the command arguments, pass the executable ELF files of entities, the kernel module with Kaspersky Security System, the KasperskyOS kernel image, and any additional files.

Page top
[Topic using_custom_build_tools]

Deploying the solution's boot image on target devices

To deploy the solution's boot image on the target device:

  1. Connect the data storage drive from which you plan to run the solution's boot image on target devices.
  2. Find the block device corresponding to the connected drive, for example, by using the following command:

    fdisk -l

  3. If required, on the storage drive, create a new partition on which the solution's boot image will be deployed by using the fdisk tool, for example.
  4. If there is no file system on the partition, create one by using the mkfs tool, for example.

    You can use any file system that is supported by the GRUB 2 bootloader.

  5. Mount the data drive.

    mkdir /mnt/kos_device && mount /dev/sdXY /mnt/kos_device

    Here, /mnt/kos_device is the mount point, /dev/sdXY is the block device name, X is the letter corresponding to the connected drive, and Y is the partition number.

  6. Install the GRUB 2 operating system bootloader on the drive.

    To install GRUB 2, run the following command:

    grub-install --force --removable \
    --boot-directory=/mnt/kos_device/boot /dev/sdX

    Here, /mnt/kos_device is the mount point, /dev/sdX is the block device name, and X is the letter corresponding to the connected drive.

  7. Copy the boot image of the solution to the root directory of the mounted drive.
  8. In the /mnt/kos_device/boot/grub/grub.cfg file, add the menuentry section that points to the solution's boot image.

    menuentry "KasperskyOS" {

    multiboot /my_kasperskyos.img

    boot

    }

  9. Unmount the drive.

    umount /mnt/kos_device

    Here, /mnt/kos_device is the mount point.

After performing these actions, you can start KasperskyOS from this drive.

Page top
[Topic deploying_solution_image]