Add hand-written encoder/decoder for RLC/MAC UL/DL data blocks

Their format is simply too complex to be used with the automatic RAW
encoder/decoder.  Let's implement it by hand, using the automatic
coder whenever possible.
diff --git a/gprs_gb/gen_links.sh b/gprs_gb/gen_links.sh
index 20b40be..e939690 100755
--- a/gprs_gb/gen_links.sh
+++ b/gprs_gb/gen_links.sh
@@ -30,5 +30,5 @@
 
 
 DIR=../library
-FILES="General_Types.ttcn GSM_Types.ttcn GSM_RR_Types.ttcn Osmocom_Types.ttcn RLCMAC_Types.ttcn L1CTL_Types.ttcn L1CTL_PortType.ttcn"
+FILES="General_Types.ttcn GSM_Types.ttcn GSM_RR_Types.ttcn Osmocom_Types.ttcn RLCMAC_Types.ttcn RLCMAC_EncDec.cc L1CTL_Types.ttcn L1CTL_PortType.ttcn"
 gen_links $DIR $FILES
diff --git a/library/RLCMAC_EncDec.cc b/library/RLCMAC_EncDec.cc
new file mode 100644
index 0000000..b2f0525
--- /dev/null
+++ b/library/RLCMAC_EncDec.cc
@@ -0,0 +1,265 @@
+#include "RLCMAC_Types.hh"
+#include "GSM_Types.hh"
+/* Decoding of TS 44.060 GPRS RLC/MAC blocks, portions requiring manual functions
+ * beyond what TITAN RAW coder can handle internally.
+ *
+ * (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ */
+
+namespace RLCMAC__Types {
+
+OCTETSTRING enc__RlcmacDlDataBlock(const RlcmacDlDataBlock& si)
+{
+	RlcmacDlDataBlock in = si;
+	OCTETSTRING ret_val;
+	TTCN_Buffer ttcn_buffer;
+	int i;
+
+	/* Fix 'e' bit of initial header based on following blocks */
+	if (!in.blocks().is_bound() || (in.blocks().size_of() == 1 && !in.blocks()[0].hdr().is_bound()))
+		in.mac__hdr().hdr__ext().e() = true;
+	else
+		in.mac__hdr().hdr__ext().e() = false;
+
+	/* use automatic/generated decoder for header */
+	in.mac__hdr().encode(DlMacDataHeader_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+
+	/* Add LI octets, if any */
+	if (in.blocks().is_bound() &&
+	    (in.blocks().size_of() != 1 || in.blocks()[0].hdr().is_bound())) {
+		/* first write LI octets */
+		for (i = 0; i < in.blocks().size_of(); i++) {
+			/* fix the 'E' bit in case it is not clear */
+			if (i < in.blocks().size_of()-1)
+				in.blocks()[i].hdr().e() = false;
+			else
+				in.blocks()[i].hdr().e() = true;
+			in.blocks()[i].hdr().encode(LlcBlockHdr_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+		}
+	}
+	if (in.blocks().is_bound()) {
+		for (i = 0; i < in.blocks().size_of(); i++) {
+			if (!in.blocks()[i].is_bound())
+				continue;
+			ttcn_buffer.put_string(in.blocks()[i].payload());
+		}
+	}
+
+	ttcn_buffer.get_string(ret_val);
+	return ret_val;
+}
+
+RlcmacDlDataBlock dec__RlcmacDlDataBlock(const OCTETSTRING& stream)
+{
+	RlcmacDlDataBlock ret_val;
+	TTCN_Buffer ttcn_buffer(stream);
+	int num_llc_blocks = 0;
+
+	/* use automatic/generated decoder for header */
+	ret_val.mac__hdr().decode(DlMacDataHeader_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+
+	/* optional extension octets, containing LI+M+E of Llc blocks */
+	if (ret_val.mac__hdr().hdr__ext().e() == false) {
+		/* extension octet follows, i.e. optional Llc length octets */
+		while (1) {
+			/* decode one more extension octet with LlcBlocHdr inside */
+			LlcBlock lb;
+			lb.hdr().decode(LlcBlockHdr_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+			ret_val.blocks()[num_llc_blocks++] = lb;
+
+			/* if E == '1'B, we can proceed further */
+			if (lb.hdr().e() == true)
+				break;
+		}
+	}
+
+	/* RLC blocks at end */
+	if (ret_val.mac__hdr().hdr__ext().e() == true) {
+		LlcBlock lb;
+		unsigned int length = ttcn_buffer.get_read_len();
+		/* LI not present: The Upper Layer PDU that starts with the current RLC data block either
+		 * fills the current RLC data block precisely or continues in the following in-sequence RLC
+		 * data block */
+		lb.payload() = OCTETSTRING(length, ttcn_buffer.get_read_data());
+		ttcn_buffer.increase_pos(length);
+		ret_val.blocks()[0] = lb;
+	} else {
+		if (ret_val.blocks().is_bound()) {
+			for (int i = 0; i < ret_val.blocks().size_of(); i++) {
+				unsigned int length = ret_val.blocks()[i].hdr().length__ind();
+				if (length > ttcn_buffer.get_read_len())
+					length = ttcn_buffer.get_read_len();
+				ret_val.blocks()[i].payload() = OCTETSTRING(length, ttcn_buffer.get_read_data());
+				ttcn_buffer.increase_pos(length);
+			}
+		}
+	}
+
+	return ret_val;
+}
+
+
+OCTETSTRING enc__RlcmacUlDataBlock(const RlcmacUlDataBlock& si)
+{
+	RlcmacUlDataBlock in = si;
+	OCTETSTRING ret_val;
+	TTCN_Buffer ttcn_buffer;
+	int i;
+
+	/* Fix 'e' bit of initial header based on following blocks */
+	if (!in.blocks().is_bound() || (in.blocks().size_of() == 1 && !in.blocks()[0].hdr().is_bound()))
+		in.mac__hdr().e() = true;
+	else
+		in.mac__hdr().e() = false;
+
+	/* Fix other presence indications */
+	in.mac__hdr().tlli__ind() = in.tlli().is_bound();
+	in.mac__hdr().pfi__ind() = in.pfi().is_bound();
+
+	/* use automatic/generated decoder for header */
+	in.mac__hdr().encode(UlMacDataHeader_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+
+	/* Add LI octets, if any */
+	if (in.blocks().is_bound() &&
+	    (in.blocks().size_of() != 1 || in.blocks()[0].hdr().is_bound())) {
+		/* first write LI octets */
+		for (i = 0; i < in.blocks().size_of(); i++) {
+			/* fix the 'E' bit in case it is not clear */
+			if (i < in.blocks().size_of()-1)
+				in.blocks()[i].hdr().e() = false;
+			else
+				in.blocks()[i].hdr().e() = true;
+			in.blocks()[i].hdr().encode(LlcBlockHdr_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+		}
+	}
+
+	if (in.mac__hdr().tlli__ind()) {
+		/* FIXME */
+		//in.tlli().encode(GSM__Types::GprsTlli_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+		INTEGER t = in.tlli();
+		unsigned int tmp = t.get_long_long_val();
+		ttcn_buffer.put_c(tmp >> 24);
+		ttcn_buffer.put_c(tmp >> 16);
+		ttcn_buffer.put_c(tmp >> 8);
+		ttcn_buffer.put_c(tmp);
+	}
+
+	if (in.mac__hdr().pfi__ind()) {
+		in.pfi().encode(RlcmacUlDataBlock_pfi_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+	}
+
+	if (in.blocks().is_bound()) {
+		for (i = 0; i < in.blocks().size_of(); i++) {
+			if (!in.blocks()[i].is_bound())
+				continue;
+			ttcn_buffer.put_string(in.blocks()[i].payload());
+		}
+	}
+
+	ttcn_buffer.get_string(ret_val);
+	return ret_val;
+}
+
+RlcmacUlDataBlock dec__RlcmacUlDataBlock(const OCTETSTRING& stream)
+{
+	RlcmacUlDataBlock ret_val;
+	TTCN_Buffer ttcn_buffer(stream);
+	int num_llc_blocks = 0;
+
+	TTCN_Logger::begin_event(TTCN_Logger::DEBUG_ENCDEC);
+	TTCN_Logger::log_event_str("==================================\n"
+				"dec_RlcmacUlDataBlock(): Stream before decoding: ");
+	stream.log();
+	TTCN_Logger::end_event();
+
+	/* use automatic/generated decoder for header */
+	ret_val.mac__hdr().decode(UlMacDataHeader_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+
+	TTCN_Logger::begin_event(TTCN_Logger::DEBUG_ENCDEC);
+	TTCN_Logger::log_event_str("dec_RlcmacUlDataBlock(): Stream after decoding hdr: ");
+	ttcn_buffer.log();
+	TTCN_Logger::end_event();
+	TTCN_Logger::begin_event(TTCN_Logger::DEBUG_ENCDEC);
+	TTCN_Logger::log_event_str("dec_RlcmacUlDataBlock(): ret_val after decoding hdr: ");
+	ret_val.log();
+	TTCN_Logger::end_event();
+
+	/* Manually decoder remainder of ttcn_buffer, containing optional header octets, 
+	 * optional tlli, optional pfi and LLC Blocks */
+
+	/* optional extension octets, containing LI+M+E of Llc blocks */
+	if (ret_val.mac__hdr().e() == false) {
+		/* extension octet follows, i.e. optional Llc length octets */
+		while (1) {
+			/* decode one more extension octet with LlcBlocHdr inside */
+			LlcBlock lb;
+			lb.hdr().decode(LlcBlockHdr_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+			ret_val.blocks()[num_llc_blocks++] = lb;
+
+			TTCN_Logger::begin_event(TTCN_Logger::DEBUG_ENCDEC);
+			TTCN_Logger::log_event_str("dec_RlcmacUlDataBlock(): Stream after decoding ExtOct: ");
+			ttcn_buffer.log();
+			TTCN_Logger::end_event();
+			TTCN_Logger::begin_event(TTCN_Logger::DEBUG_ENCDEC);
+			TTCN_Logger::log_event_str("dec_RlcmacUlDataBlock(): ret_val after decoding ExtOct: ");
+			ret_val.log();
+			TTCN_Logger::end_event();
+
+			/* if E == '1'B, we can proceed further */
+			if (lb.hdr().e() == true)
+				break;
+		}
+	}
+
+	/* parse optional TLLI */
+	if (ret_val.mac__hdr().tlli__ind()) {
+		/* FIXME: Why is this not working ?!? */
+		//ret_val.tlli().decode(GSM__Types::GprsTlli_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+		const unsigned char *cur = ttcn_buffer.get_read_data();
+		unsigned int tmp = cur[0] << 24 | cur[1] << 16 | cur[2] << 8 | cur[3];
+		INTEGER t;
+		t.set_long_long_val(tmp);
+		ret_val.tlli() = t;
+		ttcn_buffer.increase_pos(4);
+	}
+	/* parse optional PFI */
+	if (ret_val.mac__hdr().pfi__ind()) {
+		ret_val.pfi().decode(RlcmacUlDataBlock_pfi_descr_, ttcn_buffer, TTCN_EncDec::CT_RAW);
+	}
+
+	/* RLC blocks at end */
+	if (ret_val.mac__hdr().e() == true) {
+		LlcBlock lb;
+		unsigned int length = ttcn_buffer.get_read_len();
+		/* LI not present: The Upper Layer PDU that starts with the current RLC data block either
+		 * fills the current RLC data block precisely or continues in the following in-sequence RLC
+		 * data block */
+		lb.payload() = OCTETSTRING(length, ttcn_buffer.get_read_data());
+		ttcn_buffer.increase_pos(length);
+		ret_val.blocks()[0] = lb;
+	} else {
+		if (ret_val.blocks().is_bound()) {
+			for (int i = 0; i < ret_val.blocks().size_of(); i++) {
+				unsigned int length = ret_val.blocks()[i].hdr().length__ind();
+				if (length > ttcn_buffer.get_read_len())
+					length = ttcn_buffer.get_read_len();
+				ret_val.blocks()[i].payload() = OCTETSTRING(length, ttcn_buffer.get_read_data());
+				ttcn_buffer.increase_pos(length);
+			}
+		}
+	}
+
+	TTCN_Logger::begin_event(TTCN_Logger::DEBUG_ENCDEC);
+	TTCN_Logger::log_event_str("dec_RlcmacUlDataBlock(): Stream before return: ");
+	ttcn_buffer.log();
+	TTCN_Logger::end_event();
+	TTCN_Logger::begin_event(TTCN_Logger::DEBUG_ENCDEC);
+	TTCN_Logger::log_event_str("dec_RlcmacUlDataBlock(): ret_val before return: ");
+	ret_val.log();
+	TTCN_Logger::end_event();
+
+	return ret_val;
+}
+
+
+} // namespace
diff --git a/library/RLCMAC_Types.ttcn b/library/RLCMAC_Types.ttcn
index f73721c..6cd6e49 100644
--- a/library/RLCMAC_Types.ttcn
+++ b/library/RLCMAC_Types.ttcn
@@ -104,66 +104,74 @@
 		with { extension "prototype(convert) decode(RAW)" };
 
 	/* a single RLC block / LLC-segment */
-
-	type record RlcBlockHdr {
+	type record LlcBlockHdr {
 		uint6_t		length_ind,
 		/* 1 = new LLC PDU starts */
 		BIT1		more,
 		/* 0 = another extension octet after LLC PDU, 1 = no more extension octets */
-		BIT1		e
-	} with { variant "" };
-
-	type record RlcBlock {
-		uint6_t		length_ind,
-		BIT1		more,
-		BIT1		e,
-		octetstring 	rlc optional
+		boolean		e
 	} with {
-		variant (rlc) "PRESENCE (more = '1'B)"
-		variant (length_ind) "LENGTHTO(length_ind, more, e, rlc)"
+		variant (e) "FIELDLENGTH(1)"
 	};
-
-	type record of RlcBlock RlcBlocks;
+	type record LlcBlock {
+		/* Header is only present if LI field was present */
+		LlcBlockHdr	hdr,
+		octetstring 	payload
+	} with { variant "" };
+	type record of LlcBlock LlcBlocks;
 
 	/* TS 44.060 10.2.1 Downlink RLC data block */
-	type record RlcmacDlDataBlock {
+	type record DlMacHdrDataExt {
 		/* Octet 1 */
-		DlMacHeader		mac_hdr,
-		/* Octet 2 */
 		PowerReduction		pr,
 		BIT1			spare,
 		uint4_t			tfi, /* 3 or 4? */
 		boolean			fbi,
-		/* Octet 3 */
+		/* Octet 2 */
 		uint7_t			bsn,
-		BIT1			e ('1'B),
-		RlcBlocks		rlc_blocks
+		boolean			e
+	} with {
+		variant (e) "FIELDLENGTH(1)"
+	};
+	type record DlMacDataHeader {
+		DlMacHeader		mac_hdr,
+		DlMacHdrDataExt		hdr_ext
 	} with { variant "" };
+	type record RlcmacDlDataBlock {
+		DlMacDataHeader		mac_hdr,
+		/* Octet 3..M / N: manual C++ Decoder */
+		LlcBlocks		blocks
+	} with {
+		variant ""
+	};
 
-	external function enc_RlcmacDlDataBlock(in RlcmacDlDataBlock si) return octetstring
-		with { extension "prototype(convert) encode(RAW)" };
-	external function dec_RlcmacDlDataBlock(in octetstring stream) return RlcmacDlDataBlock
-		with { extension "prototype(convert) decode(RAW)" };
+	external function enc_RlcmacDlDataBlock(in RlcmacDlDataBlock si) return octetstring;
+	external function dec_RlcmacDlDataBlock(in octetstring stream) return RlcmacDlDataBlock;
 
 
 	/* TS 44.060 10.2.2 */
 	type record UlMacDataHeader {
+		/* Octet 0 */
 		MacPayloadType		pt,
 		uint4_t			countdown,
 		boolean			stall_ind,
-		boolean			retry
+		boolean			retry,
+		/* Octet 1 */
+		BIT1			spare,
+		boolean			pfi_ind,
+		uint5_t			tfi,
+		boolean			tlli_ind,
+		/* Octet 2 */
+		uint7_t			bsn,
+		boolean			e
 	} with {
-		variant (stall_ind) "FIELDLENGTH(1)"
-		variant (retry) "FIELDLENGTH(1)"
+		variant (stall_ind)	"FIELDLENGTH(1)"
+		variant (retry)		"FIELDLENGTH(1)"
+		variant (pfi_ind)	"FIELDLENGTH(1)"
+		variant (tlli_ind)	"FIELDLENGTH(1)"
+		variant (e)		"FIELDLENGTH(1)"
 	};
 
-	type record RlcMacUlTlli {
-		RlcBlockHdr		hdr,
-		uint32_t		tlli
-	} with {
-		variant ""
-	}
-
 	type record RlcMacUlPfi {
 		uint7_t			pfi,
 		boolean			m
@@ -175,26 +183,16 @@
 	type record RlcmacUlDataBlock {
 		/* MAC header */
 		UlMacDataHeader		mac_hdr,
-		/* Octet 1 */
-		BIT1			spare,
-		boolean			pfi_ind,
-		uint5_t			tfi,
-		boolean			tlli_ind,
-		/* Octet 2 */
-		uint7_t			bsn,
-		BIT1			e ('1'B),
-		/* Octet 3 (optional) */
-		RlcMacUlTlli		tlli,
-		RlcMacUlPfi		pfi,
-		RlcBlocks		blocks
+		/* Octet 3 ... M (optional): manual C++ Decoder */
+		GprsTlli		tlli optional,
+		RlcMacUlPfi		pfi optional,
+		LlcBlocks		blocks
 	} with {
-		variant (tlli) "PRESENCE(tlli_ind = true)"
-		variant (pfi) "PRESENCE(pfi_ind = true)"
+		variant (tlli) "PRESENCE(mac_hdr.tlli_ind = true)"
+		variant (pfi) "PRESENCE(mac_hdr.pfi_ind = true)"
 	};
 
-	external function enc_RlcmacUlDataBlock(in RlcmacUlDataBlock si) return octetstring
-		with { extension "prototype(convert) encode(RAW)" };
-	external function dec_RlcmacUlDataBlock(in octetstring stream) return RlcmacUlDataBlock
-		with { extension "prototype(convert) decode(RAW)" };
+	external function enc_RlcmacUlDataBlock(in RlcmacUlDataBlock si) return octetstring;
+	external function dec_RlcmacUlDataBlock(in octetstring stream) return RlcmacUlDataBlock;
 
 } with { encode "RAW"; variant "FIELDORDER(msb)" }