diff options
Diffstat (limited to 'src/sabp_common.c')
-rw-r--r-- | src/sabp_common.c | 157 |
1 files changed, 153 insertions, 4 deletions
diff --git a/src/sabp_common.c b/src/sabp_common.c index cdb0e32..75c89ef 100644 --- a/src/sabp_common.c +++ b/src/sabp_common.c @@ -1,6 +1,6 @@ /* common SABP code */ -/* (C) 2015 by Harald Welte <laforge@gnumonks.org> +/* (C) 2019 by Harald Welte <laforge@gnumonks.org> * All Rights Reserved * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,11 @@ */ #include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <errno.h> + +#include <sys/socket.h> #include <osmocom/core/msgb.h> @@ -73,14 +78,17 @@ const struct value_string sabp_cause_vals[] = { { 0, NULL } }; -static struct msgb *sabp_msgb_alloc(void) +struct msgb *osmo_sabp_msgb_alloc(void *ctx, const char *name) { - return msgb_alloc_headroom(1024+512, 512, "SABP Tx"); + /* make the messages rather large as the cell lists can be long! */ + return msgb_alloc_headroom_c(ctx, 65535, 16, name); } +extern void *tall_msgb_ctx; + static struct msgb *_sabp_gen_msg(SABP_SABP_PDU_t *pdu) { - struct msgb *msg = sabp_msgb_alloc(); + struct msgb *msg = osmo_sabp_msgb_alloc(tall_msgb_ctx, __func__); asn_enc_rval_t rval; if (!msg) @@ -213,3 +221,144 @@ void sabp_set_log_area(int log_area) { _sabp_DSABP = log_area; } + +/*********************************************************************** + * Message Reception + ***********************************************************************/ + +/* SABP was specified as ASN.1 APER encoded messages *directly* inside a TCP + * stream, without any intermediate framing layer. As TCP doesn't preserve + * message boundaries, and SABP messages are variable length, we need to + * actually parse the message up to the point of the APER-internal length + * determinant (which itself is variable-length encoded). */ + +/* Three bytes for TriggeringMessage, procedureCode and Criticality */ +#define SABP_HDR_LEN 3 + + +/*! parse a single APER length determinant. + * \param[in] in input data as received from peer + * \param[in] in_len length of 'in' + * \param[out] len_len Length of the current length determinant in octets + * \returns parsed length or -EAGAIN if more data is needed, or -EIO in case of parse error */ +static int parse_aper_len_det(const uint8_t *in, unsigned int in_len, unsigned int *len_len) +{ + /* Variable-length Length encoding according to X.961 Section 9.1 NOTE 2 */ + switch (in[0] & 0xC0) { + case 0x00: + /* total length is encoded in this octet */ + *len_len = 1; + return in[0]; + case 0x80: + /* total length (up to 16k) encoded in two octets */ + *len_len = 2; + if (in_len < 2) + return -EAGAIN; /* we need one more byte */ + return ((in[0] & 0x3F) << 8) | in[1]; + case 0xC0: + /* total length not known, encoded in chunks; first chunk length now known */ + *len_len = 1; + return (in[0] & 0x3f) * 16384; + /* we must read the chunk and then look at the variable-length encoded length + * of the next chunk */ + default: + return -EIO; + } +} + +/* msg->l1h points to first byte of current length determinant */ +#define MSGB_LEN_NEEDED(msg) (msg)->cb[0] /* total length of msgb needed (as known so far) */ +#define MSGB_APER_STATE(msg) (msg)->cb[1] /* '1' if we're expecting another length determinant */ + +/*! Read one SABP message from socket fd or store part if still not fully received. + * \param[in] ctx talloc context from which to allocate new msgb. + * \param[in] fd The fd for the socket to read from. + * \param[out] rmsg internally allocated msgb containing a fully received SABP message. + * \param[inout] tmp_msg internally allocated msgb caching data for not yet fully received message. + * + * Function is designed just like ipa_msg_recv_buffered() + */ +int osmo_sabp_recv_buffered(void *ctx, int fd, struct msgb **rmsg, struct msgb **tmp_msg) +{ + struct msgb *msg = tmp_msg ? *tmp_msg : NULL; + unsigned int len_len; + bool first_call = false; + int rc; + + if (!msg) { + msg = osmo_sabp_msgb_alloc(ctx, __func__); + if (!msg) + return -ENOMEM; + } + + if (!msgb_l1(msg)) { + /* new msgb; we expect first length determinant after 3 bytes */ + msg->l1h = msgb_data(msg) + SABP_HDR_LEN; + MSGB_LEN_NEEDED(msg) = SABP_HDR_LEN + 1; /* 1 == minimum length of length-field */ + first_call = true; + } + + /* attempt to receive missing/needed amount of bytes */ + rc = recv(fd, msg->tail, MSGB_LEN_NEEDED(msg)-msgb_length(msg), 0); + if (rc == 0) + goto discard_msg; /* dead socket */ + else if (rc < 0) { + if (errno == EAGAIN || errno == EINTR) + rc = 0; + else { + rc = -errno; + goto discard_msg; + } + } + msgb_put(msg, rc); + + /* check if we have received sufficient amount of data */ + if (msgb_length(msg) < MSGB_LEN_NEEDED(msg)) { + if (msg->len == 0) { + rc = -EAGAIN; + goto discard_msg; + } + + if (!tmp_msg) { + rc = -EIO; + goto discard_msg; + } + *tmp_msg = msg; + return -EAGAIN; + } + + if (!first_call && MSGB_APER_STATE(msg) == 0) { + /* we have read all needed bytes by now, and hence can return successfully */ + return msgb_length(msg); + } + + /* below code is only executed on first call/iteration, or if last length-det was chunked */ + OSMO_ASSERT(first_call || MSGB_APER_STATE(msg) == 1); + + OSMO_ASSERT(msg->l1h < msg->tail); + rc = parse_aper_len_det(msgb_l1(msg), msgb_l1len(msg), &len_len); + if (rc == -EIO) { + return -EIO; + } else if (rc == -EAGAIN) { + /* need (typically 1 byte) more data */ + OSMO_ASSERT(len_len > 1); + MSGB_LEN_NEEDED(msg) += len_len-1; + return -EAGAIN; + } else if (rc >= 16384) { + /* read up to next length determinant */ + MSGB_LEN_NEEDED(msg) += len_len-1 + rc + 1 /* new length determinant */; + msg->l1h += (len_len-1) + rc; /* start of next length determinant */ + MSGB_APER_STATE(msg) = 1; + return -EAGAIN; + } else { + /* full length is known now */ + MSGB_LEN_NEEDED(msg) += len_len-1 + rc; + return -EAGAIN; + } + +discard_msg: + if (tmp_msg) + *tmp_msg = NULL; + msgb_free(msg); + return rc; +} |