diff options
author | 2022-08-04 16:06:34 +0200 | |
---|---|---|
committer | 2022-08-04 16:31:51 +0200 | |
commit | 6d22a0db96aa7f8e3102ae44d00c09e36a2e9c57 (patch) | |
tree | 79546bbf09f6fbf74db7bc89117843f06ce937ea /ctrl/libhicnctrl/src/modules/hicn_light.c | |
parent | 012843b1c0bc0838e69085ed83a79ec8b6f97360 (diff) |
feat: Due to the deep modifications related to names and packet format,
this task cover a large part of the codebase and involves several changes:
- the library provides a name data structure (hicn_name_t ), which is composed
of a name prefix (hicn_name_prefix_t) and a name suffix (hicn_name_suffix_t),
and it has been extended to provide all support functions required for name
manipulation, including common prefix computation, as required for the Longest
Prefix Match (LPM)in the forwarder, in addition to Exact Prefix Match (EPM).
- all code has been rewritten to use this data structure instead of having for
instance the forwarder define its own name class (used to be Name and NameBitVector)
the code has been refactored to minimize name allocations and copies, one remaining
aspect is the difference of name storage between PIT and CS entries (respectively
in the PIT entry, and in the message buffer), which causes the packet cache
index to be updated when a PIT entry is converted into a CS entry. By storing
the name in the PIT/CS entry everytime, we might save on this operation).
- hicn-light FIB has been rewritten : code has been refactored and should now be
shorter and documented; unit tests have been drafted but more would be required
to cover all cases and match the algorithms to add/remove nodes, as specified in the doc.
all protocol details and hICN header formats are now abstracted by the library
for the forwarder (and thus header.h and protocols/*.h have been removed from
public includes, and replaced by packet.h providing protocol agnostic packet
level functions, completely replacing the compat.h header that used to provide
similar functions.
- this works by exposing a opaque buffer to the application (a kind of socket buffer)
which is used by the lib to cache the packet format and offsets of the different
layers in the buffer and provider efficient operations (the packet format is
either defined for packet construction, or guessed at ingress, and this structure
is updated accordingly only once).
Co-authored-by: Jordan Augé <jordan.auge+fdio@cisco.com>
Signed-off-by: Luca Muscariello <muscariello@ieee.org>
Change-Id: I31e321897f85f0267fe8ba4720363c180564492f
Diffstat (limited to 'ctrl/libhicnctrl/src/modules/hicn_light.c')
-rw-r--r-- | ctrl/libhicnctrl/src/modules/hicn_light.c | 1255 |
1 files changed, 1255 insertions, 0 deletions
diff --git a/ctrl/libhicnctrl/src/modules/hicn_light.c b/ctrl/libhicnctrl/src/modules/hicn_light.c new file mode 100644 index 000000000..1af6c79e2 --- /dev/null +++ b/ctrl/libhicnctrl/src/modules/hicn_light.c @@ -0,0 +1,1255 @@ +/* + * Copyright (c) 2021-2022 Cisco and/or its affiliates. + * 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. + */ + +/** + * \file modules/hicn_light.c + * \brief Implementation of hicn-light module. + */ + +#include <assert.h> // assert +#include <fcntl.h> // fcntl +#include <stdbool.h> +#include <stdio.h> // snprintf +#include <string.h> // memmove, strcasecmp +#include <sys/socket.h> // socket +#include <sys/types.h> // getpid +#include <unistd.h> // close, fcntl, getpid + +#ifdef __linux__ +#include <sys/syscall.h> +#define gettid() syscall(SYS_gettid) +#endif /* __linux__ */ + +#include <strings.h> + +#include <hicn/ctrl/hicn-light.h> +#include <hicn/ctrl/socket.h> + +#include "../api_private.h" +#include "../objects/connection.h" // hc_connection_has_local +#include "../objects/listener.h" // hc_listener_is_local +#include "../objects/route.h" // hc_route_has_face +#include "../request.h" +#include "../socket_private.h" +#include "hicn_light.h" + +#include "hicn_light/base.h" +#include "hicn_light/connection.h" +#include "hicn_light/listener.h" +#include "hicn_light/face.h" +#include "hicn_light/route.h" +#include "hicn_light/strategy.h" +#include "hicn_light/subscription.h" + +#pragma GCC diagnostic ignored "-Warray-bounds" + +#define DEFAULT_SOCK_RECV_TIMEOUT_MS 100 + +#define PORT 9695 + +#define BOOLSTR(x) ((x) ? "true" : "false") + +hc_sock_light_data_t *hc_sock_light_data_create(const char *url) { + hc_sock_light_data_t *s = malloc(sizeof(hc_sock_light_data_t)); + if (!s) goto ERR_MALLOC; + + s->roff = s->woff = 0; + s->remaining = 0; + s->got_header = false; + + s->url = url ? strdup(url) : NULL; + + s->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (s->fd < 0) goto ERR_SOCKET; + +#if 0 + struct timeval tv = {.tv_sec = 0, + .tv_usec = DEFAULT_SOCK_RECV_TIMEOUT_MS * 1000}; + if (setsockopt(s->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + perror("setsockopt"); + goto ERR_TIMEOUT; + } +#endif + + return s; + +#if 0 +ERR_TIMEOUT: +#endif + close(s->fd); +ERR_SOCKET: + if (s->url) free(s->url); + free(s); +ERR_MALLOC: + return NULL; +} + +void hc_sock_light_data_free(hc_sock_light_data_t *data) { + if (data->url) free(data->url); + free(data); +} + +static const struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT; + +/****************************************************************************** + * Control socket + ******************************************************************************/ + +#define AVAILABLE(s) ((s)->woff - (s)->roff) + +/** + * \brief Parse a connection URL into a sockaddr + * \param [in] url - URL + * \param [out] sa - Resulting struct sockaddr, expected zero'ed. + * \return 0 if parsing succeeded, a negative error value otherwise. + */ +static int hicnlight_parse_url(const char *url, struct sockaddr *sa) { + /* FIXME URL parsing is currently not implemented */ + _ASSERT(!url); + +#ifdef __linux__ + srand(time(NULL) ^ getpid() ^ gettid()); +#else + srand((unsigned int)(time(NULL) ^ getpid())); +#endif /* __linux__ */ + + /* + * A temporary solution is to inspect the sa_family fields of the passed in + * sockaddr, which defaults to AF_UNSPEC (0) and thus creates an IPv4/TCP + * connection to localhost. + */ + switch (sa->sa_family) { + case AF_UNSPEC: + case AF_INET: { + struct sockaddr_in *sai = (struct sockaddr_in *)sa; + sai->sin_family = AF_INET; + sai->sin_port = htons(PORT); + sai->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + break; + } + case AF_INET6: { + struct sockaddr_in6 *sai6 = (struct sockaddr_in6 *)sa; + sai6->sin6_family = AF_INET6; + sai6->sin6_port = htons(PORT); + sai6->sin6_addr = loopback_addr; + break; + } + default: + return -1; + } + + return 0; +} + +/* + * Return codes: + * < 0 : error; invalid buffer data -> flush + * otherwise, seq_num of the identified request + */ +static int hicnlight_process_header(hc_sock_t *sock) { + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + hc_object_type_t object_type = OBJECT_TYPE_UNDEFINED; + + /* Check we have at least a header's worth of data, and consume it */ + if (AVAILABLE(s) < sizeof(hc_msg_header_t)) return 0; + + hc_msg_t *msg = (hc_msg_t *)(s->buf + s->roff); + + // INFO("Processing header header %s", command_type_str(msg->hdr.command_id)); + s->roff += sizeof(hc_msg_header_t); + s->got_header = true; + + /* How many elements are we expecting in the reply ? */ + s->remaining = msg->header.length; + + /* Identify request being parsed */ + int seq = msg->header.seq_num; + hc_request_t *request = NULL; + if (hc_sock_map_get(sock->map, seq, &request) < 0) { + ERROR("[hc_sock_light_process] Error searching for matching request"); + return -1; + } + if (!request) { + ERROR("[hc_sock_light_process] No request matching sequence number"); + return -1; + } + sock->current_request = request; + hc_request_t *current_request = hc_request_get_current(request); + hc_data_t *data = hc_request_get_data(current_request); + _ASSERT(data); + + switch (msg->header.message_type) { + case ACK_LIGHT: + _ASSERT(s->remaining == 0); + + s->got_header = false; + if (!hc_request_is_subscription(request)) hc_data_set_complete(data); + break; + + case NACK_LIGHT: + _ASSERT(s->remaining == 0); + + s->got_header = false; + hc_data_set_error(data); + break; + + case RESPONSE_LIGHT: + if (s->remaining == 0) { + /* Empty response (i.e. containing 0 elements) */ + s->got_header = false; + hc_data_set_complete(data); + return 0; + } + + /* Allocate buffer for response */ + if (hc_data_allocate(data, s->remaining) < 0) { + ERROR("[hc_sock_light_process] Cannot allocate result buffer"); + return -99; + } + break; + + case NOTIFICATION_LIGHT: { + _ASSERT(s->remaining == 1); + /* + * Assumption: the whole notification data is returned in a single read + * and we immediately parse it. + */ + // XXX assert enough buffer for object type + validate returned object + object_type = (hc_object_type_t)msg->header.command_id; + hc_data_clear(data); + hc_data_set_object_type(data, object_type); + if (hc_data_allocate(data, s->remaining) < 0) { + ERROR("[hc_sock_light_process] Cannot allocate result buffer"); + return -1; + } + + hc_data_push(data, s->buf + s->roff); + + s->roff += AVAILABLE(s); + + hc_request_on_notification(request); + + /* + * The buffer is cleared just before the next notification, which means + * it will have to be released upon exit. Otherwise we break the code + * dumping the notification synchronously (eg. hicnctrl -s). + */ + // hc_data_clear(data); + + s->got_header = false; + break; + } + + default: + ERROR("[hc_sock_light_process] Invalid response received"); + return -99; + } + + return 0; +} + +size_t hc_light_object_size(hc_object_type_t object_type); + +static int hicnlight_process_payload(hc_sock_t *sock) { + int err = 0; + int rc; + + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + hc_request_t *request = hc_sock_get_request(sock); + hc_request_t *current_request = hc_request_get_current(request); + hc_data_t *data = hc_request_get_data(current_request); + + hc_object_type_t object_type = hc_data_get_object_type(data); + size_t object_size = hc_light_object_size(object_type); + if (object_size == 0) return -1; + + /* We only process full elements (size is stored in data) */ + size_t num_chunks = AVAILABLE(s) / object_size; + + /* Check whether we have enough data to process */ + if (num_chunks == 0) return 0; + + /* Safeguard: _ASSERT(num_chunks < s->remaining); */ + if (num_chunks > s->remaining) { + WARN( + "[hicnlight_process_payload] Unexpected num_chunks > " + "s->remaining"); + num_chunks = s->remaining; + } + + for (size_t i = 0; i < num_chunks; i++) { + /* + * Get storage offset in hc_data_t, which we assume is correctly + * provisioned. + * XXX + */ + u8 *src = s->buf + s->roff; + hc_object_t *dst = (hc_object_t *)hc_data_get_free(data); + if (!dst) { + ERROR("[hc_sock_light_process] Error in hc_data_get_next"); + err = -2; + break; + } + + // XXX we might want to display even incomplete data when printing (eg. + // string truncation), and be very strict when processing. + rc = hc_sock_parse_object(sock, hc_data_get_object_type(data), src, + object_size, dst); + s->roff += object_size; + if (rc < 0) { + ERROR("Error parsing received object"); + continue; + } + hc_data_inc_size(data); + } + + /* + * If we are not expecting any more data, mark the reply as complete + */ + s->remaining -= num_chunks; + if (s->remaining == 0) { + s->got_header = false; + hc_data_set_complete(data); + } + + return err; +} + +/*---------------------------------------------------------------------------- + * Socket operations + *----------------------------------------------------------------------------*/ + +static int hicnlight_get_fd(hc_sock_t *sock) { + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + return s->fd; +} + +static int hicnlight_get_recv_buffer(hc_sock_t *sock, uint8_t **buffer, + size_t *size) { + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + *buffer = s->buf + s->woff; + *size = RECV_BUFLEN - s->woff; + + return 0; +} + +static int hicnlight_connect(hc_sock_t *sock) { + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + struct sockaddr_storage ss; + memset(&ss, 0, sizeof(struct sockaddr_storage)); + + if (hicnlight_parse_url(s->url, (struct sockaddr *)&ss) < 0) goto ERR_PARSE; + + size_t size = ss.ss_family == AF_INET ? sizeof(struct sockaddr_in) + : sizeof(struct sockaddr_in6); + if (connect(s->fd, (struct sockaddr *)&ss, (socklen_t)size) < 0) { + perror("connect error"); + goto ERR_CONNECT; + } + return 0; + +ERR_CONNECT: +ERR_PARSE: + return -1; +} + +static int hicnlight_disconnect(hc_sock_t *sock) { + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + + /* Remove the connection created to send the command. + * + * Note this is done as a best effort and we don't expect to receive any + * answer from the forwarder (hence the NULL pdata pointer in the request). + */ + hc_object_t object; + memset(&object, 0, sizeof(hc_object_t)); + object.connection.id = 0; + int rc = + strcpy_s(object.connection.name, sizeof(object.connection.name), "SELF"); + if (rc == EOK) + hc_execute_async(sock, ACTION_DELETE, OBJECT_TYPE_CONNECTION, &object, NULL, + NULL); + + close(s->fd); + + return 0; +} + +static ssize_t hicnlight_prepare_generic(hc_sock_t *sock, hc_request_t *request, + uint8_t **buffer) { + /* Dispatch to subrequest if any */ + hc_request_t *current_request = hc_request_get_current(request); + + _ASSERT(!hc_request_get_data(current_request)); + + hc_action_t action = hc_request_get_action(current_request); + hc_object_type_t object_type = hc_request_get_object_type(current_request); + hc_object_t *object = hc_request_get_object(current_request); + + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + + _ASSERT(hc_request_get_data(current_request) == NULL); + hc_data_t *data = hc_data_create(object_type); + if (!data) { + ERROR("[hicnlight_prepare_generic] Could not create data storage"); + return -1; + } + hc_request_set_data(current_request, data); + + /* Serialize request into message */ + DEBUG("Calling serialize on %s %s", action_str(action), + object_type_str(object_type)); + ssize_t msg_len = hc_sock_serialize_object(sock, action, object_type, object, + (uint8_t *)&s->msg); + if (msg_len < 0) { + ERROR("[hicnlight_prepare_generic] Could not serialize command %s %s", + action_str(action), object_type_str(object_type)); + return INPUT_ERROR; + } + + s->msg.header.seq_num = hc_request_get_seq(current_request); + + *buffer = (uint8_t *)&s->msg; + return msg_len; +} + +static int hicnlight_send(hc_sock_t *sock, uint8_t *buffer, size_t size) { + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + + int rc = (int)send(s->fd, buffer, size, 0); + if (rc < 0) { + perror("[hicnlight_send] Error sending message"); + return -1; + } + + // XXX regular behaviour for others + return 0; +} + +// Example : face create udp +// 1) face to connection (immediate) +// connection to local listener (immediate) : why not both at the same +// time listener get +// listener create / nothing +// connection create +// connection get (if needed for populating face_id for instance, aka +// if we +// need to return data) + +static ssize_t hicnlight_prepare(hc_sock_t *sock, hc_request_t *request, + uint8_t **buffer); + +static ssize_t hicnlight_prepare_subrequest( + hc_sock_t *sock, hc_request_t *request, hc_action_t action, + hc_object_type_t object_type, hc_object_t *object, uint8_t **buffer) { + WITH_DEBUG({ + if (object) { + char buf[MAXSZ_HC_OBJECT]; + hc_object_snprintf(buf, sizeof(buf), object_type, object); + DEBUG("Creating subrequest %s/%s %s", action_str(action), + object_type_str(object_type), buf); + } + }); + hc_request_make_subrequest(request, action, object_type, object); + return hicnlight_prepare(sock, request, buffer); +} + +/* + * XXX shall we update the object in the request for faces ? it is not done + * for other objects, but for faces it is needed to further add a route !!! + */ +static ssize_t hicnlight_prepare_face_create(hc_sock_t *sock, + hc_request_t *request, + uint8_t **buffer) { + hc_request_t *current_request = hc_request_get_current(request); + hc_object_t *object = hc_request_get_object(current_request); + hc_data_t *data = hc_request_get_data(current_request); + hc_face_t *face = &object->face; + + // XXX those objects are created on stack and expected to be valid across + // several calls. A quick fix is to make them static + static hc_object_t connection; + static hc_object_t listener; + + hc_request_state_t state; + const hc_connection_t *conn; + +NEXT: + state = hc_request_get_state(current_request); + DEBUG("hicnlight_prepare_face_create > %s", hc_request_state_str(state)); + + switch (state) { + case REQUEST_STATE_INIT: + _ASSERT(!data); + + switch (face->type) { + case FACE_TYPE_HICN: + case FACE_TYPE_TCP: + case FACE_TYPE_UDP: + hc_request_set_state(current_request, + REQUEST_STATE_FACE_CREATE_CONNECTION_CREATE); + goto NEXT; + case FACE_TYPE_HICN_LISTENER: + case FACE_TYPE_TCP_LISTENER: + case FACE_TYPE_UDP_LISTENER: + hc_request_set_state(current_request, + REQUEST_STATE_FACE_CREATE_LISTENER_CREATE); + goto NEXT; + case FACE_TYPE_UNDEFINED: + case FACE_TYPE_N: + return -99; // Not implemented + } + + case REQUEST_STATE_FACE_CREATE_CONNECTION_CREATE: + if (hc_face_to_connection(face, &connection.connection, true) < 0) { + ERROR("[hc_face_create] Could not convert face to connection."); + return -1; + } + hc_request_set_state(current_request, + REQUEST_STATE_FACE_CREATE_CONNECTION_CHECK); + + return hicnlight_prepare_subrequest(sock, request, ACTION_CREATE, + OBJECT_TYPE_CONNECTION, &connection, + buffer); + + case REQUEST_STATE_FACE_CREATE_CONNECTION_CHECK: + /* + * If the newly created face_id was not need, we would only + * need to return the same data result, which contains a ack/nack, + * simply updating the object type. + * + * With the current API, once the connection is created, our only + * solution is to list all connections and compare with the current one + * to find the created connection ID, and thus face ID. + */ + /* Has the connection been successfully created ? */ + if (!data || !hc_data_get_result(data)) return -1; + + hc_request_set_state(current_request, + REQUEST_STATE_FACE_CREATE_CONNECTION_GET); + goto NEXT; + + case REQUEST_STATE_FACE_CREATE_CONNECTION_GET: + hc_request_set_state(current_request, + REQUEST_STATE_FACE_CREATE_CONNECTION_VERIFY); + return hicnlight_prepare_subrequest(sock, request, ACTION_GET, + OBJECT_TYPE_CONNECTION, &connection, + buffer); + + case REQUEST_STATE_FACE_CREATE_CONNECTION_VERIFY: + if (!data || hc_data_get_size(data) != 1) return -1; + + /* Newly created connection was found */ + conn = (hc_connection_t *)hc_data_get_buffer(data); + DEBUG("created connection id=%d", conn->id); + object->face.id = conn->id; + + break; + + case REQUEST_STATE_FACE_CREATE_LISTENER_CREATE: + if (hc_face_to_listener(face, &listener.listener) < 0) { + ERROR("Could not convert face to listener."); + return -1; + } + + hc_request_set_state(current_request, + REQUEST_STATE_FACE_CREATE_LISTENER_CHECK); + return hicnlight_prepare_subrequest(sock, request, ACTION_CREATE, + OBJECT_TYPE_LISTENER, &listener, + buffer); + + break; + + case REQUEST_STATE_FACE_CREATE_LISTENER_CHECK: + /* + * No need for face id here, simply return the hc_data_t structure + * with the ack/nack, and the proper object type + */ + if (!data) return -1; + hc_data_set_object_type(data, OBJECT_TYPE_FACE); + break; + +#if 0 + case REQUEST_STATE_COMPLETE: + hc_data_set_complete(data); + break; +#endif + + default: + return -1; + } + return 0; +} + +static ssize_t hicnlight_prepare_face_list(hc_sock_t *sock, + hc_request_t *request, + uint8_t **buffer) { + hc_request_t *current_request = hc_request_get_current(request); + hc_action_t action = hc_request_get_action(current_request); + hc_object_type_t object_type = hc_request_get_object_type(current_request); + hc_object_t *object = hc_request_get_object(current_request); + hc_data_t *data = hc_request_get_data(current_request); + hc_face_t face; + + _ASSERT(action == ACTION_LIST); + _ASSERT(object_type == OBJECT_TYPE_FACE); + + hc_request_state_t state = hc_request_get_state(current_request); + DEBUG("hicnlight_prepare_face_list > %s", hc_request_state_str(state)); + + switch (state) { + case REQUEST_STATE_INIT: + _ASSERT(!data); + + hc_request_set_state(current_request, + REQUEST_STATE_FACE_LIST_CONNECTION_LIST); + return hicnlight_prepare_subrequest( + sock, request, ACTION_LIST, OBJECT_TYPE_CONNECTION, object, buffer); + + case REQUEST_STATE_FACE_LIST_CONNECTION_LIST: + _ASSERT(data); + /* + * 'list connection' succeeded, we just need to allocate hc_data_t, + * create faces from connections, and return the data structure as if it + * was created by the query + */ + hc_data_t *face_data = hc_data_create(object_type); + hc_data_allocate(face_data, hc_data_get_size(data)); + foreach_connection(c, data) { + if (hc_face_from_connection(c, &face) < 0) { + ERROR("[hc_face_list] Could not convert connection to face."); + return -1; + } + hc_data_push(face_data, &face); + } + hc_data_set_complete(face_data); + + hc_request_reset_data(request); + hc_request_set_data(request, face_data); + + /* FACE/LIST could be part of FACE/GET */ + break; + + default: + return -1; + } + + return 0; +} + +static ssize_t hicnlight_prepare_get(hc_sock_t *sock, hc_request_t *request, + uint8_t **buffer) { + hc_request_t *current_request = hc_request_get_current(request); + + hc_object_type_t object_type = hc_request_get_object_type(current_request); + hc_object_t *object = hc_request_get_object(current_request); + hc_data_t *data = hc_request_get_data(current_request); + hc_object_t *found; + + hc_request_state_t state = hc_request_get_state(current_request); + DEBUG("hicnlight_prepare_get > %s", hc_request_state_str(state)); + + switch (state) { + case REQUEST_STATE_INIT: + _ASSERT(!data); + hc_request_set_state(current_request, REQUEST_STATE_GET_LIST); + return hicnlight_prepare_subrequest(sock, request, ACTION_LIST, + object_type, NULL, buffer); + case REQUEST_STATE_GET_LIST: + _ASSERT(data); + + found = hc_data_find(data, object); + hc_data_t *found_data = hc_data_create(object_type); + if (found) { + hc_data_allocate(found_data, 1); + hc_data_push(found_data, found); + } + hc_data_set_complete(found_data); + hc_request_reset_data(current_request); + hc_request_set_data(current_request, found_data); + return 0; + default: + return -1; /* Unexpected */ + } +} + +// XXX This should process the content of pdata (unless at init), and +// terminate by sending something +static ssize_t hicnlight_prepare_face(hc_sock_t *sock, hc_request_t *request, + uint8_t **buffer) { + hc_request_t *current_request = hc_request_get_current(request); + hc_action_t action = hc_request_get_action(current_request); + hc_object_type_t object_type = hc_request_get_object_type(request); + + _ASSERT(object_type == OBJECT_TYPE_FACE); + + switch (action) { + case ACTION_CREATE: + return hicnlight_prepare_face_create(sock, request, buffer); + case ACTION_LIST: + return hicnlight_prepare_face_list(sock, request, buffer); + default: + return -99; // Not implemented + } + return 0; +} + +static ssize_t hicnlight_prepare_connection_create(hc_sock_t *sock, + hc_request_t *request, + uint8_t **buffer) { + hc_request_t *current_request = hc_request_get_current(request); + + hc_action_t action = hc_request_get_action(current_request); + hc_object_type_t object_type = hc_request_get_object_type(current_request); + hc_object_t *object = hc_request_get_object(current_request); + + _ASSERT(action == ACTION_CREATE); + _ASSERT(object_type == OBJECT_TYPE_CONNECTION); + + hc_data_t *data = hc_request_get_data(current_request); + + size_t size; + unsigned pos; + static hc_object_t listener; + const hc_object_t *obj_listener; + hc_data_t *listener_data = NULL; + + hc_request_state_t state; + +NEXT: + state = hc_request_get_state(current_request); + DEBUG("hicnlight_prepare_connection_create > %s", + hc_request_state_str(state)); + + switch (state) { + case REQUEST_STATE_INIT: + /* Two behaviours depending on the content of local_addr and local_port: + * - empty : create connection on all existing listeners, and raise an + * error if none + * - otherwise, check whether a corresponding listener exists, and + * create it if necessary + * + * We assume connection has been already validated. + */ + if (hc_connection_has_local(&object->connection)) { + hc_request_set_state(current_request, + REQUEST_STATE_CONNECTION_CREATE_LISTENER_GET); + } else { + /* + * At least part of the local socket specification is missing, match + * against existing listeners + */ + hc_request_set_state(current_request, + REQUEST_STATE_CONNECTION_CREATE_LISTENER_LIST); + } + goto NEXT; + + case REQUEST_STATE_CONNECTION_CREATE_LISTENER_LIST: + + hc_request_set_state(current_request, + REQUEST_STATE_CONNECTION_CREATE_LISTENER_ITERATE); + // XXX We are currently assuming an object is present for rewrite, fix + // this + return hicnlight_prepare_subrequest(sock, request, ACTION_LIST, + OBJECT_TYPE_LISTENER, NULL, buffer); + + case REQUEST_STATE_CONNECTION_CREATE_LISTENER_ITERATE: + /* + * NOTE: we could create all connections in parallel to speed up + * processing + */ + size = hc_data_get_size(data); + if (size < 0) return -1; + if (size == 0) + /* We are done, we cannot create a connection, return a Nack */ + ; // XXX TODO + // + /* Save the list of listeners for later iteration */ + listener_data = data; + hc_request_clear_data(current_request); // don't free data + data = NULL; + hc_request_set_state_count(current_request, 0); + hc_request_set_state(current_request, REQUEST_STATE_CONNECTION_CREATE_N); + goto NEXT; /* Start iteration */ + + case REQUEST_STATE_CONNECTION_CREATE_N: + /* + * IMPORTANT + * + * For now we only create a connection with the first non-local + * listener. + * + * Creating N connections in a single commands requires other + * changes to the code that we might done later: + * - ack/nack is not sufficient, all create function should return the + * list of created connections + * - this would allow us to avoid a GET at the end of face creation to + * retrieve the connection id. + * - face create should correspond to N connection create (should work + * out of the box provided we don't expect a single connection back). + * - route+face creation might then create N faces, and thus we would + * have to add N routes. + */ + assert(listener_data); + + // We need to back it up as the subrequest will clear the results + pos = hc_request_get_state_count(current_request); + size = hc_data_get_size(listener_data); + /* We have data if pos > 0, and we did not skipped previous ones */ + if (data && !hc_data_get_result(data)) { + INFO("Failed to create connection for listener %d / %d", pos - 1, size); + // XXX we might allow connections that already exist... how to manage + // the names + return -1; + } + + /* + * Previous connection was successfully created, let's continue but + * first check whether we reached the last one, which would complete the + * request. + */ + if (pos >= size) { + hc_data_free(listener_data); + hc_request_set_state(request, REQUEST_STATE_COMPLETE); + goto NEXT; + } + + /* Sending count'th connection creation */ + obj_listener = hc_data_get_object(listener_data, pos); + + // Filter which listener we use + // same protocol ? ip ? port ? + // avoid local ? + if (hc_listener_is_local(&obj_listener->listener)) { + /* Skip listener */ + DEBUG("Skipped local listener"); + hc_request_set_state_count(current_request, pos + 1); + goto NEXT; + } + + DEBUG("Creating connection with listener # %d / %d", pos, size); + /* We complement missing information from listener */ + // XXX is_family, etc. + object->connection.family = obj_listener->listener.family; + object->connection.local_addr = obj_listener->listener.local_addr; + object->connection.local_port = obj_listener->listener.local_port; + snprintf(object->connection.interface_name, INTERFACE_LEN, "%s", + obj_listener->listener.interface_name); + + hc_request_set_state_count(current_request, pos + 1); + return hicnlight_prepare_subrequest( + sock, request, ACTION_CREATE, OBJECT_TYPE_CONNECTION, object, buffer); + + /* Request listener to further check existence */ + case REQUEST_STATE_CONNECTION_CREATE_LISTENER_GET: + /* Ensure we have a corresponding local listener */ + if (hc_connection_to_local_listener(&object->connection, + &listener.listener) < 0) { + ERROR( + "[hicnlight_prepare_connection_create] Could not convert face " + "to " + "local listener."); + return -1; + } + hc_request_set_state(current_request, + REQUEST_STATE_CONNECTION_CREATE_LISTENER_VERIFY); + return hicnlight_prepare_subrequest( + sock, request, ACTION_GET, OBJECT_TYPE_LISTENER, &listener, buffer); + + break; + + /* Check whether listener exists in GET results */ + case REQUEST_STATE_CONNECTION_CREATE_LISTENER_VERIFY: + if (!data) return -1; + switch (hc_data_get_size(data)) { + case 0: + hc_request_set_state(current_request, + REQUEST_STATE_CONNECTION_CREATE_LISTENER_CREATE); + break; + case 1: + hc_request_set_state(current_request, + REQUEST_STATE_CONNECTION_CREATE); + break; + default: + return -1; + } + goto NEXT; + + /* Create associated listener */ + case REQUEST_STATE_CONNECTION_CREATE_LISTENER_CREATE: + hc_request_set_state(current_request, + REQUEST_STATE_CONNECTION_CREATE_LISTENER_CHECK); + return hicnlight_prepare_subrequest(sock, request, ACTION_CREATE, + OBJECT_TYPE_LISTENER, &listener, + buffer); + + /* Check whether listener creation succeeded */ + case REQUEST_STATE_CONNECTION_CREATE_LISTENER_CHECK: + if (!data || hc_data_get_result(data)) return -1; + hc_request_set_state(current_request, REQUEST_STATE_CONNECTION_CREATE); + goto NEXT; + + /* Create connection */ + case REQUEST_STATE_CONNECTION_CREATE: + /* + * Break recursion by directly calling hicnlight_prepare_generic on + * the initial request, that can now be executed since all + * prerequisites are validated. + */ + // return hicnlight_prepare_subrequest( + // sock, request, ACTION_CREATE, OBJECT_TYPE_CONNECTION, object, + // buffer); + hc_request_reset_data(current_request); + hc_request_set_state(current_request, REQUEST_STATE_COMPLETE); + return hicnlight_prepare_generic(sock, request, buffer); + + case REQUEST_STATE_COMPLETE: + if (data) { + hc_data_set_complete(data); + } else { + /* + * No connection has been created, and we freed the data due to + * subrequest + */ + data = hc_data_create(OBJECT_TYPE_CONNECTION); + hc_data_set_error(data); + } + break; + + default: + return -1; + } + return 0; +} + +static ssize_t hicnlight_prepare_route_create(hc_sock_t *sock, + hc_request_t *request, + uint8_t **buffer) { + hc_request_t *current_request = hc_request_get_current(request); + + hc_action_t action = hc_request_get_action(current_request); + hc_object_type_t object_type = hc_request_get_object_type(current_request); + hc_object_t *object = hc_request_get_object(current_request); + + _ASSERT(action == ACTION_CREATE); + _ASSERT(object_type == OBJECT_TYPE_ROUTE); + + hc_data_t *data = hc_request_get_data(current_request); + const hc_object_t *face_obj; + + hc_request_state_t state; + +NEXT: + state = hc_request_get_state(current_request); + DEBUG("hicnlight_prepare_route_create > %s", hc_request_state_str(state)); + + switch (state) { + case REQUEST_STATE_INIT: + if (hc_route_has_face(&object->route)) + hc_request_set_state(current_request, + REQUEST_STATE_ROUTE_CREATE_FACE_CREATE); + else + hc_request_set_state(current_request, REQUEST_STATE_ROUTE_CREATE); + goto NEXT; + + case REQUEST_STATE_ROUTE_CREATE_FACE_CREATE: + hc_request_set_state(current_request, + REQUEST_STATE_ROUTE_CREATE_FACE_CHECK); + INFO(">>>>>>subrequest create face"); + return hicnlight_prepare_subrequest( + sock, request, ACTION_CREATE, OBJECT_TYPE_FACE, + (hc_object_t *)&object->route.face, buffer); + + case REQUEST_STATE_ROUTE_CREATE_FACE_CHECK: + if (!data) return -1; + int rc = hc_data_get_result(data); + if (rc < 0) return -1; + + if (hc_data_get_size(data) != 1) return -1; + + face_obj = hc_data_get_object(data, 0); + DEBUG("Created face id=%d", face_obj->face.id); + object->route.face_id = face_obj->face.id; + + hc_request_set_state(current_request, REQUEST_STATE_ROUTE_CREATE); + goto NEXT; + + /* Create route */ + case REQUEST_STATE_ROUTE_CREATE: + /* + * Break recursion by directly calling hicnlight_prepare_generic on the + * initial request, that can now be executed since all prerequisites are + * validated. + */ + hc_request_set_state(current_request, REQUEST_STATE_COMPLETE); + return hicnlight_prepare_generic(sock, request, buffer); + + case REQUEST_STATE_COMPLETE: + hc_data_set_complete(data); + break; + + default: + return -1; + } + return 0; +} + +static int hicnlight_recv(hc_sock_t *sock) { + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + int rc; + + /* + * This condition should be ensured to guarantee correct processing of + * messages. With TCP, we need at least a header as we will receive part of + * the stream. With UDP, we need the be able to receive the full datagram, + * otherwise the rest will be lost. + * + * Let's be sure to always be able to receive at least 1 JUMBO_MTU, which + * should be fine for al situations. + */ + _ASSERT(RECV_BUFLEN - s->woff > JUMBO_MTU); + + rc = (int)recv(s->fd, s->buf + s->woff, RECV_BUFLEN - s->woff, 0); + if (rc == 0) { + /* Connection has been closed */ + return 0; + } + if (rc < 0) { + /* + * Let's not return 0 which currently means the socket has been closed + */ + if (errno == EWOULDBLOCK) { + // XXX TODO ?if (hc_request_get_action(request) == ACTION_SUBSCRIBE) + // return 0; + return -1; + } + if (errno == EINTR) { + WARN("recv has been stopped by signal"); + return -1; + } + perror("hc_sock_light_recv"); + return -1; + } + DEBUG("Received rc=%ld bytes", rc); + s->woff += rc; + + return rc; +} + +/* + * + * @param [in] data - hc_data_t structure allocated for the request + * + * This function is the entry point for all requests, and from there we will + * decide whether + * + */ +static ssize_t hicnlight_prepare(hc_sock_t *sock, hc_request_t *request, + uint8_t **buffer) { + /* Dispatch to subrequest if any */ + hc_request_t *current_request = hc_request_get_current(request); + + // XXX when do we create data... once for every step + hc_action_t action = hc_request_get_action(current_request); + hc_object_type_t object_type = hc_request_get_object_type(current_request); + hc_object_t *object = hc_request_get_object(current_request); + + static hc_object_t object_subscribe; + + DEBUG("[hicnlight_prepare] %s %s", action_str(action), + object_type_str(object_type)); + + /* + * Here the request is in progress and we just need to iterate through the + * FSM, or complete it. + */ + /* + * Specific treatment for + * CREATE/ROUTE with face + * SUBSCRIBE/(*) + * GET/(*) + * (*)/FACE + */ + + /* + * Special treatment for faces. + * + * This function will be called multiple times in order to process the + * complex request, involving several calls to the API. The process is + * responsible for going through the related state machine, and complete the + * request when appropriate. + */ + if (object_type == OBJECT_TYPE_FACE) + return hicnlight_prepare_face(sock, request, buffer); + + switch (action) { + case ACTION_CREATE: + switch (object_type) { + case OBJECT_TYPE_ROUTE: + /* Route might require face creation */ + return hicnlight_prepare_route_create(sock, request, buffer); + case OBJECT_TYPE_CONNECTION: + /* Connection could have no corresponging listener, or no local info + * provided */ + return hicnlight_prepare_connection_create(sock, request, buffer); + default: + break; + } + break; + + case ACTION_GET: + return hicnlight_prepare_get(sock, request, buffer); + + case ACTION_SUBSCRIBE: + /* Transform subscription queries */ + memset(&object_subscribe, 0, sizeof(hc_object_t)); + object->subscription.topics = topic_from_object_type(object_type); + + hc_request_set(request, ACTION_CREATE, OBJECT_TYPE_SUBSCRIPTION, object); + break; + + default: + break; + } + + /* + * Generic requests should complete after a single call to hicnlight_send, + * with *pdata = NULL. If *pdata is not NULL, that means the request has + * completed and we can close it. + * It is the responsability of each state machine to complete the request + * otherwise. + */ +#if 1 + hc_data_t *data = hc_request_get_data(current_request); + if (data) { + hc_request_set_complete(current_request); + return 0; + } +#endif + + return hicnlight_prepare_generic(sock, request, buffer); +} + +/* + * This function processes incoming data in the ring buffer. Multiple requests + * might be interleaves, including regular requests and notifications. + * Responses might arrive fragment over several read events, but our + * assumption is that fragments arrive consecutively and are not interleaves + * with fragments from other requests... otherwise we would have to way to + * reconstruct a message. + * + * count != 0 when an external process has added data to the ring buffer + * without updating indices + */ +static int hicnlight_process(hc_sock_t *sock, size_t count) { + hc_sock_light_data_t *s = (hc_sock_light_data_t *)sock->data; + int rc; + + if (count > 0) s->woff += count; + + /* + * We loop consuming messages until there is no more data in the ring + * buffer, or that we can find an entire message. Messages are received + * sequentially, and we keep track of incomplete requests in s->cur_request. + */ + while (AVAILABLE(s) > 0) { + if (!s->got_header) { + rc = hicnlight_process_header(sock); + } else { + rc = hicnlight_process_payload(sock); + } + if (rc < 0) break; + } + + if ((rc == -99) || (s->roff == s->woff)) { + /* Flush buffer */ + s->woff = 0; + } else { + /* Clean up read data from buffer */ + memmove(s->buf, s->buf + s->roff, AVAILABLE(s)); + s->woff -= s->roff; + } + s->roff = 0; + + return rc; +} + +hc_sock_ops_t hc_sock_light = (hc_sock_ops_t) { + .create_data = (void *(*)(const char *))hc_sock_light_data_create, + .free_data = (void (*)(void *))hc_sock_light_data_free, + .get_fd = hicnlight_get_fd, .get_recv_buffer = hicnlight_get_recv_buffer, + .connect = hicnlight_connect, .disconnect = hicnlight_disconnect, + .prepare = hicnlight_prepare, .send = hicnlight_send, .recv = hicnlight_recv, + .process = hicnlight_process, +#if 0 + .object_vft = { + [OBJECT_TYPE_LISTENER] = HC_MODULE_OBJECT_OPS(hicnlight, listener), + [OBJECT_TYPE_CONNECTION] = HC_MODULE_OBJECT_OPS(hicnlight, connection), + [OBJECT_TYPE_FACE] = HC_MODULE_OBJECT_OPS_EMPTY, + [OBJECT_TYPE_PUNTING] = HC_MODULE_OBJECT_OPS_EMPTY, + [OBJECT_TYPE_CACHE] = HC_MODULE_OBJECT_OPS_EMPTY, + [OBJECT_TYPE_MAPME] = HC_MODULE_OBJECT_OPS_EMPTY, + [OBJECT_TYPE_WLDR] = HC_MODULE_OBJECT_OPS_EMPTY, + [OBJECT_TYPE_POLICY] = HC_MODULE_OBJECT_OPS_EMPTY, + [OBJECT_TYPE_ROUTE] = HC_MODULE_OBJECT_OPS(hicnlight, route), + [OBJECT_TYPE_STRATEGY] = HC_MODULE_OBJECT_OPS(hicnlight, strategy), + [OBJECT_TYPE_SUBSCRIPTION] = HC_MODULE_OBJECT_OPS(hicnlight, subscription), +} +#endif +}; + +size_t hc_light_object_size(hc_object_type_t object_type) { + hc_module_object_ops_t *vft = &hc_sock_light.object_vft[object_type]; + if (!vft) return 0; + return vft->serialized_size; +} + +ssize_t hc_light_command_serialize(hc_action_t action, + hc_object_type_t object_type, + hc_object_t *object, uint8_t *msg) { + hc_module_object_ops_t *vft = &hc_sock_light.object_vft[object_type]; + if (!vft || !vft->serialize[action]) return 0; + return vft->serialize[action](object, msg); +} + +// Public constructor + +int hc_sock_initialize_module(hc_sock_t *s) { + // + /* + * We do this because initialization in the static struct fails with + * 'initializer element is not constant' + */ +#if 1 + hc_sock_light.object_vft[OBJECT_TYPE_LISTENER] = + hicnlight_listener_module_ops; + hc_sock_light.object_vft[OBJECT_TYPE_CONNECTION] = + hicnlight_connection_module_ops; + hc_sock_light.object_vft[OBJECT_TYPE_FACE] = HC_MODULE_OBJECT_OPS_EMPTY; + hc_sock_light.object_vft[OBJECT_TYPE_PUNTING] = HC_MODULE_OBJECT_OPS_EMPTY; + hc_sock_light.object_vft[OBJECT_TYPE_CACHE] = HC_MODULE_OBJECT_OPS_EMPTY; + hc_sock_light.object_vft[OBJECT_TYPE_MAPME] = HC_MODULE_OBJECT_OPS_EMPTY; + hc_sock_light.object_vft[OBJECT_TYPE_WLDR] = HC_MODULE_OBJECT_OPS_EMPTY; + hc_sock_light.object_vft[OBJECT_TYPE_POLICY] = HC_MODULE_OBJECT_OPS_EMPTY; + hc_sock_light.object_vft[OBJECT_TYPE_ROUTE] = hicnlight_route_module_ops; + hc_sock_light.object_vft[OBJECT_TYPE_STRATEGY] = + hicnlight_strategy_module_ops; + hc_sock_light.object_vft[OBJECT_TYPE_SUBSCRIPTION] = + hicnlight_subscription_module_ops; +#endif + + if (s) s->ops = hc_sock_light; + return 0; +} |