## Table of contents 1. Introduction 2. API Walkthrough * 2.1 Server-side operations * 2.2 Client-side operations 3. API Specification * 3.1 rina_open * 3.2 rina_register * 3.3 rina_unregister * 3.4 rina_register_wait * 3.5 rina_flow_accept * 3.6 rina_flow_respond * 3.7 rina_flow_alloc * 3.8 rina_flow_alloc_wait * 3.9 rina_flow_spec_default 4. Example applications ## 1. Introduction Since existing network applications are written using the socket API, it comes natural to design a C RINA API which closely resembles the socket API, provided that the differences in the naming and addressing scheme and the QoS support are taken into account. The socket API is currently defined by the POSIX.1-2008 standard; for this reason the API presented in this section will be referred to as a POSIX-like API for RINA. The advantages of a C POSIX-like API include the following:  * POSIX standards are universally accepted and widely known, so that it would be easy for developers to catch up with the RINA API and write applications. * It would be easy to port existing network applications to RINA, starting from the definition of a simple mapping between socket API calls and RINA API calls. * File descriptors are used as universal handlers to interact with the API; this makes it possible to reuse standard system calls (e.g. read, write, close, ...), and synchronisation mechanism (e.g. select, poll, ...). * The C language is widely used for mission-critical network applications, and a C API can also be used directly by C++ programs. This C POSIX-like API was originally designed for the [rlite RINA implementation](https://github.com/vmaffione/rlite). ## 2. API Walkthrough A convenient way to introduce the API is to show how a simple application would use the client-side and server-side API calls. This also eases the comparison with sockets, where a similar walkthrough is often presented. Note that in this context the term client simply refers to the initiator of the flow allocation procedure, while the term server refers to the other peer. The discussion here, in other words, does not imply that the client/server paradigm must be applied; the walkthrough is more general, being valid also for other distributed application paradigms (e.g. peer-to-peer). ![dd](https://github.com/IRATI/stack/wiki/images/posix-api1.png) The workflow presented in this subsection, depicted in figure 1, refers to the case of blocking operation, that is the API calls may block waiting for asynchronous events; moreover, for the sake of exposition, we assume that the operations do not fail. Non-blocking operations and errors are however covered by the API specification (section 2.2) and the examples (section 2.4). ### 2.1 Server-side operations The first operation needed by the server, **(1)** in figure 1, is **rina_open**, which takes no arguments and returns a listening file descriptor (an integer, as usual) to be used for subsequent server-side calls. This file descriptor is the handler for an instance of a RINA control device which acts as a receiver for incoming flow allocation requests. At **(2)**, the server calls **rina_register** to register a name with the RINA control device, specifying the associated listening file descriptor (lfd), the name of the DIF to register to (dif) and the name to be registered (appl). The DIF argument is optional and advisory: the API implementation may choose to ignore it, and use some namespace management strategy to decide into which DIF the name should be registered. After a successful registration, the server can receive flow allocation requests, by calling **rina_flow_accept** on the listening file descriptor **(3)**. Since the listening file descriptor was not put in non-blocking mode, this call will block until a flow request arrives. When this happens, the function returns a new file descriptor (cfd), the name of the remote application (src) and the QoS granted to the flow. The returned file descriptor is an handler for an instance of a RINA I/O device, to be used for data I/O. At this point **(4)**, the flow allocation is complete, and the server can exchange SDUs with the client, using the **write** and **read** blocking calls or working in non-blocking mode (possibly mutliplexing with other I/O devices, sockets, etc.) by means of **poll** or **select**. This I/O phase is completely analogous to the I/O exchange that happens with TCP or UDP sockets, only the QoS may be different. Once the I/O session ends, the server can close the flow, triggering flow deallocation, using the **close** system call **(5)**. The server can then decide whether to terminate or accept another flow allocation request **(3)**. ### 2.2 Client-side operations Client operation is straightforward; the client calls **rina_flow_alloc** **(1)** to issue a flow allocation request, passing as arguments the name of the DIF that is asked to support the flow (dif), the name of the client (src, i.e. the source application name), the name of the destination application (dst, i.e. the server name) and the required QoS parameters for the flow (qos). The call will block until the flow allocation completes successfully, returning an file descriptor (fd) to be used for data I/O. At this point the client can exchange SDUs with the server **(2)**, using the I/O file descriptor either in blocking or not blocking mode, similarly to what is possible to do with sockets. When the I/O session terminates, the client can deallocate the flow with the **close** system call. ## 3. API Specification The API is defined at the following file: https://github.com/IRATI/stack/blob/master/librina/wrap/rina-api/include/rina/api.h Some general considerations: * Names (e.g. for applications and DIFs) are specified using C strings. Application names are composed of four components: process name, process instance, entity name and entity instance. Only process name is mandatory. The API mandates a standard separator to allow application developers to provide these 4 pieces of information using a single string. This separator is the **'|'** character, which **cannot be used as part of the process name, instance, entity name or instance**. Some examples of valid app names: * traffic.generator * traffic.generator|23 * traffic.generator||perf * traffic.generator|23|perf * traffic.generator|23|perf|12 * The API functions typically return 0 or a positive value on success. On error, -1 is returned with the errno variable set accordingly to the specific error. ### 3.1 rina_open int rina_open(void) This function opens a RINA control device that can be used to register/unregister names, and manage incoming flow allocation requests. On success, it returns a file descriptor that can be later passed to **rina_register()**, **rina_unregister()**, **rina_flow_accept()**, and **rina_flow_respond()**. On error -1 is returned with errno set properly. Applications typically call this function as a first step to implement server-side functionalities. ### 3.2 rina_register int rina_register(int fd, const char *dif, const char *appl, int flags) This function registers the application name _appl_ to a DIF in the system. After a successful registration, flow allocation requests can be received on _fd_ by means of **rina_flow_accept()**. If _dif_ is not NULL, the system may register the application to _dif_. However, the _dif_ argument is only advisory and the implementation is free to ignore it. If _dif_ is NULL, the system autonomously decide to which DIF _appl_ will be registered to. If **RINA_F_NOWAIT** is not specified in flags, this function will block the caller until the operation completes, and 0 is returned on success. If **RINA_F_NOWAIT** is specified in flags, the function returns a file descriptor (different from _fd_) which can be used to wait for the operation to complete (e.g. using POLLIN with **poll()** or **select()**). In this case the operation can be completed by a subsequent call to **rina_register_wait()**. On error -1 is returned, with the errno code properly set. ### 3.3 rina_unregister int rina_unregister(int fd, const char *dif, const char *appl, int flags) This function unregisters the application name _appl_ from the DIF where it was registered to. The _dif_ argument must match the one passed to **rina_register()**. After a successful unregistration, flow allocation requests can no longer be received on _fd_. The meaning of the **RINA_F_NOWAIT** flag is the same as in **rina_register()**, allowing non-blocking unregistration, to be later completed by calling **rina_register_wait()**. Returns 0 on success, -1 on error, with the errno code properly set. ### 3.4 rina_register_wait int rina_register_wait(int fd, int wfd) This function is called to wait for the completion of a (un)registration procedure previously initiated with a call to **rina_register()** or **rina_unregister()** on _fd_ which had the **RINA_F_NOWAIT** flag set. The _wfd_ file descriptor must match the one that was returned by **rina_[un]register()**. It returns 0 on success, -1 error, with the errno code properly set. ### 3.5 rina_flow_accept int rina_flow_accept(int fd, char **remote_appl, struct rina_flow_spec *spec, unsigned int flags) This function is called to accept an incoming flow request arrived on _fd_. If flags does not contain **RINA_F_NORESP**, it also sends a positive response to the requesting application; otherwise, the response (positive or negative) can be sent by a subsequent call to the **rina_flow_respond()**. On success, the char* pointed by remote_appl, if not NULL, is assigned the name of the requesting application. The memory for the requestor name is allocated by the callee and must be freed by the caller. Moreover, if spec is not NULL, the referenced data structure is filled with the QoS specification specified by the requesting application. If _flags_ does not contain **RINA_F_NORESP**, on success this function returns a file descriptor that can be subsequently used with standard I/O system calls (**write()**, **read()**, **select()**, ...) to exchange SDUs on the flow and synchronize. If _flags_ does contain **RINA_F_NORESP**, on success a positive number is returned as an handle to be passed to a subsequent call to **rina_flow_respond()**. This code cfd = rina_flow_accept(fd, &x, flags & ˜RINA_F_NORESP) is functionally equivalent to this code: h = rina_flow_accept(sfd, &x, flags | RINA_F_NORESP); cfd = rina_flow_respond(sfd, h, 0 /* positive response */); On error -1 is returned, with the errno code properly set. ### 3.6 rina_flow_accept int rina_flow_respond(int fd, int handle, int response) This function is called to emit a verdict on the flow allocation request identified by handle, that was previously received on _fd_ by calling **rina_flow_accept()** with the **RINA_F_NORESP** flag set. A zero response indicates a positive response, which completes the flow allocation procedure. A non-zero response indicates that the flow allocation request is denied. In both cases response is sent to the requesting application to inform it about the verdict. When the response is positive, on success this function returns a file descriptor that can be subsequently used with standard I/O system calls (**write()**, **read()**, **select()**, ...) to exchange SDUs on the flow and synchronize. When the response is negative, 0 is returned on success. In any case, -1 is returned on error, with the errno code properly set. ### 3.7 rina_flow_alloc int rina_flow_alloc(const char *dif, const char *local_appl, const char *remote_appl, const struct rina_flow_spec *flowspec, unsigned int flags); This function is called to issue a flow allocation request towards the destination application called remote_appl, using local_appl as a source application name. If _flowspec_ is not NULL, it specifies the QoS parameters to be used for the flow, if the flow allocation request is successful. If _flowspec_ is NULL, an implementation-specific default QoS will be assumed (which typically corresponds to a best-effort QoS). If _dif_ is not NULL the system may look for remote_appl in a DIF called _dif_. However, the _dif_ argument is only advisory and the system is free to ignore it and take an autonomous decision. If _flags_ specifies **RINA_F_NOWAIT**, a call to this function does not wait until the completion of the flow allocation procedure; on success, it just returns a control file descriptor that can be subsequently fed to **rina_flow_alloc_wait()** to wait for completion and obtain the flow I/O file descriptor. Moreover, the control file descriptor can be used with **poll()**, **select()** and similar. If _flags_ does not specify **RINA_F_NOWAIT**, a call to this function waits until the flow allocation procedure is complete. On success, it returns a file descriptor that can be subsequently used with standard I/O system calls (**write()**, **read()**, **select()**, ...) to exchange SDUs on the flow and synchronize. In any case, -1 is returned on error, with the errno code properly set. ### 3.8 rina_flow_alloc_wait int rina_flow_alloc_wait(int wfd) This function waits for the completion of a flow allocation procedure previosuly initiated with a call to **rina_flow_alloc()** with the **RINA_F_NOWAIT** flag set. The _wfd_ file descriptor must match the one returned by **rina_flow_alloc()**. On success, it returns a file descriptor that can be subsequently used with standard I/O system calls (**write()**, **read()**, **select()**, ...) to exchange SDUs on the flow and synchronize. On error -1 is returned, with the errno code properly set. ### 3.9 rina_flow_spec_default struct rina_flow_spec { uint64_t max_sdu\_gap; /* in SDUs */ uint64_t avg_bandwidth; /* in bits per second */ uint32_t max_delay; /* in microseconds */ uint16_t max_loss; /* percentage */ uint32_t max_jitter; /* in microseconds */ uint8_t in_order\_delivery; /* boolean */ uint8_t msg_boundaries; /* boolean */ }; void rina_flow_spec_default(struct rina_flow_spec *spec) This function fills in the provided _spec_ with an implementation-specific default QoS, which should correspond to a best-effort QoS. The fields of the **rina_flow_spec** data structure specify the QoS of a RINA flow as follows: * **max_sdu_gap** specifies the maximum number of consecutive SDUs that can be lost without violating the QoS. Specifying -1 means that there is no maximum, and so the flow is unreliable; 0 means that no SDU can be lost and so the flow is reliable. * **avg_bandwidth** specifies the maximum bandwidth that should be guaranteed on this flow, in bits per second. * **max_delay** specifies the maximum one-way latency that can be experienced by SDUs of this flow without violating the QoS, expressed in microseconds. * **max_loss** specifies the maximum percentage of SDUs that can be lost on this flow without violating the QoS. * **max_jitter** specifies the maximum jitter that can be experienced by SDUs on this flow without violating the QoS. * **in_order_delivery**, if true requires that the SDUs are delivered in order on this flow (no SDU reordering is allowed). * **msg_boundaries**: if true, the flow is stream-oriented, like TCP; a stream-oriented flow does not preserve message boundaries, and therefore write() and read() system calls are used to exchange a stream of bytes, and the granularity of the exchange is the byte. If false, the flow is message-oriented, like UDP, and does preserve message boundaries. The I/O system calls are used to exchanges messages (SDUs), and the granularity of the exchange is the message. ## 4. Example applications Two example applications written to this API are available at the **rina-tools** folder: https://github.com/IRATI/stack/tree/master/rina-tools/src/rlite