Merge branch 'zecke/mgcp-rtp-statistics'
diff --git a/openbsc/include/openbsc/mgcp.h b/openbsc/include/openbsc/mgcp.h
index 751943a..d618f3c 100644
--- a/openbsc/include/openbsc/mgcp.h
+++ b/openbsc/include/openbsc/mgcp.h
@@ -168,6 +168,7 @@
 int mgcp_endpoints_allocate(struct mgcp_trunk_config *cfg);
 void mgcp_free_endp(struct mgcp_endpoint *endp);
 int mgcp_reset_transcoder(struct mgcp_config *cfg);
+void mgcp_format_stats(struct mgcp_endpoint *endp, char *stats, size_t size);
 
 /*
  * format helper functions
diff --git a/openbsc/include/openbsc/mgcp_internal.h b/openbsc/include/openbsc/mgcp_internal.h
index 6e0451e..025b813 100644
--- a/openbsc/include/openbsc/mgcp_internal.h
+++ b/openbsc/include/openbsc/mgcp_internal.h
@@ -1,8 +1,8 @@
 /* MGCP Private Data */
 
 /*
- * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2009-2011 by On-Waves
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
  * All Rights Reserved
  *
  * This program is free software; you can redistribute it and/or modify
@@ -46,16 +46,22 @@
 
 	uint32_t orig_ssrc;
 	uint32_t ssrc;
-	uint16_t seq_no;
-	int lost_no;
+
+	uint16_t base_seq;
+	uint16_t max_seq;
 	int seq_offset;
+	int cycles;
+
 	uint32_t last_timestamp;
 	int32_t  timestamp_offset;
+	uint32_t jitter;
+	int32_t transit;
 };
 
 struct mgcp_rtp_end {
 	/* statistics */
 	unsigned int packets;
+	unsigned int octets;
 	struct in_addr addr;
 
 	/* in network byte order */
@@ -147,5 +153,9 @@
 struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index);
 struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index);
 
+void mgcp_state_calc_loss(struct mgcp_rtp_state *s, struct mgcp_rtp_end *,
+			uint32_t *expected, int *loss);
+uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *);
+
 
 #endif
diff --git a/openbsc/src/libmgcp/mgcp_network.c b/openbsc/src/libmgcp/mgcp_network.c
index 8824dc8..03d0f35 100644
--- a/openbsc/src/libmgcp/mgcp_network.c
+++ b/openbsc/src/libmgcp/mgcp_network.c
@@ -2,8 +2,8 @@
 /* The protocol implementation */
 
 /*
- * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2009-2011 by On-Waves
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
  * All Rights Reserved
  *
  * This program is free software; you can redistribute it and/or modify
@@ -40,6 +40,7 @@
 /* attempt to determine byte order */
 #include <sys/param.h>
 #include <limits.h>
+#include <time.h>
 
 #ifndef __BYTE_ORDER
 # ifdef __APPLE__
@@ -73,6 +74,10 @@
 	uint32_t ssrc;
 } __attribute__((packed));
 
+#define RTP_SEQ_MOD		(1 << 16)
+#define RTP_MAX_DROPOUT		3000
+#define RTP_MAX_MISORDER	100
+
 
 enum {
 	DEST_NETWORK = 0,
@@ -87,6 +92,29 @@
 #define DUMMY_LOAD 0x23
 
 
+/**
+ * This does not need to be a precision timestamp and
+ * is allowed to wrap quite fast. The returned value is
+ * milli seconds now.
+ */
+uint32_t get_current_ts(void)
+{
+	struct timespec tp;
+	uint64_t ret;
+
+	memset(&tp, 0, sizeof(tp));
+	if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
+		LOGP(DMGCP, LOGL_NOTICE,
+			"Getting the clock failed.\n");
+
+	/* convert it to useconds */
+	ret = tp.tv_sec;
+	ret *= 1000;
+	ret += tp.tv_nsec / 1000 / 1000;
+
+	return ret;
+}
+
 static int udp_send(int fd, struct in_addr *addr, int port, char *buf, int len)
 {
 	struct sockaddr_in out;
@@ -105,10 +133,20 @@
 			endp->net_end.rtp_port, buf, 1);
 }
 
+/**
+ * The RFC 3550 Appendix A assumes there are multiple sources but
+ * some of the supported endpoints (e.g. the nanoBTS) can only handle
+ * one source and this code will patch packages to appear as if there
+ * is only one source.
+ * There is also no probation period for new sources. Every package
+ * we receive will be seen as a switch in streams.
+ */
 static void patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state,
 			    int payload, struct sockaddr_in *addr, char *data, int len)
 {
-	uint16_t seq;
+	uint32_t arrival_time;
+	int32_t transit, d;
+	uint16_t seq, udelta;
 	uint32_t timestamp;
 	struct rtp_hdr *rtp_hdr;
 
@@ -118,15 +156,19 @@
 	rtp_hdr = (struct rtp_hdr *) data;
 	seq = ntohs(rtp_hdr->sequence);
 	timestamp = ntohl(rtp_hdr->timestamp);
+	arrival_time = get_current_ts();
 
 	if (!state->initialized) {
-		state->seq_no = seq - 1;
+		state->base_seq = seq;
+		state->max_seq = seq - 1;
 		state->ssrc = state->orig_ssrc = rtp_hdr->ssrc;
 		state->initialized = 1;
 		state->last_timestamp = timestamp;
+		state->jitter = 0;
+		state->transit = arrival_time - timestamp;
 	} else if (state->ssrc != rtp_hdr->ssrc) {
 		state->ssrc = rtp_hdr->ssrc;
-		state->seq_offset = (state->seq_no + 1) - seq;
+		state->seq_offset = (state->max_seq + 1) - seq;
 		state->timestamp_offset = state->last_timestamp - timestamp;
 		state->patch = endp->allow_patch;
 		LOGP(DMGCP, LOGL_NOTICE,
@@ -145,11 +187,35 @@
 		rtp_hdr->timestamp = htonl(timestamp);
 	}
 
-	/* seq changed, now compare if we have lost something */
-	if (state->seq_no + 1u != seq)
-		state->lost_no = abs(seq - (state->seq_no + 1));
-	state->seq_no = seq;
+	/*
+	 * The below takes the shape of the validation from Appendix A. Check
+	 * if there is something weird with the sequence number, otherwise check
+	 * for a wrap around in the sequence number.
+	 */
+	udelta = seq - state->max_seq;
+	if (udelta < RTP_MAX_DROPOUT) {
+		if (seq < state->max_seq)
+			state->cycles += RTP_SEQ_MOD;
+	} else if (udelta <= RTP_SEQ_MOD + RTP_MAX_MISORDER) {
+		LOGP(DMGCP, LOGL_NOTICE,
+			"RTP seqno made a very large jump on 0x%x delta: %u\n",
+			ENDPOINT_NUMBER(endp), udelta);
+	}
 
+	/*
+	 * calculate the jitter between the two packages. The TS should be
+	 * taken closer to the read function. This was taken from the
+	 * Appendix A of RFC 3550. The local timestamp has a usec resolution.
+	 */
+	transit = arrival_time - timestamp;
+	d = transit - state->transit;
+	state->transit = transit;
+	if (d < 0)
+		d = -d;
+	state->jitter += d - ((state->jitter + 8) >> 4);
+
+
+	state->max_seq = seq;
 	state->last_timestamp = timestamp;
 
 	if (payload < 0)
@@ -300,6 +366,7 @@
 
 	proto = fd == &endp->net_end.rtp ? PROTO_RTP : PROTO_RTCP;
 	endp->net_end.packets += 1;
+	endp->net_end.octets += rc;
 
 	forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc);
 	if (endp->is_transcoded)
@@ -378,6 +445,7 @@
 
 	/* do this before the loop handling */
 	endp->bts_end.packets += 1;
+	endp->bts_end.octets += rc;
 
 	forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc);
 	if (endp->is_transcoded)
@@ -581,3 +649,38 @@
 
 	return 0;
 }
+
+
+void mgcp_state_calc_loss(struct mgcp_rtp_state *state,
+			struct mgcp_rtp_end *end, uint32_t *expected,
+			int *loss)
+{
+	*expected = state->cycles + state->max_seq;
+	*expected = *expected - state->base_seq + 1;
+
+	if (!state->initialized) {
+		*expected = 0;
+		*loss = 0;
+		return;
+	}
+
+	/*
+	 * Make sure the sign is correct and use the biggest
+	 * positive/negative number that fits.
+	 */
+	*loss = *expected - end->packets;
+	if (*expected < end->packets) {
+		if (*loss > 0)
+			*loss = INT_MIN;
+	} else {
+		if (*loss < 0)
+			*loss = INT_MAX;
+	}
+}
+
+uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *state)
+{
+	if (!state->initialized)
+		return 0;
+	return state->jitter >> 4;
+}
diff --git a/openbsc/src/libmgcp/mgcp_protocol.c b/openbsc/src/libmgcp/mgcp_protocol.c
index ac7dea1..4b0222f 100644
--- a/openbsc/src/libmgcp/mgcp_protocol.c
+++ b/openbsc/src/libmgcp/mgcp_protocol.c
@@ -107,9 +107,9 @@
 	return msg;
 }
 
-struct msgb *mgcp_create_response_with_data(int code, const char *txt,
-					    const char *msg, const char *trans,
-					    const char *data)
+static struct msgb *create_resp(int code, const char *txt, const char *msg,
+				const char *trans, const char *param,
+				const char *sdp)
 {
 	int len;
 	struct msgb *res;
@@ -118,10 +118,12 @@
 	if (!res)
 		return NULL;
 
-	if (data) {
-		len = snprintf((char *) res->data, 2048, "%d %s%s\r\n%s", code, trans, txt, data);
-	} else {
-		len = snprintf((char *) res->data, 2048, "%d %s%s\r\n", code, trans, txt);
+	len = snprintf((char *) res->data, 2048, "%d %s%s%s\r\n%s",
+			code, trans, txt, param ? param : "", sdp ? sdp : "");
+	if (len < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to sprintf MGCP response.\n");
+		msgb_free(res);
+		return NULL;
 	}
 
 	res->l2h = msgb_put(res, len);
@@ -129,9 +131,22 @@
 	return res;
 }
 
+struct msgb *mgcp_create_response_with_data(int code, const char *txt,
+					    const char *msg, const char *trans,
+					    const char *data)
+{
+	return create_resp(code, txt, msg, trans, NULL, data);
+}
+
+static struct msgb *create_ok_resp_with_param(int code, const char *msg,
+					const char *trans, const char *param)
+{
+	return create_resp(code, " OK", msg, trans, param, NULL);
+}
+
 static struct msgb *create_ok_response(int code, const char *msg, const char *trans)
 {
-	return mgcp_create_response_with_data(code, " OK", msg, trans, NULL);
+	return create_ok_resp_with_param(code, msg, trans, NULL);
 }
 
 static struct msgb *create_err_response(int code, const char *msg, const char *trans)
@@ -707,6 +722,7 @@
 	int error_code = 400;
 	int silent = 0;
 	char *line, *save;
+	char stats[1048];
 
 	for_each_line((char *) msg->l3h, line, save) {
 		/* skip first line */
@@ -769,6 +785,9 @@
 	LOGP(DMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n",
 		ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port));
 
+	/* save the statistics of the current call */
+	mgcp_format_stats(endp, stats, sizeof(stats));
+
 	delete_transcoder(endp);
 	mgcp_free_endp(endp);
 	if (cfg->change_cb)
@@ -776,7 +795,7 @@
 
 	if (silent)
 		goto out_silent;
-	return create_ok_response(250, "DLCX", trans_id);
+	return create_ok_resp_with_param(250, "DLCX", trans_id, stats);
 
 error3:
 	return create_err_response(error_code, "DLCX", trans_id);
@@ -897,6 +916,7 @@
 	}
 
 	end->packets = 0;
+	end->octets = 0;
 	memset(&end->addr, 0, sizeof(end->addr));
 	end->rtp_port = end->rtcp_port = 0;
 	end->payload_type = -1;
@@ -1098,3 +1118,18 @@
 
 	return send_trans(cfg, mgcp_reset, sizeof mgcp_reset -1);
 }
+
+void mgcp_format_stats(struct mgcp_endpoint *endp, char *msg, size_t size)
+{
+	uint32_t expected, jitter;
+	int ploss;
+	mgcp_state_calc_loss(&endp->net_state, &endp->net_end,
+				&expected, &ploss);
+	jitter = mgcp_state_calc_jitter(&endp->net_state);
+
+	snprintf(msg, size, "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%d",
+			endp->bts_end.packets, endp->bts_end.octets,
+			endp->net_end.packets, endp->net_end.octets,
+			ploss, jitter);
+	msg[size - 1] = '\0';
+}
diff --git a/openbsc/src/libmgcp/mgcp_vty.c b/openbsc/src/libmgcp/mgcp_vty.c
index 314faa8..122fa84 100644
--- a/openbsc/src/libmgcp/mgcp_vty.c
+++ b/openbsc/src/libmgcp/mgcp_vty.c
@@ -128,13 +128,12 @@
 		struct mgcp_endpoint *endp = &cfg->endpoints[i];
 		vty_out(vty,
 			" Endpoint 0x%.2x: CI: %d net: %u/%u bts: %u/%u on %s "
-			"traffic received bts: %u/%u  remote: %u/%u transcoder: %u/%u%s",
+			"traffic received bts: %u  remote: %u transcoder: %u/%u%s",
 			i, endp->ci,
 			ntohs(endp->net_end.rtp_port), ntohs(endp->net_end.rtcp_port),
 			ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port),
 			inet_ntoa(endp->bts_end.addr),
-			endp->bts_end.packets, endp->bts_state.lost_no,
-			endp->net_end.packets, endp->net_state.lost_no,
+			endp->bts_end.packets, endp->net_end.packets,
 			endp->trans_net.packets, endp->trans_bts.packets,
 			VTY_NEWLINE);
 	}
diff --git a/openbsc/src/osmo-bsc_mgcp/Makefile.am b/openbsc/src/osmo-bsc_mgcp/Makefile.am
index 166e83d..3f58d5d 100644
--- a/openbsc/src/osmo-bsc_mgcp/Makefile.am
+++ b/openbsc/src/osmo-bsc_mgcp/Makefile.am
@@ -6,5 +6,5 @@
 
 osmo_bsc_mgcp_SOURCES = mgcp_main.c
 osmo_bsc_mgcp_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
-		 $(top_builddir)/src/libmgcp/libmgcp.a \
+		 $(top_builddir)/src/libmgcp/libmgcp.a -lrt \
 		 $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS)
diff --git a/openbsc/tests/mgcp/mgcp_test.c b/openbsc/tests/mgcp/mgcp_test.c
index d151ebb..9b65666 100644
--- a/openbsc/tests/mgcp/mgcp_test.c
+++ b/openbsc/tests/mgcp/mgcp_test.c
@@ -1,6 +1,6 @@
 /*
- * (C) 2011 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2011 by On-Waves
+ * (C) 2011-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2011-2012 by On-Waves
  * All Rights Reserved
  *
  * This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osmocom/core/application.h>
 #include <osmocom/core/talloc.h>
 #include <string.h>
+#include <limits.h>
 
 #define AUEP1	"AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n"
 #define AUEP1_RET "200 158663169 OK\r\n"
@@ -67,6 +68,13 @@
 		 "m=audio 5904 RTP/AVP 97\r"	\
 		 "a=rtpmap:97 GSM-EFR/8000\r"
 
+#define DLCX	 "DLCX 7 1@mgw MGCP 1.0\r\n"	\
+		 "C: 2\r\n"
+
+#define DLCX_RET "250 7 OK\r\n"			\
+		 "P: PS=0, OS=0, PR=0, OR=0, PL=0, JI=0\r\n"
+
+
 struct mgcp_test {
 	const char *name;
 	const char *req;
@@ -83,6 +91,7 @@
 	{ "SHORT2", SHORT2, SHORT2_RET },
 	{ "SHORT3", SHORT3, SHORT2_RET },
 	{ "SHORT4", SHORT4, SHORT2_RET },
+	{ "DLCX", DLCX, DLCX_RET },
 };
 
 static struct msgb *create_msg(const char *str)
@@ -128,11 +137,64 @@
 	talloc_free(cfg);
 }
 
+struct pl_test {
+	int		cycles;
+	uint16_t	base_seq;
+	uint16_t	max_seq;
+	uint32_t	packets;
+
+	uint32_t	expected;
+	int		loss;
+};
+
+static const struct pl_test pl_test_dat[] = {
+	/* basic.. just one package */
+	{ .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = 1, .expected = 1, .loss = 0},
+	/* some packages and a bit of loss */
+	{ .cycles = 0, .base_seq = 0, .max_seq = 100, .packets = 100, .expected = 101, .loss = 1},
+	/* wrap around */
+	{ .cycles = 1<<16, .base_seq = 0xffff, .max_seq = 2, .packets = 4, .expected = 4, .loss = 0},
+	/* min loss */
+	{ .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = UINT_MAX, .expected = 1, .loss = INT_MIN },
+	/* max loss, with wrap around on expected max */
+	{ .cycles = INT_MAX, .base_seq = 0, .max_seq = UINT16_MAX, .packets = 0, .expected = ((uint32_t)(INT_MAX) + UINT16_MAX + 1), .loss = INT_MAX }, 
+};
+
+static void test_packet_loss_calc(void)
+{
+	int i;
+	printf("Testing packet loss calculation.\n");
+
+	for (i = 0; i < ARRAY_SIZE(pl_test_dat); ++i) {
+		uint32_t expected;
+		int loss;
+		struct mgcp_rtp_state state;
+		struct mgcp_rtp_end rtp;
+		memset(&state, 0, sizeof(state));
+		memset(&rtp, 0, sizeof(rtp));
+
+		state.initialized = 1;
+		state.base_seq = pl_test_dat[i].base_seq;
+		state.max_seq = pl_test_dat[i].max_seq;
+		state.cycles = pl_test_dat[i].cycles;
+
+		rtp.packets = pl_test_dat[i].packets;
+		mgcp_state_calc_loss(&state, &rtp, &expected, &loss);
+
+		if (loss != pl_test_dat[i].loss || expected != pl_test_dat[i].expected) {
+			printf("FAIL: Wrong exp/loss at idx(%d) Loss(%d vs. %d) Exp(%u vs. %u)\n",
+				i, loss, pl_test_dat[i].loss,
+				expected, pl_test_dat[i].expected);
+		}
+	}
+}
+
 int main(int argc, char **argv)
 {
 	osmo_init_logging(&log_info);
 
 	test_messages();
+	test_packet_loss_calc();
 
 	printf("Done\n");
 	return EXIT_SUCCESS;
diff --git a/openbsc/tests/mgcp/mgcp_test.ok b/openbsc/tests/mgcp/mgcp_test.ok
index 45882a5..e61c0bc 100644
--- a/openbsc/tests/mgcp/mgcp_test.ok
+++ b/openbsc/tests/mgcp/mgcp_test.ok
@@ -7,4 +7,6 @@
 Testing SHORT2
 Testing SHORT3
 Testing SHORT4
+Testing DLCX
+Testing packet loss calculation.
 Done