gbproxy/test: Save and check received messages

Currently the only way to check, whether the right message have been
generated is to look into the generated text output. This is
error-prone if there are many messages.

This patch adds a way to optionally store all received messages into
a FIFO. They can then be checked by calling expect_msg() which
removes the first message from the FIFO and returns a pointer to it
or NULL if there is none. The pointer is only valid until the next
call to this function.

A few convenience functions are added to check for common message
types:

  - expect_gmm_msg checks for certain GSM 04.08 messages in LLC/GMM
  - expect_llc_msg checks for arbitrary LLC messages in BSSGP/UD
  - expect_bssgp_msg checks for arbitrary BSSG messages

Each of their arguments can be set by MATCH_ANY to ignore it while
matching. On success, they return a pointer to a statically
allocated struct containing the pointer to the msg and the full parse
context.

Recording is enabled by setting the global variable received_messages
to a pointer to a struct llist_head. It can be disabled again by
setting it to NULL.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/tests/gbproxy/gbproxy_test.c b/openbsc/tests/gbproxy/gbproxy_test.c
index a128b39..84d51bd 100644
--- a/openbsc/tests/gbproxy/gbproxy_test.c
+++ b/openbsc/tests/gbproxy/gbproxy_test.c
@@ -33,6 +33,8 @@
 #include <openbsc/gb_proxy.h>
 #include <openbsc/gprs_utils.h>
 #include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_gb_parse.h>
+#include <openbsc/gsm_04_08_gprs.h>
 #include <openbsc/debug.h>
 
 #define REMOTE_BSS_ADDR 0x01020304
@@ -43,8 +45,12 @@
 #define REMOTE_SGSN2_ADDR 0x15161718
 #define SGSN2_NSEI 0x0102
 
+#define MATCH_ANY (-1)
+
 struct gbproxy_config gbcfg = {0};
 
+struct llist_head *received_messages = NULL;
+
 static int dump_global(FILE *stream, int indent)
 {
 	unsigned int i;
@@ -955,9 +961,158 @@
 		       "msg length %d (%s)\n",
 		       bvci, len, __func__);
 
+	if (received_messages) {
+		struct msgb *msg_copy;
+		msg_copy = gprs_msgb_copy(msg, "received_messages");
+		llist_add_tail(&msg_copy->list, received_messages);
+	}
+
 	return real_gprs_ns_sendmsg(nsi, msg);
 }
 
+/* Get the next message from the receive FIFO
+ *
+ * \returns a pointer to the message which will be invalidated at the next call
+ *          to expect_msg. Returns NULL, if there is no message left.
+ */
+static struct msgb *expect_msg(void)
+{
+	static struct msgb *msg = NULL;
+
+	msgb_free(msg);
+	msg = NULL;
+
+	if (!received_messages)
+		return NULL;
+
+	if (llist_empty(received_messages))
+		return NULL;
+
+	msg = llist_entry(received_messages->next, struct msgb, list);
+	llist_del(&msg->list);
+
+	return msg;
+}
+
+struct expect_result {
+	struct msgb *msg;
+	struct gprs_gb_parse_context parse_ctx;
+};
+
+static struct expect_result *expect_bssgp_msg(
+	int match_nsei, int match_bvci, int match_pdu_type)
+{
+	static struct expect_result result;
+	static const struct expect_result empty_result = {0,};
+	static struct msgb *msg;
+	uint16_t nsei;
+	int rc;
+
+	memcpy(&result, &empty_result, sizeof(result));
+
+	msg = expect_msg();
+	if (!msg)
+		return NULL;
+
+	nsei = msgb_nsei(msg);
+
+	if (match_nsei != MATCH_ANY && match_nsei != nsei) {
+		fprintf(stderr, "%s: NSEI mismatch (expected %u, got %u)\n",
+			__func__, match_nsei, nsei);
+		return NULL;
+	}
+
+	if (match_bvci != MATCH_ANY && match_bvci != msgb_bvci(msg)) {
+		fprintf(stderr, "%s: BVCI mismatch (expected %u, got %u)\n",
+			__func__, match_bvci, msgb_bvci(msg));
+		return NULL;
+	}
+
+	result.msg = msg;
+
+	result.parse_ctx.to_bss = nsei != SGSN_NSEI && nsei != SGSN2_NSEI;
+	result.parse_ctx.peer_nsei = nsei;
+
+	if (!msgb_bssgph(msg)) {
+		fprintf(stderr, "%s: Expected BSSGP\n", __func__);
+		return NULL;
+	}
+
+	rc = gprs_gb_parse_bssgp(msgb_bssgph(msg), msgb_bssgp_len(msg),
+				 &result.parse_ctx);
+
+	if (!rc) {
+		fprintf(stderr, "%s: Failed to parse message\n", __func__);
+		return NULL;
+	}
+
+	if (match_pdu_type != MATCH_ANY &&
+	    match_pdu_type != result.parse_ctx.pdu_type) {
+		fprintf(stderr, "%s: PDU type mismatch (expected %u, got %u)\n",
+			__func__, match_pdu_type, result.parse_ctx.pdu_type);
+		return NULL;
+	}
+
+	return &result;
+}
+
+static struct expect_result *expect_llc_msg(
+	int match_nsei, int match_bvci, int match_sapi, int match_type)
+{
+	static struct expect_result *result;
+
+	result = expect_bssgp_msg(match_nsei, match_bvci, MATCH_ANY);
+	if (!result)
+		return NULL;
+
+	if (!result->parse_ctx.llc) {
+		fprintf(stderr, "%s: Expected LLC message\n", __func__);
+		return NULL;
+	}
+
+	if (match_sapi != MATCH_ANY &&
+	    match_sapi != result->parse_ctx.llc_hdr_parsed.sapi) {
+		fprintf(stderr, "%s: LLC SAPI mismatch (expected %u, got %u)\n",
+			__func__, match_sapi, result->parse_ctx.llc_hdr_parsed.sapi);
+		return NULL;
+	}
+
+	if (match_type != MATCH_ANY &&
+	    match_type != result->parse_ctx.llc_hdr_parsed.cmd) {
+		fprintf(stderr,
+			"%s: LLC command/type mismatch (expected %u, got %u)\n",
+			__func__, match_type, result->parse_ctx.llc_hdr_parsed.cmd);
+		return NULL;
+	}
+
+	return result;
+}
+
+static struct expect_result *expect_gmm_msg(int match_nsei, int match_bvci,
+					    int match_type)
+{
+	static struct expect_result *result;
+
+	result = expect_llc_msg(match_nsei, match_bvci, GPRS_SAPI_GMM, GPRS_LLC_UI);
+	if (!result)
+		return NULL;
+
+	if (!result->parse_ctx.g48_hdr) {
+		fprintf(stderr, "%s: Expected GSM 04.08 message\n", __func__);
+		return NULL;
+	}
+
+	if (match_type != MATCH_ANY &&
+	    match_type != result->parse_ctx.g48_hdr->msg_type) {
+		fprintf(stderr,
+			"%s: GSM 04.08 message type mismatch (expected %u, got %u)\n",
+			__func__, match_type, result->parse_ctx.g48_hdr->msg_type);
+		return NULL;
+	}
+
+	return result;
+}
+
 static void dump_rate_ctr_group(FILE *stream, const char *prefix,
 			    struct rate_ctr_group *ctrg)
 {