sccp: Introduce initial support for SCCP LUDT + LUDTS messages

* LUDT/LUDTS is now properly received and passed along to the app.
* LUDT/LUDTS is now used if the data to transmit spans more than 255
  bytes.

Related: SYS#6566
Change-Id: Ic91abfc921f5e4f36045bfa325333112cddd9fa6
This commit is contained in:
Harald Welte 2021-10-26 16:13:35 +02:00 committed by Pau Espin Pedrol
parent 033072fc2d
commit 743deba334
8 changed files with 433 additions and 47 deletions

View File

@ -488,6 +488,64 @@ struct sccp_data_ext_unitdata_service {
uint8_t data[0];
} __attribute__((packed));
/* Long unitdata (LUDT), ITU-T Q.713 4.20 */
struct sccp_data_long_unitdata {
/* mandatory */
uint8_t type;
uint8_t proto_class;
uint8_t hop_counter;
/* variable */
uint16_t variable_called;
uint16_t variable_calling;
uint16_t variable_data;
/* optional */
uint16_t optional_start;
#if VARIABLE
called party address
calling party address
data
#endif
#if OPTIONAL
segmentation
importance
#endif
uint8_t data[0];
} __attribute__((packed));
/* Long unitdata service (LUDTS), ITU-T Q.713 4.21 */
struct sccp_data_long_unitdata_service {
/* mandantory */
uint8_t type;
uint8_t return_cause;
uint8_t hop_counter;
/* variable */
uint16_t variable_called;
uint16_t variable_calling;
uint16_t variable_data;
/* optional */
uint16_t optional_start;
#if VARIABLE
called party address
calling party address
data
#endif
#if OPTIONAL
segmentation
importance
#endif
uint8_t data[0];
} __attribute__((packed));
struct sccp_data_it {
/* mandatory */
uint8_t type;

View File

@ -94,6 +94,7 @@ uint32_t xua_msg_get_u32(const struct xua_msg *xua, uint16_t iei);
const uint32_t *xua_msg_get_u32p(const struct xua_msg *xua, uint16_t iei, uint32_t *out);
const char *xua_msg_part_get_str(const struct xua_msg_part *part);
const char *xua_msg_get_str(const struct xua_msg *xua, uint16_t iei);
int xua_msg_get_len(const struct xua_msg *xua, uint16_t iei);
void xua_part_add_gt(struct msgb *msg, const struct osmo_sccp_gt *gt);
int xua_msg_add_sccp_addr(struct xua_msg *xua, uint16_t iei, const struct osmo_sccp_addr *addr);

View File

@ -338,10 +338,17 @@ static int sccp_addr_to_sua(struct xua_msg *xua, uint16_t iei, const uint8_t *ad
}
/*! \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)
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 = ptr_addr + *ptr_addr + 1;
unsigned int addrlen = *(ptr_addr + *ptr_addr);
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);
}
@ -368,9 +375,10 @@ static int sua_addr_to_sccp(struct msgb *msg, const struct xua_msg_part *part)
/*! \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, const struct xua_msg *xua, uint16_t iei)
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;
@ -383,7 +391,10 @@ static int sccp_add_var_addr(struct msgb *msg, uint8_t *var_ptr, const struct xu
/* 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;
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);
@ -426,6 +437,37 @@ static int sccp_add_variable_part(struct msgb *msg, uint8_t *var_ptr, const stru
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
@ -455,6 +497,45 @@ static bool sccp_ptr_part_consistent(const struct msgb *msg, const uint8_t *ptr_
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)
{
@ -464,6 +545,20 @@ static int sccp_data_to_sua_ptr(struct xua_msg *xua, uint16_t iei, const uint8_t
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)
@ -697,22 +792,34 @@ static int sccp_msg_add_sua_opt(enum sccp_message_types type, struct msgb *msg,
/*! \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, struct xua_msg *xua)
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 || ptr_opt > msg->tail)
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 == 0)
if (ptr_opt_value == 0)
return xua;
opt_start = ptr_opt + *ptr_opt;
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;
@ -983,7 +1090,7 @@ static bool sccp_option_permitted(enum sccp_message_types type, const struct xua
return false;
}
static int xua_ies_to_sccp_opts(struct msgb *msg, uint8_t *ptr_opt,
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;
@ -1002,10 +1109,15 @@ static int xua_ies_to_sccp_opts(struct msgb *msg, uint8_t *ptr_opt,
if (any_added) {
msgb_put_u8(msg, SCCP_PNC_END_OF_OPTIONAL);
/* store relative pointer to start of optional part */
*ptr_opt = old_tail - ptr_opt;
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;
@ -1036,9 +1148,9 @@ static struct xua_msg *sccp_to_xua_cr(const struct msgb *msg, struct xua_msg *xu
/* 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);
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, xua);
return sccp_to_xua_opt(msg, &req->optional_start, false, xua);
}
/*! \returns \ref xua in case of success, NULL on error (xua not freed!) */
@ -1055,10 +1167,10 @@ static int sua_to_sccp_cr(struct msgb *msg, const struct xua_msg *xua)
if (rc < 0)
return rc;
/* Variable Part */
sccp_add_var_addr(msg, &req->variable_called, xua, SUA_IEI_DEST_ADDR);
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, req->type, xua);
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!) */
@ -1071,7 +1183,7 @@ static struct xua_msg *sccp_to_xua_cc(const struct msgb *msg, struct xua_msg *xu
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);
return sccp_to_xua_opt(msg, &cnf->optional_start, false, xua);
}
/*! \returns \ref xua in case of success, NULL on error (xua not freed!) */
@ -1091,7 +1203,7 @@ static int sua_to_sccp_cc(struct msgb *msg, const struct xua_msg *xua)
if (rc < 0)
return rc;
/* Optional Part */
return xua_ies_to_sccp_opts(msg, &cnf->optional_start, cnf->type, xua);
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!) */
@ -1103,7 +1215,7 @@ static struct xua_msg *sccp_to_xua_cref(const struct msgb *msg, struct xua_msg *
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);
return sccp_to_xua_opt(msg, &ref->optional_start, false, xua);
}
/*! \returns \ref xua in case of success, NULL on error (xua not freed!) */
@ -1120,7 +1232,7 @@ static int sua_to_sccp_cref(struct msgb *msg, const struct xua_msg *xua)
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, ref->type, xua);
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!) */
@ -1133,7 +1245,7 @@ static struct xua_msg *sccp_to_xua_rlsd(const struct msgb *msg, struct xua_msg *
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);
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)
@ -1153,7 +1265,7 @@ static int sua_to_sccp_rlsd(struct msgb *msg, const struct xua_msg *xua)
rlsd->release_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff;
/* Optional Part */
return xua_ies_to_sccp_opts(msg, &rlsd->optional_start, rlsd->type, xua);
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!) */
@ -1227,10 +1339,10 @@ static struct xua_msg *sccp_to_xua_udt(const struct msgb *msg, struct xua_msg *x
/* 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);
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);
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);
@ -1239,11 +1351,18 @@ static struct xua_msg *sccp_to_xua_udt(const struct msgb *msg, struct xua_msg *x
}
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);
@ -1254,8 +1373,8 @@ static int sua_to_sccp_udt(struct msgb *msg, const struct xua_msg *xua)
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, xua, SUA_IEI_DEST_ADDR);
sccp_add_var_addr(msg, &udt->variable_calling, xua, SUA_IEI_SRC_ADDR);
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;
}
@ -1271,21 +1390,28 @@ static struct xua_msg *sccp_to_xua_xudt(const struct msgb *msg, struct xua_msg *
/* 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);
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);
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, xua);
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 */
@ -1293,11 +1419,50 @@ static int sua_to_sccp_xudt(struct msgb *msg, const struct xua_msg *xua)
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, xua, SUA_IEI_DEST_ADDR);
sccp_add_var_addr(msg, &xudt->variable_calling, xua, SUA_IEI_SRC_ADDR);
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, xudt->type, xua);
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!) */
@ -1311,10 +1476,10 @@ static struct xua_msg *sccp_to_xua_udts(const struct msgb *msg, struct xua_msg *
/* 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);
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);
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);
@ -1323,11 +1488,18 @@ static struct xua_msg *sccp_to_xua_udts(const struct msgb *msg, struct xua_msg *
}
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);
@ -1338,8 +1510,8 @@ static int sua_to_sccp_udts(struct msgb *msg, const struct xua_msg *xua)
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, xua, SUA_IEI_DEST_ADDR);
sccp_add_var_addr(msg, &udts->variable_calling, xua, SUA_IEI_SRC_ADDR);
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;
}
@ -1354,17 +1526,17 @@ static struct xua_msg *sccp_to_xua_xudts(const struct msgb *msg, struct xua_msg
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, &xudts->variable_called))
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);
if (!sccp_ptr_part_consistent(msg, &xudts->variable_calling))
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);
if (!sccp_ptr_part_consistent(msg, &xudts->variable_data))
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, xua);
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)
@ -1377,11 +1549,51 @@ static int sua_to_sccp_xudts(struct msgb *msg, const struct xua_msg *xua)
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, xua, SUA_IEI_DEST_ADDR);
sccp_add_var_addr(msg, &xudts->variable_calling, xua, SUA_IEI_SRC_ADDR);
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, xudts->type, xua);
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!) */
@ -1526,6 +1738,16 @@ struct xua_msg *osmo_sccp_to_xua(struct msgb *msg)
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:
@ -1533,8 +1755,6 @@ struct xua_msg *osmo_sccp_to_xua(struct msgb *msg)
case SCCP_MSG_TYPE_EA:
case SCCP_MSG_TYPE_RSR:
case SCCP_MSG_TYPE_RSC:
case SCCP_MSG_TYPE_LUDT:
case SCCP_MSG_TYPE_LUDTS:
LOGP(DLSUA, LOGL_ERROR, "Unsupported SCCP message %s\n",
osmo_sccp_msg_type_name(msg->l2h[0]));
xua_msg_free(xua);

View File

@ -330,6 +330,14 @@ const char *xua_msg_get_str(const struct xua_msg *xua, uint16_t iei)
return xua_msg_part_get_str(part);
}
int xua_msg_get_len(const struct xua_msg *xua, uint16_t iei)
{
struct xua_msg_part *part = xua_msg_find_tag(xua, iei);
if (!part)
return -1;
return part->len;
}
void xua_part_add_gt(struct msgb *msg, const struct osmo_sccp_gt *gt)
{
uint16_t *len_ptr;

View File

@ -100,3 +100,56 @@ const uint8_t tcap_global_title[183] = {
0x01, 0x41, 0x84, 0x01, 0x04, 0x30, 0x03, 0x82,
0x01, 0x18, 0x00, 0x00, 0x00, 0x00
};
/* SCCP LUDT message containing a RANAP RESET */
const uint8_t ludt_ranap_reset[40] = {
0x13, 0x00, 0x0f, 0x07, 0x00, 0x0a, 0x00, 0x0d,
0x00, 0x00, 0x00, 0x04, 0x43, 0xc4, 0x04, 0x8e,
0x04, 0x43, 0xe9, 0x03, 0x8e, 0x11, 0x00, 0x00,
0x09, 0x00, 0x0d, 0x00, 0x00, 0x02, 0x00, 0x04,
0x40, 0x01, 0x42, 0x00, 0x03, 0x00, 0x01, 0x00
};
const uint8_t ludt_data300bytes[323] = {
0x13, 0x00, 0x0f, 0x07, 0x00, 0x0a, 0x00, 0x0d,
0x00, 0x00, 0x00, 0x04, 0x43, 0xc4, 0x04, 0x8e,
0x04, 0x43, 0xe9, 0x03, 0x8e, 0x2c, 0x01, 0xab, /* 0x2c, 0x01 (network order) = len = 300 */
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
0xab, 0xab, 0xab,
};

View File

@ -12,3 +12,5 @@ extern const uint8_t bssmap_clear[13];
extern const uint8_t bssmap_released[14];
extern const uint8_t bssmap_release_complete[7];
extern const uint8_t tcap_global_title[183];
extern const uint8_t ludt_ranap_reset[40];
extern const uint8_t ludt_data300bytes[323];

View File

@ -375,6 +375,7 @@ struct sccp2sua_testcase {
const uint32_t sua_proto_class0 = 0;
const uint32_t sua_proto_class2 = 2;
const uint32_t sua_hop_ctr_0x0f = 0x0f;
const uint32_t sua_loc_ref_bsc = 0x10203;
const uint32_t sua_loc_ref_msc = 0x00003;
const uint32_t sua_cause0 = 0x00003;
@ -500,13 +501,41 @@ static const struct sccp2sua_testcase sccp2sua_testcases[] = {
.parts = {
},
},
}, {
/* This case expectedly prints "Input != re-encoded output!" because input is a
* LUDT with a small payload size fitting 1-byte length, while our (re-)encoder
* encodes it as UDT in this case because it's enough to encode and transmit the
* message. Both are valid. */
.name = "LUDT-RANAP_RELEASE",
.sccp = PANDSIZ(ludt_ranap_reset),
.sua = {
.hdr = _XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT),
.parts = {
PARTU32(SUA_IEI_PROTO_CLASS, &sua_proto_class0),
PARTU32(SUA_IEI_S7_HOP_CTR, &sua_hop_ctr_0x0f),
PARTARR(SUA_IEI_DEST_ADDR, sua_addr_ssn_bssmap),
PARTARR(SUA_IEI_SRC_ADDR, sua_addr_ssn_bssmap),
},
},
}, {
.name = "LUDT-data300bytes",
.sccp = PANDSIZ(ludt_data300bytes),
.sua = {
.hdr = _XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT),
.parts = {
PARTU32(SUA_IEI_PROTO_CLASS, &sua_proto_class0),
PARTU32(SUA_IEI_S7_HOP_CTR, &sua_hop_ctr_0x0f),
PARTARR(SUA_IEI_DEST_ADDR, sua_addr_ssn_bssmap),
PARTARR(SUA_IEI_SRC_ADDR, sua_addr_ssn_bssmap),
},
},
},
};
static void test_sccp2sua_case(const struct sccp2sua_testcase *tcase)
{
struct xua_msg *xua;
struct msgb *msg = msgb_alloc(300, "SCCP2SUA Test Input");
struct msgb *msg = msgb_alloc(500, "SCCP2SUA Test Input");
struct msgb *msg2;
printf("\n=> %s\n", tcase->name);

View File

@ -92,6 +92,21 @@ Transcoding message SCCP -> XUA
Decoded SUA: HDR=(CL:CLDT,V=0,LEN=0), PART(T=Protocol Class,L=4,D=00000081), PART(T=Destination Address,L=32,D=0001000580010014000000040a00010453840900170000008003000800000007), PART(T=Source Address,L=32,D=0001000580010014000000040c00010444872000206500008003000800000006), PART(T=Data,L=154,D=6581974804260001984904510103df6c8188a181850201440201073080a780a08004012b30803012830110840107850791445776671697860120300682011884010400000000a306040142840105a306040151840105a306040131840105a309040112840105820102a309040111840105810101a306040114840100a30b0401418401043003830110a30b040141840104300382011800000000)
Re-Encoding decoded SUA to SCCP
SCCP Output: [L2]> 09 81 03 0d 18 0a 12 07 00 12 04 53 84 09 00 17 0b 12 06 00 12 04 44 87 20 00 20 65 9a 65 81 97 48 04 26 00 01 98 49 04 51 01 03 df 6c 81 88 a1 81 85 02 01 44 02 01 07 30 80 a7 80 a0 80 04 01 2b 30 80 30 12 83 01 10 84 01 07 85 07 91 44 57 76 67 16 97 86 01 20 30 06 82 01 18 84 01 04 00 00 00 00 a3 06 04 01 42 84 01 05 a3 06 04 01 51 84 01 05 a3 06 04 01 31 84 01 05 a3 09 04 01 12 84 01 05 82 01 02 a3 09 04 01 11 84 01 05 81 01 01 a3 06 04 01 14 84 01 00 a3 0b 04 01 41 84 01 04 30 03 83 01 10 a3 0b 04 01 41 84 01 04 30 03 82 01 18 00 00 00 00
=> LUDT-RANAP_RELEASE
SCCP Input: [L2]> 13 00 0f 07 00 0a 00 0d 00 00 00 04 43 c4 04 8e 04 43 e9 03 8e 11 00 00 09 00 0d 00 00 02 00 04 40 01 42 00 03 00 01 00
Transcoding message SCCP -> XUA
Decoded SUA: HDR=(CL:CLDT,V=0,LEN=0), PART(T=Protocol Class,L=4,D=00000000), PART(T=SS7 Hop Counter,L=4,D=0000000f), PART(T=Destination Address,L=20,D=0002000380020008000004c4800300080000008e), PART(T=Source Address,L=20,D=0002000380020008000003e9800300080000008e), PART(T=Data,L=17,D=0009000d00000200044001420003000100)
Re-Encoding decoded SUA to SCCP
SCCP Output: [L2]> 11 00 0f 04 08 0c 00 04 43 c4 04 8e 04 43 e9 03 8e 11 00 09 00 0d 00 00 02 00 04 40 01 42 00 03 00 01 00
Input != re-encoded output!
=> LUDT-data300bytes
SCCP Input: [L2]> 13 00 0f 07 00 0a 00 0d 00 00 00 04 43 c4 04 8e 04 43 e9 03 8e 2c 01 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
Transcoding message SCCP -> XUA
Decoded SUA: HDR=(CL:CLDT,V=0,LEN=0), PART(T=Protocol Class,L=4,D=00000000), PART(T=SS7 Hop Counter,L=4,D=0000000f), PART(T=Destination Address,L=20,D=0002000380020008000004c4800300080000008e), PART(T=Source Address,L=20,D=0002000380020008000003e9800300080000008e), PART(T=Data,L=300,D=abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab)
Re-Encoding decoded SUA to SCCP
SCCP Output: [L2]> 13 00 0f 07 00 0a 00 0d 00 00 00 04 43 c4 04 8e 04 43 e9 03 8e 2c 01 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
Parsing M3UA Message
Parsing Nested M3UA Routing Key IE
Testing SCCP Address Encode/Decode