summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2017-02-13 02:47:00 (UTC)
committerHarald Welte <laforge@gnumonks.org>2017-02-13 03:11:26 (UTC)
commitdc923e49cd3b623dab05f6016a3e935d7c652cb3 (patch)
tree6136f71e53a90b649ec3f3163b9d8262ab01a485
parent104002bc943455b6f59e918347b5e8d9d8ec197f (diff)
downloadlibosmo-sccp-dc923e49cd3b623dab05f6016a3e935d7c652cb3.zip
libosmo-sccp-dc923e49cd3b623dab05f6016a3e935d7c652cb3.tar.gz
libosmo-sccp-dc923e49cd3b623dab05f6016a3e935d7c652cb3.tar.bz2
libosmo-sccp-dc923e49cd3b623dab05f6016a3e935d7c652cb3.tar.xz
WIP: SCCP <-> SUA transcoding routines
Using related code, SCCP can be transcoded to SUA and vice-versa. This way the common code such as the state machines for connection oriented SCCP work with one format (SUA) only, and the conversion from/to SCCP is done when SCCP is used as protocol. Change-Id: I7a77e7d418f2427e9e379867a78a3f1b9ad718cb
-rw-r--r--src/Makefile.am2
-rw-r--r--src/sccp2sua.c937
2 files changed, 938 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 26482a0..babd595 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,6 @@ lib_LTLIBRARIES = libosmo-sigtran.la
# documentation before making any modification
LIBVERSION=0:0:0
-libosmo_sigtran_la_SOURCES = sccp_sap.c sua.c xua_msg.c sccp_helpers.c
+libosmo_sigtran_la_SOURCES = sccp_sap.c sua.c xua_msg.c sccp_helpers.c sccp2sua.c
libosmo_sigtran_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -export-symbols-regex '^osmo_'
libosmo_sigtran_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMONETIF_LIBS) $(LIBSCTP_LIBS)
diff --git a/src/sccp2sua.c b/src/sccp2sua.c
new file mode 100644
index 0000000..5ca74bb
--- /dev/null
+++ b/src/sccp2sua.c
@@ -0,0 +1,937 @@
+/* SCCP <-> SUA transcoding routines */
+
+/* (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * based on my 2011 Erlang implementation osmo_ss7/src/sua_sccp_conv.erl
+ *
+ * References: ITU-T Q.713 and IETF RFC 3868
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sccp/sccp.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/protocol/sua.h>
+#include <osmocom/sigtran/xua_msg.h>
+
+/* libosmocore candidates */
+
+static void msgb_put_u24be(struct msgb *msg, uint32_t val)
+{
+ msgb_put_u8(msg, (val >> 16) & 0xff);
+ msgb_put_u8(msg, (val >> 8) & 0xff);
+ msgb_put_u8(msg, val & 0xff);
+}
+
+static void msgb_put_u16le(struct msgb *msg, uint16_t val)
+{
+ msgb_put_u8(msg, val & 0xff);
+ msgb_put_u8(msg, (val >> 8) & 0xff);
+}
+
+/*! \brief load a 24bit value as big-endian */
+static uint32_t load_24be(void *ptr)
+{
+ uint8_t *data = ptr;
+ return (data[0] << 16) | (data[1] << 8) | data[2];
+}
+
+
+
+/*! \brief Parse ISUP style address of BCD digets
+ * \param[out] out_digits user-allocated buffer for ASCII digits
+ * \param[in] in BCD-encoded digits
+ * \param[in] in_num_bytes Size of \ref in in bytes
+ * \param[in] odd Odd (true) or even (false) number of digits
+ * \returns number of digits generated
+ * */
+int osmo_isup_party_parse(char *out_digits, const uint8_t *in,
+ unsigned int in_num_bytes, bool odd)
+{
+ char *out = out_digits;
+ unsigned int i;
+
+ for (i = 0; i < in_num_bytes; i++) {
+ *out_digits++ = osmo_bcd2char(in[i] & 0x0F);
+ if (i+1 == in_num_bytes && odd)
+ break;
+ *out_digits++ = osmo_bcd2char(in[i] >> 4);
+ }
+ *out_digits = '\0';
+ return (out_digits - out);
+}
+
+int osmo_isup_party_encode(struct msgb *msg, const char *in_digits)
+{
+ unsigned int num_digits = strlen(in_digits);
+
+ /* FIXME */
+}
+
+/*! \brief Parse wire-encoded SCCP address into omso_sccp_addr
+ * \param[out] out user-allocated output data structure
+ * \param[in] addr wire-encoded SCCP address
+ * \param[in] addrlen Size of \ref addr in bytes
+ * \returns 0 in case of success, negative on error
+ * According to Q.713/3.4 and RFC3868/3.10.2 */
+int osmo_sccp_addr_parse(struct osmo_sccp_addr *out,
+ const uint8_t *addr, unsigned int addrlen)
+{
+ struct sccp_called_party_address *sca;
+ uint8_t *cur;
+ bool odd;
+ int rc;
+
+ sca = (struct sccp_called_party_address *) addr;
+ cur = sca->data;
+
+ if (sca->routing_indicator)
+ out->ri = OSMO_SCCP_RI_SSN_PC;
+ else
+ out->ri = OSMO_SCCP_RI_GT;
+
+ if (sca->point_code_indicator) {
+ out->presence |= OSMO_SCCP_ADDR_T_PC;
+ out->pc = ((cur[1] << 8) & 0x3f) | cur[0];
+ cur += 2;
+ }
+
+ if (sca->ssn_indicator) {
+ out->presence |= OSMO_SCCP_ADDR_T_SSN;
+ out->ssn = *cur;
+ cur += 1;
+ }
+
+ switch (sca->global_title_indicator) {
+ case SCCP_TITLE_IND_NONE:
+ out->gt.gti = OSMO_SCCP_GTI_NO_GT;
+ return 0;
+ case SCCP_TITLE_IND_NATURE_ONLY:
+ out->presence |= OSMO_SCCP_ADDR_T_GT;
+ out->gt.gti = OSMO_SCCP_GTI_NAI_ONLY;
+ out->gt.nai = *cur & 0x7f;
+ if (*cur++ & 0x80)
+ odd = true;
+ else
+ odd = false;
+ break;
+ case SCCP_TITLE_IND_TRANSLATION_ONLY:
+ out->presence |= OSMO_SCCP_ADDR_T_GT;
+ out->gt.gti = OSMO_SCCP_GTI_TT_ONLY;
+ out->gt.tt = *cur++;
+ /* abort, for national use only */
+ return -EINVAL;
+ case SCCP_TITLE_IND_TRANS_NUM_ENC:
+ out->presence |= OSMO_SCCP_ADDR_T_GT;
+ out->gt.gti = OSMO_SCCP_GTI_TT_NPL_ENC;
+ out->gt.tt = *cur++;
+ out->gt.npi = *cur >> 4;
+ switch (*cur++ & 0xF) {
+ case 1:
+ odd = true;
+ break;
+ case 2:
+ odd = false;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case SCCP_TITLE_IND_TRANS_NUM_ENC_NATURE:
+ out->presence |= OSMO_SCCP_ADDR_T_GT;
+ out->gt.gti = OSMO_SCCP_GTI_TT_NPL_ENC_NAI;
+ out->gt.tt = *cur++;
+ out->gt.npi = *cur >> 4;
+ switch (*cur++ & 0xF) {
+ case 1:
+ odd = true;
+ break;
+ case 2:
+ odd = false;
+ break;
+ default:
+ return -1;
+ }
+ out->gt.nai = *cur++ & 0x7f;
+ break;
+ default:
+ return -EINVAL;
+ }
+ rc = osmo_isup_party_parse(&out->gt.digits, cur, (addr+addrlen-cur), odd);
+ if (rc < 0)
+ return rc;
+ out->gt.nr_digits = rc;
+
+ return 0;
+}
+
+/*! \brief encode a SCCP address from parsed format to wire format
+ * \param[out] msg message buffer to which address is to be appended
+ * \param[in] in data structure describing SCCP address
+ * \returns number of bytes written to \ref msg */
+int osmo_sccp_addr_encode(struct msgb *msg, const struct osmo_sccp_addr *in)
+{
+ struct sccp_called_party_address *sca;
+ bool odd;
+
+ sca = (struct sccp_called_party_address *) msgb_put(msg, sizeof(*sca));
+ switch (in->ri) {
+ case OSMO_SCCP_RI_SSN_PC:
+ sca->routing_indicator = 1;
+ break;
+ case OSMO_SCCP_RI_GT:
+ sca->routing_indicator = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (in->presence & OSMO_SCCP_ADDR_T_PC) {
+ sca->point_code_indicator = 1;
+ msgb_put_u24be(msg, in->pc);
+ }
+
+ if (in->presence & OSMO_SCCP_ADDR_T_SSN) {
+ sca->ssn_indicator = 1;
+ msgb_put_u16le(msg, in->pc & 0x3ff);
+ }
+
+ if (!(in->presence & OSMO_SCCP_ADDR_T_GT)) {
+ sca->global_title_indicator = SCCP_TITLE_IND_NONE;
+ return 0;
+ }
+
+ odd = in->gt.nr_digits & 1;
+ switch (in->gt.gti) {
+ case OSMO_SCCP_GTI_NO_GT:
+ sca->global_title_indicator = SCCP_TITLE_IND_NONE;
+ return 0;
+ case OSMO_SCCP_GTI_NAI_ONLY:
+ sca->global_title_indicator = SCCP_TITLE_IND_NATURE_ONLY;
+ msgb_put_u8(msg, (odd << 7) | (in->gt.nai & 0x7f));
+ break;
+ case OSMO_SCCP_GTI_TT_ONLY:
+ sca->global_title_indicator = SCCP_TITLE_IND_TRANSLATION_ONLY;
+ msgb_put_u8(msg, in->gt.tt);
+ /* abort, for national use only */
+ return -EINVAL;
+ case OSMO_SCCP_GTI_TT_NPL_ENC:
+ sca->global_title_indicator = SCCP_TITLE_IND_TRANS_NUM_ENC;
+ msgb_put_u8(msg, in->gt.tt);
+ msgb_put_u8(msg, (in->gt.npi << 4) | odd ? 1 : 2);
+ break;
+ case OSMO_SCCP_GTI_TT_NPL_ENC_NAI:
+ sca->global_title_indicator = SCCP_TITLE_IND_TRANS_NUM_ENC_NATURE;
+ msgb_put_u8(msg, in->gt.tt);
+ msgb_put_u8(msg, (in->gt.npi << 4) | odd ? 1 : 2);
+ msgb_put_u8(msg, in->gt.nai & 0x7f);
+ break;
+ }
+ osmo_isu_party_encode(msg, in->gt.digits);
+
+ /* return number of bytes written */
+ return msg->tail - (uint8_t *)sca;
+}
+
+/*! \brief convert SCCP address to SUA address
+ * \param xua user-provided xUA message to which address shall be added
+ * \param[in] iei SUA Information Element Identifier for address
+ * \param[in] addr SCCP wire format binary address
+ * \param[in] addrlen Size of \ref addr in bytes
+ * \returns 0 in case of success; negative on error */
+static int sccp_addr_to_sua(struct xua_msg *xua, uint16_t iei, const uint8_t *addr,
+ unsigned int addrlen)
+{
+ struct osmo_sccp_addr osa;
+ int rc;
+
+ /* First decode the address from SCCP wire format to
+ * osmo_sccp_addr */
+ memset(&osa, 0, sizeof(osa));
+ rc = osmo_sccp_addr_parse(&osa, addr, addrlen);
+ if (rc < 0)
+ return rc;
+
+ /* Then re-encode it as SUA address */
+ return xua_msg_add_sccp_addr(xua, iei, &osa);
+}
+
+/*! \brief convenience wrapper around sccp_addr_to_sua() for variable mandatory addresses */
+static int sccp_addr_to_sua_ptr(struct xua_msg *xua, uint16_t iei, struct msgb *msg, uint8_t *ptr_addr)
+{
+ uint8_t *addr = ptr_addr + *ptr_addr + 1;
+ unsigned int addrlen = *(ptr_addr + *ptr_addr);
+
+ return sccp_addr_to_sua(xua, iei, addr, addrlen);
+}
+
+/*! \brief convert SUA address to SCCP address
+ * \param msg user-provided message buffer to which address shall be * appended
+ * \param[in] part SUA wire format binary address
+ * \returns 0 in case of success; negative on error */
+static int sua_addr_to_sccp(struct msgb *msg, struct xua_msg_part *part)
+{
+ struct osmo_sccp_addr osa;
+ int rc;
+
+ /* First decode the address from SUA wire format to
+ * osmo_sccp_addr */
+ memset(&osa, 0, sizeof(osa));
+ rc = smo_sua_addr_parse(&osa, part);
+ if (rc < 0)
+ return rc;
+
+ /* Then re-encode it as SCCP address */
+ return osmo_sccp_addr_encode(msg, &osa);
+}
+
+/*! \brief validate that SCCP part with pointer + length doesn't exceed msg tail
+ * \param[in] msg Message containing SCCP address
+ * \param[in] ptr_addr pointer to byte with relative SCCP pointer
+ * \returns true if OK; false if message inconsistent */
+static bool sccp_ptr_part_consistent(struct msgb *msg, uint8_t *ptr_addr)
+{
+ uint8_t *ptr;
+
+ /* check the address of the relative pointer is within msg */
+ if (ptr_addr < msg->data || ptr_addr > msg->tail) {
+ fprintf(stderr, "ptr_addr outside msg boundary\n");
+ return false;
+ }
+
+ ptr = ptr_addr + *ptr_addr;
+ if (ptr > msg->tail) {
+ fprintf(stderr, "ptr points outside msg boundary\n");
+ return false;
+ }
+
+ /* at destination of relative pointer is the length */
+ if (ptr + 1 + *ptr > msg->tail) {
+ fprintf(stderr, "ptr + len points outside msg boundary\n");
+ return false;
+ }
+ return true;
+}
+
+/*! \brief Convert a given SCCP option to SUA and add it to given xua_msg
+ * \param xua caller-provided xUA message to which option is to be added
+ * \param[in] sccp_opt_type SCCP option type (PNC)
+ * \param[in] opt_len size of \ref opt in bytes
+ * \param[in] opt pointer to wire-format encoded SCCP option data
+ * \returns 0 in case of success; negative on error */
+static int xua_msg_add_sccp_opt(struct xua_msg *xua, uint8_t sccp_opt_type,
+ uint16_t opt_len, const uint8_t *opt)
+{
+ switch (sccp_opt_type) {
+ case SCCP_PNC_DESTINATION_LOCAL_REFERENCE:
+ if (opt_len != 3)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(opt));
+ break;
+ case SCCP_PNC_SOURCE_LOCAL_REFERENCE:
+ if (opt_len != 3)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(opt));
+ break;
+ case SCCP_PNC_CALLED_PARTY_ADDRESS:
+ if (opt_len < 3)
+ return -EINVAL;
+ sccp_addr_to_sua(xua, SUA_IEI_DEST_ADDR, opt, opt_len);
+ break;
+ case SCCP_PNC_CALLING_PARTY_ADDRESS:
+ if (opt_len < 3)
+ return -EINVAL;
+ sccp_addr_to_sua(xua, SUA_IEI_SRC_ADDR, opt, opt_len);
+ break;
+ case SCCP_PNC_PROTOCOL_CLASS:
+ if (opt_len < 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, *opt & 0xF);
+ /* FIXME */
+ break;
+ case SCCP_PNC_CREDIT:
+ if (opt_len != 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_IMPORTANCE, *opt & 0x7);
+ break;
+ case SCCP_PNC_RELEASE_CAUSE:
+ if (opt_len != 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | *opt);
+ break;
+ case SCCP_PNC_RETURN_CAUSE:
+ if (opt_len != 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RETURN | *opt);
+ break;
+ case SCCP_PNC_RESET_CAUSE:
+ if (opt_len != 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RESET | *opt);
+ break;
+ case SCCP_PNC_ERROR_CAUSE:
+ if (opt_len != 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_ERROR | *opt);
+ break;
+ case SCCP_PNC_REFUSAL_CAUSE:
+ if (opt_len != 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | *opt);
+ break;
+ case SCCP_PNC_DATA:
+ xua_msg_add_data(xua, SUA_IEI_DATA, opt_len, opt);
+ break;
+ case SCCP_PNC_HOP_COUNTER:
+ if (opt_len != 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_S7_HOP_CTR, *opt);
+ break;
+ case SCCP_PNC_IMPORTANCE:
+ if (opt_len != 1)
+ return -EINVAL;
+ xua_msg_add_u32(xua, SUA_IEI_IMPORTANCE, *opt & 0x7);
+ break;
+ case SCCP_PNC_LONG_DATA:
+ xua_msg_add_data(xua, SUA_IEI_DATA, opt_len, opt);
+ break;
+ case SCCP_PNC_SEGMENTATION:
+ case SCCP_PNC_SEGMENTING:
+ case SCCP_PNC_RECEIVE_SEQ_NUMBER:
+ /* only in class 3 */
+ case SCCP_PNC_SEQUENCING:
+ /* only in class 3 */
+ default:
+ /* FIXME: print error */
+ return -1;
+ }
+}
+
+static void msgb_put_sccp_opt_hdr(struct msgb *msg, uint8_t pnc, uint8_t len)
+{
+ msgb_put_u8(msg, pnc);
+ msgb_put_u8(msg, len);
+}
+
+static void msgb_put_sccp_opt(struct msgb *msg, uint8_t pnc, uint8_t len, const uint8_t *data)
+{
+ uint8_t *cur;
+
+ msgb_put_sccp_opt_hdr(msg, pnc, len);
+ cur = msgb_put(msg, len);
+ memcpy(cur, data, len);
+}
+
+/*! \brief Convert a given SUA option/IE to SCCP and add it to given * msgb
+ * \param msg caller-provided message buffer to which option is to be appended
+ * \param[in] opt xUA option/IE (messge part) to be converted+added
+ * \returns 0 in case of success; negative on error */
+static int sccp_msg_add_sua_opt(struct msgb *msg, struct xua_msg_part *opt)
+{
+ uint32_t tmp32;
+ uint8_t pnc, *lenptr;
+ int rc;
+
+ switch (opt->tag) {
+ case SUA_IEI_DEST_REF:
+ msgb_put_sccp_opt_hdr(msg, SCCP_PNC_DESTINATION_LOCAL_REFERENCE, 3);
+ msgb_put_u24be(msg, xua_msg_part_get_u32(opt));
+ break;
+ case SUA_IEI_SRC_REF:
+ msgb_put_sccp_opt_hdr(msg, SCCP_PNC_SOURCE_LOCAL_REFERENCE, 3);
+ msgb_put_u24be(msg, xua_msg_part_get_u32(opt));
+ break;
+ case SUA_IEI_DEST_ADDR:
+ msgb_put_u8(msg, SCCP_PNC_CALLED_PARTY_ADDRESS);
+ lenptr = msgb_put(msg, 1);
+ rc = sua_addr_to_sccp(msg, opt);
+ if (rc < 0)
+ return rc;
+ *lenptr = rc;
+ break;
+ case SUA_IEI_SRC_ADDR:
+ msgb_put_u8(msg, SCCP_PNC_CALLING_PARTY_ADDRESS);
+ lenptr = msgb_put(msg, 1);
+ rc = sua_addr_to_sccp(msg, opt);
+ if (rc < 0)
+ return rc;
+ *lenptr = rc;
+ break;
+ case SUA_IEI_PROTO_CLASS:
+ msgb_put_sccp_opt_hdr(msg, SCCP_PNC_PROTOCOL_CLASS, 1);
+ msgb_put_u8(msg, xua_msg_part_get_u32(opt) & 0xF);
+ break;
+ case SUA_IEI_CREDIT:
+ msgb_put_sccp_opt_hdr(msg, SCCP_PNC_CREDIT, 1);
+ msgb_put_u8(msg, xua_msg_part_get_u32(opt) & 0x7);
+ break;
+ case SUA_IEI_CAUSE:
+ tmp32 = xua_msg_part_get_u32(opt);
+ switch (tmp32 & SUA_CAUSE_T_MASK) {
+ case SUA_CAUSE_T_RETURN:
+ pnc = SCCP_PNC_RETURN_CAUSE;
+ break;
+ case SUA_CAUSE_T_REFUSAL:
+ pnc = SCCP_PNC_REFUSAL_CAUSE;
+ break;
+ case SUA_CAUSE_T_RELEASE:
+ pnc = SCCP_PNC_RELEASE_CAUSE;
+ break;
+ case SUA_CAUSE_T_RESET:
+ pnc = SCCP_PNC_RESET_CAUSE;
+ break;
+ case SUA_CAUSE_T_ERROR:
+ pnc = SCCP_PNC_ERROR_CAUSE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ msgb_put_sccp_opt_hdr(msg, pnc, 1);
+ msgb_put_u8(msg, tmp32 & 0xff);
+ break;
+ case SUA_IEI_DATA:
+ msgb_put_sccp_opt(msg, SCCP_PNC_DATA, opt->len, opt->dat);
+ break;
+ case SUA_IEI_S7_HOP_CTR:
+ msgb_put_sccp_opt_hdr(msg, SCCP_PNC_HOP_COUNTER, 1);
+ msgb_put_u8(msg, xua_msg_part_get_u32(opt));
+ break;
+ case SUA_IEI_IMPORTANCE:
+ msgb_put_sccp_opt_hdr(msg, SCCP_PNC_IMPORTANCE, 1);
+ msgb_put_u8(msg, xua_msg_part_get_u32(opt) & 0x7);
+ break;
+ default:
+ /* FIXME: print error */
+ return -1;
+ }
+ return 0;
+}
+
+/*! \brief convert SCCP optional part to list of SUA options
+ * \param[in] msg Message buffer holding SCCP message
+ * \param[in] ptr_opt address of relative pointer to optional part
+ * \param xua caller-provided xUA message to which options are added
+ * \returns \ref xua in case of success, NULL on error (xua not freed!) */
+static struct xua_msg *sccp_to_xua_opt(struct msgb *msg, uint8_t *ptr_opt, struct xua_msg *xua)
+{
+ uint8_t *opt_start, *oneopt;
+
+ /* some bounds checking */
+ if (ptr_opt < msg->data || ptr_opt > msg->tail)
+ return NULL;
+ opt_start = ptr_opt + *ptr_opt;
+ if (opt_start > msg->tail)
+ return NULL;
+
+ oneopt = opt_start;
+
+ while (oneopt < msg->tail) {
+ uint8_t opt_type = oneopt[0];
+
+ if (opt_type == SCCP_PNC_END_OF_OPTIONAL)
+ return xua;
+
+ if (opt_type == SCCP_PNC_LONG_DATA) {
+ uint16_t opt_len16;
+ /* two byte length field */
+ if (oneopt + 2 > msg->tail)
+ return NULL;
+ opt_len16 = oneopt[1] << 8 | oneopt[2];
+ if (oneopt + 3 + opt_len16 > msg->tail)
+ return NULL;
+ xua_msg_add_sccp_opt(xua, opt_type, opt_len16, oneopt+3);
+ oneopt += 3 + opt_len16;
+ } else {
+ uint8_t opt_len;
+ /* one byte length field */
+ if (oneopt + 1 > msg->tail)
+ return NULL;
+
+ opt_len = oneopt[1];
+ if (oneopt + 2 + opt_len > msg->tail)
+ return NULL;
+ xua_msg_add_sccp_opt(xua, opt_type, opt_len, oneopt+2);
+ oneopt += 2 + opt_len;
+ }
+ }
+ return NULL;
+}
+
+static int xua_ies_to_sccp_opts(struct msgb *msg, struct xua_msg *xua)
+{
+ struct xua_msg_part *part;
+
+ llist_for_each_entry(part, &xua->headers, entry) {
+ sccp_msg_add_sua_opt(msg, part);
+ }
+ msgb_put_u8(msg, SCCP_PNC_END_OF_OPTIONAL);
+
+ return 0;
+}
+
+static struct xua_msg *sccp_to_xua_cr(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_request *req = (struct sccp_connection_request *)msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, req->proto_class & 0xF);
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&req->source_local_reference));
+ /* Variable Part */
+ if (!sccp_ptr_part_consistent(msg, &req->variable_called))
+ return NULL;
+ sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, msg, &req->variable_called);
+ /* Optional Part */
+ return sccp_to_xua_opt(msg, &req->optional_start, xua);
+}
+
+static int sua_to_sccp_cr(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_request *req;
+ req = (struct sccp_connection_request *) msgb_put(msg, sizeof(*req));
+
+ /* Fixed Part */
+ req->proto_class =
+ req->source_local_ref
+ /* Variable Part */
+ req->variable_called;
+
+ /* Optional Part */
+ return xua_ies_to_sccp_opts(msg, xua);
+}
+
+static struct xua_msg *sccp_to_xua_cc(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_confirm *cnf = (struct sccp_connection_confirm *)msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, cnf->proto_class & 0xF);
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&cnf->destination_local_reference));
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&cnf->source_local_reference));
+ /* Optional Part */
+ return sccp_to_xua_opt(msg, &cnf->optional_start, xua);
+}
+
+static int sua_to_sccp_cc(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_confirm *cnf;
+ cnf = (struct sccp_connection_confirm *) msgb_put(msg, sizeof(*cnf));
+
+ /* Fixed Part */
+ cnf->proto_class;
+ cnf->destination_local_reference;
+ cnf->source_local_reference;
+ /* Optional Part */
+ return xua_ies_to_sccp_opts(msg, xua);
+}
+
+static struct xua_msg *sccp_to_xua_cref(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_refused *ref = (struct sccp_connection_refused *)msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&ref->destination_local_reference));
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | ref->cause);
+ /* Optional Part */
+ return sccp_to_xua_opt(msg, &ref->optional_start, xua);
+}
+
+static int sua_to_sccp_cref(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_refused *ref
+ ref = (struct sccp_connection_refused *) msgb_put(msg, sizeof(*ref));
+
+ /* Fixed Part */
+ ref->destination_local_reference;
+ ref->cause;
+ /* Optional Part */
+ return xua_ies_to_sccp_opts(msg, xua);
+}
+
+static struct xua_msg *sccp_to_xua_rlsd(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_released *rlsd = (struct sccp_connection_released *)msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&rlsd->destination_local_reference));
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&rlsd->source_local_reference));
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | rlsd->release_cause);
+ /* Optional Part */
+ return sccp_to_xua_opt(msg, &rlsd->optional_start, xua);
+}
+
+static int sua_to_sccp_rlsd(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_released *rlsd;
+ rlsd =(struct sccp_connection_released *) msgb_put(msg, sizeof(*rlsd));
+
+ /* Fixed Part */
+ rlsd->destination_local_reference
+ rlsd->sourcea_local_reference
+ rlsd->release_cause
+
+ /* Optional Part */
+ return xua_ies_to_sccp_opts(msg, xua);
+}
+
+static struct xua_msg *sccp_to_xua_rlc(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_release_complete *rlc;
+ rlc = (struct sccp_connection_release_complete *) msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&rlc->destination_local_reference));
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&rlc->source_local_reference));
+ return xua;
+}
+
+static int sua_to_sccp_rlc(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_connection_release_complete *rlc;
+ rlc = (struct sccp_connection_release_complete *) msgb_put(msg, sizeof(*rlc));
+
+ /* Fixed Part */
+}
+
+static struct xua_msg *sccp_to_xua_dt1(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_data_form1 *dt1 = (struct sccp_data_form1 *) msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&dt1->destination_local_reference));
+ xua_msg_add_u32(xua, SUA_IEI_SEGMENTATION, dt1->segmenting);
+ /* Variable Part */
+ if (!sccp_ptr_part_consistent(msg, &dt1->variable_start))
+ return NULL;
+ return xua;
+}
+
+static int sua_to_sccp_dt1(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_data_form1 *dt1;
+ dt1 = (struct sccp_data_form1 *) msgb_put(msg, sizeof(*dt1));
+
+ /* Fixed Part */
+ /* Variable Part */
+}
+
+static struct xua_msg *sccp_to_xua_udt(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_data_unitdata *udt = (struct sccp_data_unitdata *)msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, udt->proto_class & 0xF);
+ /* Variable Part */
+ if (!sccp_ptr_part_consistent(msg, &udt->variable_called))
+ return NULL;
+ if (!sccp_ptr_part_consistent(msg, &udt->variable_calling))
+ return NULL;
+ if (!sccp_ptr_part_consistent(msg, &udt->variable_data))
+ return NULL;
+ sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, msg, &udt->variable_called);
+ sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, msg, &udt->variable_calling);
+ return xua;
+
+}
+
+static int sua_to_sccp_udt(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_data_unitdata *udt;
+ udt = (struct sccp_data_unitdata *) msgb_put(msg, sizeof(*udt));
+
+ /* Fixed Part */
+ /* Variable Part */
+}
+
+static struct xua_msg *sccp_to_xua_udts(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_data_unitdata_service *udts;
+ udts =(struct sccp_data_unitdata_service *)msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RETURN | udts->return_cause);
+ /* Variable Part */
+ if (!sccp_ptr_part_consistent(msg, &udt->variable_called))
+ return NULL;
+ if (!sccp_ptr_part_consistent(msg, &udt->variable_calling))
+ return NULL;
+ if (!sccp_ptr_part_consistent(msg, &udt->variable_data))
+ return NULL;
+ sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, msg, &udt->variable_called);
+ sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, msg, &udt->variable_calling);
+ return xua;
+
+}
+
+static int sua_to_sccp_udts(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_data_unitdata_service *udts;
+ udts = (struct sccp_data_unitdata_service *) msgb_put(msg, sizeof(*udts));
+
+ /* Fixed Part */
+ /* Variable Part */
+}
+
+static struct xua_msg *sccp_to_xua_it(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_data_it *it = (struct sccp_data_it *)msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, it->proto_class & 0xF);
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&it->source_local_reference));
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&it->destination_local_reference));
+ if ((it->proto_class & 0xF) == 3) {
+ //xua_msg_add_u32(xua, SUA_IEI_SEQUENCING, it->sequencing);
+ xua_msg_add_u32(xua, SUA_IEI_CREDIT, it->credit);
+ }
+ return xua;
+}
+
+static int sua_to_sccp_it(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_data_it *it;
+ it = (struct sccp_data_it *) msgb_put(msg, sizeof(*it));
+
+ /* Fixed Part */
+}
+
+static struct xua_msg *sccp_to_xua_err(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_proto_err *err = (struct sccp_proto_err *)msg->l2h;
+
+ /* Fixed Part */
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&err->destination_local_reference));
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_ERROR | err->error_cause);
+ return xua;
+}
+
+static int sua_to_sccp_err(struct msgb *msg, struct xua_msg *xua)
+{
+ struct sccp_proto_err *err;
+ err = (struct sccp_proto_err *) msgb_put(msg, sizeof(*err));
+
+ /* Fixed Part */
+}
+
+/*! \brief convert SCCP message to a SUA message
+ * \param[in] msg message buffer holding SCCP message at l2h
+ * \returns callee-allocated xUA message on success; NULL on error */
+struct xua_msg *osmo_sccp_to_xua(struct msgb *msg)
+{
+ struct xua_msg *xua;
+
+ if (msgb_l2len(msg) < 1)
+ return NULL;
+
+ xua = xua_msg_alloc();
+ if (!xua)
+ return NULL;
+
+ switch (msg->l2h[0]) {
+ case SCCP_MSG_TYPE_CR:
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CORE);
+ return sccp_to_xua_cr(msg, xua);
+ case SCCP_MSG_TYPE_CC:
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COAK);
+ return sccp_to_xua_cc(msg, xua);
+ case SCCP_MSG_TYPE_CREF:
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF);
+ return sccp_to_xua_cref(msg, xua);
+ case SCCP_MSG_TYPE_RLSD:
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE);
+ return sccp_to_xua_rlsd(msg, xua);
+ case SCCP_MSG_TYPE_RLC:
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO);
+ return sccp_to_xua_rlc(msg, xua);
+ case SCCP_MSG_TYPE_DT1:
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT);
+ return sccp_to_xua_dt1(msg, xua);
+ case SCCP_MSG_TYPE_UDT:
+ xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT);
+ return sccp_to_xua_udt(msg, xua);
+ case SCCP_MSG_TYPE_UDTS:
+ xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDR);
+ return sccp_to_xua_udts(msg, xua);
+ case SCCP_MSG_TYPE_IT:
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COIT);
+ return sccp_to_xua_it(msg, xua);
+ case SCCP_MSG_TYPE_ERR:
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COERR);
+ return sccp_to_xua_err(msg, xua);
+ /* Unsupported Message Types */
+ case SCCP_MSG_TYPE_DT2:
+ case SCCP_MSG_TYPE_AK:
+ case SCCP_MSG_TYPE_ED:
+ case SCCP_MSG_TYPE_EA:
+ case SCCP_MSG_TYPE_RSR:
+ case SCCP_MSG_TYPE_RSC:
+ case SCCP_MSG_TYPE_XUDT:
+ case SCCP_MSG_TYPE_XUDTS:
+ case SCCP_MSG_TYPE_LUDT:
+ case SCCP_MSG_TYPE_LUDTS:
+ xua_msg_free(xua);
+ return NULL;
+ }
+
+ return NULL;
+}
+
+struct msgb *osmo_sua_to_sccp(struct xua_msg *xua)
+{
+ struct msgb *msg = msgb_alloc(2048, "SCCP from SUA");
+
+ switch (xua->hdr.msg_class) {
+ case SUA_MSGC_CL:
+ switch (xua->hdr.msg_type) {
+ case SUA_CL_CLDT:
+ return sua_to_sccp_udt(msg, xua);
+ default:
+ goto out_err;
+ }
+ break;
+ case SUA_MSGC_CO:
+ switch (xua->hdr.msg_type) {
+ case SUA_CO_CORE:
+ return sua_to_sccp_cr(msg, xua);
+ case SUA_CO_COAK:
+ return sua_to_sccp_cc(msg, xua);
+ case SUA_CO_COREF:
+ return sua_to_sccp_cref(msg, xua);
+ case SUA_CO_RELRE:
+ return sua_to_sccp_rlsd(msg, xua);
+ case SUA_CO_RELCO:
+ return sua_to_sccp_rlc(msg, xua);
+ case SUA_CO_CODT:
+ return sua_to_sccp_dt1(msg, xua);
+ case SUA_CO_COIT:
+ return sua_to_sccp_it(msg, xua);
+ case SUA_CO_COERR:
+ return sua_to_sccp_err(msg, xua);
+ default:
+ goto out_err;
+ }
+ break;
+ default:
+ goto out_err;
+ }
+
+out_err:
+ msgb_free(msg);
+ return NULL;
+}