ud3tn/components/aap2/aap2_unified_api_backend.c
Felix Walter f684fabc5a aap2_client: Make type of keepalive timeout consistent with AAP2
AAP2 uses uint32, so we do, too. This moves the check for a negative
value to the UAPI backend implementation.

Signed-off-by: Felix Walter <felix.walter@d3tn.com>
2025-11-13 16:59:33 +01:00

502 lines
13 KiB
C

// SPDX-License-Identifier: Apache-2.0
/** \file ud3tn_unified_api_backend.c
*
* \brief This file contains the functions that implement a C API on top of the
* ud3tn AAP2. Destined to be moved to ud3tn official code.
*
* \copyright (c) 2024 Alma Mater Studiorum, University of Bologna.
*
* \par License
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* \author Fabio Colonna, fabio.colonna3@studio.unibo.it
* \author Beatrice Barbieri, beatrice.barbieri7@studio.unibo.it
*/
#include "aap2/aap2_client.h"
#include "aap2/aap2_unified_api_backend.h"
#include "platform/hal_io.h"
#include "ud3tn/eid.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
/* --------------- PUBLIC FUNCTIONS --------------- */
ud3tn_error_t ud3tn_close(ud3tn_handle_t handle)
{
LOGF_DEBUG("Entering %s", __func__);
aap2_client_close(handle->input_client);
aap2_client_close(handle->output_client);
free(handle);
return UD3TN_SUCCESS;
}
/**
* @note This function creates two connections on two different sockets in order to perform
* bidirectional communication.
*/
ud3tn_error_t ud3tn_open(ud3tn_handle_t *out_handle, char *sock_path)
{
LOGF_DEBUG("Entering %s", __func__);
*out_handle = NULL;
LOGF_DEBUG("Using AAP2 UNIX socket path: %s", sock_path);
UD3TNClientInfo *const out_client = malloc(sizeof(UD3TNClientInfo));
if (!out_client) {
LOG_ERROR("Could not allocate memory for client");
return UD3TN_EOPEN;
}
*out_client = (UD3TNClientInfo){
aap2_client_open_socket(sock_path, 0),
aap2_client_open_socket(sock_path, 0)
};
if (!out_client->input_client || !out_client->output_client) {
LOG_WARN("Opening AAP2 client connections failed");
aap2_client_close(out_client->input_client);
aap2_client_close(out_client->output_client);
free(out_client);
*out_handle = NULL;
return UD3TN_EOPEN;
}
LOG_DEBUG("Connected to AAP2 through a UNIX socket");
LOGF_DEBUG("Node ID: %s", aap2_client_get_node_id(out_client->input_client));
*out_handle = out_client;
return UD3TN_SUCCESS;
}
ud3tn_error_t ud3tn_open_with_ip(const char *const addr, int port, ud3tn_handle_t *out_handle)
{
LOGF_DEBUG("Entering %s", __func__);
*out_handle = NULL;
// Params check
if (port < 0 || port > (int)UINT16_MAX) {
LOGF_WARN("Invalid port number: %d", port);
return UD3TN_EINVAL;
}
if (!addr) {
LOG_WARN("INET address is null");
return UD3TN_EINVAL;
}
char service[6];
const int rv = snprintf(service, 6, "%d", port);
if (rv <= 0 || rv >= 6) {
LOGF_WARN("Failed to convert port number: %d", port);
return UD3TN_EINVAL;
}
UD3TNClientInfo *const out_client = malloc(sizeof(UD3TNClientInfo));
if (!out_client) {
LOG_ERROR("Could not allocate memory for client");
return UD3TN_EOPEN;
}
*out_client = (UD3TNClientInfo){
aap2_client_open_tcp(addr, service, 0),
aap2_client_open_tcp(addr, service, 0)
};
if (!out_client->input_client || !out_client->output_client) {
LOG_WARN("Opening AAP2 client connections failed");
aap2_client_close(out_client->input_client);
aap2_client_close(out_client->output_client);
free(out_client);
return UD3TN_EOPEN;
}
LOGF_DEBUG(
"Connected to AAP2 through a INET socket towards: %s:%d",
addr,
port
);
LOGF_DEBUG("Node ID: %s", aap2_client_get_node_id(out_client->input_client));
*out_handle = out_client;
return UD3TN_SUCCESS;
}
// Generate a random string consisting of valid ASCII characters != '\0'
static bool generate_random_ascii_string(uint8_t *buf, size_t buf_len)
{
ASSERT(buf_len != 0 && buf_len <= (size_t)SSIZE_MAX);
const int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0)
return false;
const ssize_t rv = read(fd, buf, buf_len);
if (rv != (ssize_t)buf_len)
return false;
// Ensure that it is an ASCII character != '\0'
for (ssize_t c = 0; c < rv; c++)
buf[c] = (buf[c] % 127) + 1;
return true;
}
/**
* @note This function performs the configuration of both connections.
* One where the client is passive (to receive AAPMessage
* sent by the AA) and one where the client is active (to send AAPMessage to the AA).
*/
ud3tn_error_t ud3tn_register(ud3tn_handle_t handle, ud3tn_reg_info_t *const reg_info)
{
// Params check
if (!handle->input_client || !handle->output_client || !reg_info ||
reg_info->keepalive_seconds < 0 ||
aap2_client_is_configured(handle->input_client) ||
aap2_client_is_configured(handle->output_client)) {
LOG_WARN("Invalid input param(s)");
return UD3TN_EINVAL;
}
LOGF_DEBUG("Entering %s", __func__);
// Generate a random secret.
// As we only want to send/receive bundles, the sole requirement is that both secrets match.
char secret[64];
if (!generate_random_ascii_string((uint8_t *)secret, 64)) {
LOG_WARN("Cannot generate random secret for AAP2 connection");
return UD3TN_EREG;
}
const enum aap2_client_error rv_output = aap2_client_register(
handle->output_client,
(struct aap2_client_spec){
reg_info->endpoint.uri,
secret,
false,
reg_info->keepalive_seconds,
},
0
);
if (rv_output < 0) {
LOGF_WARN("Registration of output client failed with status code: %d", rv_output);
return UD3TN_EREG;
}
const enum aap2_client_error rv_input = aap2_client_register(
handle->input_client,
(struct aap2_client_spec){
reg_info->endpoint.uri,
secret,
true,
reg_info->keepalive_seconds,
},
0
);
if (rv_input < 0) {
LOGF_WARN("Registration of input client failed with status code: %d", rv_input);
return UD3TN_EREG;
}
LOG_DEBUG("Successfully registered to AAP2");
return UD3TN_SUCCESS;
}
/**
* @note This function fills a BundleADU with input parameters, sends it along with its payload
* and retreives the timestamp and seqno from the response.
*/
ud3tn_error_t ud3tn_send(ud3tn_handle_t handle, ud3tn_bundle_spec_t *const spec,
ud3tn_bundle_payload_t *const payload)
{
LOGF_DEBUG("Entering %s", __func__);
// Params check
if (!handle->output_client || !spec || !payload ||
!aap2_client_is_configured(handle->output_client) ||
aap2_client_is_subscriber(handle->output_client)) {
LOG_WARN("Invalid input param(s)");
return UD3TN_EINVAL;
}
struct aap2_client_bundle_spec aap2_cspec = {
.source_eid = spec->source.uri,
.dest_eid = spec->dest.uri,
.creation_timestamp_dtn_ms = 0, // assign automatically
.sequence_number = 0, // assign automatically
.flags = (struct aap2_bundle_adu_flags){
.is_bpdu = (spec->proc_flags == BUNDLE_FLAG_ADMINISTRATIVE_RECORD),
.with_bdm_auth = spec->bdm_auth_validated,
},
};
const enum aap2_client_error rv = aap2_client_send(
handle->output_client,
aap2_cspec,
(uint8_t *)payload->buf.buf_val,
payload->buf.buf_len,
0,
&aap2_cspec // fine if contents are overwritten
);
switch (rv) {
case AAP2_CLIENT_OK:
break;
default:
// NOTE: We may want to handle the individual error codes differently.
LOGF_WARN("Failed sending ADU via AAP2, code = %d", rv);
return UD3TN_ESEND;
}
LOG_DEBUG("Got SUCCESS back. Successfully sent bundle");
#ifndef __clang_analyzer__
// NOTE: clang-tidy reports a false positive for a double free here.
free(aap2_cspec.source_eid); // unused
free(aap2_cspec.dest_eid); // unused
#endif // __clang_analyzer__
spec->creation_ts.time = aap2_cspec.creation_timestamp_dtn_ms;
spec->creation_ts.seqno = aap2_cspec.sequence_number;
return UD3TN_SUCCESS;
}
/**
* @note This function receives a BundleADU and its payload.
*/
ud3tn_error_t ud3tn_recv(ud3tn_handle_t handle, ud3tn_bundle_spec_t *out_spec,
ud3tn_bundle_payload_t *out_payload, ud3tn_timeval_t timeout)
{
// Params check
if (!handle->input_client || !out_spec || !out_payload ||
!aap2_client_is_configured(handle->input_client) ||
!aap2_client_is_subscriber(handle->input_client)) {
LOG_WARN("Invalid input param(s)");
return UD3TN_EINVAL;
}
LOGF_DEBUG("Entering %s", __func__);
if (timeout == UD3TN_NO_TIMEOUT || timeout == 0)
LOG_DEBUG("About to recv with no timeout");
else
LOGF_DEBUG("About to start recv with timeout: %lu", timeout);
struct aap2_client_bundle_spec aap2_cspec = {
.source_eid = NULL,
.dest_eid = NULL,
.creation_timestamp_dtn_ms = 0,
.sequence_number = 0,
.flags = (struct aap2_bundle_adu_flags){false, false, false},
};
uint8_t *payload;
size_t payload_len;
enum aap2_client_error rv;
do {
rv = aap2_client_recv(
handle->input_client,
&aap2_cspec,
&payload,
&payload_len,
timeout
);
} while (rv == AAP2_CLIENT_EAGAIN); // auto-retry if wrong message type was received
switch (rv) {
case AAP2_CLIENT_OK:
break;
default:
// NOTE: We may want to handle the individual error codes differently.
LOGF_WARN("Failed receiving ADU via AAP2, code = %d", rv);
return UD3TN_ERECV;
}
out_payload->buf.buf_len = payload_len;
out_payload->buf.buf_val = (char *)payload;
out_payload->status_report = NULL;
snprintf(
out_spec->source.uri,
ARRAY_LENGTH(out_spec->source.uri),
"%s",
aap2_cspec.source_eid
);
free(aap2_cspec.source_eid);
free(aap2_cspec.dest_eid);
out_spec->creation_ts.time = aap2_cspec.creation_timestamp_dtn_ms;
out_spec->creation_ts.seqno = aap2_cspec.sequence_number;
return UD3TN_SUCCESS;
}
ud3tn_error_t ud3tn_build_local_eid(ud3tn_handle_t handle, ud3tn_endpoint_id_t *out_local_eid,
const char *const service_tag, ud3tn_scheme_t eid_scheme)
{
LOGF_DEBUG("Entering %s", __func__);
// Params check
if (!handle->input_client || !out_local_eid || !service_tag) {
LOG_WARN("Invalid input param(s)");
return UD3TN_EINVAL;
}
const char *nodeID = aap2_client_get_node_id(handle->input_client);
const enum eid_scheme auto_determined_scheme = get_eid_scheme(nodeID);
switch (eid_scheme) {
case UD3TN_DTN_SCHEME:
if (auto_determined_scheme != EID_SCHEME_DTN) {
LOG_WARN("Cannot register as DTN as the node EID is not dtn");
return UD3TN_EINVAL;
}
break;
case UD3TN_IPN_SCHEME:
if (auto_determined_scheme != EID_SCHEME_IPN) {
LOG_WARN("Cannot register as IPN as the node EID is not ipn");
return UD3TN_EINVAL;
}
break;
default:
LOGF_WARN("Wrong EID scheme: %d", eid_scheme);
return UD3TN_EINVAL;
}
char *eid = aap2_client_get_eid_for_agent(nodeID, service_tag);
if (!eid) {
LOG_WARN("Failed to determine EID for registered AAP2 client");
return UD3TN_EINVAL;
}
const int rv = snprintf(out_local_eid->uri, ARRAY_LENGTH(out_local_eid->uri), "%s", eid);
if (rv <= 0 || rv >= (int)ARRAY_LENGTH(out_local_eid->uri)) {
LOG_WARN("Failed to copy resulting EID to dest buffer (too long?)");
free(eid);
return UD3TN_EINVAL;
}
free(eid);
return UD3TN_SUCCESS;
}
ud3tn_error_t ud3tn_copy_eid(ud3tn_endpoint_id_t *dst, ud3tn_endpoint_id_t *const src)
{
// Params check
if (!dst || !src) {
LOG_WARN("Invalid input param(s)");
return UD3TN_EINVAL;
}
memset(dst->uri, 0, sizeof(dst->uri));
if (src->uri[0] == '\0')
return UD3TN_SUCCESS;
const int rv = snprintf(dst->uri, ARRAY_LENGTH(dst->uri), "%s", src->uri);
if (rv <= 0 || rv >= (int)ARRAY_LENGTH(dst->uri)) {
LOG_WARN("Failed to copy EID (too long?)");
return UD3TN_EINVAL;
}
return UD3TN_SUCCESS;
}
ud3tn_error_t ud3tn_parse_eid_string(ud3tn_endpoint_id_t *out_eid, const char *const str)
{
// Params check
if (!str || !out_eid) {
LOG_WARN("Invalid input param(s)");
return UD3TN_EINVAL;
}
memset(out_eid->uri, 0, sizeof(out_eid->uri));
const int rv = snprintf(out_eid->uri, ARRAY_LENGTH(out_eid->uri), "%s", str);
if (rv <= 0 || rv >= (int)ARRAY_LENGTH(out_eid->uri)) {
LOG_WARN("Failed to copy EID (too long?)");
return UD3TN_EINVAL;
}
return UD3TN_SUCCESS;
}
ud3tn_error_t ud3tn_set_payload(ud3tn_bundle_payload_t *out_payload, const uint8_t *const buf,
uint32_t buf_length)
{
// Params check
if (!out_payload || !out_payload->buf.buf_val || !buf) {
LOG_WARN("Invalid input param(s)");
return UD3TN_EINVAL;
}
// FIXME: Dangerous API. Implicitly assumes that out_payload->buf.buf_val is allocated with
// len == buf_length. Should be adapted in upstream Unified API backend.
memcpy(out_payload->buf.buf_val, buf, buf_length);
out_payload->buf.buf_len = buf_length;
return UD3TN_SUCCESS;
}
void ud3tn_free_payload(ud3tn_bundle_payload_t *const payload)
{
LOGF_DEBUG("Entering %s", __func__);
free(payload->buf.buf_val);
payload->buf.buf_val = NULL;
payload->buf.buf_len = 0;
}
ud3tn_error_t ud3tn_ping_aap(ud3tn_handle_t handle)
{
LOGF_DEBUG("Entering %s", __func__);
// Params check
if (!handle->output_client ||
!aap2_client_is_configured(handle->output_client) ||
aap2_client_is_subscriber(handle->output_client)) {
LOG_WARN("Invalid input param(s)");
return UD3TN_EINVAL;
}
const enum aap2_client_error rv = aap2_client_keepalive(
handle->output_client,
0
);
switch (rv) {
case AAP2_CLIENT_OK:
break;
default:
// NOTE: We may want to handle the individual error codes differently.
LOGF_WARN("Failed sending keepalive via AAP2, code = %d", rv);
return UD3TN_ESEND;
}
LOG_DEBUG("Got reply. AAP2 connection is still alive.");
return UD3TN_SUCCESS;
}