KasperskyOS Community Edition 1.0

Part 2. Interaction between entities

The preceding part of the Guide shows how to configure interaction with the entities provided in KasperskyOS Community Edition. To do so, just add several strings to the init.yaml file and link the client library of the entity (vfs_remote).

But how do you create a server entity, or in other words, an application that provides functionality to other (client) entities? To do so, utilize IPC transport, auxiliary tools and libraries provided in KasperskyOS Community Edition.

This part of the Guide illustrates the following:

  • Mechanism for interaction between entities in KasperskyOS
  • Tools and libraries that implement transport
  • Step-by-step actions for exchanging IPC messages

To simplify the descriptions, the examples in this part of the Guide are built without the ksm.module. For this reason, when examples are started, a notification WARNING! Booting an insecure kernel! is displayed. The third part of the Guide examines a solution security policy, use of security policies, and building ksm.module.

In this Help section

IPC transport tools

echo example

Page top
[Topic ch2_interaction]

IPC transport tools

To set up interactions between entities, you do not have to implement the correct packaging and unpacking of messages from scratch. In addition, you do not have to write separate code for creating IPC channels.

To resolve these and other problems associated with IPC transport, KasperskyOS provides a special set of tools:

  • NkKosTransport
  • EDL, CDL and IDL descriptions
  • NK compiler
  • Init description and einit tool
  • Service Locator

Combined use of these tools is shown in the echo example.

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 nk_transport_call(), nk_transport_recv() and nk_transport_reply() functions are used to call transport.

Example:

/* 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 NkKosTransport structure and methods for working with it are declared in the transport-kos.h file:

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

For more details about the constant part and the arena, refer to IPC message structure.

For more details about using NkKosTransport, refer to NkKosTransport.

EDL, CDL and IDL descriptions

The EDL, CDL and IDL languages are used to describe interfaces that implement server entities.

For more details, refer to Description of entities, components, and interfaces (EDL, CDL, IDL).

Description files (*.edl, *.cdl and *.idl) are processed by the NK compiler during the build. This results in the creation of the *.edl.h, *.cdl.h, and *.idl.h files, which contain the transport methods and types used on the client and on the server.

NK compiler

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

Most important transport methods:

  • Interface methods. When an interface method is called on the client, the server is sent an IPC request for calling the appropriate method.
  • Dispatch methods (dispatchers). When receiving a request, the server calls the dispatcher, which in turn calls the implementation of the appropriate method.

Most important transport types:

  • Types that define the structure of the constant part of a message. They are sent to interface methods, dispatchers and transport functions (nk_transport_recv(), nk_transport_reply()).
  • Types of proxy objects. A proxy object is used as an argument in an interface method.

For more details, refer to Generated methods and types.

Init description and einit tool

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

To enable the Einit entity to create a connection between entities A and B during startup, you need to specify the following in the init.yaml file:

init.yaml

# Start B

- name: B

# Start A

- name: A

  connections:

  # Create a connection with server entity B.

  - target: B

  # Name of the new connection: some_connection_to_B

    id: some_connection_to_B

For more details, refer to Entity startup.

Service Locator

The Service Locator is a library containing the following functions:

  • ServiceLocatorConnect() lets you find out the client IPC handle of the channel with a specified name.
  • ServiceLocatorRegister() lets you find out the server IPC handle of the channel with a specified name.
  • ServiceLocatorGetRiid() lets you find out the RIID (Runtime Interface ID) using an interface implementation name.

The values of the IPC handle and RIID are used during NkKosTransport initialization.

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

#include <coresrv/sl/sl_api.h>

For more details, refer to Service Locator.

Page top
[Topic ipc_transport_instruments]

echo example

The echo example demonstrates the use of IPC transport.

It shows how to use the main tools that let you implement interaction between entities.

In this section

About the echo example

Implementation of the Client entity in the echo example

Implementation of the Server entity in the echo example

Description files in the echo example

Building and running the echo example

Page top
[Topic echo_example]

About the echo example

The echo example describes a basic case of interaction between two entities:

  1. The Client entity sends a number (value) to the Server entity.
  2. The Server entity modifies this number and sends the new number (result) to the Client entity.
  3. The Client entity prints the result to the screen.

To set up this interaction between entities:

  1. Connect the Client and Server entities by using the init description.
  2. On the server, implement an interface with a single Ping method that has one input argument (the original number (value)) and one output argument (the modified number (result)).

    Description of the Ping method in the IDL language:

    Ping(in UInt32 value, out UInt32 result);

  3. Create static description files in the EDL, CDL and IDL languages. Use the NK compiler to generate files containing transport methods and types (proxy object, dispatchers, etc.).
  4. In the code of the Client entity, initialize all required objects (transport, proxy object, request structure, etc.) and call the interface method.
  5. In the code of the Server entity, prepare all the required objects (transport, component dispatcher and entity dispatcher, etc.), accept the request from the client, process it and send a response.

The echo example consists of the following source files:

  • client/src/client.c – implementation of the Client entity.
  • server/src/server.c – implementation of the Server entity.
  • resources/Server.edl, resources/Client.edl, resources/Ping.cdl, resources/Ping.idl – static descriptions.
  • init.yaml – init description.
Page top
[Topic about_echo_example]

Implementation of the Client entity in the echo example

The code of the Client entity uses the transport types and methods that will be generated during the solution build by the NK compiler based on the IDL description of the Ping interface.

However, to obtain the descriptions of types and signatures of methods required for implementing the entity, you can use the NK compiler immediately after creating an EDL description of the entity, CDL descriptions of components and IDL descriptions of the interfaces used for interaction. As a result, the required types and signatures of methods will be declared in the generated *.h files.

In the Client entity implementation, the following is required:

  1. Get the client IPC handle of the connection (channel) by using the ServiceLocatorConnect() function.

    Input the name of the IPC server_connection predefined in the init.yaml file.

  2. Initialize NkKosTransport by passing the obtained IPC handle to the NkKosTransport_Init() function.
  3. Obtain the ID of the required interface (RIID) by using the ServiceLocatorGetRiid() function.
  4. Input the full name of the Ping interface implementation. It consists of the names of the component instance (Echo.ping) and interface (ping) that were previously defined in Server.edl and Ping.cdl.
  5. Initialize the proxy object by passing the transport and interface ID to the Ping_proxy_init() function.
  6. Prepare the request and response structures.
  7. Call the Ping_Ping() interface method by passing the proxy object and the pointers to the request and response.

client.c

#include <stdio.h>

#include <stdlib.h>

#include <stdint.h>

/* Files required for transport initialization. */

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

#include <coresrv/sl/sl_api.h>

/* Description of the server interface used by the client entity. */

#include <echo/Ping.idl.h>

#include <assert.h>

#define EXAMPLE_VALUE_TO_SEND 777

/* Client entity entry point. */

int main(int argc, const char *argv[])

{

NkKosTransport transport;

struct echo_Ping_proxy proxy;

int i;

fprintf(stderr, "Hello I'm client\n");

/* Get client IPC handle of

* "server_connection". */

Handle handle = ServiceLocatorConnect("server_connection");

assert(handle != INVALID_HANDLE);

/* Initialize IPC transport for interaction with the server entity. */

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

/* Get Runtime Interface ID (RIID) for interface echo.Ping.ping.

* Here ping is the name of the echo.Ping component instance,

* echo.Ping.ping is the name of the Ping interface implementation. */

nk_iid_t riid = ServiceLocatorGetRiid(handle, "echo.Ping.ping");

assert(riid != INVALID_RIID);

/* Initialize proxy object by specifying transport (&transport)

* and ID of the server interface (riid). Each method

* of the proxy object will be implemented by sending a request to the server. */

echo_Ping_proxy_init(&proxy, &transport.base, riid);

/* Request and response structures */

echo_Ping_Ping_req req;

echo_Ping_Ping_res res;

/* Test loop. */

req.value = EXAMPLE_VALUE_TO_SEND;

for (i = 0; i < 10; ++i)

{

/* Call Ping interface method.

* Server will be sent a request for calling Ping interface method

* ping_comp.ping_impl with the value argument. Calling thread is locked

* until a response is received from the server. */

if (echo_Ping_Ping(&proxy.base, &req, NULL, &res, NULL) == rcOk)

{

/* Print "result" value from response

* (result is the output argument of the Ping method). */

fprintf(stderr, "result = %d\n", (int) res.result);

/* Include received "result" value into "value" argument

* to resend to server in next iteration. */

req.value = res.result;

}

else

fprintf(stderr, "Failed to call echo.Ping.Ping()\n");

}

return EXIT_SUCCESS;

}

Page top
[Topic echo_client_implementation]

Implementation of the Server entity in the echo example

The code of the Server entity uses the transport types and methods that will be generated during the solution build by the NK compiler based on the EDL description of the Server entity.

However, to obtain the types and signatures of methods required for implementing the entity, you can use the NK compiler immediately after creating an EDL description of the entity, CDL descriptions of components and IDL descriptions of the interfaces used for interaction. As a result, the required types and signatures of methods will be declared in the generated *.h files.

In the server entity implementation, the following is required:

  1. Implement the Ping() method.

    The signature of the Ping() method implementation must exactly match the signature of the Ping_Ping() interface method that is declared in the Ping.idl.h file.

  2. Get the server IPC handle of the connection (channel) by using the ServiceLocatorRegister() function.

    Input the name of the IPC server_connection predefined in the init.yaml file.

  3. Initialize NkKosTransport by passing the obtained IPC handle to the NkKosTransport_Init() function.
  4. Prepare the request and response structures.
  5. Initialize the dispatch method (dispatcher) of the Ping component by using the Ping_component_init() function.
  6. Initialize the dispatch method (dispatcher) of the Server entity by using the Server_entity_init() function.
  7. Receive a request by calling nk_transport_recv().
  8. Process the received request by calling the Server_entity_dispatch() dispatcher.

    The dispatcher calls the required implementation of the method based on the interface ID (RIID) received from the client.

  9. Send the response to the Client entity by calling nk_transport_reply().

server.c

#include <stdio.h>

#include <stdlib.h>

#include <stdbool.h>

/* Files required for transport initialization. */

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

#include <coresrv/sl/sl_api.h>

/* Server entity descriptions in EDL. */

#include <echo/Server.edl.h>

#include <assert.h>

/* Type of interface implementing object. */

typedef struct IPingImpl {

struct echo_Ping base; // base interface of object

int step; // Additional parameters

} IPingImpl;

/* Implementation of the Ping method. */

static nk_err_t Ping_impl(struct echo_Ping *self,

const echo_Ping_req *req,

const struct nk_arena* req_arena,

echo_Ping_res* res,

struct nk_arena* res_arena)

{

IPingImpl *impl = (IPingImpl *)self;

/* Increment value in the client request by

* one step and include into result argument that will be

* sent to the client in the server response. */

res->Ping.result = req->Ping.value + impl->step;

return NK_EOK;

}

/* IPing object constructor.

* step is the number by which the input value is increased. */

static struct echo_Ping *CreateIPingImpl(int step)

{

/* Table of IPing interface method implementations. */

static const struct echo_Ping_ops ops = {

.Ping = Ping_impl

};

/* Object implementing the interface. */

static struct IPingImpl impl = {

.base = {&ops}

};

impl.step = step;

return &impl.base;

}

/* Server entry point. */

int main(void)

{

NkKosTransport transport;

ServiceId iid;

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

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

assert(handle != INVALID_HANDLE);

/* Initialize transport to client. */

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

/* Prepare the structures of the request to the server entity: constant

* part and arena. Because none of the methods of the server entity has

* sequence type arguments, only constant parts are used

* request and response. Arenas are effectively unused. However, the valid

* arenas of the request and response must be passed to

* the server transport methods (nk_transport_recv, nk_transport_reply) and

* the dispatch method server_entity_dispatch. */

echo_Server_entity_req req;

char req_buffer[echo_Server_entity_req_arena_size];

struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_buffer, req_buffer + sizeof(req_buffer));

/* Prepare response structures: constant part and arena. */

echo_Server_entity_res res;

char res_buffer[echo_Server_entity_res_arena_size];

struct nk_arena res_arena = NK_ARENA_INITIALIZER(res_buffer, res_buffer + sizeof(res_buffer));

/* Initialize ping component dispatcher. 3 is the value of the "step",

* which is the number by which the input value is increased. */

echo_Ping_component component;

echo_Ping_component_init(&component, CreateIPingImpl(3));

/* Initialize server entity dispatcher. */

echo_Server_entity entity;

echo_Server_entity_init(&entity, &component);

fprintf(stderr, "Hello I'm server\n");

/* Dispatch loop implementation. */

do

{

/* Reset request/response buffers. */

nk_req_reset(&req);

nk_arena_reset(&req_arena);

nk_arena_reset(&res_arena);

/* Wait for request from client entity. */

if (nk_transport_recv(&transport.base, &req.base_, &req_arena) != NK_EOK) {

fprintf(stderr, "nk_transport_recv error\n");

} else {

/* Process received request by calling Ping_impl implementation

* of the requested Ping interface method. */

echo_Server_entity_dispatch(&entity, &req.base_, &req_arena, &res.base_, &res_arena);

}

/* Send response. */

if (nk_transport_reply(&transport.base, &res.base_, &res_arena) != NK_EOK) {

fprintf(stderr, "nk_transport_reply error\n");

}

}

while (true);

return EXIT_SUCCESS;

}

Page top
[Topic echo_server_implementation]

Description files in the echo example

Description of the Client entity

The Client entity does not implement an interface, so all you need to do is declare the entity name in its EDL description:

Client.edl

/* Description of the Client entity. */

entity echo.Client

Server entity description

The description of the Server entity must contain information stating that it implements the Ping interface. Using static descriptions, you need to insert the implementation of the Ping interface into the new component (for example, Ping) and insert the instance of this component into the Server entity.

The Server entity contains an instance of the Ping component:

Server.edl

/* Description of the Server entity. */

entity echo.Server

/* Server is a named instance of the echo.Ping component. */

components {

Server: echo.Ping

}

The Ping component contains the implementation of the Ping interface:

Ping.cdl

/* Description of the Ping component. */

component echo.Ping

/* ping is the Ping interface implementation. */

interfaces {

ping: echo.Ping

}

The Ping package contains a declaration of the Ping interface:

Ping.idl

/* Description of the Ping package. */

package echo.Ping

interface {

Ping(in UInt32 value, out UInt32 result);

}

Init description

To enable the Client entity to call a method of the Server entity, a connection (IPC channel) must be created between them.

To do so, in the init description indicate that the Client and Server entities must be started and connect them:

init.yaml

entities:

- name: echo.Client

connections:

- target: echo.Server

id: server_connection

- name: Server

The Server entity is indicated as - target, so it will perform the server entity role, meaning it will accept requests from the Client entity and respond to them.

The created IPC channel is named server_connection. You can obtain the handle of this channel by using the Service Locator.

Page top
[Topic echo_description_files]

Building and running the echo example

See the Building and running examples section.

The build scheme for the echo example looks as follows:

Page top

[Topic echo_example_build]