/* SCCP <-> SUA transcoding routines */ /* (C) 2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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 General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "xua_internal.h" #include "sccp_internal.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(const void *ptr) { const 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); } /*! \brief Encode an ISUP style address of BCD digits * \param[out] msg Message to which the encoded address is appended * \param[in] in_digits NUL-terminated ASCII string of digits * \returns number of octets used for encoding \ref in_digits */ int osmo_isup_party_encode(struct msgb *msg, const char *in_digits) { unsigned int num_digits = strlen(in_digits); unsigned int i, num_octets = num_digits/2; const char *cur_digit = in_digits; uint8_t *cur; if (num_digits & 1) num_octets++; cur = msgb_put(msg, num_octets); for (i = 0; i < num_octets; i++) { cur[i] = osmo_char2bcd(*cur_digit++); if (cur_digit - in_digits < num_digits) cur[i] |= osmo_char2bcd(*cur_digit++) << 4; } return num_octets; } /*! \brief Parse wire-encoded SCCP address into osmo_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; uint8_t encoding; bool odd; int rc; memset(out, 0, sizeof(*out)); 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 = (uint16_t) (cur[1] & 0x3f) << 8; out->pc |= 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 */ LOGP(DLSUA, LOGL_ERROR, "Unsupported national GTI %u\n", sca->global_title_indicator); 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; encoding = *cur++ & 0xF; switch (encoding) { case 1: odd = true; break; case 2: odd = false; break; default: LOGP(DLSUA, LOGL_ERROR, "Unknown GT encoding 0x%x\n", encoding); 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; encoding = *cur++ & 0xF; switch (encoding) { case 1: odd = true; break; case 2: odd = false; break; default: LOGP(DLSUA, LOGL_ERROR, "Unknown GT encoding 0x%x\n", encoding); return -EINVAL; } out->gt.nai = *cur++ & 0x7f; break; default: LOGP(DLSUA, LOGL_ERROR, "Unknown GTI %u in SCCP message\n", sca->global_title_indicator); return -EINVAL; } rc = osmo_isup_party_parse(out->gt.digits, cur, (addr+addrlen-cur), odd); if (rc < 0) return 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: LOGP(DLSUA, LOGL_ERROR, "Unknown CCP Routing Indicator %u" " requested\n", in->ri); return -EINVAL; } if (in->presence & OSMO_SCCP_ADDR_T_PC) { sca->point_code_indicator = 1; /* ITU-T Q.713 states that signalling point codes are 14bit */ if (in->pc > 0x3fff) { LOGP(DLSUA, LOGL_ERROR, "Invalid Point Code %u requested\n", in->pc); return -EINVAL; } msgb_put_u16le(msg, in->pc & 0x3fff); } if (in->presence & OSMO_SCCP_ADDR_T_SSN) { sca->ssn_indicator = 1; if (in->ssn > 0xff) { LOGP(DLSUA, LOGL_ERROR, "Invalid SSN %u requested\n", in->ssn); return -EINVAL; } msgb_put_u8(msg, in->ssn); } if (!(in->presence & OSMO_SCCP_ADDR_T_GT)) { sca->global_title_indicator = SCCP_TITLE_IND_NONE; goto out; } if (in->gt.npi && (in->gt.npi > 0xF)) { LOGP(DLSUA, LOGL_ERROR, "Unsupported Numbering Plan %u", in->gt.npi); return -EINVAL; } if (in->gt.nai && (in->gt.nai > 0x7F)) { LOGP(DLSUA, LOGL_ERROR, "Unsupported Nature of Address %u", in->gt.nai); return -EINVAL; } odd = strlen(in->gt.digits) & 1; switch (in->gt.gti) { case OSMO_SCCP_GTI_NO_GT: sca->global_title_indicator = SCCP_TITLE_IND_NONE; goto out; 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 */ LOGP(DLSUA, LOGL_ERROR, "Unsupported Translation Type %u" "requested\n", in->gt.gti); 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; default: LOGP(DLSUA, LOGL_ERROR, "Unsupported GTI %u requested\n", in->gt.gti); return -EINVAL; } osmo_isup_party_encode(msg, in->gt.digits); out: /* 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 */ rc = osmo_sccp_addr_parse(&osa, addr, addrlen); if (rc < 0) return rc; LOGP(DLSUA, LOGL_DEBUG, "IEI %u: Parsed Addr: %s\n", iei, osmo_sccp_addr_dump(&osa)); /* 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, const uint8_t *ptr_addr, bool ptr_addr_is_long) { const uint8_t *addr; unsigned int addrlen; if (ptr_addr_is_long) /* +1: Distance from MSB of pointer */ addr = ptr_addr + 1 + osmo_load16le(ptr_addr); else addr = ptr_addr + *ptr_addr; addrlen = *addr; 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, const struct xua_msg_part *part) { struct osmo_sccp_addr osa; int rc; /* First decode the address from SUA wire format to * osmo_sccp_addr */ rc = sua_addr_parse_part(&osa, part); if (rc < 0) return rc; /* Then re-encode it as SCCP address */ return osmo_sccp_addr_encode(msg, &osa); } /*! \brief Add a "SCCP Variable Mandatory Part" (Address format) to the given msgb * \param msg Message buffer to which part shall be added * \param[out] var_ptr pointer to relative pointer in SCCP header * \param[in] var_ptr_is_long Whether the var_ptr field is 2 bytes long (network order) * \param[in] xua xUA message from which to use address * \param[in] iei xUA information element identifier of address */ static int sccp_add_var_addr(struct msgb *msg, uint8_t *var_ptr, bool var_ptr_is_long, const struct xua_msg *xua, uint16_t iei) { struct xua_msg_part *part = xua_msg_find_tag(xua, iei); uint8_t *lenbyte; int rc; if (!part) { LOGP(DLSUA, LOGL_ERROR, "Cannot find IEI %u in SUA message\n", iei); return -ENODEV; } /* first allocate one byte for the length */ lenbyte = msgb_put(msg, 1); /* update the relative pointer to the length byte */ if (var_ptr_is_long) /* -1: Distance from MSB of pointer */ osmo_store16le(lenbyte - var_ptr - 1, var_ptr); else *var_ptr = lenbyte - var_ptr; /* then append the encoded SCCP address */ rc = sua_addr_to_sccp(msg, part); if (rc < 0) return rc; /* store the encoded length of the address */ *lenbyte = rc; return rc; } /*! \brief Add a "SCCP Variable Mandatory Part" to the given msgb * \param msg Message buffer to which part shall be added * \param[out] var_ptr pointer to relative pointer in SCCP header * \param[in] xua xUA message from which to use source data * \param[in] iei xUA information element identifier of source data */ static int sccp_add_variable_part(struct msgb *msg, uint8_t *var_ptr, const struct xua_msg *xua, uint16_t iei) { struct xua_msg_part *part = xua_msg_find_tag(xua, iei); uint8_t *lenbyte; uint8_t *cur; if (!part) { LOGP(DLSUA, LOGL_ERROR, "Cannot find IEI %u in SUA message\n", iei); return -ENODEV; } /* first allocate one byte for the length */ lenbyte = msgb_put(msg, 1); /* update the relative pointer to the length byte */ *var_ptr = lenbyte - var_ptr; /* then append the encoded SCCP address */ cur = msgb_put(msg, part->len); memcpy(cur, part->dat, part->len); /* store the encoded length of the address */ *lenbyte = part->len; return part->len; } /*! \brief Add a "SCCP Long Variable Mandatory Part" to the given msgb * \param msg Message buffer to which part shall be added * \param[out] var_ptr pointer to relative pointer in SCCP header * \param[in] xua xUA message from which to use source data * \param[in] iei xUA information element identifier of source data */ static int sccp_add_long_variable_part(struct msgb *msg, uint8_t *var_ptr, const struct xua_msg *xua, uint16_t iei) { struct xua_msg_part *part = xua_msg_find_tag(xua, iei); uint8_t *lenbyte; uint8_t *cur; if (!part) { LOGP(DLSUA, LOGL_ERROR, "Cannot find IEI %u in SUA message\n", iei); return -ENODEV; } /* first allocate 2 bytes for the length */ lenbyte = msgb_put(msg, 2); /* update the relative pointer to the length byte. * This is only used in LUDT and LUDTS, so the pointer is always 2 bytes. * -1: Distance from MSB of pointer */ osmo_store16le(lenbyte - var_ptr - 1, var_ptr); /* then append the encoded SCCP address */ cur = msgb_put(msg, part->len); memcpy(cur, part->dat, part->len); /* store the encoded length of the address, LSB first */ osmo_store16le(part->len, lenbyte); return part->len; } /*! \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(const struct msgb *msg, const uint8_t *ptr_addr) { const uint8_t *ptr; /* check the address of the relative pointer is within msg */ if (ptr_addr < msg->data || ptr_addr > msg->tail) { LOGP(DLSUA, LOGL_ERROR, "ptr_addr outside msg boundary\n"); return false; } ptr = ptr_addr + *ptr_addr; if (ptr > msg->tail) { LOGP(DLSUA, LOGL_ERROR, "ptr points outside msg boundary\n"); return false; } /* at destination of relative pointer is the length */ if (ptr + 1 + *ptr > msg->tail) { LOGP(DLSUA, LOGL_ERROR, "ptr + len points outside msg boundary\n"); return false; } return true; } /*! \brief validate that SCCP part with long pointer (2 bytes) + length doesn't exceed msg tail * \param[in] msg Message containing SCCP address (LUDT or LUDTS) * \param[in] ptr_addr pointer to byte with relative SCCP long pointer (uint16_t, 2 bytes in network order) * \param[in] len_is_long whether the length field at the starting of the value field pointer to by ptr_addr is 2 bytes long. * \returns true if OK; false if message inconsistent */ static bool sccp_longptr_part_consistent(const struct msgb *msg, const uint8_t *ptr_addr, bool len_is_long) { const uint8_t *ptr; uint8_t offs; uint16_t len; /* check the address of the relative pointer is within msg */ if (ptr_addr < msg->data || ptr_addr > msg->tail) { LOGP(DLSUA, LOGL_ERROR, "ptr_addr outside msg boundary\n"); return false; } /* +1: Distance from MSB of pointer */ ptr = ptr_addr + 1 + osmo_load16le(ptr_addr); if (ptr > msg->tail) { LOGP(DLSUA, LOGL_ERROR, "ptr %p points outside msg boundary %p\n", ptr, msg->tail); return false; } /* at destination of relative pointer is the length */ if (len_is_long) { offs = 2; len = osmo_load16le(ptr); } else { offs = 1; len = *ptr; } if (ptr + offs + len > msg->tail) { LOGP(DLSUA, LOGL_ERROR, "ptr + len points outside msg boundary\n"); return false; } return true; } /*! \brief convenience wrapper around xua_msg_add_data() for variable mandatory data */ static int sccp_data_to_sua_ptr(struct xua_msg *xua, uint16_t iei, const uint8_t *ptr_addr) { const uint8_t *addr = ptr_addr + *ptr_addr + 1; unsigned int addrlen = *(ptr_addr + *ptr_addr); return xua_msg_add_data(xua, iei, addrlen, addr); } /*! \brief convenience wrapper around xua_msg_add_data() for variable mandatory data */ static int sccp_longdata_to_sua_ptr(struct xua_msg *xua, uint16_t iei, const uint8_t *ptr_addr) { const uint16_t ptr_value = osmo_load16le(ptr_addr); /* +1: Distance from MSB of pointer */ const uint8_t *addrlen_ptr = ptr_addr + 1 + ptr_value; /* +2: size of length field */ const uint8_t *addr = addrlen_ptr + 2; unsigned int addrlen = osmo_load16le(addrlen_ptr); return xua_msg_add_data(xua, iei, addrlen, addr); } /*! \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); 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: LOGP(DLSUA, LOGL_ERROR, "Unsupported SCCP option type %u\n", sccp_opt_type); return -1; } return 0; } /*! \brief append a SCCP option header to the given message * \param msg Message to which header is to be appended * \param[in] pnc PNC of the option header * \param[in] len length of the option, excluding the header */ 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); } /*! \brief append a SCCP option to the given message * \param msg Message to which option is to be appended * \param[in] pnc PNC of the option header * \param[in] len length of the option, excluding the header * \param[in] data actual data to be appended */ 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(enum sccp_message_types type, struct msgb *msg, const 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: switch (type) { case SCCP_MSG_TYPE_CC: case SCCP_MSG_TYPE_CREF: /* The Destination of a CC message is the * originator of the connection: Calling Party */ msgb_put_u8(msg, SCCP_PNC_CALLING_PARTY_ADDRESS); break; default: msgb_put_u8(msg, SCCP_PNC_CALLED_PARTY_ADDRESS); break; } 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: switch (type) { case SCCP_MSG_TYPE_CC: case SCCP_MSG_TYPE_CREF: /* The Source of a CC message is the * responder of the connection: Called Party */ msgb_put_u8(msg, SCCP_PNC_CALLED_PARTY_ADDRESS); break; default: msgb_put_u8(msg, SCCP_PNC_CALLING_PARTY_ADDRESS); break; } 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)); 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: LOGP(DLSUA, LOGL_ERROR, "Unknown SUA Cause Class 0x%04x\n", tmp32); 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; case SUA_IEI_ROUTE_CTX: break; case SUA_IEI_SEQ_CTRL: /* TODO */ break; default: LOGP(DLSUA, LOGL_ERROR, "Unknown SUA IEI 0x%04x\n", opt->tag); 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[in] ptr_opt_is_long whether ptr_opt is a long pointer (2 bytes, network order) * \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(const struct msgb *msg, const uint8_t *ptr_opt, bool ptr_opt_is_long, struct xua_msg *xua) { const uint8_t *opt_start, *oneopt; uint16_t ptr_opt_value; /* some bounds checking */ if (ptr_opt < msg->data) return NULL; if (ptr_opt > msg->tail - (ptr_opt_is_long ? 2 : 1)) return NULL; if (ptr_opt_is_long) ptr_opt_value = osmo_load16le(ptr_opt); else ptr_opt_value = *ptr_opt; /* Q.713 section 2.3 "Coding of pointers": pointer value all zeros used to indicate that no optional param is present. */ if (ptr_opt_value == 0) return xua; if (ptr_opt_is_long) /* +1: Distance from MSB of pointer */ opt_start = ptr_opt + 1 + ptr_opt_value; else opt_start = ptr_opt + ptr_opt_value; if (opt_start > msg->tail) return NULL; oneopt = opt_start; enum sccp_parameter_name_codes opt_type = 0; /* dummy value not used */ while (oneopt < msg->tail) { uint8_t opt_len; uint16_t opt_len16; opt_type = oneopt[0]; switch (opt_type) { case SCCP_PNC_END_OF_OPTIONAL: return xua; case SCCP_PNC_LONG_DATA: /* two byte length field */ if (oneopt + 2 > msg->tail) goto malformed; opt_len16 = oneopt[1] << 8 | oneopt[2]; if (oneopt + 3 + opt_len16 > msg->tail) goto malformed; xua_msg_add_sccp_opt(xua, opt_type, opt_len16, oneopt+3); oneopt += 3 + opt_len16; break; default: /* one byte length field */ if (oneopt + 1 > msg->tail) goto malformed; opt_len = oneopt[1]; if (oneopt + 2 + opt_len > msg->tail) goto malformed; xua_msg_add_sccp_opt(xua, opt_type, opt_len, oneopt+2); oneopt += 2 + opt_len; } } LOGP(DLSUA, LOGL_ERROR, "Parameter %s not found\n", osmo_sccp_pnc_name(SCCP_PNC_END_OF_OPTIONAL)); return NULL; malformed: LOGP(DLSUA, LOGL_ERROR, "Malformed parameter %s (%d)\n", osmo_sccp_pnc_name(opt_type), opt_type); return NULL; } #define MAX_IES 6 #define NUM_SCCP_MSGT (SCCP_MSG_TYPE_LUDTS+1) /* This table indicates which information elements are mandatory and not * optional in SCCP, per message type */ static const uint16_t sccp_mandatory[NUM_SCCP_MSGT][MAX_IES] = { /* Table 3/Q.713 */ [SCCP_MSG_TYPE_CR] = { SUA_IEI_SRC_REF, SUA_IEI_PROTO_CLASS, SUA_IEI_DEST_ADDR , 0 }, /* Table 4/Q.713 */ [SCCP_MSG_TYPE_CC] = { SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, SUA_IEI_PROTO_CLASS, 0 }, /* Table 5/Q.713 */ [SCCP_MSG_TYPE_CREF] = { SUA_IEI_DEST_REF, SUA_IEI_CAUSE, 0 }, /* Table 6/Q.713 */ [SCCP_MSG_TYPE_RLSD] = { SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, SUA_IEI_CAUSE, 0 }, /* Table 7/Q.713 */ [SCCP_MSG_TYPE_RLC] = { SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, 0 }, /* Table 8/Q.713 */ [SCCP_MSG_TYPE_DT1] = { SUA_IEI_DEST_REF, SUA_IEI_SEGMENTATION, 0 }, /* Table 9/Q.713 */ [SCCP_MSG_TYPE_DT2] = { SUA_IEI_DEST_REF, SUA_IEI_SEGMENTATION, 0 }, /* Table 10/Q.713 */ [SCCP_MSG_TYPE_AK] = { SUA_IEI_DEST_REF, SUA_IEI_RX_SEQ_NR, 0 }, /* Table 11/Q.713 */ [SCCP_MSG_TYPE_UDT] = { SUA_IEI_PROTO_CLASS, SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 }, /* Table 12/Q.713 */ [SCCP_MSG_TYPE_UDTS] = { SUA_IEI_CAUSE, SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 }, /* Table 13/Q.713 */ [SCCP_MSG_TYPE_ED] = { SUA_IEI_DEST_REF, 0 }, /* Table 14/Q.713 */ [SCCP_MSG_TYPE_EA] = { SUA_IEI_DEST_REF, 0 }, /* Table 15/Q.713 */ [SCCP_MSG_TYPE_RSR] = { SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, SUA_IEI_CAUSE, 0 }, /* Table 16/Q.713 */ [SCCP_MSG_TYPE_RSC] = { SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, 0 }, /* Table 17/Q.713 */ [SCCP_MSG_TYPE_ERR] = { SUA_IEI_DEST_REF, SUA_IEI_CAUSE, 0 }, /* Table 18/Q.713 */ [SCCP_MSG_TYPE_IT] = { SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, SUA_IEI_PROTO_CLASS, SUA_IEI_SEGMENTATION, SUA_IEI_CREDIT, 0 }, /* Table 19/Q.713 */ [SCCP_MSG_TYPE_XUDT] = { SUA_IEI_PROTO_CLASS, SUA_IEI_S7_HOP_CTR, SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 }, /* Table 20/Q.713 */ [SCCP_MSG_TYPE_XUDTS] = { SUA_IEI_CAUSE, SUA_IEI_S7_HOP_CTR, SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 }, /* Table 21/Q.713 */ [SCCP_MSG_TYPE_LUDT] = { SUA_IEI_PROTO_CLASS, SUA_IEI_S7_HOP_CTR, SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 }, /* Table 22/Q.713 */ [SCCP_MSG_TYPE_LUDTS] = { SUA_IEI_CAUSE, SUA_IEI_S7_HOP_CTR, SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 }, }; /* This table indicates which information elements are optionally * permitted in the respective SCCP message type */ static const uint16_t sccp_optional[NUM_SCCP_MSGT][MAX_IES] = { /* Table 3/Q.713 */ [SCCP_MSG_TYPE_CR] = { SUA_IEI_CREDIT, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, SUA_IEI_S7_HOP_CTR, SUA_IEI_IMPORTANCE, 0 }, /* Table 4/Q.713 */ [SCCP_MSG_TYPE_CC] = { SUA_IEI_CREDIT, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, SUA_IEI_IMPORTANCE, 0 }, /* Table 5/Q.713 */ [SCCP_MSG_TYPE_CREF] = { SUA_IEI_SRC_ADDR, SUA_IEI_DATA, SUA_IEI_IMPORTANCE, 0 }, /* Table 6/Q.713 */ [SCCP_MSG_TYPE_RLSD] = { SUA_IEI_DATA, SUA_IEI_IMPORTANCE, 0 }, /* Table 7/Q.713 */ [SCCP_MSG_TYPE_RLC] = { 0 }, /* Table 8/Q.713 */ [SCCP_MSG_TYPE_DT1] = { 0 }, /* Table 9/Q.713 */ [SCCP_MSG_TYPE_DT2] = { 0 }, /* Table 10/Q.713 */ [SCCP_MSG_TYPE_AK] = { 0 }, /* Table 11/Q.713 */ [SCCP_MSG_TYPE_UDT] = { 0 }, /* Table 12/Q.713 */ [SCCP_MSG_TYPE_UDTS] = { 0 }, /* Table 13/Q.713 */ [SCCP_MSG_TYPE_ED] = { 0 }, /* Table 14/Q.713 */ [SCCP_MSG_TYPE_EA] = { 0 }, /* Table 15/Q.713 */ [SCCP_MSG_TYPE_RSR] = { 0 }, /* Table 16/Q.713 */ [SCCP_MSG_TYPE_RSC] = { 0 }, /* Table 17/Q.713 */ [SCCP_MSG_TYPE_ERR] = { 0 }, /* Table 18/Q.713 */ [SCCP_MSG_TYPE_IT] = { 0 }, /* Table 19/Q.713 */ [SCCP_MSG_TYPE_XUDT] = { SUA_IEI_SEGMENTATION, SUA_IEI_IMPORTANCE, 0 }, /* Table 20/Q.713 */ [SCCP_MSG_TYPE_XUDTS] = { SUA_IEI_SEGMENTATION, SUA_IEI_IMPORTANCE, 0 }, /* Table 21/Q.713 */ [SCCP_MSG_TYPE_LUDT] = { SUA_IEI_SEGMENTATION, SUA_IEI_IMPORTANCE, 0 }, /* Table 22/Q.713 */ [SCCP_MSG_TYPE_LUDTS] = { SUA_IEI_SEGMENTATION, SUA_IEI_IMPORTANCE, 0 }, }; static bool sccp_is_mandatory(enum sccp_message_types type, const struct xua_msg_part *part) { unsigned int i; if (type >= ARRAY_SIZE(sccp_mandatory)) return false; for (i = 0; i < MAX_IES; i++) { uint16_t val = sccp_mandatory[type][i]; if (val == 0) { /* end of list, don't iterate further */ return false; } if (val == part->tag) { /* found in list, it's mandatory */ return true; } } /* not mandatory */ return false; } static bool sccp_option_permitted(enum sccp_message_types type, const struct xua_msg_part *part) { unsigned int i; if (type >= ARRAY_SIZE(sccp_optional)) return false; for (i = 0; i < MAX_IES; i++) { uint16_t val = sccp_optional[type][i]; if (val == 0) { /* end of list, don't iterate further */ return false; } if (val == part->tag) { /* found in list, it's permitted */ return true; } } /* not permitted */ return false; } static int xua_ies_to_sccp_opts(struct msgb *msg, uint8_t *ptr_opt, bool ptr_opt_is_long, enum sccp_message_types type, const struct xua_msg *xua) { const struct xua_msg_part *part; bool any_added = false; uint8_t *old_tail = msg->tail; llist_for_each_entry(part, &xua->headers, entry) { /* make sure we don't add a SCCP option for information * that is already present in mandatory fixed or * mandatory variable parts of the header */ if (!sccp_is_mandatory(type, part) && sccp_option_permitted(type, part)) { sccp_msg_add_sua_opt(type, msg, part); any_added = true; } } if (any_added) { msgb_put_u8(msg, SCCP_PNC_END_OF_OPTIONAL); /* store relative pointer to start of optional part */ if (ptr_opt_is_long) /* -1: Distance from MSB of pointer */ osmo_store16le(old_tail - ptr_opt - 1, ptr_opt); else *ptr_opt = old_tail - ptr_opt; } else { /* If nothing was added, simply update the pointer to 0 to signal the optional part is omitted. */ ptr_opt[0] = 0; if (ptr_opt_is_long) ptr_opt[1] = 0; } return 0; } /* store a 'local reference' as big-endian 24bit value at local_ref */ static int store_local_ref(struct sccp_source_reference *local_ref, const struct xua_msg *xua, uint16_t iei) { uint32_t tmp32 = xua_msg_get_u32(xua, iei); if (tmp32 > 0x00fffffe) { LOGP(DLSUA, LOGL_ERROR, "SUA->SCCP: Local Reference value 0x%" PRIx32 " > 0x00fffffe\n", tmp32); return -1; } local_ref->octet1 = (tmp32 >> 16) & 0xff; local_ref->octet2 = (tmp32 >> 8) & 0xff; local_ref->octet3 = tmp32 & 0xff; return 0; } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_cr(const 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); 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, &req->variable_called, false); /* Optional Part */ return sccp_to_xua_opt(msg, &req->optional_start, false, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static int sua_to_sccp_cr(struct msgb *msg, const struct xua_msg *xua) { struct sccp_connection_request *req; int rc; req = (struct sccp_connection_request *) msgb_put(msg, sizeof(*req)); /* Fixed Part */ req->type = SCCP_MSG_TYPE_CR; req->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); rc = store_local_ref(&req->source_local_reference, xua, SUA_IEI_SRC_REF); if (rc < 0) return rc; /* Variable Part */ sccp_add_var_addr(msg, &req->variable_called, false, xua, SUA_IEI_DEST_ADDR); /* Optional Part */ return xua_ies_to_sccp_opts(msg, &req->optional_start, false, req->type, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_cc(const 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); 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, false, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static int sua_to_sccp_cc(struct msgb *msg, const struct xua_msg *xua) { struct sccp_connection_confirm *cnf; int rc; cnf = (struct sccp_connection_confirm *) msgb_put(msg, sizeof(*cnf)); /* Fixed Part */ cnf->type = SCCP_MSG_TYPE_CC; cnf->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); rc = store_local_ref(&cnf->destination_local_reference, xua, SUA_IEI_DEST_REF); if (rc < 0) return rc; rc = store_local_ref(&cnf->source_local_reference, xua, SUA_IEI_SRC_REF); if (rc < 0) return rc; /* Optional Part */ return xua_ies_to_sccp_opts(msg, &cnf->optional_start, false, cnf->type, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_cref(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_connection_refused *ref = (const 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, false, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static int sua_to_sccp_cref(struct msgb *msg, const struct xua_msg *xua) { struct sccp_connection_refused *ref; int rc; ref = (struct sccp_connection_refused *) msgb_put(msg, sizeof(*ref)); /* Fixed Part */ ref->type = SCCP_MSG_TYPE_CREF; rc = store_local_ref(&ref->destination_local_reference, xua, SUA_IEI_DEST_REF); if (rc < 0) return rc; ref->cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; /* Optional Part */ return xua_ies_to_sccp_opts(msg, &ref->optional_start, false, ref->type, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_rlsd(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_connection_released *rlsd = (const 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, false, xua); } static int sua_to_sccp_rlsd(struct msgb *msg, const struct xua_msg *xua) { struct sccp_connection_released *rlsd; int rc; rlsd =(struct sccp_connection_released *) msgb_put(msg, sizeof(*rlsd)); /* Fixed Part */ rlsd->type = SCCP_MSG_TYPE_RLSD; rc = store_local_ref(&rlsd->destination_local_reference, xua, SUA_IEI_DEST_REF); if (rc < 0) return rc; rc = store_local_ref(&rlsd->source_local_reference, xua, SUA_IEI_SRC_REF); if (rc < 0) return rc; rlsd->release_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; /* Optional Part */ return xua_ies_to_sccp_opts(msg, &rlsd->optional_start, false, rlsd->type, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_rlc(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_connection_release_complete *rlc; rlc = (const 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, const struct xua_msg *xua) { struct sccp_connection_release_complete *rlc; int rc; rlc = (struct sccp_connection_release_complete *) msgb_put(msg, sizeof(*rlc)); /* Fixed Part */ rlc->type = SCCP_MSG_TYPE_RLC; rc = store_local_ref(&rlc->destination_local_reference, xua, SUA_IEI_DEST_REF); if (rc < 0) return rc; rc = store_local_ref(&rlc->source_local_reference, xua, SUA_IEI_SRC_REF); if (rc < 0) return rc; return 0; } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_dt1(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_data_form1 *dt1 = (const 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; sccp_data_to_sua_ptr(xua, SUA_IEI_DATA, &dt1->variable_start); return xua; } static int sua_to_sccp_dt1(struct msgb *msg, const struct xua_msg *xua) { struct sccp_data_form1 *dt1; int rc; dt1 = (struct sccp_data_form1 *) msgb_put(msg, sizeof(*dt1)); /* Fixed Part */ dt1->type = SCCP_MSG_TYPE_DT1; rc = store_local_ref(&dt1->destination_local_reference, xua, SUA_IEI_DEST_REF); if (rc < 0) return rc; dt1->segmenting = xua_msg_get_u32(xua, SUA_IEI_SEGMENTATION); /* Variable Part */ sccp_add_variable_part(msg, &dt1->variable_start, xua, SUA_IEI_DATA); return 0; } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_udt(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_data_unitdata *udt = (const struct sccp_data_unitdata *)msg->l2h; /* Fixed Part */ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, udt->proto_class); /* Variable Part */ if (!sccp_ptr_part_consistent(msg, &udt->variable_called)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, &udt->variable_called, false); if (!sccp_ptr_part_consistent(msg, &udt->variable_calling)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, &udt->variable_calling, false); if (!sccp_ptr_part_consistent(msg, &udt->variable_data)) return NULL; sccp_data_to_sua_ptr(xua, SUA_IEI_DATA, &udt->variable_data); return xua; } static int sua_to_sccp_xudt(struct msgb *msg, const struct xua_msg *xua); static int sua_to_sccp_ludt(struct msgb *msg, const struct xua_msg *xua); static int sua_to_sccp_udt(struct msgb *msg, const struct xua_msg *xua) { struct sccp_data_unitdata *udt; /* Use LUDT if length exceeds 255 (single byte length field) */ /* TODO: start using LUDT sooner if called/calling party contain GT or if * segmentation and/or importance present, see Q.715 Section 8.3.2 */ if (xua_msg_get_len(xua, SUA_IEI_DATA) > 255) return sua_to_sccp_ludt(msg, xua); /* Use XUDT if we have a hop counter on the SUA side */ if (xua_msg_find_tag(xua, SUA_IEI_S7_HOP_CTR)) return sua_to_sccp_xudt(msg, xua); udt = (struct sccp_data_unitdata *) msgb_put(msg, sizeof(*udt)); /* Fixed Part */ udt->type = SCCP_MSG_TYPE_UDT; udt->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); /* Variable Part */ sccp_add_var_addr(msg, &udt->variable_called, false, xua, SUA_IEI_DEST_ADDR); sccp_add_var_addr(msg, &udt->variable_calling, false, xua, SUA_IEI_SRC_ADDR); sccp_add_variable_part(msg, &udt->variable_data, xua, SUA_IEI_DATA); return 0; } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_xudt(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_data_ext_unitdata *xudt = (const struct sccp_data_ext_unitdata *)msg->l2h; /* Fixed Part */ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, xudt->proto_class); xua_msg_add_u32(xua, SUA_IEI_S7_HOP_CTR, xudt->hop_counter); /* Variable Part */ if (!sccp_ptr_part_consistent(msg, &xudt->variable_called)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, &xudt->variable_called, false); if (!sccp_ptr_part_consistent(msg, &xudt->variable_calling)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, &xudt->variable_calling, false); if (!sccp_ptr_part_consistent(msg, &xudt->variable_data)) return NULL; sccp_data_to_sua_ptr(xua, SUA_IEI_DATA, &xudt->variable_data); /* Optional Part */ return sccp_to_xua_opt(msg, &xudt->optional_start, false, xua); } static int sua_to_sccp_xudt(struct msgb *msg, const struct xua_msg *xua) { struct sccp_data_ext_unitdata *xudt; /* Use LUDT if length exceeds 255 (single byte length field) */ /* TODO: start using LUDTS sooner if called/calling party contain GT or if * segmentation and/or importance present, see Q.715 Section 8.3.2 */ if (xua_msg_get_len(xua, SUA_IEI_DATA) > 255) return sua_to_sccp_ludt(msg, xua); xudt = (struct sccp_data_ext_unitdata *) msgb_put(msg, sizeof(*xudt)); /* Fixed Part */ xudt->type = SCCP_MSG_TYPE_XUDT; xudt->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); xudt->hop_counter = xua_msg_get_u32(xua, SUA_IEI_S7_HOP_CTR); /* Variable Part */ sccp_add_var_addr(msg, &xudt->variable_called, false, xua, SUA_IEI_DEST_ADDR); sccp_add_var_addr(msg, &xudt->variable_calling, false, xua, SUA_IEI_SRC_ADDR); sccp_add_variable_part(msg, &xudt->variable_data, xua, SUA_IEI_DATA); /* Optional Part */ return xua_ies_to_sccp_opts(msg, &xudt->optional_start, false, xudt->type, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_ludt(struct msgb *msg, struct xua_msg *xua) { struct sccp_data_long_unitdata *ludt = (struct sccp_data_long_unitdata *)msg->l2h; /* Fixed Part */ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, ludt->proto_class); xua_msg_add_u32(xua, SUA_IEI_S7_HOP_CTR, ludt->hop_counter); /* Variable Part */ if (!sccp_longptr_part_consistent(msg, (uint8_t *)&ludt->variable_called, false)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, (uint8_t *)&ludt->variable_called, true); if (!sccp_longptr_part_consistent(msg, (uint8_t *)&ludt->variable_calling, false)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, (uint8_t *)&ludt->variable_calling, true); if (!sccp_longptr_part_consistent(msg, (uint8_t *)&ludt->variable_data, true)) return NULL; sccp_longdata_to_sua_ptr(xua, SUA_IEI_DATA, (uint8_t *)&ludt->variable_data); /* Optional Part */ return sccp_to_xua_opt(msg, (uint8_t *)&ludt->optional_start, true, xua); } static int sua_to_sccp_ludt(struct msgb *msg, const struct xua_msg *xua) { struct sccp_data_long_unitdata *ludt; ludt = (struct sccp_data_long_unitdata *) msgb_put(msg, sizeof(*ludt)); /* Fixed Part */ ludt->type = SCCP_MSG_TYPE_LUDT; ludt->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); ludt->hop_counter = xua_msg_get_u32(xua, SUA_IEI_S7_HOP_CTR); /* Variable Part */ sccp_add_var_addr(msg, (uint8_t *)&ludt->variable_called, true, xua, SUA_IEI_DEST_ADDR); sccp_add_var_addr(msg, (uint8_t *)&ludt->variable_calling, true, xua, SUA_IEI_SRC_ADDR); sccp_add_long_variable_part(msg, (uint8_t *)&ludt->variable_data, xua, SUA_IEI_DATA); /* Optional Part */ return xua_ies_to_sccp_opts(msg, (uint8_t *)&ludt->optional_start, true, ludt->type, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_udts(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_data_unitdata_service *udts; udts = (const 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, &udts->variable_called)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, &udts->variable_called, false); if (!sccp_ptr_part_consistent(msg, &udts->variable_calling)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, &udts->variable_calling, false); if (!sccp_ptr_part_consistent(msg, &udts->variable_data)) return NULL; sccp_data_to_sua_ptr(xua, SUA_IEI_DATA, &udts->variable_data); return xua; } static int sua_to_sccp_xudts(struct msgb *msg, const struct xua_msg *xua); static int sua_to_sccp_ludts(struct msgb *msg, const struct xua_msg *xua); static int sua_to_sccp_udts(struct msgb *msg, const struct xua_msg *xua) { struct sccp_data_unitdata_service *udts; /* Use LUDTS if length exceeds 255 (single byte length field) */ /* TODO: start using LUDTS sooner if called/calling party contain GT, * see Q.715 Section 8.3.2 */ if (xua_msg_get_len(xua, SUA_IEI_DATA) > 255) return sua_to_sccp_ludts(msg, xua); /* Use XUDTS if we have a hop counter */ if (xua_msg_find_tag(xua, SUA_IEI_S7_HOP_CTR)) return sua_to_sccp_xudts(msg, xua); udts = (struct sccp_data_unitdata_service *) msgb_put(msg, sizeof(*udts)); /* Fixed Part */ udts->type = SCCP_MSG_TYPE_UDTS; udts->return_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; /* Variable Part */ sccp_add_var_addr(msg, &udts->variable_called, false, xua, SUA_IEI_DEST_ADDR); sccp_add_var_addr(msg, &udts->variable_calling, false, xua, SUA_IEI_SRC_ADDR); sccp_add_variable_part(msg, &udts->variable_data, xua, SUA_IEI_DATA); return 0; } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_xudts(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_data_ext_unitdata_service *xudts; xudts = (const struct sccp_data_ext_unitdata_service *)msg->l2h; /* Fixed Part */ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RETURN | xudts->return_cause); xua_msg_add_u32(xua, SUA_IEI_S7_HOP_CTR, xudts->hop_counter); /* Variable Part */ if (!sccp_ptr_part_consistent(msg, (uint8_t *)&xudts->variable_called)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, &xudts->variable_called, false); if (!sccp_ptr_part_consistent(msg, (uint8_t *)&xudts->variable_calling)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, &xudts->variable_calling, false); if (!sccp_ptr_part_consistent(msg, (uint8_t *)&xudts->variable_data)) return NULL; sccp_data_to_sua_ptr(xua, SUA_IEI_DATA, &xudts->variable_data); /* Optional Part */ return sccp_to_xua_opt(msg, &xudts->optional_start, false, xua); } static int sua_to_sccp_xudts(struct msgb *msg, const struct xua_msg *xua) { struct sccp_data_ext_unitdata_service *xudts; xudts = (struct sccp_data_ext_unitdata_service *) msgb_put(msg, sizeof(*xudts)); /* Fixed Part */ xudts->type = SCCP_MSG_TYPE_XUDTS; xudts->return_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; xudts->hop_counter = xua_msg_get_u32(xua, SUA_IEI_S7_HOP_CTR); /* Variable Part */ sccp_add_var_addr(msg, &xudts->variable_called, false, xua, SUA_IEI_DEST_ADDR); sccp_add_var_addr(msg, &xudts->variable_calling, false, xua, SUA_IEI_SRC_ADDR); sccp_add_variable_part(msg, &xudts->variable_data, xua, SUA_IEI_DATA); /* Optional Part */ return xua_ies_to_sccp_opts(msg, &xudts->optional_start, false, xudts->type, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_ludts(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_data_long_unitdata_service *ludts; ludts = (const struct sccp_data_long_unitdata_service *)msg->l2h; /* Fixed Part */ xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RETURN | ludts->return_cause); xua_msg_add_u32(xua, SUA_IEI_S7_HOP_CTR, ludts->hop_counter); /* Variable Part */ if (!sccp_longptr_part_consistent(msg, (uint8_t *)&ludts->variable_called, false)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, (uint8_t *)&ludts->variable_called, true); if (!sccp_longptr_part_consistent(msg, (uint8_t *)&ludts->variable_calling, false)) return NULL; sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, (uint8_t *)&ludts->variable_calling, true); if (!sccp_longptr_part_consistent(msg, (uint8_t *)&ludts->variable_data, true)) return NULL; sccp_longdata_to_sua_ptr(xua, SUA_IEI_DATA, (uint8_t *)&ludts->variable_data); /* Optional Part */ return sccp_to_xua_opt(msg, (uint8_t *)&ludts->optional_start, true, xua); } static int sua_to_sccp_ludts(struct msgb *msg, const struct xua_msg *xua) { struct sccp_data_long_unitdata_service *ludts; ludts = (struct sccp_data_long_unitdata_service *) msgb_put(msg, sizeof(*ludts)); /* Fixed Part */ ludts->type = SCCP_MSG_TYPE_LUDTS; ludts->return_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; ludts->hop_counter = xua_msg_get_u32(xua, SUA_IEI_S7_HOP_CTR); /* Variable Part */ sccp_add_var_addr(msg, (uint8_t *)&ludts->variable_called, true, xua, SUA_IEI_DEST_ADDR); sccp_add_var_addr(msg, (uint8_t *)&ludts->variable_calling, true, xua, SUA_IEI_SRC_ADDR); sccp_add_long_variable_part(msg, (uint8_t *)&ludts->variable_data, xua, SUA_IEI_DATA); /* Optional Part */ return xua_ies_to_sccp_opts(msg, (uint8_t *)&ludts->optional_start, true, ludts->type, xua); } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_it(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_data_it *it = (const struct sccp_data_it *)msg->l2h; /* Fixed Part */ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, it->proto_class); 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, const struct xua_msg *xua) { struct sccp_data_it *it; int rc; it = (struct sccp_data_it *) msgb_put(msg, sizeof(*it)); /* Fixed Part */ it->type = SCCP_MSG_TYPE_IT; it->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); rc = store_local_ref(&it->destination_local_reference, xua, SUA_IEI_DEST_REF); if (rc < 0) return rc; rc = store_local_ref(&it->source_local_reference, xua, SUA_IEI_SRC_REF); if (rc < 0) return rc; if ((it->proto_class & 0xF) == 3) { //it->sequencing it->credit = xua_msg_get_u32(xua, SUA_IEI_CREDIT); } return 0; } /*! \returns \ref xua in case of success, NULL on error (xua not freed!) */ static struct xua_msg *sccp_to_xua_err(const struct msgb *msg, struct xua_msg *xua) { const struct sccp_proto_err *err = (const 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, const struct xua_msg *xua) { struct sccp_proto_err *err; int rc; err = (struct sccp_proto_err *) msgb_put(msg, sizeof(*err)); /* Fixed Part */ err->type = SCCP_MSG_TYPE_ERR; rc = store_local_ref(&err->destination_local_reference, xua, SUA_IEI_DEST_REF); if (rc < 0) return rc; err->error_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; return 0; } /*! \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) { LOGP(DLSUA, LOGL_ERROR, "Short SCCP Message, cannot transcode\n"); 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); if (!sccp_to_xua_cr(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_CC: xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COAK); if (!sccp_to_xua_cc(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_CREF: xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF); if (!sccp_to_xua_cref(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_RLSD: xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE); if (!sccp_to_xua_rlsd(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_RLC: xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO); if (!sccp_to_xua_rlc(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_DT1: xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT); if (!sccp_to_xua_dt1(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_UDT: xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT); if (!sccp_to_xua_udt(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_UDTS: xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDR); if (!sccp_to_xua_udts(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_IT: xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COIT); if (!sccp_to_xua_it(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_ERR: xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COERR); if (!sccp_to_xua_err(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_XUDT: xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT); if (!sccp_to_xua_xudt(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_XUDTS: xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDR); if (!sccp_to_xua_xudts(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_LUDT: xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT); if (!sccp_to_xua_ludt(msg, xua)) goto malformed; return xua; case SCCP_MSG_TYPE_LUDTS: xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDR); if (!sccp_to_xua_ludts(msg, xua)) goto malformed; return 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: LOGP(DLSUA, LOGL_ERROR, "Unsupported SCCP message %s\n", osmo_sccp_msg_type_name(msg->l2h[0])); xua_msg_free(xua); return NULL; default: LOGP(DLSUA, LOGL_ERROR, "Unsupported SCCP message type %u\n", msg->l2h[0]); xua_msg_free(xua); return NULL; } return NULL; malformed: LOGP(DLSUA, LOGL_ERROR, "Malformed SCCP message %s\n", osmo_sccp_msg_type_name(msg->l2h[0])); xua_msg_free(xua); return NULL; } /*! \brief convert parsed SUA message to SCCP message * \param[in] xua parsed SUA message to be converted * \returns callee-allocated msgb containing encoded SCCP message */ struct msgb *osmo_sua_to_sccp(struct xua_msg *xua) { struct msgb *msg = sccp_msgb_alloc("SCCP from SUA"); int rc; switch (xua->hdr.msg_class) { case SUA_MSGC_CL: switch (xua->hdr.msg_type) { case SUA_CL_CLDT: rc = sua_to_sccp_udt(msg, xua); break; case SUA_CL_CLDR: rc = sua_to_sccp_udts(msg, xua); break; default: LOGP(DLSUA, LOGL_ERROR, "Unsupported SUA message %s\n", xua_hdr_dump(xua, &xua_dialect_sua)); goto out_err; } break; case SUA_MSGC_CO: switch (xua->hdr.msg_type) { case SUA_CO_CORE: rc = sua_to_sccp_cr(msg, xua); break; case SUA_CO_COAK: rc = sua_to_sccp_cc(msg, xua); break; case SUA_CO_COREF: rc = sua_to_sccp_cref(msg, xua); break; case SUA_CO_RELRE: rc = sua_to_sccp_rlsd(msg, xua); break; case SUA_CO_RELCO: rc = sua_to_sccp_rlc(msg, xua); break; case SUA_CO_CODT: rc = sua_to_sccp_dt1(msg, xua); break; case SUA_CO_COIT: rc = sua_to_sccp_it(msg, xua); break; case SUA_CO_COERR: rc = sua_to_sccp_err(msg, xua); break; default: LOGP(DLSUA, LOGL_ERROR, "Unsupported SUA message %s\n", xua_hdr_dump(xua, &xua_dialect_sua)); goto out_err; } break; default: LOGP(DLSUA, LOGL_ERROR, "Unsupported SUA message class %s\n", xua_hdr_dump(xua, &xua_dialect_sua)); goto out_err; } if (rc < 0) { LOGP(DLSUA, LOGL_ERROR, "Malformed SUA message %s\n", xua_hdr_dump(xua, &xua_dialect_sua)); goto out_err; } return msg; out_err: msgb_free(msg); return NULL; }