KasperskyOS Community Edition 1.0

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]