Contents
IPC basics in KasperskyOS
In KasperskyOS, the only means of interprocess communication (IPC) is exchange of messages.
Types of messages and roles of entities
Messaging in KasperskyOS is built on a client-server model.
When two entities interact, one of them is the client (client entity), and the other is the server (server entity). The client initiates the interaction by sending a request message (or simply "request"). The server receives the request and responds by sending a response message (or simply "response") to the client.
IPC channels
To enable two entities to exchange messages, an IPC channel, also referred to as "channel" or "connection", must be established between them. Each entity can have multiple connections, acting as a client for some entities while acting as a server for others.
System calls
KasperskyOS provides three system calls for IPC message exchange: Call
, Recv
and Reply
. The corresponding functions are declared in the syscalls.h
file:
Call()
is used by the client to send a request and receive a response. It receives an IPC handle, buffer containing the request, and buffer for the response.Recv()
is used by the server to receive a request. It receives an IPC handle and buffer for the request.Reply()
is used by the server to send a response. It receives an IPC handle and buffer for the response.
The Call()
, Recv()
and Reply()
functions return rcOk
or an error code.
The Call()
and Recv()
system calls are locking calls, meaning that messages are exchanged according to the rendezvous principle:
- The server thread that executes the
Recv()
call remains locked until a request is received. - The client thread that executes the
Call()
call remains locked until a response is received from the server.
System calls are rarely used in entity code. It is recommended that you instead use more convenient NK-generated methods that execute system calls.
IPC channels
IPC channels connect entities with each other and are used for IPC messaging.
The channel is provided by a pair of IPC handles (client and server), which are linked to each other.
Two entities linked by an IPC channel
A channel is directional. The entity associated with the client handle of the channel (client entity) can send requests and receive responses over this channel. The entity associated with the server handle of the channel (server entity) can receive requests and send responses over this channel.
An entity can have multiple connections (channels) with other entities, acting as a client in some connections while acting as a server in others.
Entity A is a client for the C entity and a server for the B entity. Designations: cl_1, cl_2 – client IPC handles; srv_1, srv_2 – server IPC handles.
Creating and using channels
IPC channels can be created statically or dynamically.
Statically created IPC channels are created by the Einit
entity when the solution is started. The code of the Einit
entity is generated based on the init description that specifies all channels (connections) that need to be created.
In addition to statically created IPC channels, entities may use dynamically created IPC channels.
To send and receive an IPC message over a specific channel, the corresponding values of IPC handles (client and server) must be known. Service Locator is used to obtain the values of IPC handles.
Dynamically created IPC channels
The capability to dynamically create IPC channels allows you to change the topology of interaction between entities on the fly. This is necessary if it is unknown which specific server entity will become the resource provider required by a client entity. For example, you may not know which specific drive you will need to write data to.
In contrast to a statically created IPC channel, a dynamically created IPC channel has the following characteristics:
- It is created between entities that have been started.
- It is created jointly by the client entity and server entity.
- It can be deleted.
- It involves the use of a name server (
kl.core.NameServer
entity), which facilitates the transfer of information about available interface implementations from server entities to client entities.
A dynamically created IPC channel uses the following functions:
- Name Server interface
- Connection Manager
The Name Server interface is provided for the user in the following files:
coresrv/ns/ns_api.h
is a header file of thelibkos
library;coresrv/ns/NameServer.idl
is an IDL description of the IPC interface of the Name Server.
The Connection Manager is provided for the user in the following files:
coresrv/cm/cm_api.h
is a header file of thelibkos
library;services/cm/CM.idl
is an IDL description of the Connection Manager's IPC interface.
An IPC channel is dynamically created according to the following scenario:
- A client entity, server entity and name server are started.
- The server entity connects to the name server by using the
NsCreate()
function and publishes the server entity name, interface name, and interface implementation name by using theNsPublishService()
function. - The client entity uses the
NsCreate()
function to connect to the name server and then uses theNsEnumServices()
function to search for the name of the server entity and the name of the interface implementation based on the interface name. - The client entity uses the
KnCmConnect()
function to request access to the interface implementation and passes the found server entity name and interface implementation name as arguments to the function. - The server entity calls the
KnCmListen()
function to check if the client entity sent a request to access the provided interface implementation. - The server entity accepts the request to access the provided interface implementation by using the
KnCmAccept()
function, to which it passes the client entity name and interface implementation name received from theKnCmListen()
function as arguments.
To use a name server, the solution security policy must allow the kl.core.NameServer
entity to interact with entities for which IPC channels can be dynamically created.
The server entity can use the NsUnPublishService()
function to unpublish interface implementations that were previously published on the name server.
The server entity can use the KnCmDrop()
function to reject requests to access interface implementations.
ns_api.h (fragment)
/**
* Attempts to connect to name server "name"
* for the specified number of msecs. If the "name" parameter has the value
* RTL_NULL, the function attempts to connect to name server "ns"
* (default name server). Output parameter "ns" contains the handle
* of the connection with the name server.
* If successful, the function returns rcOk, otherwise it returns an error code.
*/
Retcode NsCreate(const char *name, rtl_uint32_t msecs, NsHandle *ns);
/**
* Publishes the interface implementation on the name server.
* The "ns" parameter defines the handle for the connection with the name server.
* The "server" parameter defines the name of the server entity (for example, ata).
* The "type" parameter defines the name of the published interface (for example, IBlkDev).
* The "service" parameter defines the name of the published interface implementation
* (for example, blkdev.blkdev).
* If successful, the function returns rcOk, otherwise it returns an error code.
*/
Retcode NsPublishService(NsHandle ns, const char *type, const char *server,
const char *service);
/**
* Unpublishes the interface implementation on the name server.
* The "ns" parameter defines the handle for the connection with the name server.
* The "server" parameter defines the name of the server entity. The "type" parameter defines the name
* of the published interface. The "service" parameter defines the name of the implementation
* of the published interface.
* If successful, the function returns rcOk, otherwise it returns an error code.
*/
Retcode NsUnPublishService( NsHandle ns, const char *type, const char *server,
const char *service);
/**
* Enumerates the interface implementations published on the
* name server. The "ns" parameter defines the handle for the connection with the name server.
* The "type" parameter defines the name of the required interface. The "index" parameter
* defines the index for enumerating the interface implementations.
* Output parameter "server" contains the name of the server entity providing the
* interface implementation. Output parameter "service" contains the name of the
* interface implementation.
* For example, IBlkDev interface implementations are enumerated
* as follows.
* rc = NsEnumServices(ns, "IBlkDev", 0, outServerName, outServiceName);
* rc = NsEnumServices(ns, "IBlkDev", 1, outServerName, outServiceName);
* ...
* rc = NsEnumServices(ns, "IBlkDev", N, outServerName, outServiceName);
* Function calls with index incrementation continue until
* the function returns rcResourceNotFound.
* If successful, the function returns rcOk, otherwise it returns an error code.
*/
Retcode NsEnumServices(NsHandle ns, const char *type, unsigned index,
char *server, char *service);
cm_api.h (fragment)
/**
* Requests access to the interface implementation named "service"
* provided by server entity "server". The "msecs" parameter defines the
* timeout period (in milliseconds) for the server entity to accept the request. Output
* parameter "endpoint" contains the client IPC handle. Output parameter
* "rsid" contains the identifier of the interface implementation.
* If successful, the function returns rcOk, otherwise it returns an error code.
*/
Retcode KnCmConnect(const char *server, const char *service,
rtl_uint32_t msecs, Handle *endpoint,
rtl_uint32_t *rsid);
/**
* Checks if there is a request to access the interface implementation
* named "filter". If the "filter" parameter has the value RTL_NULL,
* it checks if there is a request to access any
* interface implementation. The "msecs" parameter defines the request timeout period
* in milliseconds. Output parameter "client" contains the name
* of the client entity. Output parameter "service" contains the name of the
* interface implementation.
* If successful, the function returns rcOk, otherwise it returns an error code.
*/
Retcode KnCmListen(const char *filter, rtl_uint32_t msecs, char *client,
char *service);
/**
* Rejects the request to access the interface implementation
* named "service" by client entity "client".
* If successful, the function returns rcOk, otherwise it returns an error code.
*/
Retcode KnCmDrop(const char *client, const char *service);
/**
* Accepts the request to access the interface implementation
* named "service" by client entity "client". The "rsid" parameter defines
/* interface implementation identifier. The "listener" parameter defines
* the listener handle. If the "listener" parameter has the value
* INVALID_HANDLE, a new listener handle is created.
* Output parameter "endpoint" contains the server IPC handle.
* If successful, the function returns rcOk, otherwise it returns an error code.
*/
Retcode KnCmAccept(const char *client, const char *service, rtl_uint32_t rsid,
Handle listener, Handle *endpoint);
The filter
parameter of the KnCmListen()
function is reserved and does not affect the behavior of the function. The behavior of the function corresponds to the RTL_NULL
value of the filter
parameter.
The listener handle is a server IPC handle with expanded rights. It is created when the KnCmAccept()
function is called with the INVALID_HANDLE
argument in the listener
parameter. If a listener handle is passed to the KnCmAccept()
function when it is called, the created server IPC handle will provide the capability to receive requests over all IPC channels associated with this listener handle. (The first IPC channel associated with the listener handle is created when the KnCmAccept()
function is called with the INVALID_HANDLE
argument in the listener
parameter. The second and subsequent IPC channels associated with the listener handle are created during the second and subsequent calls of the KnCmAccept()
function, to which the listener handle obtained during the first call is passed.)
The listener handle is also used when statically creating IPC channels. It is created when the KnHandleConnectEx()
function is called with the INVALID_HANDLE
argument in the srListener
parameter. If a listener handle is passed to the KnHandleConnectEx()
function when it is called, the created server IPC handle will provide the capability to receive requests over all IPC channels associated with this listener handle. A listener handle is associated with multiple IPC channels if multiple IPC channels with identical names are created for one server entity.
If the dynamically created IPC channel is no longer required, its client and server handles should be deleted. The IPC channel can be created again if necessary.
Page topMessaging overview
Let us examine two entities (client and server) that have an IPC channel established between them. Let cl
be the client IPC handle of this channel while sr
is the server IPC handle of this channel.
The code provided below is intended to demonstrate the IPC mechanism. System calls are not normally used directly in entity code. To allow a convenient exchange of messages, special NK-generated methods are provided, which use system calls.
Client entity code:
client.c
…
// Get the client IPC handle cl using Service Locator
…
// Send request
Call(cl, &RequestBuffer, &ResponseBuffer);
…
Server entity code:
server.c
…
// Get the server IPC handle sr using Service Locator
…
// Receive request
Recv(sr, &RequestBuffer);
…
// Process request
…
// Send response
Reply(sr, &ResponseBuffer);
…
Messages are exchanged as follows:
- One of the client threads executes the
Call()
system call, passing as arguments thecl
handle (client handle of the utilized channel), the pointer to the buffer containing the request message, and the pointer to the buffer for the response. - The request message is sent to Kaspersky Security System to be checked. If Kaspersky Security System returns an "allowed" decision, we proceed to step 3. Otherwise, the
Call()
is terminated with thercSecurityDisallow
error code, and we proceed to step 9. - If the server is waiting for a request from this client—i.e. the server has executed the
Recv()
call, passingsr
as the first argument—we proceed to step 4. Otherwise, the client thread remains locked until one of the server threads executes aRecv()
system call with the firstsr
argument. - The request message is copied to the address space of the server. The server thread is unlocked, and the
Recv()
call is terminated with anrcOk
code. - The server processes the received message. The client thread remains locked.
- The server executes the
Reply()
system call, passing as arguments thesr
handle and the pointer to the buffer with the response message. - The response message is sent to Kaspersky Security System to be checked. If Kaspersky Security System returns an "allowed" decision, we proceed to step 8. Otherwise, the
Call()
andReply()
calls are terminated with anrcSecurityDisallow
- The response message is copied to the address space of the client. The server thread is unlocked, and the
Reply()
call is terminated with anrcOk
code. The client thread is unlocked, and theCall()
is terminated with anrcOk
code. - The exchange is complete.
If an error occurs when sending the request (insufficient memory, invalid message format, etc.), the threads are unlocked and the Call()
and Reply()
calls return an error code.
Service Locator
Service Locator is a small library that lets you:
- find out the value of an IPC handle based on the connection name;
- find out the value of the interface ID (RIID) based on the name of the interface implementation.
The values of the IPC handle and RIID are required for calling a specific interface of a specific server entity. These values are used when initializing a transport.
To use Service Locator, you need to include the sl_api.h
file in the entity code:
Main functions of Service Locator
Client-side functions:
ServiceLocatorConnect()
receives the connection name and returns the client IPC handle corresponding to this connection (channel).ServiceLocatorGetRiid()
receives the IPC handle and interface implementation name in the format<component instance name>.<interface implementation name>
. Returns a corresponding RIID (sequence number of the interface implementation).
The connection name is specified in the init.yaml
file, the component instance name is specified in the EDL file, and the interface implementation name is specified in the CDL file.
Server-side functions:
ServiceLocatorRegister()
receives the connection name and returns the server IPC handle corresponding to this connection (channel). If the channel is part of a group, the function returns the listener handle of this group.
Example use of Service Locator
Examine the next solution consisting of two entities: Client
and Server
.
The client entity does not implement any interface.
Client.edl
entity Client
The server entity contains an instance of the Ping
component. Instance name: ping_comp
.
Server.edl
entity Server
components {
ping_comp: Ping
}
The Ping
component contains a named implementation of the Ping
interface declared in the Ping.idl
file. Implementation name: ping_impl
.
Ping.cdl
component Ping
interfaces {
ping_impl: Ping
}
(For brevity, the Ping.idl
file is not provided.)
In the init description, we indicate that the Client
and Server
entities must be connected through a channel named server_connection
:
init.yaml
entities:
- name: Client
connections:
- target: Server
id: server_connection
- name: Server
Use of Service Locator on the client side:
client.c
…
/* Connect Service Locator */
…
/* Get client IPC handle "server_connection" channel */
Handle handle = ServiceLocatorConnect("server_connection");
…
/* Get ID (RIID) of ping_impl implementation contained in ping_comp instance */
nk_iid_t riid = ServiceLocatorGetRiid(handle, "ping_comp.ping_impl");
…
Use Service Locator on the server side:
server.c
…
/* Connect Service Locator */
…
nk_iid_t iid;
/* Get server IPC handle of "server_connection" channel */
Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &iid);
…
Channel groups
The channels in which an entity serves as a server may be combined into one or more groups.
Each group of channels has its own listener handle.
Two IPC channels have the same name, "second_connection_to_A", and are combined into a group. Another channel, named "first_connection_to_A", is not included in this group. Designations: cl_1, cl_2, cl_3 – client IPC handles; srv_1 – server IPC handle; ls_1 – listener IPC handle.
The listener handle allows the server to receive requests immediately over all channels in the group. There is no need to create a separate thread for each channel. It is enough to execute one Recv()
system call in one server thread by specifying a listener handle.
Creating a group of channels using an init description
For channels to form a group, they must connect to the same server entity and have identical names. For example, to produce the system of channels depicted above, the following init description can be used:
init.yaml
entities:
# Entity A acts as a server, so its list of connections is empty.
- name: A
# Entity B will be connected with entity A via two different channels.
- name: B
connections:
- target: A
id: first_connection_to_A
- target: A
id: second_connection_to_A
# Entity C will be connected with entity A via a channel named
# "second_connection_to_A". Two channels with identical names will be combined
# into a group: on the entity A side, they will have the same
# IPC handle (listener handle ls_1).
- name: C
connections:
- target: A
id: second_connection_to_A
Messaging within a channel group
Let us look at how messages are exchanged for the group of channels described above.
The client entities B
and C
receive a client handle value based on the name of the first_connection_to_A
connection and send a request:
entity_B.c, entity_C.c
…
// Receive client IPC handle cl corresponding to "second_connection_to_A".
Handle cl = ServiceLocatorConnect("second_connection_to_A");
…
// Send request
Call(cl, &RequestBuffer, &ResponseBuffer);
…
Both channels being used have the same name: second_connection_to_A
. However, by this name the entities B
and C
will receive different handle values for that name: entity B
will receive the value cl_2
, whereas entity C
will receive the value cl_3
.
Server entity A
receives the listener handle value ls_1
. Entity A
awaits requests over two channels at the same time (from B
and from C
), using the ls_1
handle. After receiving and processing the request, entity A
sends a response using the ls_1
handle. The response will be sent to the client that initiated the request:
entity_A.c
…
nk_iid_t iid;
// Receive listener handle ls_1 corresponding to
"second_connection_to_A"
Handle ls_1 = ServiceLocatorRegister("second_connection_to_A", NULL, 0, &iid);
…
// Wait for the request from entity B or C
Recv(ls_1, &RequestBuffer);
…
// Process received request
…
// Send response to client from which request was received
Reply(ls_1, &ResponseBuffer);
…
IPC message structure
In KasperskyOS, all interactions between entities have statically defined types. The permissible structures of an IPC message are defined in the IDL description of the interfaces of the entity that receives the message (server).
A correct IPC message (request and response) contains a constant part and an arena.
Constant part of a message
The constant part of a message contains arguments of a fixed size, and the RIID and MID.
Fixed-size arguments can be arguments of any IDL types except the sequence
type.
The RIID and MID identify the interface and method being called:
- The RIID (Runtime Implementation ID) is the number of the entity interface implementation being called, starting at zero.
- The MID (Method ID) is the number of the method within the interface that contains it, starting at zero.
The type of the constant part of the message is generated by the NK compiler based on the IDL description of the interface. A separate structure is generated for each interface method. Union
types are also generated for storing any request to a server, component or interface.
For example, for the Ping
method of the Ping
interface (the Ping
component of the Server
entity in the echo example), the NK compiler will create the Ping_Ping_req
type for the constant part of the request and the Ping_Ping_res
type for the constant part of the response. The following union
types will also be generated:
Ping_req
andPing_res
are constant parts of the request and response for any method of thePing
interface.Ping_component_req
andPing_component_res
are constant parts of the request and response for any method of any interface whose implementation is included in thePing
component.If embedded components are present, these types also contain structures of the constant part of a message for any method of any interface whose implementations are included in all embedded components. For more details, refer to Generated methods and types.
Server_entity_req
andServer_entity_res
are the constant parts of the request and response for any method of any interface whose implementation is included in any component whose instance is included in theServer
entity.
Arena
The arena is a buffer for storing variable-size arguments (sequence
IDL type).
Validating a message in Kaspersky Security System
The Kaspersky Security Module verifies that the structure of the message being sent is correct. Requests and responses are both validated. If the message has an incorrect structure, it will be rejected without calling the security model methods associated with it.
Forming a message structure
KasperskyOS Community Edition includes the following tools that make it easier for the developer to create and package an IPC message:
- The
transport-kos
library for working with NkKosTransport. - The NK compiler that lets you generate special methods and types.
The echo example shows the creation of a simple IPC message.
NkKosTransport
NkKosTransport
is a convenient wrapper for the Call
, Recv
and Reply
system calls. It lets you work separately with messages' constant parts and arenas.
The NkKosTransport
structure and methods for working with it are declared in the transport-kos.h
file.
To initialize the transport, it is sufficient to call the NkKosTransport_Init()
function by specifying the IPC handle of the channel that should be used to transmit messages (handle
):
…
NkKosTransport transport;
NkKosTransport_Init(&transport, handle, NK_NULL, 0);
A channel has two IPC handles: client and server. Therefore, when initializing the transport, you need to specify the client handle for the utilized channel on the client side and the server handle on the server side.
The functions nk_transport_call()
, nk_transport_recv()
and nk_transport_reply()
declared in transport.h
(included in the transport-kos.h
file) are used to call the transport.
The nk_transport_call()
function is intended for sending a request and receiving a response:
/* The constant part (req) and arena of the request (req_arena) must be initialized
* with the actual input arguments of the method being called. The constant part (req)
* must also contain the RIID and MID values.
* The nk_transport_call() function generates a request and executes the Call system call.
* The response received from the server is inserted into res (constant part of the response) and
* res_arena (response arena). */
nk_transport_call(&transport.base, (struct nk_message *)&req, &req_arena, (struct nk_message *)&res, &res_arena);
When using a generated interface method (for example, the IPing_Ping
method from the echo example) on the client side, the corresponding values of the RIID and MID are automatically inserted into the request, after which the nk_transport_call()
function is called.
The nk_transport_recv()
function is intended for receiving a request:
/* The nk_transport_recv () function executes the Recv system call.
* The request received from the client is inserted into req (constant part of the response) and
* req_arena (response arena). */
nk_transport_recv(&transport.base, (struct nk_message *)&req, &req_arena);
The nk_transport_reply()
function is intended for sending a response:
/* The constant part (res) and request arena (res_arena) must be initialized
* with the actual output arguments of the server method being called.
* The nk_transport_reply() function generates a response and executes a Reply system call. */
nk_transport_reply(&transport.base, (struct nk_message *)&res, &res_arena);
Generated methods and types
When building a solution, the NK compiler uses the EDL-, CDL- and IDL descriptions to generate a set of special methods and types that simplify the creation, forwarding, receipt and processing of IPC messages.
Examine the static description of the Server
entity from the echo example. This description consists of three files: Server.edl
, Ping.cdl
and Ping.idl
:
Server.edl
/* Description of the Server entity. */
entity Server
/* pingComp is a named instance of the Ping component. */
components {
pingComp: Ping
}
Ping.cdl
/* Description of the Ping component. */
component Ping
/* pingImpl is the Ping interface implementation. */
interfaces {
pingImpl: Ping
}
Ping.idl
/* Description of the Ping interface. */
package Ping
interface {
Ping(in UInt32 value, out UInt32 result);
}
These files will be used to generate the files named Server.edl.h
, Ping.cdl.h
, and Ping.idl.h
, which contain the following methods and types:
Methods and types that are common to the client and server
- Abstract interfaces containing the pointers to the implementations of the methods included in them.
In our example, one abstract interface (
Ping
) will be generated:struct Ping_ops {
nk_err_t (*Ping)(struct Ping *,
const struct Ping_req *,
const struct nk_arena *,
struct Ping_res *,
struct nk_arena *); };
struct Ping {
const struct Ping_ops *ops;
};
- Set of interface methods.
When calling an interface method, corresponding values of the RIID and MID are automatically inserted into the request, after which the
nk_transport_call()
function is called.In our example, a single
Ping_Ping
interface method will be generated:nk_err_t Ping_Ping(struct Ping *,
const struct Ping_Ping_req *,
const struct nk_arena *,
struct Ping_Ping_res *,
struct nk_arena *);
Methods and types used only on the client
- Types of proxy objects.
A proxy object is used as an argument in an interface method. In our example, a single
Ping_proxy
proxy object type will be generated:struct Ping_proxy {
struct Ping base;
struct nk_transport *transport;
nk_iid_t iid;
};
- Functions for initializing proxy objects.
In our example, the single initializing function
Ping_proxy_init
will be generated:void Ping_proxy_init(struct Ping_proxy *, struct nk_transport *, nk_iid_t);
- Types that define the structure of the constant part of a message for each specific method.
In our example, two such types will be generated:
Ping_Ping_req
(for a request) andPing_Ping_res
(for a response).struct Ping_Ping_req {
struct nk_message base_;
nk_uint32_t value;
};
struct Ping_Ping_res {
struct nk_message base_;
nk_uint32_t result;
};
Methods and types used only on the server
- Type containing implementations of all component interfaces, and the initializing function. (For each server component.)
If there are embedded components, this type also contains their instances, and the initializing function takes their corresponding initialized structures. Therefore, if embedded components are present, their initialization must begin with the most deeply embedded component.
In our example, the
Ping_component
structure andPing_component_init
function will be generated:struct Ping_component {
struct Ping *pingImpl;
};
void Ping_component_init(struct Ping_component *, struct Ping *);
- Type containing implementations of all interfaces provided directly by the server entity; all instances of components included in the server entity; and the initializing function.
In our example, the
Server_entity
structure andServer_entity_init
function will be generated:struct Server_entity {
struct Ping_component *pingComp;
};
void Server_entity_init(struct Server_entity *, struct Ping_component *);
- Types that define the structure of the constant part of a message for any method of a specific interface.
In our example, two such types will be generated:
Ping_req
(for a request) andPing_res
(for a response).union Ping_req {
struct nk_message base_;
struct Ping_Ping_req Ping;
};
union Ping_res {
struct nk_message base_;
struct Ping_Ping_res Ping;
};
- Types that define the structure of the constant part of a message for any method of any interface whose implementation is included in the specific component.
If embedded components are present, these types also contain structures of the constant part of a message for any method of any interface whose implementations are included in all embedded components.
In our example, two such types will be generated:
Ping_component_req
(for a request) andPing_component_res
(for a response).union Ping_component_req {
struct nk_message base_;
union Ping_req pingImpl;
};
union Ping_component_res {
struct nk_message base_;
union Ping_res pingImpl;
};
- Types that define the structure of the constant part of a message for any method of any interface whose implementation is included in any component whose instance is included in the server entity.
If embedded components are present, these types also contain structures of the constant part of a message for any method of any interface whose implementations are included in all embedded components.
In our example, two such types will be generated:
Server_entity_req
(for a request) andServer_entity_res
(for a response).union Server_entity_req {
struct nk_message base_;
union Ping_req pingComp_pingImpl;
};
union Server_entity_res {
struct nk_message base_;
union Ping_res pingComp_pingImpl;
};
- Dispatch methods (dispatchers) for a separate interface, component, or the entity.
Dispatchers analyze the received request (the RIID and MID values), call the implementation of the corresponding method, and then save the response in the buffer. In our example, three dispatchers will be generated:
Ping_dispatch
,Ping_component_dispatch
, andServer_entity_dispatch
.The entity dispatcher handles the request and calls the methods implemented by this entity. If the request contains an incorrect RIID (for example, an RIID for a different interface implementation that this entity does not have) or an incorrect MID, the dispatcher returns
NK_EOK
orNK_ENOENT
.nk_err_t Server_entity_dispatch(struct Server_entity *,
const union Server_entity_req *,
const struct nk_arena *,
union Server_entity_res *,
struct nk_arena *);
In special cases, you can use dispatchers of the interface and the component. They take an additional argument: interface implementation ID (
nk_iid_t
). The request will be handled only if the passed argument and RIID from the request match, and if the MID is correct. Otherwise, the dispatchers returnNK_EOK
orNK_ENOENT
.nk_err_t Ping_dispatch(struct Ping *,
nk_iid_t,
const union Ping_req *,
const struct nk_arena *,
union Ping_res *,
struct nk_arena *);
nk_err_t Ping_component_dispatch(struct Ping_component *,
nk_iid_t,
const union Ping_component_req *,
const struct nk_arena *,
union Ping_component_res *,
struct nk_arena *);