diff --git a/configure.ac b/configure.ac index 83885d49d..dc7551fd9 100644 --- a/configure.ac +++ b/configure.ac @@ -206,6 +206,7 @@ AC_OUTPUT( tests/atlocal tests/mgcp_client/Makefile tests/mgcp/Makefile + tests/iuup/Makefile doc/Makefile doc/examples/Makefile doc/manuals/Makefile diff --git a/include/osmocom/mgcp/Makefile.am b/include/osmocom/mgcp/Makefile.am index b94cdcd08..5f7adb6ef 100644 --- a/include/osmocom/mgcp/Makefile.am +++ b/include/osmocom/mgcp/Makefile.am @@ -13,4 +13,6 @@ noinst_HEADERS = \ mgcp_e1.h \ mgcp_network.h \ mgcp_protocol.h \ + iuup_cn_node.h \ + iuup_protocol.h \ $(NULL) diff --git a/include/osmocom/mgcp/debug.h b/include/osmocom/mgcp/debug.h index 7044c1eea..31057ad98 100644 --- a/include/osmocom/mgcp/debug.h +++ b/include/osmocom/mgcp/debug.h @@ -30,6 +30,7 @@ enum { DRTP, DE1, + DIUUP, Debug_LastEntry, }; diff --git a/include/osmocom/mgcp/iuup_cn_node.h b/include/osmocom/mgcp/iuup_cn_node.h new file mode 100644 index 000000000..ca69b4db7 --- /dev/null +++ b/include/osmocom/mgcp/iuup_cn_node.h @@ -0,0 +1,47 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* IuUP CN node, minimal implementation */ + +/* _____IuUP_CN_____ + * | | + * UE <--> RNC --PDU-> osmo_iuup_cn_rx_pdu() -+-> ---+-> rx_payload() + * | | | + * | <-PDU-- tx_msg() <-------------+-- <-+--- osmo_iuup_cn_tx_payload() + * | | + * ----------------- + */ + +#pragma once + +struct msgb; + +typedef int (*osmo_iuup_data_cb_t)(struct msgb *msg, void *node_priv); + +struct osmo_iuup_cn_cfg { + void *node_priv; + + /* When the IuUP peer sent a voice packet, the clean RTP without the IuUP header is fed to this + * callback. */ + osmo_iuup_data_cb_t rx_payload; + + /* IuUP handler requests that a PDU shall be sent to the IuUP peer (e.g. the RNC). + * It is guaranteed that the msgb->dst pointer is preserved or copied from the msgb that + * originated the request. */ + osmo_iuup_data_cb_t tx_msg; +}; + +struct osmo_iuup_cn { + struct osmo_iuup_cn_cfg cfg; + char *name; + uint8_t next_frame_nr; + int rtp_payload_type; +}; + +bool osmo_iuup_cn_is_iuup_init(struct msgb *msg); + +struct osmo_iuup_cn *osmo_iuup_cn_init(void *ctx, struct osmo_iuup_cn_cfg *cfg, + const char *name_fmt, ...); +void osmo_iuup_cn_free(struct osmo_iuup_cn *cn); + +int osmo_iuup_cn_tx_payload(struct osmo_iuup_cn *cn, struct msgb *payload); + +int osmo_iuup_cn_rx_pdu(struct osmo_iuup_cn *cn, struct msgb *pdu); diff --git a/include/osmocom/mgcp/iuup_protocol.h b/include/osmocom/mgcp/iuup_protocol.h new file mode 100644 index 000000000..f4aec1f8c --- /dev/null +++ b/include/osmocom/mgcp/iuup_protocol.h @@ -0,0 +1,117 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* IuUP protocol handling, minimal implementation */ + +#pragma once + +#include +#include + +#define OSMO_IUUP_HEADROOM 32 + +enum osmo_iuup_pdu_type { + OSMO_IUUP_PDU_DATA_WITH_CRC = 0, + OSMO_IUUP_PDU_CONTROL_PROCEDURE = 14, +}; + +enum osmo_iuup_acknack { + OSMO_IUUP_ACKNACK_PROCEDURE = 0, + OSMO_IUUP_ACKNACK_ACK = 1, + OSMO_IUUP_ACKNACK_NACK = 2, +}; + +enum osmo_iuup_procedure { + OSMO_IUUP_PROC_INITIALIZATION = 0, + OSMO_IUUP_PROC_RATE_CONTROL = 1, + OSMO_IUUP_PROC_TIME_ALIGNMENT = 2, + OSMO_IUUP_PROC_ERROR_EVENT = 3, +}; + +enum osmo_iuup_frame_good { + OSMO_IUUP_FRAME_GOOD = 0, + OSMO_IUUP_FRAME_BAD = 1, + OSMO_IUUP_FRAME_BAD_DUE_TO_RADIO = 2, +}; + +struct osmo_iuup_hdr_ctrl { +#if OSMO_IS_BIG_ENDIAN + uint8_t pdu_type:4, + ack_nack:2, + frame_nr:2; + uint8_t mode_version:4, + procedure:4; + uint8_t header_crc:6, + payload_crc_hi:2; + uint8_t payload_crc_lo; + uint8_t payload[0]; +#elif OSMO_IS_LITTLE_ENDIAN + uint8_t frame_nr:2, + ack_nack:2, + pdu_type:4; + uint8_t procedure:4, + mode_version:4; + uint8_t payload_crc_hi:2, + header_crc:6; + uint8_t payload_crc_lo; + uint8_t payload[0]; +#endif +} __attribute__((packed)); + +union osmo_iuup_hdr_ctrl_payload { + struct { +#if OSMO_IS_BIG_ENDIAN + uint8_t spare:3, + iptis_present:1, + subflows:3, + chain:1; +#elif OSMO_IS_LITTLE_ENDIAN + uint8_t spare:3, + iptis_present:1, + subflows:3, + chain:1; +#endif + } initialization; + + struct { +#if OSMO_IS_BIG_ENDIAN + uint8_t error_distance:2, + error_cause:6; +#elif OSMO_IS_LITTLE_ENDIAN + uint8_t error_cause:6, + error_distance:2; +#endif + } error_event; +}; + +extern const struct value_string osmo_iuup_error_cause_names[]; +static inline const char *osmo_iuup_error_cause_name(uint8_t val) +{ return get_value_string(osmo_iuup_error_cause_names, val); } + +struct osmo_iuup_hdr_data { +#if OSMO_IS_BIG_ENDIAN + uint8_t pdu_type:4, + frame_nr:4; + uint8_t frame_good:2, + rfci:6; + uint8_t header_crc:6, + payload_crc_hi:2; + uint8_t payload_crc_lo; +#elif OSMO_IS_LITTLE_ENDIAN + uint8_t frame_nr:4, + pdu_type:4; + uint8_t rfci:6, + frame_good:2; + uint8_t payload_crc_hi:2, + header_crc:6; + uint8_t payload_crc_lo; +#endif + uint8_t payload[0]; +} __attribute__((packed)); + +int osmo_iuup_classify(bool log_errors, + const char *log_label, + struct msgb *pdu, + struct osmo_iuup_hdr_ctrl **is_ctrl, + struct osmo_iuup_hdr_data **is_data); +bool osmo_iuup_is_init(struct msgb *pdu); +void osmo_iuup_make_init_ack(struct msgb *ack); +void osmo_iuup_set_checksums(uint8_t *iuup_header_and_payload, unsigned int header_and_payload_len); diff --git a/include/osmocom/mgcp/mgcp_conn.h b/include/osmocom/mgcp/mgcp_conn.h index 4f882e9e2..5fae13a1c 100644 --- a/include/osmocom/mgcp/mgcp_conn.h +++ b/include/osmocom/mgcp/mgcp_conn.h @@ -30,6 +30,8 @@ #include #include +struct osmo_iuup_cn; + #define LOGPCONN(conn, cat, level, fmt, args...) \ LOGPENDP((conn)->endp, cat, level, "CI:%s " fmt, \ (conn)->id, \ @@ -94,6 +96,8 @@ struct mgcp_conn_rtp { } osmux; struct rate_ctr_group *rate_ctr_group; + + struct osmo_iuup_cn *iuup; }; /*! MGCP connection (untyped) */ diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am index 91b2bf688..dd386ab7c 100644 --- a/src/libosmo-mgcp/Makefile.am +++ b/src/libosmo-mgcp/Makefile.am @@ -48,4 +48,6 @@ libosmo_mgcp_a_SOURCES = \ mgcp_ctrl.c \ mgcp_ratectr.c \ mgcp_e1.c \ + iuup_protocol.c \ + iuup_cn_node.c \ $(NULL) diff --git a/src/libosmo-mgcp/iuup_cn_node.c b/src/libosmo-mgcp/iuup_cn_node.c new file mode 100644 index 000000000..239a15964 --- /dev/null +++ b/src/libosmo-mgcp/iuup_cn_node.c @@ -0,0 +1,211 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* IuUP Core Network side protocol handling, minimal implementation */ + +/* + * (C) 2018 by sysmocom - s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +#define LOG_IUUP_CN(cn, level, fmt, args...) \ + LOGP(DIUUP, level, "(%s) " fmt, (cn)->name, ## args) + +struct osmo_iuup_cn *osmo_iuup_cn_init(void *ctx, struct osmo_iuup_cn_cfg *cfg, + const char *name_fmt, ...) +{ + va_list ap; + struct osmo_iuup_cn *cn = talloc_zero(ctx, struct osmo_iuup_cn); + OSMO_ASSERT(cn); + + cn->cfg = *cfg; + + if (!name_fmt) + name_fmt = "-"; + + va_start(ap, name_fmt); + cn->name = talloc_vasprintf(cn, name_fmt, ap); + va_end(ap); + + LOGP(DIUUP, LOGL_INFO, "(%s) Initializing IuUP node\n", cn->name); + + if (!osmo_identifier_valid(cn->name)) { + LOGP(DIUUP, LOGL_ERROR, "Attempting to set illegal id for IuUP CN instance: %s\n", + osmo_quote_str(cn->name, -1)); + talloc_free(cn); + return NULL; + } + + return cn; +} + +void osmo_iuup_cn_free(struct osmo_iuup_cn *cn) +{ + talloc_free(cn); +} + +static int rx_data(struct osmo_iuup_cn *cn, struct msgb *pdu, + struct osmo_iuup_hdr_data *hdr) +{ + /* Remove the IuUP bit from the middle of the buffer by writing the RTP header forward. */ + /* And append AMR 12.2k header "0xf03c". - AD HOC fix */ + unsigned int pre_hdr_len = ((uint8_t*)hdr) - pdu->data; + memmove(pdu->data + sizeof(*hdr) - 2, pdu->data, pre_hdr_len); + ((uint8_t*)hdr)[2] = 0xf0; + ((uint8_t*)hdr)[3] = 0x3c; + msgb_pull(pdu, sizeof(*hdr) - 2); + + LOGP(DIUUP, LOGL_DEBUG, "(%s) IuUP stripping IuUP header from RTP data\n", cn->name); + cn->cfg.rx_payload(pdu, cn->cfg.node_priv); + + return 0; +} + +static int tx_init_ack(struct osmo_iuup_cn *cn, struct msgb *src_pdu) +{ + /* Send Initialization Ack PDU back to the sender */ + int rc; + struct msgb *ack = msgb_alloc(4096, "IuUP Initialization Ack"); + OSMO_ASSERT(ack); + ack->dst = src_pdu->dst; + + /* Just copy the RTP header that was sent... TODO: tweak some RTP values?? */ + memcpy(msgb_put(ack, sizeof(struct rtp_hdr)), src_pdu->data, sizeof(struct rtp_hdr)); + + osmo_iuup_make_init_ack(ack); + + LOGP(DIUUP, LOGL_DEBUG, "(%s) Sending Initialization ACK %p\n", cn->name, cn->cfg.node_priv); + rc = cn->cfg.tx_msg(ack, cn->cfg.node_priv); + msgb_free(ack); + return rc; +} + +static int rx_control(struct osmo_iuup_cn *cn, struct msgb *pdu, + struct osmo_iuup_hdr_ctrl *hdr) +{ + switch (hdr->procedure) { + case OSMO_IUUP_PROC_INITIALIZATION: + switch (hdr->ack_nack) { + case OSMO_IUUP_ACKNACK_PROCEDURE: + LOGP(DIUUP, LOGL_INFO, "(%s) Rx IuUP Initialization, sending ACK\n", cn->name); + cn->rtp_payload_type = ((struct rtp_hdr*)pdu->data)->payload_type; + return tx_init_ack(cn, pdu); + + default: + LOGP(DIUUP, LOGL_DEBUG, "(%s) Rx IuUP Initialization, unhandled ack_nack = %d\n", + cn->name, hdr->ack_nack); + break; + } + /* Continue to log "unexpected procedure" below. */ + break; + + case OSMO_IUUP_PROC_ERROR_EVENT: + { + union osmo_iuup_hdr_ctrl_payload *p = (void*)hdr->payload; + LOGP(DIUUP, LOGL_ERROR, + "(%s) Rx IuUP Error Event: distance=%u, cause=%u=\"%s\"\n", + cn->name, p->error_event.error_distance, p->error_event.error_cause, + osmo_iuup_error_cause_name(p->error_event.error_cause)); + return 0; + } + + default: + break; + } + LOG_IUUP_CN(cn, LOGL_ERROR, + "Rx control PDU with unexpected procedure: 0x%x acknack=0x%x\n", + hdr->procedure, hdr->ack_nack); + return -EINVAL; +} + +/* Feed a received PDU to the IuUP CN node. This function takes ownership of the msgb, it must not be + * freed by the caller. */ +int osmo_iuup_cn_rx_pdu(struct osmo_iuup_cn *cn, struct msgb *pdu) +{ + struct osmo_iuup_hdr_ctrl *is_ctrl; + struct osmo_iuup_hdr_data *is_data; + int rc; + + rc = osmo_iuup_classify(true, cn->name, pdu, &is_ctrl, &is_data); + if (rc) + return rc; + + if (is_ctrl) + return rx_control(cn, pdu, is_ctrl); + if (is_data) + return rx_data(cn, pdu, is_data); + return rc; +} + +static uint8_t next_frame_nr(struct osmo_iuup_cn *cn) +{ + uint8_t frame_nr = cn->next_frame_nr; + cn->next_frame_nr = (frame_nr + 1) & 0x0f; + return frame_nr; +} + +/* Send this RTP packet to the IuUP peer: add IuUP header and call the tx_msg() to transmit the resulting + * message to the IuUP peer. + * Returns 0 on success, negative on error. */ +int osmo_iuup_cn_tx_payload(struct osmo_iuup_cn *cn, struct msgb *pdu) +{ + struct rtp_hdr *rtp_was, *rtp; + struct osmo_iuup_hdr_data *iuup_hdr; + + /* Splice an IuUP header in between RTP header and payload data */ + rtp_was = (void*)pdu->data; + + /* copy the RTP header part backwards by the size needed for the IuUP header */ + /* also strips 2 bytes from the front of RTP payload - AMR header - AD HOC fix */ + rtp = (void*)msgb_push(pdu, sizeof(*iuup_hdr) - 2); + memmove(rtp, rtp_was, sizeof(*rtp)); + + /* The IuUP side negotiated a payload type number to use during Initialization. The RTP packet going to the IuUP + * peer should reflect this payload_type. This should already have happened in mgcp_patch_pt(), but can't hurt + * to patch over it again. */ + rtp->payload_type = cn->rtp_payload_type; + + iuup_hdr = (void*)rtp->data; + + *iuup_hdr = (struct osmo_iuup_hdr_data){ + .pdu_type = OSMO_IUUP_PDU_DATA_WITH_CRC, + .frame_nr = next_frame_nr(cn), + .frame_good = OSMO_IUUP_FRAME_GOOD, + }; + + osmo_iuup_set_checksums((uint8_t*)iuup_hdr, pdu->tail - (uint8_t*)iuup_hdr); + LOGP(DIUUP, LOGL_DEBUG, "(%s) IuUP inserting IuUP header in RTP data (frame nr %u)\n", + cn->name, iuup_hdr->frame_nr); + + return cn->cfg.tx_msg(pdu, cn->cfg.node_priv); +} diff --git a/src/libosmo-mgcp/iuup_protocol.c b/src/libosmo-mgcp/iuup_protocol.c new file mode 100644 index 000000000..e32bc9759 --- /dev/null +++ b/src/libosmo-mgcp/iuup_protocol.c @@ -0,0 +1,286 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* IuUP Core Network side protocol, minimal implementation */ + +/* + * (C) 2018 by sysmocom - s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +/* Calculating two bytes of CRC is ok to do by a loop */ +static uint8_t header_crc6(const uint8_t *hdr) +{ + int bit; + /* Polynomial: D^6 + D^5 + D^3 + D^2 + D^1 + 1 + * that's 1101111 or 0x6f; + * align its lowest bit with a uint16_t's highest bit: */ + uint32_t polynomial = 0x6f << 15; // 00110111 10000000 00000000 + uint32_t remainder = ( ((uint32_t)hdr[0]) << 8 | hdr[1] ) << 6; + + for (bit = 15; bit >= 0; bit--) + { + if (remainder & (0x40 << bit)) + remainder ^= polynomial; + polynomial >>= 1; + } + + return remainder; +} + +/* + * Charles Michael Heard's CRC-10 code, from + * + * http://web.archive.org/web/20061005231950/http://cell-relay.indiana.edu/cell-relay/publications/software/CRC/crc10.html + * + * with the CRC table initialized with values computed by + * his "gen_byte_crc10_table()" routine, rather than by calling that + * routine at run time, and with various data type cleanups. + */ +static const uint16_t byte_crc10_table[256] = { + 0x0000, 0x0233, 0x0255, 0x0066, 0x0299, 0x00aa, 0x00cc, 0x02ff, + 0x0301, 0x0132, 0x0154, 0x0367, 0x0198, 0x03ab, 0x03cd, 0x01fe, + 0x0031, 0x0202, 0x0264, 0x0057, 0x02a8, 0x009b, 0x00fd, 0x02ce, + 0x0330, 0x0103, 0x0165, 0x0356, 0x01a9, 0x039a, 0x03fc, 0x01cf, + 0x0062, 0x0251, 0x0237, 0x0004, 0x02fb, 0x00c8, 0x00ae, 0x029d, + 0x0363, 0x0150, 0x0136, 0x0305, 0x01fa, 0x03c9, 0x03af, 0x019c, + 0x0053, 0x0260, 0x0206, 0x0035, 0x02ca, 0x00f9, 0x009f, 0x02ac, + 0x0352, 0x0161, 0x0107, 0x0334, 0x01cb, 0x03f8, 0x039e, 0x01ad, + 0x00c4, 0x02f7, 0x0291, 0x00a2, 0x025d, 0x006e, 0x0008, 0x023b, + 0x03c5, 0x01f6, 0x0190, 0x03a3, 0x015c, 0x036f, 0x0309, 0x013a, + 0x00f5, 0x02c6, 0x02a0, 0x0093, 0x026c, 0x005f, 0x0039, 0x020a, + 0x03f4, 0x01c7, 0x01a1, 0x0392, 0x016d, 0x035e, 0x0338, 0x010b, + 0x00a6, 0x0295, 0x02f3, 0x00c0, 0x023f, 0x000c, 0x006a, 0x0259, + 0x03a7, 0x0194, 0x01f2, 0x03c1, 0x013e, 0x030d, 0x036b, 0x0158, + 0x0097, 0x02a4, 0x02c2, 0x00f1, 0x020e, 0x003d, 0x005b, 0x0268, + 0x0396, 0x01a5, 0x01c3, 0x03f0, 0x010f, 0x033c, 0x035a, 0x0169, + 0x0188, 0x03bb, 0x03dd, 0x01ee, 0x0311, 0x0122, 0x0144, 0x0377, + 0x0289, 0x00ba, 0x00dc, 0x02ef, 0x0010, 0x0223, 0x0245, 0x0076, + 0x01b9, 0x038a, 0x03ec, 0x01df, 0x0320, 0x0113, 0x0175, 0x0346, + 0x02b8, 0x008b, 0x00ed, 0x02de, 0x0021, 0x0212, 0x0274, 0x0047, + 0x01ea, 0x03d9, 0x03bf, 0x018c, 0x0373, 0x0140, 0x0126, 0x0315, + 0x02eb, 0x00d8, 0x00be, 0x028d, 0x0072, 0x0241, 0x0227, 0x0014, + 0x01db, 0x03e8, 0x038e, 0x01bd, 0x0342, 0x0171, 0x0117, 0x0324, + 0x02da, 0x00e9, 0x008f, 0x02bc, 0x0043, 0x0270, 0x0216, 0x0025, + 0x014c, 0x037f, 0x0319, 0x012a, 0x03d5, 0x01e6, 0x0180, 0x03b3, + 0x024d, 0x007e, 0x0018, 0x022b, 0x00d4, 0x02e7, 0x0281, 0x00b2, + 0x017d, 0x034e, 0x0328, 0x011b, 0x03e4, 0x01d7, 0x01b1, 0x0382, + 0x027c, 0x004f, 0x0029, 0x021a, 0x00e5, 0x02d6, 0x02b0, 0x0083, + 0x012e, 0x031d, 0x037b, 0x0148, 0x03b7, 0x0184, 0x01e2, 0x03d1, + 0x022f, 0x001c, 0x007a, 0x0249, 0x00b6, 0x0285, 0x02e3, 0x00d0, + 0x011f, 0x032c, 0x034a, 0x0179, 0x0386, 0x01b5, 0x01d3, 0x03e0, + 0x021e, 0x002d, 0x004b, 0x0278, 0x0087, 0x02b4, 0x02d2, 0x00e1 +}; + +static uint16_t crc10(uint16_t crc10_accum, const uint8_t *payload, unsigned int payload_len) +{ + int i; + + for (i = 0; i < payload_len; i++) { + crc10_accum = ((crc10_accum << 8) & 0x300) + ^ byte_crc10_table[(crc10_accum >> 2) & 0xff] + ^ payload[i]; + } + return crc10_accum; +} + +/* When a payload of a multiple of bytes has run through, we need to still feed 10 bits of zeros into the + * CRC10 to get the payload's checksum result that we can send to a peer. That can't be done with above + * table, because it acts as if full 16 bits are fed. This stops after 10 bits. */ +static uint16_t crc10_remainder(uint16_t crc10_accum) +{ + int bit; + /* Polynomial: D^10 + D^9 + D^5 + D^4 + D^1 + 1 + * that's 11000110011 or 0x633; + * align its lowest bit with a 10bit value's highest bit: */ + uint32_t polynomial = 0x633 << 9; // 1100 01100110 00000000 + uint32_t remainder = ((uint32_t)crc10_accum) << 10; + + /* Run on 10 bits */ + for (bit = 9; bit >= 0; bit--) + { + if (remainder & ((1 << 10) << bit)) + remainder ^= polynomial; + polynomial >>= 1; + } + + return remainder & 0x3ff; +} + +static uint16_t payload_crc10(const uint8_t *payload, unsigned int payload_len) +{ + uint16_t crc10_accum = crc10(0, payload, payload_len); + return crc10_remainder(crc10_accum); +} + +/* Given an IuUP PDU data block, write the correct header and payload CRC checksums at the right places. + */ +void osmo_iuup_set_checksums(uint8_t *iuup_header_and_payload, unsigned int header_and_payload_len) +{ + /* For both data and ctrl, the checksums and payload are at the same offset */ + struct osmo_iuup_hdr_data *hdr = (void*)iuup_header_and_payload; + uint16_t crc; + unsigned int payload_len; + + hdr->header_crc = header_crc6(iuup_header_and_payload); + + payload_len = iuup_header_and_payload + header_and_payload_len - hdr->payload; + crc = payload_crc10(hdr->payload, payload_len); + hdr->payload_crc_hi = (crc >> 8) & 0x3; + hdr->payload_crc_lo = crc & 0xff; + +} + +/* Validate minimum message sizes, IuUP PDU type, header- and payload checksums. If it is a Control + * Procedure PDU, return the header position in is_ctrl, if it is a Data PDU, return the header position + * in is_data. If log_errors is true, log on DIUUP with the given log label for context. Return NULL in + * both is_ctrl and is_data, and return a negative error code if the PDU could not be identified as a + * valid RTP PDU containing an IuUP part. */ +int osmo_iuup_classify(bool log_errors, + const char *log_label, + struct msgb *pdu, + struct osmo_iuup_hdr_ctrl **is_ctrl, + struct osmo_iuup_hdr_data **is_data) +{ + struct rtp_hdr *rtp = (void*)pdu->data; + struct osmo_iuup_hdr_ctrl *hdr = (void*)rtp->data; + unsigned int payload_len; + uint16_t crc_calculated; + uint16_t crc_from_peer; + +#define ERR(fmt, args...) do { \ + if (log_errors) \ + LOGP(DIUUP, LOGL_ERROR, "(%s) " fmt, log_label? : "-", ## args); \ + return -EINVAL; \ + } while (0) + + if (is_ctrl) + *is_ctrl = NULL; + if (is_data) + *is_data = NULL; + + /* We need at least a header of 4 bytes. The osmo_iuup_hdr_ctrl already includes a byte of + * payload, so use osmo_iuup_hdr_data to check the minimum here. */ + if (pdu->len < (sizeof(*rtp) + sizeof(struct osmo_iuup_hdr_data))) + ERR("IuUP PDU too short: %u\n", pdu->len); + + /* Let's not validate checksums if the header type isn't sane */ + switch (hdr->pdu_type) { + case OSMO_IUUP_PDU_DATA_WITH_CRC: + /* If the caller isn't interested in data PDUs, cut short here. */ + if (!is_data) + return 0; + break; + case OSMO_IUUP_PDU_CONTROL_PROCEDURE: + /* If the caller isn't interested in control PDUs, cut short here. */ + if (!is_ctrl) + return 0; + if (pdu->len < (sizeof(*rtp) + sizeof(struct osmo_iuup_hdr_ctrl))) + ERR("IuUP control PDU too short: %u\n", pdu->len); + break; + default: + ERR("IuUP with invalid type: %u\n", hdr->pdu_type); + } + + /* For both data and ctrl, the checksums and payload are at the same offset */ + + crc_calculated = header_crc6((uint8_t*)hdr); + if (crc_calculated != hdr->header_crc) + ERR("IuUP PDU with invalid header CRC (peer sent 0x%x, calculated 0x%x)\n", + hdr->header_crc, crc_calculated); + + payload_len = pdu->tail - hdr->payload; + crc_calculated = payload_crc10(hdr->payload, payload_len); + crc_from_peer = (((uint16_t)hdr->payload_crc_hi) << 8) | hdr->payload_crc_lo; + if (crc_from_peer != crc_calculated) + ERR("IuUP PDU with invalid payload CRC (peer sent 0x%x, calculated 0x%x)\n", + crc_from_peer, crc_calculated); + + switch (hdr->pdu_type) { + case OSMO_IUUP_PDU_DATA_WITH_CRC: + if (is_data) + *is_data = (void*)hdr; + return 0; + case OSMO_IUUP_PDU_CONTROL_PROCEDURE: + if (is_ctrl) + *is_ctrl = hdr; + return 0; + default: + ERR("IuUP with invalid type: %u\n", hdr->pdu_type); + } +#undef ERR +} + +/* Return true if this RTP packet contains an IuUP Initialization header (detect IuUP peer). */ +bool osmo_iuup_is_init(struct msgb *pdu) +{ + struct osmo_iuup_hdr_ctrl *is_ctrl; + osmo_iuup_classify(false, NULL, pdu, &is_ctrl, NULL); + return is_ctrl + && is_ctrl->procedure == OSMO_IUUP_PROC_INITIALIZATION + && is_ctrl->ack_nack == OSMO_IUUP_ACKNACK_PROCEDURE; +} + +/* Append an IuUP Initialization ACK message */ +void osmo_iuup_make_init_ack(struct msgb *ack) +{ + /* Send Initialization Ack PDU back to the sender */ + struct osmo_iuup_hdr_ctrl *hdr; + OSMO_ASSERT(ack); + + hdr = (void*)msgb_put(ack, sizeof(*hdr)); + + *hdr = (struct osmo_iuup_hdr_ctrl){ + .pdu_type = OSMO_IUUP_PDU_CONTROL_PROCEDURE, + .ack_nack = OSMO_IUUP_ACKNACK_ACK, + .procedure = OSMO_IUUP_PROC_INITIALIZATION, + }; + + osmo_iuup_set_checksums((uint8_t*)hdr, sizeof(*hdr)); +} + +const struct value_string osmo_iuup_error_cause_names[] = { + { 0, "CRC error of frame header" }, + { 1, "CRC error of frame payload" }, + { 2, "Unexpected frame number" }, + { 3, "Frame loss" }, + { 4, "PDU type unknown" }, + { 5, "Unknown procedure" }, + { 6, "Unknown reserved value" }, + { 7, "Unknown field" }, + { 8, "Frame too short" }, + { 9, "Missing fields" }, + { 16, "Unexpected PDU type" }, + { 17, "spare" }, + { 18, "Unexpected procedure" }, + { 19, "Unexpected RFCI" }, + { 20, "Unexpected value" }, + { 42, "Initialisation failure" }, + { 43, "Initialisation failure (network error, timer expiry)" }, + { 44, "Initialisation failure (Iu UP function error, repeated NACK)" }, + { 45, "Rate control failure" }, + { 46, "Error event failure" }, + { 47, "Time Alignment not supported" }, + { 48, "Requested Time Alignment not possible" }, + { 49, "Iu UP Mode version not supported" }, + {} +}; diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c index b4522019d..ebefc7835 100644 --- a/src/libosmo-mgcp/mgcp_network.c +++ b/src/libosmo-mgcp/mgcp_network.c @@ -48,6 +48,8 @@ #include #include #include +#include +#include #define RTP_SEQ_MOD (1 << 16) @@ -541,10 +543,29 @@ static int mgcp_patch_pt(struct mgcp_conn_rtp *conn_src, rtp_hdr = (struct rtp_hdr *)msgb_data(msg); - pt_in = rtp_hdr->payload_type; - pt_out = mgcp_codec_pt_translate(conn_src, conn_dst, pt_in); - if (pt_out < 0) - return -EINVAL; + if (conn_src->iuup) { + /* The source is an IuUP payload. We have received a dynamic payload type number on the IuUP side, and + * towards the pure RTP side it should go out as "AMR/8000". Make sure that the payload type number in + * the RTP packet matches the a=rtpmap:N payload type number configured for AMR. */ + const struct mgcp_rtp_codec *amr_codec = mgcp_codec_pt_find_by_subtype_name(conn_dst, "AMR", 0); + + if (!amr_codec) { + /* There is no AMR codec configured on the outgoing conn. */ + return -EINVAL; + } + + pt_out = amr_codec->payload_type; + } else if (conn_dst->iuup) { + /* The destination is an IuUP payload. Use whatever payload number was negotiated during IuUP + * Initialization. */ + pt_out = conn_dst->iuup->rtp_payload_type; + } else { + /* Both sides are normal RTP payloads. Consult the rtpmap settings received by SDP. */ + pt_in = rtp_hdr->payload_type; + pt_out = mgcp_codec_pt_translate(conn_src, conn_dst, pt_in); + if (pt_out < 0) + return -EINVAL; + } rtp_hdr->payload_type = (uint8_t) pt_out; return 0; @@ -981,6 +1002,7 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct osmo_sockaddr *addr forward_data(rtp_end->rtp.fd, &conn_src->tap_out, msg); +#if 0 /* FIXME: HACK HACK HACK. See OS#2459. * The ip.access nano3G needs the first RTP payload's first two bytes to read hex * 'e400', or it will reject the RAB assignment. It seems to not harm other femto @@ -998,9 +1020,13 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct osmo_sockaddr *addr " to fake an IuUP Initialization Ack\n"); } } +#endif - len = mgcp_udp_send(rtp_end->rtp.fd, &rtp_end->addr, rtp_end->rtp_port, - (char*)msgb_data(msg), msgb_length(msg)); + if (conn_dst->iuup) + len = osmo_iuup_cn_tx_payload(conn_dst->iuup, msg); + else + len = mgcp_udp_send(rtp_end->rtp.fd, &rtp_end->addr, rtp_end->rtp_port, + (char*)msgb_data(msg), msgb_length(msg)); if (len <= 0) return len; @@ -1179,6 +1205,8 @@ static int check_rtcp(struct mgcp_conn_rtp *conn_src, struct msgb *msg) static int check_rtp(struct mgcp_conn_rtp *conn_src, struct msgb *msg) { size_t min_size = sizeof(struct rtp_hdr); + if (conn_src->iuup) + min_size += sizeof(struct osmo_iuup_hdr_data); if (msgb_length(msg) < min_size) { LOG_CONN_RTP(conn_src, LOGL_ERROR, "RTP packet too short (%u < %zu)\n", msgb_length(msg), min_size); @@ -1395,6 +1423,68 @@ static bool is_dummy_msg(enum rtp_proto proto, struct msgb *msg) return msgb_length(msg) == 1 && msgb_data(msg)[0] == MGCP_DUMMY_LOAD; } +/* IuUP CN node has stripped an IuUP header and forwards RTP data to distribute to the peers. */ +int iuup_rx_payload(struct msgb *msg, void *node_priv) +{ + struct osmo_rtp_msg_ctx *mc = OSMO_RTP_MSG_CTX(msg); + struct mgcp_conn_rtp *conn_src = mc->conn_src; + LOG_CONN_RTP(conn_src, LOGL_DEBUG, "iuup_rx_payload(%u bytes)\n", msgb_length(msg)); + return rx_rtp(msg); +} + +/* IuUP CN node has composed a message that contains an IuUP header and asks us to send to the IuUP peer. + */ +int iuup_tx_msg(struct msgb *msg, void *node_priv) +{ + const struct in_addr zero_addr = {}; + struct osmo_rtp_msg_ctx *mc = OSMO_RTP_MSG_CTX(msg); + struct mgcp_conn_rtp *conn_src = mc->conn_src; + struct mgcp_conn_rtp *conn_dst = node_priv; + struct sockaddr_in *from_addr = mc->from_addr; + struct mgcp_rtp_end *rtp_end = &conn_dst->end; + struct in_addr to_addr = rtp_end->addr; + uint16_t to_port = rtp_end->rtp_port; + + if (conn_src == conn_dst + && !memcmp(&zero_addr, &to_addr, sizeof(zero_addr)) && !to_port) { + LOG_CONN_RTP(conn_dst, LOGL_DEBUG, "iuup_tx_msg(): direct IuUP reply\n"); + /* IuUP wants to send a message back to the same peer that sent an RTP package, but there + * is no address configured for that peer yet. It is probably an IuUP Initialization ACK + * reply. Use the sender address to send the reply. + * + * During 3G RAB Assignment, a 3G cell might first probe the MGW and expect an IuUP + * Initialization ACK before it replies to the MSC with a successful RAB Assignment; only + * after that reply does MSC officially know which RTP address+port the 3G cell wants to + * use and can tell this MGW about it, so this "loopback" is, for some 3G cells, the only + * chance we have to get a successful RAB Assignment done (particularly the nano3G does + * this). */ + to_addr = from_addr->sin_addr; + to_port = from_addr->sin_port; + } + + LOG_CONN_RTP(conn_dst, LOGL_DEBUG, "iuup_tx_msg(%u bytes) to %s:%u\n", msgb_length(msg), + inet_ntoa(to_addr), ntohs(to_port)); + + return mgcp_udp_send(rtp_end->rtp.fd, &to_addr, to_port, (char*)msgb_data(msg), msgb_length(msg)); +} + +static void iuup_init(struct mgcp_conn_rtp *conn_src) +{ + struct osmo_iuup_cn_cfg cfg = { + .node_priv = conn_src, + .rx_payload = iuup_rx_payload, + .tx_msg = iuup_tx_msg, + }; + + if (conn_src->iuup) { + LOG_CONN_RTP(conn_src, LOGL_NOTICE, "Rx IuUP init, but already initialized. Ignoring.\n"); + return; + } + + conn_src->iuup = osmo_iuup_cn_init(conn_src->conn, &cfg, "endp_%s_conn_%s", + conn_src->conn->endp->name, conn_src->conn->id); +} + /* Handle incoming RTP data from NET */ static int rtp_data_net(struct osmo_fd *fd, unsigned int what) { @@ -1413,7 +1503,8 @@ static int rtp_data_net(struct osmo_fd *fd, unsigned int what) int ret; enum rtp_proto proto; struct osmo_rtp_msg_ctx *mc; - struct msgb *msg = msgb_alloc(RTP_BUF_SIZE, "RTP-rx"); + struct msgb *msg = msgb_alloc_headroom(RTP_BUF_SIZE + OSMO_IUUP_HEADROOM, + OSMO_IUUP_HEADROOM, "RTP-rx"); int rc; conn_src = (struct mgcp_conn_rtp *)fd->data; @@ -1474,7 +1565,13 @@ static int rtp_data_net(struct osmo_fd *fd, unsigned int what) /* Forward a copy of the RTP data to a debug ip/port */ forward_data(fd->fd, &conn_src->tap_in, msg); - rc = rx_rtp(msg); + if (proto == MGCP_PROTO_RTP && osmo_iuup_is_init(msg)) + iuup_init(conn_src); + + if (conn_src->iuup && proto == MGCP_PROTO_RTP) + rc = osmo_iuup_cn_rx_pdu(conn_src->iuup, msg); + else + rc = rx_rtp(msg); out: msgb_free(msg); diff --git a/src/osmo-mgw/mgw_main.c b/src/osmo-mgw/mgw_main.c index 413917224..55caadf3c 100644 --- a/src/osmo-mgw/mgw_main.c +++ b/src/osmo-mgw/mgw_main.c @@ -280,6 +280,12 @@ static const struct log_info_cat log_categories[] = { .color = "\033[1;31m", .enabled = 1,.loglevel = LOGL_NOTICE, }, + [DIUUP] = { + .name = "DIUUP", + .description = "IuUP within RTP stream handling", + .color = "\033[1;31m", + .enabled = 1,.loglevel = LOGL_NOTICE, + }, }; const struct log_info log_info = { diff --git a/tests/Makefile.am b/tests/Makefile.am index 49a659fc8..302fa52fb 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,7 @@ SUBDIRS = \ mgcp_client \ mgcp \ + iuup \ $(NULL) # The `:;' works around a Bash 3.2 bug when the output is not writeable. diff --git a/tests/iuup/Makefile.am b/tests/iuup/Makefile.am new file mode 100644 index 000000000..12806b1e2 --- /dev/null +++ b/tests/iuup/Makefile.am @@ -0,0 +1,45 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_srcdir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + iuup_test.ok \ + iuup_test.err \ + $(NULL) + +noinst_PROGRAMS = \ + iuup_test \ + $(NULL) + +iuup_test_SOURCES = \ + iuup_test.c \ + $(NULL) + +iuup_test_LDADD = \ + $(top_builddir)/src/libosmo-mgcp/libosmo-mgcp.a \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBRARY_DL) \ + $(LIBOSMONETIF_LIBS) \ + -lm \ + $(NULL) + +update_exp: + $(builddir)/iuup_test >$(srcdir)/iuup_test.ok 2>$(srcdir)/iuup_test.err diff --git a/tests/iuup/iuup_test.c b/tests/iuup/iuup_test.c new file mode 100644 index 000000000..5240430ec --- /dev/null +++ b/tests/iuup/iuup_test.c @@ -0,0 +1,156 @@ +#include +#include + +#include +#include +#include + +#include +#include + +void *ctx = NULL; + +static const char *dump(struct msgb *msg) +{ + return osmo_hexdump_nospc(msgb_data(msg), msgb_length(msg)); +} + +struct msgb *msgb_from_hex(const char *label, const char *hex) +{ + struct msgb *msg = msgb_alloc_headroom(4096 + OSMO_IUUP_HEADROOM, + OSMO_IUUP_HEADROOM, label); + unsigned char *rc; + msg->l2h = msgb_data(msg); + rc = msgb_put(msg, osmo_hexparse(hex, msgb_data(msg), msgb_tailroom(msg))); + OSMO_ASSERT(rc == msg->l2h); + return msg; +} + +const char *expect_rx_payload = NULL; +int rx_payload(struct msgb *msg, void *node_priv) +{ + printf("rx_payload() invoked by iuup_cn!\n"); + printf(" [IuUP] -RTP->\n"); + printf("%s\n", dump(msg)); + printf("node_priv=%p\n", node_priv); + if (!expect_rx_payload) { + printf("ERROR: did not expect rx_payload()\n"); + exit(-1); + } else if (strcmp(expect_rx_payload, dump(msg))) { + printf("ERROR: mismatches expected msg %s\n", expect_rx_payload); + exit(-1); + } else + printf("ok: matches expected msg\n"); + expect_rx_payload = NULL; + return 0; +} + +const char *expect_tx_msg = NULL; +int tx_msg(struct msgb *msg, void *node_priv) +{ + printf("tx_msg() invoked by iuup_cn!\n"); + printf(" <-PDU- [IuUP]\n"); + printf("%s\n", dump(msg)); + printf("node_priv=%p\n", node_priv); + if (!expect_tx_msg) { + printf("ERROR: did not expect tx_msg()\n"); + exit(-1); + } else if (strcmp(expect_tx_msg, dump(msg))) { + printf("ERROR: mismatches expected msg %s\n", expect_tx_msg); + exit(-1); + } else + printf("ok: matches expected msg\n"); + expect_tx_msg = NULL; + return 0; +} + +static int rx_pdu(struct osmo_iuup_cn *cn, struct msgb *msg) +{ + int rc; + printf(" -PDU-> [IuUP]\n"); + printf("%s\n", dump(msg)); + rc = osmo_iuup_cn_rx_pdu(cn, msg); + printf("rc=%d\n", rc); + return rc; +} + +static int tx_payload(struct osmo_iuup_cn *cn, struct msgb *msg) +{ + int rc; + printf(" [IuUP] <-RTP-\n"); + printf("%s\n", dump(msg)); + rc = osmo_iuup_cn_tx_payload(cn, msg); + printf("rc=%d\n", rc); + return rc; +} + +void test_cn_session() +{ + void *node_priv = (void*)0x2342; + + struct osmo_iuup_cn_cfg cfg = { + .node_priv = node_priv, + .rx_payload = rx_payload, + .tx_msg = tx_msg, + }; + + struct osmo_iuup_cn *cn = osmo_iuup_cn_init(ctx, &cfg, __func__); + OSMO_ASSERT(cn); + + printf("\nSend IuUP Initialization. Expecting direct tx_msg() of the Initialization Ack\n"); + expect_tx_msg = "8060dc5219495e3f00010111" /* RTP header */ + "e4002400"; /* IuUP Init Ack */ + rx_pdu(cn, + msgb_from_hex("IuUP-Init", + "8060dc5219495e3f00010111" /* <- RTP header */ + "e000df99" /* <- IuUP header */ + "160051673c01270000820000001710000100" /* IuUP params */)); + +#define RTP_HEADER "8060944c6256042c00010102" +#define IUUP_HEADER "0100e2b3" +#define RTP_PAYLOAD "6cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0" + printf("\nReceive payload encapsulated in IuUP. Expecting rx_payload() of just RTP packet\n"); + printf("i.e. should strip away " IUUP_HEADER "\n"); + expect_rx_payload = RTP_HEADER "f03c" RTP_PAYLOAD; + rx_pdu(cn, + msgb_from_hex("IuUP-Data", + RTP_HEADER IUUP_HEADER RTP_PAYLOAD)); + + printf("\nTransmit RTP. Expecting tx_msg() with inserted IuUP header\n"); + expect_tx_msg = RTP_HEADER "000002b3" RTP_PAYLOAD; + tx_payload(cn, + msgb_from_hex("RTP data", RTP_HEADER "f03c" RTP_PAYLOAD)); + + printf("\nMore RTP, each time the Frame Nr advances, causing a new header CRC.\n"); + expect_tx_msg = RTP_HEADER "0100e2b3" RTP_PAYLOAD; + tx_payload(cn, + msgb_from_hex("RTP data", RTP_HEADER "f03c" RTP_PAYLOAD)); + expect_tx_msg = RTP_HEADER "02007eb3" RTP_PAYLOAD; + tx_payload(cn, + msgb_from_hex("RTP data", RTP_HEADER "f03c" RTP_PAYLOAD)); + expect_tx_msg = RTP_HEADER "03009eb3" RTP_PAYLOAD; + tx_payload(cn, + msgb_from_hex("RTP data", RTP_HEADER "f03c" RTP_PAYLOAD)); + + printf("All done.\n"); +} + +static const struct log_info_cat log_categories[] = { +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(void) +{ + ctx = talloc_named_const(NULL, 0, __FILE__); + void *msgb_ctx = msgb_talloc_ctx_init(ctx, 0); + osmo_init_logging2(ctx, &log_info); + + test_cn_session(); + + talloc_free(msgb_ctx); + return 0; +} diff --git a/tests/iuup/iuup_test.err b/tests/iuup/iuup_test.err new file mode 100644 index 000000000..e69de29bb diff --git a/tests/iuup/iuup_test.ok b/tests/iuup/iuup_test.ok new file mode 100644 index 000000000..8c473d620 --- /dev/null +++ b/tests/iuup/iuup_test.ok @@ -0,0 +1,58 @@ + +Send IuUP Initialization. Expecting direct tx_msg() of the Initialization Ack + -PDU-> [IuUP] +8060dc5219495e3f00010111e000df99160051673c01270000820000001710000100 +tx_msg() invoked by iuup_cn! + <-PDU- [IuUP] +8060dc5219495e3f00010111e4002400 +node_priv=0x2342 +ok: matches expected msg +rc=0 + +Receive payload encapsulated in IuUP. Expecting rx_payload() of just RTP packet +i.e. should strip away 0100e2b3 + -PDU-> [IuUP] +8060944c6256042c000101020100e2b36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +rx_payload() invoked by iuup_cn! + [IuUP] -RTP-> +8060944c6256042c00010102f03c6cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +node_priv=0x2342 +ok: matches expected msg +rc=0 + +Transmit RTP. Expecting tx_msg() with inserted IuUP header + [IuUP] <-RTP- +8060944c6256042c00010102f03c6cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +tx_msg() invoked by iuup_cn! + <-PDU- [IuUP] +8060944c6256042c00010102000002b36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +node_priv=0x2342 +ok: matches expected msg +rc=0 + +More RTP, each time the Frame Nr advances, causing a new header CRC. + [IuUP] <-RTP- +8060944c6256042c00010102f03c6cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +tx_msg() invoked by iuup_cn! + <-PDU- [IuUP] +8060944c6256042c000101020100e2b36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +node_priv=0x2342 +ok: matches expected msg +rc=0 + [IuUP] <-RTP- +8060944c6256042c00010102f03c6cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +tx_msg() invoked by iuup_cn! + <-PDU- [IuUP] +8060944c6256042c0001010202007eb36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +node_priv=0x2342 +ok: matches expected msg +rc=0 + [IuUP] <-RTP- +8060944c6256042c00010102f03c6cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +tx_msg() invoked by iuup_cn! + <-PDU- [IuUP] +8060944c6256042c0001010203009eb36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0 +node_priv=0x2342 +ok: matches expected msg +rc=0 +All done. diff --git a/tests/testsuite.at b/tests/testsuite.at index 3585bf05c..0c3f80289 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -13,3 +13,10 @@ AT_KEYWORDS([mgcp]) cat $abs_srcdir/mgcp/mgcp_test.ok > expout AT_CHECK([$abs_top_builddir/tests/mgcp/mgcp_test], [], [expout], [ignore]) AT_CLEANUP + +AT_SETUP([iuup]) +AT_KEYWORDS([iuup]) +cat $abs_srcdir/iuup/iuup_test.ok > expout +cat $abs_srcdir/iuup/iuup_test.err > experr +AT_CHECK([$abs_top_builddir/tests/iuup/iuup_test], [], [expout], [experr]) +AT_CLEANUP