KasperskyOS Community Edition 1.2
[Topic sc_using_new_endpoints]

Overview: IPC message structure

In KasperskyOS, all interactions between processes have statically defined types. The permissible structures of an IPC message are defined by the IDL descriptions of servers.

An IPC message (request and response) contains a constant part and an (optional) arena.

Constant part of an IPC message

The constant part of an IPC message contains the RIID, MID, and (optionally) fixed-size parameters of interface methods.

Fixed-size parameters are parameters that have IDL types of a fixed size.

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

  • The RIID (Runtime Implementation ID) is the sequence number of the utilized endpoint within the set of server endpoints (starting at zero).
  • The MID (Method ID) is the sequence number of the called method within the set of methods of the utilized endpoint (starting at zero).

The type of the constant part of the IPC 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 process, component or interface. For more details, refer to Example generation of transport methods and types.

IPC message arena

An IPC message arena (hereinafter also referred to as an arena) contains variable-size parameters of interface methods (and/or elements of these parameters).

Variable-size parameters are parameters that have IDL types of a variable size.

For more details, refer to "Working with an IPC message arena".

Maximum IPC message size

The maximum size of an IPC message is determined by the KasperskyOS kernel parameters. On most hardware platforms supported by KasperskyOS, the cumulative size of the constant part and arena of an IPC message cannot exceed 4, 8, or 16 MB.

IPC message structure verification by the security module

Prior to querying IPC message-related rules, the Kaspersky Security Module verifies that the sent IPC message is correct. Requests and responses are both validated. If the IPC message has an incorrect structure, it will be rejected without calling the security model methods associated with it.

Implementation of IPC interaction

To make it easier for a developer to implement IPC interaction, KasperskyOS Community Edition provides the following:

Implementation of simple IPC interaction is demonstrated in the echo and ping examples (/opt/KasperskyOS-Community-Edition-<version>/examples/).

Page top
[Topic ipc_message_structure_overview]

Getting an IPC handle

The client and server IPC handles must be obtained if there are no ready-to-use transport libraries for the utilized endpoint (for example, if you wrote your own endpoint). To independently work with IPC transport, you need to first initialize it by using the NkKosTransport_Init() method and pass the IPC handle of the utilized channel as the second argument.

For more details, see the echo and ping examples (/opt/KasperskyOS-Community-Edition-<version>/examples/).

You do not need to get an IPC handle to utilize endpoints that are implemented in executable files provided in KasperskyOS Community Edition. The provided transport libraries are used to perform all transport operations, including obtaining IPC handles.

See the gpio_*, net_*, net2_* and multi_vfs_* examples (/opt/KasperskyOS-Community-Edition-<version>/examples/).

Getting an IPC handle when statically creating a channel

When statically creating an IPC channel, both the client and server can obtain their IPC handles immediately after startup by using the ServiceLocatorRegister() and ServiceLocatorConnect() methods and specifying the name of the created IPC channel.

For example, if the IPC channel is named server_connection, the following must be called on the client side:

#include <coresrv/sl/sl_api.h> … Handle handle = ServiceLocatorConnect("server_connection");

The following must be called on the server side:

#include <coresrv/sl/sl_api.h> … ServiceId id; Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &id);

For more details, see the echo and ping examples (/opt/KasperskyOS-Community-Edition-<version>/examples/), and the header file /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/coresrv/sl/sl_api.h.

Closing an obtained IPC handle will cause the IPC channel to become unavailable. After an IPC handle is closed, it is impossible to obtain it again or restore access to the IPC channel.

Getting an IPC handle when dynamically creating a channel

Both the client and server receive their own IPC handles immediately after dynamic creation of an IPC channel is successful.

The client IPC handle is one of the output (out) arguments of the KnCmConnect() method. The server IPC handle is an output argument of the KnCmAccept() method. For more details, see the header file /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/coresrv/cm/cm_api.h.

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

Page top
[Topic ipc_find_ipc_desc]

Getting an endpoint ID (riid)

The endpoint ID (riid) must be obtained on the client side if there are no ready-to-use transport libraries for the utilized endpoint (for example, if you wrote your own endpoint). To call methods of the server, you must first call the proxy object initialization method on the client side and pass the endpoint ID as the third parameter. For example, for the Filesystem interface:

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

For more details, see the echo and ping examples (/opt/KasperskyOS-Community-Edition-<version>/examples/).

You do not need to get the endpoint ID to utilize endpoints that are implemented in executable files provided in KasperskyOS Community Edition. The provided transport libraries are used to perform all transport operations.

See the gpio_*, net_*, net2_* and multi_vfs_* examples (/opt/KasperskyOS-Community-Edition-<version>/examples/).

Getting an endpoint ID when statically creating a channel

When statically creating an IPC channel, the client can obtain the ID of the necessary endpoint by using the ServiceLocatorGetRiid() method and specifying the IPC channel handle and the qualified name of the endpoint. For example, if the OpsComp component instance provides the FS endpoint, the following must be called on the client side:

#include <coresrv/sl/sl_api.h>nk_iid_t riid = ServiceLocatorGetRiid(handle, "OpsComp.FS");

For more details, see the echo and ping examples (/opt/KasperskyOS-Community-Edition-<version>/examples/), and the header file /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/coresrv/sl/sl_api.h.

Getting an endpoint ID when dynamically creating a channel

The client receives the endpoint ID immediately after dynamic creation of an IPC channel is successful. The client IPC handle is one of the output (out) arguments of the KnCmConnect() method. For more details, see the header file /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/coresrv/cm/cm_api.h.

Page top
[Topic ipc_find_riid]

Example generation of transport 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.

As an example, we will examine the Server process class that provides the FS endpoint, which contains a single Open() method:

Server.edl

entity Server /* OpsComp is the named instance of the Operations component */ components { OpsComp: Operations }

Operations.cdl

component Operations /* FS is the local name of the endpoint implementing the Filesystem interface */ endpoints { FS: Filesystem }

Filesystem.idl

package Filesystem interface { Open(in string<256> name, out UInt32 h); }

These descriptions will be used to generate the files named Server.edl.h, Operations.cdl.h, and Filesystem.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 (Filesystem) will be generated:

    typedef struct Filesystem { const struct Filesystem_ops *ops; } Filesystem; typedef nk_err_t Filesystem_Open_fn(struct Filesystem *, const struct Filesystem_Open_req *, const struct nk_arena *, struct Filesystem_Open_res *, struct nk_arena *); typedef struct Filesystem_ops { Filesystem_Open_fn *Open; } Filesystem_ops;
  • Set of interface methods.

    When calling an interface method, the corresponding values of the RIID and MID are automatically inserted into the request.

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

    nk_err_t Filesystem_Open(struct Filesystem *self, struct Filesystem_Open_req *req, const struct nk_arena *req_arena, struct Filesystem_Open_res *res, struct nk_arena *res_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 Filesystem_proxy proxy object type will be generated:

    typedef struct Filesystem_proxy { struct Filesystem base; struct nk_transport *transport; nk_iid_t iid; } Filesystem_proxy;
  • Functions for initializing proxy objects.

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

    void Filesystem_proxy_init(struct Filesystem_proxy *self, struct nk_transport *transport, nk_iid_t iid)
  • 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: Filesystem_Open_req (for a request) and Filesystem_Open_res (for a response).

    typedef struct __nk_packed Filesystem_Open_req { __nk_alignas(8) struct nk_message base_; __nk_alignas(4) nk_ptr_t name; } Filesystem_Open_req; typedef struct Filesystem_Open_res { union { struct { __nk_alignas(8) struct nk_message base_; __nk_alignas(4) nk_uint32_t h; }; struct { __nk_alignas(8) struct nk_message base_; __nk_alignas(4) nk_uint32_t h; } res_; struct Filesystem_Open_err err_; }; } Filesystem_Open_res;

Methods and types used only on the server

  • Type containing all endpoints of a component, 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 Operations_component structure and Operations_component_init function will be generated:

    typedef struct Operations_component { struct Filesystem *FS; }; void Operations_component_init(struct Operations_component *self, struct Filesystem *FS)
  • Type containing all endpoints provided directly by the server; all instances of components included in the server; and the initializing function.

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

    #define Server_entity Server_component typedef struct Server_component { struct : Operations_component *OpsComp; } Server_component; void Server_entity_init(struct Server_entity *self, struct Operations_component *OpsComp)
  • 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: Filesystem_req (for a request) and Filesystem_res (for a response).

    typedef union Filesystem_req { struct nk_message base_; struct Filesystem_Open_req Open; }; typedef union Filesystem_res { struct nk_message base_; struct Filesystem_Open_res Open; };
  • Types that define the structure of the constant part of a message for any method of any endpoint of a specific component.

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

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

    typedef union Operations_component_req { struct nk_message base_; Filesystem_req FS; } Operations_component_req; typedef union Operations_component_res { struct nk_message base_; Filesystem_res FS; } Operations_component_res;
  • Types that define the structure of the constant part of a message for any method of any endpoint of a specific component whose instance is included in the server.

    If embedded components are present, these types also contain structures of the constant part of a message for any method of any endpoint 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).

    #define Server_entity_req Server_component_req typedef union Server_component_req { struct nk_message base_; Filesystem_req OpsComp_FS; } Server_component_req; #define Server_entity_res Server_component_res typedef union Server_component_res { struct nk_message base_; Filesystem_res OpsComp_FS; } Server_component_res;
  • Dispatch methods (dispatchers) for a separate interface, component, or process class.

    Dispatchers analyze the received query (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: Filesystem_interface_dispatch, Operations_component_dispatch, and Server_entity_dispatch.

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

    nk_err_t Server_entity_dispatch(struct Server_entity *self, const struct nk_message *req, const struct nk_arena *req_arena, struct nk_message *res, struct nk_arena *res_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 Operations_component_dispatch(struct Operations_component *self, nk_iid_t iidOffset, const struct nk_message *req, const struct nk_arena *req_arena, struct nk_message *res, struct nk_arena *res_arena) nk_err_t Filesystem_interface_dispatch(struct Filesystem *impl, nk_iid_t iid, const struct nk_message *req, const struct nk_arena *req_arena, struct nk_message *res, struct nk_arena *res_arena)
Page top
[Topic transport_code_overview]

Working with an IPC message arena

Arena overview

From the perspective of a developer of KasperskyOS-based solutions, an IPC message arena is a byte buffer in the memory of a process intended for storing variable-size data transmitted over IPC. This variable-size data includes input parameters, output parameters, and error parameters of interface methods (and/or elements of these parameters) that have variable-size IDL types. An arena is also used when querying the Kaspersky Security Module to store input parameters of security interface methods (and/or elements of these parameters) that have variable-size IDL types. (Parameters of fixed-size interface methods are stored in the constant part of an IPC message.) Arenas are used on the client side and on the server side. One arena is intended either for transmitting or for receiving variable-size data through IPC, but not for both transmitting and receiving this data at the same time. In other words, arenas can be divided into IPC request arenas (containing input parameters of interface methods) and IPC response arenas (containing output parameters and error parameters of interface methods).

Only the utilized part of an arena that is occupied by data is transmitted over IPC. (If it has no data, the arena is not transmitted.) The utilized part of an arena includes one or more segments. One segment of an arena stores an array of same-type objects, such as an array of single-byte objects or an array of structures. Arrays of different types of objects may be stored in different segments of an arena. The starting address of an arena must be equal to the boundary of a 2^N-byte sequence, where 2^N is a value that is greater than or equal to the size of the largest primitive type in the arena (for example, the largest field of a primitive type in a structure). The address of an arena chunk must also be equal to the boundary of a 2^N-byte sequence, where 2^N is a value that is greater than or equal to the size of the largest primitive type in the arena chunk.

An arena must have multiple segments if the interface method has multiple variable-size input, output, or error parameters, or if multiple elements of input, output, or error parameters of the interface method have a variable size. For example, if an interface method has an input parameter of the sequence IDL type and an input parameter of the bytes IDL type, the IPC request arena will have at least two segments. In this case, it may even require additional segments if a parameter of the sequence IDL type consists of elements of a variable-size IDL type (for example, if elements of a sequence are string buffers). As another example, if an interface method has one output parameter of the struct IDL type that contains two fields of the bytes and string type, the IPC response arena will have two segments.

Due to the alignment of arena chunk addresses, there may be unused intervals between these chunks. Therefore, the size of the utilized part of an arena may exceed the size of the data it contains.

API for working with an arena

The set of functions and macros for working with an arena is defined in the header file sysroot-*-kos/include/nk/arena.h from the KasperskyOS SDK. In addition, the function for copying a string to an arena is declared in the header file sysroot-*-kos/include/coresrv/nk/transport-kos.h from the KasperskyOS SDK.

Information on the functions and macros defined in the header file sysroot-*-kos/include/nk/arena.h is provided in the table below. In these functions and macros, an arena and arena chunk are identified by an arena descriptor (the nk_arena type) and an arena chunk descriptor (the nk_ptr_t type), respectively. An arena descriptor is a structure containing three pointers: one pointer to the start of the arena, one pointer to the start of the unused part of the arena, and one pointer to the end of the arena. An arena chunk descriptor is a structure containing the offset of the arena chunk in bytes (relative to the start of the arena) and the size of the arena chunk in bytes. (The arena chunk descriptor type is defined in the header file sysroot-*-kos/include/nk/types.h from the KasperskyOS SDK.)

Creating an arena

To pass variable-size parameters of interface methods over IPC, you must create arenas on the client side and on the server side. (When IPC requests are handled on the server side using the NkKosDoDispatch() function defined in the header file sysroot-*-kos/include/coresrv/nk/transport-kos-dispatch.h from the KasperskyOS SDK, the IPC request arena and IPC response arena are created automatically.)

To create an arena, you must create a buffer (in the stack or heap) and initialize the arena descriptor.

The address of the buffer must be aligned to comply with the maximum size of a primitive type that can be put into this buffer. The address of a dynamically created buffer usually has adequate alignment to hold the maximum amount of data of the primitive type. To ensure the required alignment of the address of a statically created buffer, you can use the alignas specifier.

To initialize an arena descriptor using the pointer to an already created buffer, you must use an API function or macro:

  • NK_ARENA_INITIALIZER() macro
  • nk_arena_init() function
  • nk_arena_create() function
  • NK_ARENA_FINAL() macro
  • nk_arena_init_final() macro

The type of pointer makes no difference because this pointer is converted into a pointer to a single-byte object in the code of API functions and macros.

The NK_ARENA_INITIALIZER() macro and the nk_arena_init() and nk_arena_create() functions initialize arena descriptor that may contain one or more segments. The NK_ARENA_FINAL() and nk_arena_init_final() macros initialize arena descriptor that contains only one segment spanning the entire arena throughout its entire life cycle.

To create a buffer in the stack and initialize the handle in one step, use the NK_ARENA_AUTO() macro. This macro creates an arena that may contain one or more segments, and the address of the buffer created by this macro has adequate alignment to hold the maximum amount of data of the primitive type.

The size of an arena must be sufficient to hold variable-size parameters for IPC requests or IPC responses of one interface method or a set of interface methods corresponding to one interface, component, or process class while accounting for the alignment of segment addresses. Automatically generated transport code (the header files *.idl.h, *.cdl.h, and *.edl.h) contain *_arena_size constants whose values are guaranteed to comply with sufficient sizes of arenas in bytes.

The header files *.idl.h, *.cdl.h, and *.edl.h contain the following *_arena_size constants:

  • <interface name>_<interface method name>_req_arena_size – size of an IPC request arena for the specified interface method of the specified interface
  • <interface name>_<interface method name>_res_arena_size – size of an IPC response arena for the specified interface method of the specified interface
  • <interface name>_req_arena_size – size of an IPC request arena for any interface method of the specified interface
  • <interface name>_res_arena_size – size of an IPC response arena for any interface method of the specified interface

The header files *.cdl.h and *.edl.h also contain the following *_arena_size constants:

  • <component name>_component_req_arena_size – size of an IPC request arena for any interface method of the specified component
  • <component name>_component_res_arena_size – size of an IPC response arena for any interface method of the specified component

The *.edl.h header files also contain the following *_arena_size constants:

  • <process class name>_entity_req_arena_size – size of an IPC request arena for any interface method of the specified process class
  • <process class name>_entity_res_arena_size – size of an IPC response arena for any interface method of the specified process class

Constants containing the size of an IPC request arena or IPC response arena for one interface method (<interface name>_<interface method name>_req_arena_size and <interface name>_<interface method name>_res_arena_size) are intended for use on the client side. All other constants can be used on the client side and on the server side.

Examples of creating an arena:

/* Example 1 */ alignas(8) char reqBuffer[Write_WriteInLog_req_arena_size]; struct nk_arena reqArena = NK_ARENA_INITIALIZER( reqBuffer, reqBuffer + sizeof(reqBuffer)); /* Example 2 */ struct nk_arena res_arena; char res_buf[kl_rump_DhcpcdConfig_GetOptionNtpServers_res_arena_size]; nk_arena_init(&res_arena, res_buf, res_buf + sizeof(res_buf)); /* Example 3 */ char req_buffer[kl_CliApplication_Run_req_arena_size]; struct nk_arena req_arena = nk_arena_create(req_buffer, sizeof(req_buffer)); /* Example 4 */ nk_ptr_t ptr; const char *cstr = "example"; nk_arena arena = NK_ARENA_FINAL(&ptr, cstr, strlen(cstr)); /* Example 5 */ const char *path = "path_to_file"; size_t len = strlen(path); /* Structure for saving the constant part of an IPC request */ struct kl_VfsFilesystem_Rmdir_req req; struct nk_arena req_arena; nk_arena_init_final(&req_arena, &req.path, path, len); /* Example 6 */ struct nk_arena res_arena = NK_ARENA_AUTO(kl_Klog_component_res_arena_size);

Adding data to an arena before transmission over IPC

Before transmitting an IPC request on the client side or an IPC response on the server side, data must be added to the arena. If the NK_ARENA_FINAL() macro or the nk_arena_init_final() macro is used to create an arena, you do not need to reserve an arena chunk. Instead, you only need to add data to this chunk. If the NK_ARENA_INITIALIZER() or NK_ARENA_AUTO() macro, or the nk_arena_init() or nk_arena_create() function is used to create an arena, one or multiple segments in the arena must be reserved to hold data. To reserve an arena chunk, you must use an API function or macro:

  • __nk_arena_alloc() function
  • nk_arena_store() macro
  • __nk_arena_store() function
  • nk_arena_alloc() macro
  • NkKosCopyStringToArena() function

The arena chunk descriptor, which is passed through the output parameter of these functions and macros and through the output parameter of the NK_ARENA_FINAL() and nk_arena_init_final() macros, must be put into the constant part or into the arena of an IPC message. If an interface method has a variable-size parameter, the constant part of IPC messages contains arena chunk descriptor containing the parameter instead of the actual parameter. If an interface method has a fixed-size parameter with variable-size elements, the constant part of IPC messages contains arena chunk descriptors containing the parameter elements instead of the actual parameter elements. If an interface method has a variable-size parameter containing variable-size elements, the constant part of IPC messages contains arena chunk descriptor containing the descriptors of other arena chunks that contain these parameter elements.

The nk_arena_store() macro and the __nk_arena_store() and NkKosCopyStringToArena() functions not only reserve an arena chunk, but also copy data to this chunk.

The nk_arena_alloc() macro gets the address of a reserved arena chunk. An arena chunk address can also be received by using the __nk_arena_get() function or the nk_arena_get() macro, which additionally pass the arena size through the output parameter.

A reserved arena chunk can be reduced. To do so, use the nk_arena_shrink() macro or the _nk_arena_shrink() function.

To undo a current reservation of arena chunks so that new chunks can be reserved for other data (after sending an IPC message), call the nk_arena_reset() function. If the NK_ARENA_FINAL() macro or nk_arena_init_final() macro is used to create an arena, you do not need to undo a segment reservation because the arena contains one segment spanning the entire arena throughout its entire life cycle.

Examples of adding data to an arena:

/* Example 1 */ char req_buffer[kl_rump_NpfctlFilter_TableAdd_req_arena_size]; struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_buffer, req_buffer + sizeof(req_buffer)); /* Structure for saving the constant part of an IPC request */ struct kl_rump_NpfctlFilter_TableAdd_req req; if (nk_arena_store(char, &req_arena, &req.tid, tid, tidlen)) return ENOMEM; if (nk_arena_store(char, &req_arena, &req.cidrAddr, cidr_addr, cidr_addrlen)) return ENOMEM; /* Example 2 */ char req_arena_buf[StringMaxSize]; struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_arena_buf, req_arena_buf + sizeof(req_arena_buf)); /* Structure for saving the constant part of an IPC request */ kl_drivers_FBConsole_SetFont_req req; size_t buf_size = strlen(fileName) + 1; char *buf = nk_arena_alloc(char, &req_arena, &req.fileName, buf_size); memcpy(buf, fileName, buf_size); /* Example 3 */ char reqArenaBuf[kl_core_DCM_req_arena_size]; struct nk_arena reqArena = NK_ARENA_INITIALIZER(reqArenaBuf, reqArenaBuf + sizeof(reqArenaBuf)); /* Structure for saving the constant part of an IPC request */ kl_core_DCM_Subscribe_req req; rc = NkKosCopyStringToArena(&reqArena, &req.endpointType, endpointType); if (rc != rcOk) return rc; rc = NkKosCopyStringToArena(&reqArena, &req.endpointName, endpointName); if (rc != rcOk) return rc; rc = NkKosCopyStringToArena(&reqArena, &req.serverName, serverName); if (rc != rcOk) return rc; /* Example 4 */ unsigned counter = 0; nk_ptr_t *paths; /* Reserve an arena chunk for descriptors of other arena chunks */ paths = nk_arena_alloc(nk_ptr_t, resArena, &res->logRes, msgCount); while(...) { ... /* Reserve arena chunks and save their descriptors in * a previously reserved arena chunk with the paths address */ char *str = nk_arena_alloc( char, resArena, &paths[counter], stringLength + 1); if (str == NK_NULL) return NK_ENOMEM; snprintf(str, (stringLength + 1), "%s", buffer); ... counter++; }

Retrieving data from an arena after receipt over IPC

Prior to receiving an IPC request on the server side or an IPC response on the client side for an arena that will store the data received over IPC, you must undo the current reservation of segments by calling the nk_arena_reset() function. This must be done even if the NK_ARENA_FINAL() macro or the nk_arena_init_final() macro is used to create the arena. (The NK_ARENA_INITIALIZER() and NK_ARENA_AUTO() macros, and the nk_arena_init() and nk_arena_create() functions create an arena without reserved segments. You do not need to call the nk_arena_reset() function if this arena will only be used once to save data received over IPC.)

To receive pointers to arena chunks and the sizes of these chunks, you must use the __nk_arena_get() function or the nk_arena_get() macro while using the input parameter to pass the corresponding arena chunk descriptors received from the constant part and arena of the IPC message.

Example of receiving data from an arena:

struct nk_arena res_arena; char res_buf[kl_rump_DhcpcdConfig_Version_res_ver_size]; nk_arena_init(&res_arena, res_buf, res_buf + sizeof(res_buf)); /* Structure for saving an IPC request */ struct kl_rump_DhcpcdConfig_Version_req req; req.buflen = buflen; /* Structure for saving an IPC response */ struct kl_rump_DhcpcdConfig_Version_res res; /* Call the interface method */ if (kl_rump_DhcpcdConfig_Version(dhcpcd.proxy, &req, NULL, &res, &res_arena) != NK_EOK) return -1; size_t ptrlen; char *ptr = nk_arena_get(char, &res_arena, &res.ver, &ptrlen); memcpy(buf, ptr, ptrlen);

Additional capabilities of the API

To get the arena size, call the nk_arena_capacity() function.

To get the size of the utilized part of the arena, call the nk_arena_allocated_size() function.

To verify that the arena chunk descriptor is correct, use the nk_arena_validate() macro or the __nk_arena_validate() function.

Information about API functions and macros

Functions and macros of arena.h

Function/Macro

Information about the function/macro

NK_ARENA_INITIALIZER()

Purpose

Initializes the arena descriptor.

Parameters

  • [in] _start – pointer to the start of the arena.
  • [in] _end – pointer to the end of the arena.

Macro values

Arena descriptor initialization code.

nk_arena_init()

Purpose

Initializes the arena descriptor.

Parameters

  • [out] self – pointer to the arena descriptor.
  • [in] start – pointer to the start of the arena.
  • [in] end – pointer to the end of the arena.

Returned values

N/A

nk_arena_create()

Purpose

Creates and initializes the arena descriptor.

Parameters

  • [in] start – pointer to the start of the arena.
  • [in] size – arena size in bytes.

Returned values

Arena descriptor.

NK_ARENA_AUTO()

Purpose

Creates a buffer in the stack, and creates and initializes the arena descriptor.

Parameters

  • [in] size – arena size in bytes. It must be defined as a constant.

Macro values

Arena descriptor.

NK_ARENA_FINAL()

Purpose

Initializes the arena descriptor containing only one segment.

Parameters

  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] start – pointer to the start of the arena.
  • [in] count – number of objects in the arena chunk.

Macro values

Arena descriptor.

nk_arena_reset()

Purpose

Resets the reservation of arena chunks.

Parameters

  • [in,out] self – pointer to the arena descriptor.

Returned values

N/A

__nk_arena_alloc()

Purpose

Reserves an arena chunk with a specific size and a specific alignment.

Parameters

  • [in,out] self – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] size – arena chunk size in bytes.
  • [in] align – value defining the arena chunk alignment. The arena chunk address can be unaligned (align=1) or aligned (align=2,4,...,2^N) to the boundary of a 2^N-byte sequence (for example, two-byte or four-byte).

Returned values

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

nk_arena_capacity()

Purpose

Gets the size of an arena.

Parameters

  • [in] self – pointer to the arena descriptor.

Returned values

Size of the arena in bytes.

Additional information

If the parameter has the NK_NULL value, it returns 0.

nk_arena_validate()

Purpose

Verifies that the arena chunk descriptor is correct.

Parameters

  • [in] type – type of objects for which the arena chunk is intended.
  • [in] arena – pointer to the arena descriptor.
  • [in] ptr – pointer to the arena chunk descriptor.

Macro values

It has a value of 0 when the arena size is not zero if all of the following conditions are fulfilled:

  1. The offset specified in the arena chunk descriptor does not exceed the arena size.
  2. The size specified in the arena chunk descriptor does not exceed the arena size reduced by the offset specified in the arena chunk descriptor.
  3. The size specified in the arena chunk descriptor is a multiple of the size of the type of objects for which this arena chunk is intended.

It has a value of 0 when the arena size is zero if all of the following conditions are fulfilled:

  1. The offset specified in the arena chunk descriptor is equal to zero.
  2. The size specified in the arena chunk descriptor is equal to zero.

It has a value of -1 if even one of the conditions is not fulfilled (regardless of whether the arena size is zero or non-zero), or if the ptr parameter has the NK_NULL value.

__nk_arena_validate()

Purpose

Verifies that the arena chunk descriptor is correct.

Parameters

  • [in] self – pointer to the arena descriptor.
  • [in] ptr – pointer to the arena chunk descriptor.

Returned values

Returns 0 when the arena size is not zero if all of the following conditions are fulfilled:

  1. The offset specified in the arena chunk descriptor does not exceed the arena size.
  2. The size specified in the arena chunk descriptor does not exceed the arena size reduced by the offset specified in the arena chunk descriptor.

Returns 0 when the arena size is zero if all of the following conditions are fulfilled:

  1. The offset specified in the arena chunk descriptor is equal to zero.
  2. The size specified in the arena chunk descriptor is equal to zero.

Returns -1 if even one of the conditions is not fulfilled (regardless of whether the arena size is zero or non-zero), or if the ptr parameter has the NK_NULL value.

__nk_arena_get()

Purpose

Gets the pointer to the arena chunk and the size of this chunk.

Parameters

  • [in] self – pointer to the arena descriptor.
  • [in] ptr – pointer to the arena chunk descriptor.
  • [out] size – arena chunk size in bytes.

Returned values

Pointer to the arena chunk or NK_NULL if even one parameter has the NK_NULL value.

nk_arena_allocated_size()

Purpose

Gets the size of the utilized part of the arena.

Parameters

  • [in] self – pointer to the arena descriptor.

Returned values

Size of the utilized part of the arena, in bytes.

Additional information

If the parameter has the NK_NULL value, it returns 0.

nk_arena_store()

Purpose

Reserves an arena chunk for the specified number of objects of the defined type and copies these objects to the reserved chunk.

Parameters

  • [in] type – type of objects that need to be copied to the arena chunk.
  • [in,out] arena – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] src – pointer to the buffer containing the objects that need to be copied to the arena chunk.
  • [in] count – number of objects that need to be copied to the arena chunk.

Macro values

It has a value of 0 if successful, otherwise it has a value of -1.

__nk_arena_store()

Purpose

Reserves an arena chunk with a defined alignment for data of a specific size and copies this data to the reserved segment.

Parameters

  • [in,out] self – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] src – pointer to the buffer containing the data that needs to be copied to the arena chunk.
  • [in] size – size of the data that needs to be copied to the arena chunk, in bytes.
  • [in] align – value defining the arena chunk alignment. The arena chunk address can be unaligned (align=1) or aligned (align=2,4,...,2^N) to the boundary of a 2^N-byte sequence (for example, two-byte or four-byte).

Returned values

Returns 0 if successful, otherwise returns -1.

nk_arena_init_final()

Purpose

Initializes the arena descriptor containing only one segment.

Parameters

  • [out] arena – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] start – pointer to the start of the arena.
  • [in] count – number of objects for which the arena chunk is intended.

Macro values

N/A

nk_arena_alloc()

Purpose

Reserves an arena chunk for the defined number of objects of the specified type.

Parameters

  • [in] type – type of objects for which the arena chunk is intended.
  • [in,out] arena – pointer to the arena descriptor.
  • [out] ptr – pointer to the arena chunk descriptor.
  • [in] count – number of objects for which the arena chunk is intended.

Macro values

It has the address of the reserved arena chunk if successful, otherwise NK_NULL.

nk_arena_get()

Purpose

Gets the address of an arena chunk and the number of objects of the specified type that can be put into this chunk.

Parameters

  • [in] type – type of objects for which the arena chunk is intended.
  • [in] arena – pointer to the arena descriptor.
  • [in] ptr – pointer to the arena chunk descriptor.
  • [out] count – pointer to the number of objects that can be put into the arena chunk.

Macro values

It has the address of the arena chunk if successful, otherwise NK_NULL.

Additional information

If the size of the arena chunk is not a multiple of the size of the type of objects for which this chunk is intended, it has the NK_NULL value.

nk_arena_shrink()

Purpose

Reduces the size of an arena chunk.

Parameters

  • [in] type – type of objects for which the reduced arena chunk is intended.
  • [in,out] arena – pointer to the arena descriptor.
  • [in,out] ptr – pointer to the arena chunk descriptor.
  • [in] count – number of objects for which the reduced arena chunk is intended.

Macro values

It has the address of the reduced arena chunk if successful, otherwise NK_NULL.

Additional information

If the required size of the arena chunk exceeds the current size, it has the NK_NULL value.

If the alignment of the arena chunk that needs to be reduced does not match the type of objects for which the reduced chunk is intended, it has the NK_NULL value.

If the arena chunk that needs to be reduced is the last chunk in the arena, the freed part of this chunk will become available for reservation of subsequent chunks.

_nk_arena_shrink()

Purpose

Reduces the size of an arena chunk.

Parameters

  • [in,out] self – pointer to the arena descriptor.
  • [in,out] ptr – pointer to the arena chunk descriptor.
  • [in] size – size of the reduced arena chunk, in bytes.
  • [in] align – value used by the function to verify alignment of the arena chunk that needs to be reduced. The arena chunk address can be unaligned (align=1) or aligned (align=2,4,...,2^N) to the boundary of a 2^N-byte sequence (for example, two-byte or four-byte).

Returned values

Returns the address of the reduced arena chunk if successful, otherwise returns NK_NULL.

Additional information

If the required size of the arena chunk exceeds the current size, it returns NK_NULL.

If the alignment of the arena chunk that needs to be reduced does not match the specified alignment, it returns NK_NULL.

If the arena chunk that needs to be reduced is the last chunk in the arena, the freed part of this chunk will become available for reservation of subsequent chunks.

Page top
[Topic ipc_arena]

Transport code in C++

Before reading this section, you should review the information on the IPC mechanism in KasperskyOS and the IDL, CDL, and EDL descriptions.

Implementation of interprocess interaction requires transport code, which is responsible for generating, sending, receiving, and processing IPC messages.

However, a developer of a KasperskyOS-based solution does not have to write their own transport code. Instead, you can use the special tools and libraries included in the KasperskyOS SDK. These libraries enable a solution component developer to generate transport code based on IDL, CDL and EDL descriptions related to this component.

Transport code

The KasperskyOS SDK includes the nkppmeta compiler for generating transport code in C++.

The nkppmeta compiler lets you generate transport C++ proxy objects and stubs for use by both a client and a server.

Proxy objects are used by the client to pack the parameters of the called method into an IPC request, execute the IPC request, and unpack the IPC response.

Stubs are used by the server to unpack the parameters from the IPC request, dispatch the call to the appropriate method implementation, and pack the IPC response.

Generating transport code for development in C++

The CMake commands add_nk_idl(), add_nk_cdl() and add_nk_edl() are used to generate transport proxy objects and stubs using the nkppmeta compiler when building a solution.

С++ types in the *.idl.cpp.h file

Each interface is defined in an IDL description. This description defines the interface name, signatures of interface methods, and data types for the parameters of interface methods.

The CMake command add_nk_idl() is used to generate transport code when building a solution. This command creates a CMake target for generating header files for the defined IDL file when using the nkppmeta compiler.

The generated header files contain a C++ representation for the interface and data types described in the IDL file, and the methods required for use of proxy objects and stubs.

The mapping of data types declared in an IDL file to C++ types are presented in the table below.

Mapping IDL types to C++ types

IDL type

C++ type

SInt8

int8_t

SInt16

int16_t

SInt32

int32_t

SInt64

int64_t

UInt8

uint8_t

UInt16

uint16_t

UInt32

uint32_t

UInt64

uint64_t

Handle

Handle (defined in coresrv/handle/handletype.h)

string

std::string

union

std::variant

struct

struct

array

std::array

sequence

std::vector

bytes

std::vector<std::byte>

Working with transport code in C++

The scenarios for developing a client and server that exchange IPC messages are presented in the sections titled Statically creating IPC channels for C++ development and Dynamically creating IPC channels for C++ development.

Page top
[Topic cpp_proxy_stubs]

Statically creating IPC channels for C++ development

To implement a client program that calls a method of an endpoint provided by a server program:

  1. Include the generated header file (*.edl.cpp.h) of the client program description.
  2. Include the generated header files of the descriptions of the utilized interfaces (*.idl.cpp.h).
  3. Include the header files:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/api.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connect_static_channel.h
  4. Initialize the application object by calling the kosipc::MakeApplicationAutodetect() function. (You can also use the kosipc::MakeApplication() and kosipc::MakeApplicationPureClient() functions.)
  5. Get the client IPC handle of the channel and the endpoint ID (riid) by calling the kosipc::ConnectStaticChannel() function.

    This function gets the name of the IPC channel (from the init.yaml file) and the qualified name of the endpoint (from the CDL and EDL descriptions of the solution component).

  6. Initialize the proxy object for the utilized endpoint by calling the MakeProxy() function.

Example

// Create and initialize the application object kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Create and initialize the proxy object auto proxy = app.MakeProxy<IDLInterface>( kosipc::ConnectStaticChannel(channelName, endpointName)) // Call the method of the required endpoint proxy->DoSomeWork();

To implement a server program that provides endpoints to other programs:

  1. Include the generated header file *.edl.cpp.h containing a description of the component structure of the program, including all provided endpoints.
  2. Include the header files:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/event_loop.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/api.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/serve_static_channel.h
  3. Create classes containing the implementations of interfaces that this program and its components provide as endpoints.
  4. Initialize the application object by calling the kosipc::MakeApplicationAutodetect() function.
  5. Initialize the kosipc::components::Root structure, which describes the component structure of the program and describes the interfaces of all endpoints provided by the program.
  6. Bind fields of the kosipc::components::Root structure to the objects that implement the corresponding endpoints.

    Fields of the Root structure replicate the hierarchy of components and endpoints that are collectively defined by the CDL and EDL files.

  7. Get the server IPC handle of the channel by calling the ServeStaticChannel() function.

    This function gets the name of the IPC channel (from the init.yaml file) and the structure created at step 5.

  8. Create the kosipc::EventLoop object by calling the MakeEventLoop() function.
  9. Start the loop for dispatching incoming IPC messages by calling the Run() method of the kosipc::EventLoop object.

Example

// Create class objects that implement interfaces // provided by the server as endpoints MyIDLInterfaceImp_1 impl_1; MyIDLInterfaceImp_2 impl_2; // Create and initialize the application object kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Create and initialize the root object that describes // the components and endpoints of the server kosipc::components::Root root; // Bind the root object to the class objects that implement the server endpoints root.component1.endpoint1 = &impl_1; root.component2.endpoint2 = &impl_2; // Create and initialize the object that implements the // loop for dispatching incoming IPC messages kosipc::EventLoop loop = app.MakeEventLoop(ServeStaticChannel(channelName, root)); // Start the loop in the current thread loop.Run();
Page top
[Topic static_IPC_kosipc]

Dynamically creating IPC channels for C++ development

Dynamic creation of an IPC channel on the client side includes the following steps:

  1. Include the generated description header file (*.edl.cpp.h) in the client program.
  2. Include the generated header files of the descriptions of the utilized interfaces (*.idl.cpp.h).
  3. Include the header files:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/make_application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connect_dynamic_channel.h
  4. Get the pointers to the server name and the qualified name of the endpoint by using a name server, which is a special kernel service provided by the NameServer program. To do so, you must connect to the name server by calling the NsCreate() function and find the server that provides the required endpoint by using the NsEnumServices() function. For more details, refer to Dynamically creating IPC channels (cm_api.h, ns_api.h).
  5. Create an application object by calling the kosipc::MakeApplicationAutodetect() function. (You can also use the kosipc::MakeApplication() and kosipc::MakeApplicationPureClient() functions.)
  6. Create a proxy object for the required endpoint by calling the MakeProxy() function. Use the kosipc::ConnectDynamicChannel() function call as the input parameter of the MakeProxy() function. Pass the pointers for the server name and qualified name of the endpoint obtained at step 4 to the kosipc::ConnectDynamicChannel() function.

After successful initialization of the proxy object, the client can call methods of the required endpoint.

Example

NsHandle ns; // Connect to a name server Retcode rc = NsCreate(RTL_NULL, INFINITE_TIMEOUT, &ns); char serverName[kl_core_Types_UCoreStringSize]; char endpointName[kl_core_Types_UCoreStringSize]; // Get pointers to the server name and qualified name of the endpoint rc = NsEnumServices( ns, interfaceName, 0, serverName, kl_core_Types_UCoreStringSize, endpointName, kl_core_Types_UCoreStringSize); // Create and initialize the application object kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Create and initialize the proxy object auto proxy = app.MakeProxy<IDLInterface>( kosipc::ConnectDynamicChannel(serverName, endpointName)) // Call the method of the required endpoint proxy->DoSomeWork();

Dynamic creation of an IPC channel on the server side includes the following steps:

  1. Include the generated header file (*.edl.cpp.h) containing a description of the component structure of the server, including all provided endpoints, in the server program.
  2. Include the header files:
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/event_loop.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/make_application.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/root_component.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/serve_dynamic_channel.h
    • /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/simple_connection_acceptor.h
  3. Create classes containing the implementations of interfaces that the server provides as endpoints. Create and initialize the objects of these classes.
  4. Create an application object by calling the kosipc::MakeApplicationAutodetect() function.
  5. Create and initialize the kosipc::components::Root class object that describes the structure of components and endpoints of the server. This structure is generated from the descriptions in the CDL and EDL files.
  6. Bind the kosipc::components::Root class object to the class objects created at step 3.
  7. Create and initialize the kosipc::EventLoop class object that implements a loop for dispatching incoming IPC messages by calling the MakeEventLoop() function. Use the ServeDynamicChannel() function call as an input parameter of the MakeEventLoop() function. Pass the kosipc::components::Root class object created at step 5 to the ServeDynamicChannel() function.
  8. Start the loop for dispatching incoming IPC messages in a separate thread by calling the Run() method of the kosipc::EventLoop object.
  9. Create and initialize the object that implements the handler for receiving incoming requests to dynamically create an IPC channel.

    When creating an object, you can use the kosipc::SimpleConnectionAcceptor class, which is the standard implementation of the kosipc::IConnectionAcceptor interface. (The kosipc::IConnectionAcceptor interface is defined in the file named /opt/KasperskyOS-Community-Edition-<version>/sysroot-*-kos/include/kosipc/connection_acceptor.h.) In this case, the handler will implement the following logic: if the endpoint requested by the client was published on the server, the request from the client will be accepted. Otherwise, it will be rejected.

    If you need to create your own handler, you should implement your own request handling logic in the OnConnectionRequest() method inherited from the kosipc::IConnectionAcceptor interface. This method will be called by the server when it receives a request for dynamic IPC channel creation from the client.

  10. Create a kosipc::EventLoop class object that implements a loop for receiving incoming requests to dynamically create an IPC channel by calling the MakeEventLoop() function. Use the ServeConnectionRequests() function call as an input parameter of the MakeEventLoop() function. Pass the object created at step 9 to the ServeConnectionRequests() function.

    There can only be one loop for receiving incoming requests to dynamically create an IPC channel. The loop must work in one thread. The loop for receiving incoming requests to dynamically create an IPC channel must be created after the loop for dispatching incoming IPC channels is created (see step 7).

  11. Start the loop for receiving incoming requests for a dynamic connection in the current thread by calling the Run() method of the kosipc::EventLoop object.

Example

// Create class objects that implement interfaces // provided by the server as endpoints MyIDLInterfaceImp_1 impl_1; MyIDLInterfaceImp_2 impl_2; // Create and initialize the application object kosipc::Application app = kosipc::MakeApplicationAutodetect(); // Create and initialize the root object that describes // the components and endpoints of the server kosipc::components::Root root; // Bind the root object to the class objects that implement the server endpoints. // The fields of the root object repeat the description of components and endpoints // defined collectively by the CDL and EDL files. root.component1.endpoint1 = &impl_1; root.component2.endpoint2 = &impl_2; // Create and initialize the object that implements the // loop for dispatching incoming IPC messages kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeDynamicChannel(root)); // Start the loop for dispatching incoming IPC messages in a separate thread std::thread dynChannelThread( [&loopDynamicChannel]() { loopDynamicChannel.Run(); } ); // Create an object that implements a standard handler for receiving incoming requests // to dynamically create an IPC channel kosipc::SimpleConnectionAcceptor acceptor(root); // Create an object that implements a loop for receiving incoming requests // to dynamically create an IPC channel kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeConnectionRequests(&acceptor)); // Start the loop for receiving incoming requests to dynamically create an IPC channel in the current thread loopConnectionReq.Run();

If necessary, you can create and initialize multiple kosipc::components::Root class objects combined into a list of objects of the ServiceList type using the AddServices() method. For example, use of multiple objects enables you to separate components and endpoints of the server into groups or publish endpoints under different names.

Example

// Create and initialize the group_1 object kosipc::components::Root group_1; group_1.component1.endpoint1 = &impl_1; group_1.component2.endpoint2 = &impl_2; // Create and initialize the group_2 object kosipc::components::Root group_2; group_2.component1.endpoint1 = &impl_3; group_2.component2.endpoint2 = &impl_4; // Create and initialize the group_3 object kosipc::components::Root group_3; group_3.component1.endoint1 = &impl_5; // Create a list of objects ServiceList endpoints; endpoints.AddServices(group_1); endpoints.AddServices(group_2); endpoints.AddServices(group_3.component1.endpoint1, "SomeCustomEndpointName"); // Create an object that implements the handler for receiving incoming requests // to dynamically create an IPC channel kosipc::SimpleConnectionAcceptor acceptor(std::move(endpoints)); // Create an object that implements a loop for receiving incoming requests // to dynamically create an IPC channel kosipc::EventLoop loopDynamicChannel = app.MakeEventLoop(ServeConnectionRequests(&acceptor));
Page top
[Topic dynamic_IPC_kosipc]