Expand BSSGP helpers + codec to cover expansion + compaction
diff --git a/gprs_gb/BSSGP_Helper.cc b/gprs_gb/BSSGP_Helper.cc
index 8cf97d2..4665dcd 100644
--- a/gprs_gb/BSSGP_Helper.cc
+++ b/gprs_gb/BSSGP_Helper.cc
@@ -9,7 +9,7 @@
 
 /* convert a buffer filled with TLVs that have variable-length "length" fields (Osmocom TvLV) into a
  * buffer filled with TLVs that have fixed 16-bit length values (TL16V format) */
-static OCTETSTRING transcode_tlv_part(OCTETSTRING const &in)
+static OCTETSTRING expand_tlv_part(OCTETSTRING const &in)
 {
 	const unsigned char *in_ptr = (const unsigned char *)in;
 	int in_len = in.lengthof();
@@ -33,7 +33,7 @@
 			tl_length = 2;
 		} else {
 			/* E bit is not set, 15 bit length field */
-			if (in_len < 3) {
+			if (remain_len < 3) {
 				TTCN_error("Remaining input length insufficient for 2-octet length");
 				break;
 			}
@@ -67,12 +67,67 @@
 	return out;
 }
 
+/* convert a buffer filled with TLVs that have fixed-length "length" fields (Osmocom TL16V) into a
+ * buffer filled with TLVs that have variable-length values (TvLV format) */
+static OCTETSTRING compact_tlv_part(OCTETSTRING const &in)
+{
+	const unsigned char *in_ptr = (const unsigned char *)in;
+	int in_len = in.lengthof();
+	int ofs = 0;
+	uint16_t data_len;
+	OCTETSTRING out(0, (const unsigned char *)"");
+
+	while (ofs < in_len) {
+		int remain_len = in_len - ofs;
+		int tl_length;
+
+		if (remain_len < 3) {
+			TTCN_error("Remaining input length (%d) insufficient for Tag+Length", remain_len);
+			break;
+		}
+
+		data_len = (in_ptr[ofs+1] << 8) | in_ptr[ofs+2];
+
+		/* Tag + 16bit length */
+		uint8_t hdr_buf[3];
+		hdr_buf[0] = in_ptr[ofs+0];
+
+		if (data_len <= 0x7f) {
+			/* E bit is set, 7-bit length field */
+			hdr_buf[1] = 0x80 | data_len;
+			tl_length = 2;
+		} else {
+			/* E bit is not set, 15 bit length field */
+			hdr_buf[1] = data_len >> 8;
+			hdr_buf[2] = data_len & 0xff;
+			tl_length = 3;
+		}
+		if (in_len < 3 + data_len) {
+			TTCN_error("Remaining input length insufficient for TLV value length");
+			break;
+		}
+
+		OCTETSTRING tlv_hdr(tl_length, hdr_buf);
+		out += tlv_hdr;
+
+		if (data_len) {
+			/* append octet string of current TLV to output octetstring */
+			OCTETSTRING tlv_val(data_len, in_ptr + ofs + 3);
+			out += tlv_val;
+		}
+
+		/* advance input offset*/
+		ofs += data_len + 3;
+	}
+
+	return out;
+}
+
 #define BSSGP_PDUT_DL_UNITDATA	0x00
 #define BSSGP_PDUT_UL_UNITDATA	0x01
 
-/* expand all the variable-length "length" fields of a BSSGP message (Osmocom TvLV) into 
- * statlc TL16V format */
-OCTETSTRING f__BSSGP__preprocess__pdu(OCTETSTRING const &in)
+/* expand all the variable-length "length" fields of a BSSGP message (Osmocom TvLV) into statlc TL16V format */
+OCTETSTRING f__BSSGP__expand__len(OCTETSTRING const &in)
 {
 	const unsigned char *in_ptr = (const unsigned char *)in;
 	int in_len = in.lengthof();
@@ -90,7 +145,65 @@
 	OCTETSTRING prefix(static_hdr_len, in_ptr);
 	OCTETSTRING tlv_part_in(in_len - static_hdr_len, in_ptr + static_hdr_len);
 
-	return prefix + transcode_tlv_part(tlv_part_in);
+	return prefix + expand_tlv_part(tlv_part_in);
+}
+
+OCTETSTRING f__BSSGP__compact__len(OCTETSTRING const &in)
+{
+	const unsigned char *in_ptr = (const unsigned char *)in;
+	int in_len = in.lengthof();
+	uint8_t pdu_type = in_ptr[0];
+	uint8_t static_hdr_len = 1;
+
+	if (pdu_type == BSSGP_PDUT_DL_UNITDATA || pdu_type == BSSGP_PDUT_UL_UNITDATA)
+		static_hdr_len = 8;
+
+	if (in_len < static_hdr_len)
+		TTCN_error("BSSGP message is shorter (%u bytes) than minimum header length (%u bytes) for msg_type 0x%02x",
+				in_len, static_hdr_len, pdu_type);
+
+	/* prefix = non-TLV section of header */
+	OCTETSTRING prefix(static_hdr_len, in_ptr);
+	OCTETSTRING tlv_part_in(in_len - static_hdr_len, in_ptr + static_hdr_len);
+
+	return prefix + compact_tlv_part(tlv_part_in);
+}
+
+/* expand all the variable-length "length" fields of a NS message (Osmocom TvLV) into statlc TL16V format */
+OCTETSTRING f__NS__expand__len(OCTETSTRING const &in)
+{
+	const unsigned char *in_ptr = (const unsigned char *)in;
+	int in_len = in.lengthof();
+	uint8_t pdu_type = in_ptr[0];
+	uint8_t static_hdr_len = 1;
+
+	if (in_len < static_hdr_len)
+		TTCN_error("NS message is shorter (%u bytes) than minimum header length (%u bytes) for msg_type 0x%02x",
+				in_len, static_hdr_len, pdu_type);
+
+	/* prefix = non-TLV section of header */
+	OCTETSTRING prefix(static_hdr_len, in_ptr);
+	OCTETSTRING tlv_part_in(in_len - static_hdr_len, in_ptr + static_hdr_len);
+
+	return prefix + expand_tlv_part(tlv_part_in);
+}
+
+OCTETSTRING f__NS__compact__len(OCTETSTRING const &in)
+{
+	const unsigned char *in_ptr = (const unsigned char *)in;
+	int in_len = in.lengthof();
+	uint8_t pdu_type = in_ptr[0];
+	uint8_t static_hdr_len = 1;
+
+	if (in_len < static_hdr_len)
+		TTCN_error("NS message is shorter (%u bytes) than minimum header length (%u bytes) for msg_type 0x%02x",
+				in_len, static_hdr_len, pdu_type);
+
+	/* prefix = non-TLV section of header */
+	OCTETSTRING prefix(static_hdr_len, in_ptr);
+	OCTETSTRING tlv_part_in(in_len - static_hdr_len, in_ptr + static_hdr_len);
+
+	return prefix + compact_tlv_part(tlv_part_in);
 }
 
 }
diff --git a/gprs_gb/BSSGP_Helper_Functions.ttcn b/gprs_gb/BSSGP_Helper_Functions.ttcn
index 9c8af92..731d2ab 100644
--- a/gprs_gb/BSSGP_Helper_Functions.ttcn
+++ b/gprs_gb/BSSGP_Helper_Functions.ttcn
@@ -1,3 +1,6 @@
 module BSSGP_Helper_Functions {
-	external function f_BSSGP_preprocess_pdu(in octetstring inp) return octetstring;
+	external function f_BSSGP_expand_len(in octetstring inp) return octetstring;
+	external function f_BSSGP_compact_len(in octetstring inp) return octetstring;
+	external function f_NS_expand_len(in octetstring inp) return octetstring;
+	external function f_NS_compact_len(in octetstring inp) return octetstring;
 };
diff --git a/gprs_gb/NS_Types.ttcn b/gprs_gb/NS_Types.ttcn
new file mode 100644
index 0000000..ee2444d
--- /dev/null
+++ b/gprs_gb/NS_Types.ttcn
@@ -0,0 +1,103 @@
+module NS_Types {
+	import from General_Types all;
+	import from Osmocom_Types all;
+	import from GSM_Types all;
+	import from BSSGP_Types all;
+
+	/* TS 48.016 10.3.7 */
+	type enumerated NsPduType {
+		NS_PDUT_NS_UNITDATA	('00000000'B),
+		NS_PDUT_NS_RESET	('00000010'B),
+		NS_PDUT_NS_RESET_ACK	('00000011'B),
+		NS_PDUT_NS_BLOCK	('00000100'B),
+		NS_PDUT_NS_BLOCK_ACK	('00000101'B),
+		NS_PDUT_NS_UNBLOCK	('00000110'B),
+		NS_PDUT_NS_UNBLOCK_ACK	('00000111'B),
+		NS_PDUT_NS_STATUS	('00001000'B),
+		NS_PDUT_NS_ALIVE	('00001010'B),
+		NS_PDUT_NS_ALIVE_ACK	('00001011'B)
+		/* FIXME: SNS */
+	} with { variant "FIELDLENGTH(8)" };
+
+	/* TS 48.016 10.3 */
+	type enumerated NsIEI {
+		NS_IEI_CAUSE		('00000000'B),
+		NS_IEI_NSVCI		('00000001'B),
+		NS_IEI_NS_PDU		('00000010'B),
+		NS_IEI_BVCI		('00000011'B),
+		NS_IEI_NSEI		('00000100'B),
+		NS_IEI_LIST_IPv4	('00000101'B),
+		NS_IEI_LIST_IPv6	('00000110'B),
+		NS_IEI_MAX_NUM_NSVC	('00000111'B),
+		NS_IEI_NUM_IPv4_EP	('00001000'B),
+		NS_IEI_NUM_IPv6_EP	('00001001'B),
+		NS_IEI_RESET_FLAG	('00001010'B),
+		NS_IEI_IP_ADDRESS	('00001011'B)
+	} with { variant "FIELDLENGTH(8)" };
+
+	/* TS 48.016 10.3.2 */
+	type enumerated NsCause {
+		NS_CAUSE_TRANSIT_NETWORK_FAILURE		('00000000'B),
+		NS_CAUSE_OM_INTERVENTION			('00000001'B),
+		NS_CAUSE_EQUIPMENT_FAILURE			('00000010'B),
+		NS_CAUSE_NSVC_BLOCKED				('00000011'B),
+		NS_CAUSE_NSVC_UNKNOWN				('00000100'B),
+		NS_CAUSE_BVCI_UNKNOWN_AT_NSE			('00000101'B),
+		NS_CAUSE_SEMANTICALLY_INCORRECT_PDU		('00001000'B),
+		NS_CAUSE_PDU_NOT_COMPATIBLE_WITH_PROTOCOL_STATE	('00001010'B),
+		NS_CAUSE_PROTOCOL_ERROR_UNSPEIFIED		('00001011'B),
+		NS_CAUSE_INVALID_ESSENTIAL_IE			('00001100'B),
+		NS_CAUSE_MISSING_ESSENTIAL_IE			('00001101'B),
+		NS_CAUSE_INVALID_NR_OF_IPv4_ENDPOINTS		('00001110'B),
+		NS_CAUSE_INVALID_NR_OF_IPv6_ENDPOINTS		('00001111'B),
+		NS_CAUSE_INVALID_NR_OF_NSVCS			('00010000'B),
+		NS_CAUSE_INVALID_WEIGHTS			('00010001'B),
+		NS_CAUSE_UNKNOWN_IP_ENDPOINT			('00010010'B),
+		NS_CAUSE_UNKNOWN_IP_ADDRESS			('00010011'B),
+		NS_CAUSE_IP_TEST_FAILEDA			('00010100'B)
+	} with { variant "FIELDLENGTH(8)" };
+
+	type uint16_t Nsvci;
+	type uint16_t Nsei;
+
+	type union NsIeUnion {
+		BssgpBvci		bvci,		/* 10.3.1 */
+		NsCause			cause,		/* 10.3.2 */
+		uint16_t		max_num_nsvc,	/* 10.3.2e */
+		uint16_t		num_ipv4_ep,	/* 10.3.2f */
+		uint16_t		num_ipv6_ep,	/* 10.3.2g */
+		Nsvci			nsvci,		/* 10.3.5 */
+		Nsei			nsei,		/* 10.3.6 */
+		octetstring 		other
+	};
+
+	type record NsTLV {
+		NsIEI		iei,
+		uint16_t	len,
+		NsIeUnion	u
+	} with {
+		variant (u) "CROSSTAG(
+				bvci, 			iei = NS_IEI_BVCI;
+				cause,			iei = NS_IEI_CAUSE;
+				max_num_nsvc,		iei = NS_IEI_MAX_NUM_NSVC;
+				num_ipv4_ep,		iei = NS_IEI_NUM_IPv4_EP;
+				num_ipv6_ep,		iei = NS_IEI_NUM_IPv6_EP;
+				nsvci,			iei = NS_IEI_NSVCI;
+				nsei,			iei = NS_IEI_NSEI;
+				other,			OTHERWISE)"
+		variant (len) "LENGTHTO(u)"
+	};
+
+	type record of NsTLV NsTLVs;
+
+	type record NsPdu {
+		NsPduType	pdu_type,
+		NsTLVs		tlvs optional
+	} with { variant "" };
+
+	external function enc_NsPdu(in NsPdu pdu) return octetstring
+		with { extension "prototype(convert) encode(RAW)" };
+	external function dec_NsPdu(in octetstring stream) return NsPdu
+		with { extension "prototype(convert) decode(RAW)" };
+
+} with { encode "RAW" };
diff --git a/gprs_gb/Test.ttcn b/gprs_gb/Test.ttcn
index 88a78a8..33d43bf 100644
--- a/gprs_gb/Test.ttcn
+++ b/gprs_gb/Test.ttcn
@@ -9,7 +9,7 @@
 	function f_assert_prepr(in octetstring a, in octetstring b) {
 		log ("Input: ", a);
 		log ("Expected: ", b);
-		var octetstring a_preprocessed := f_BSSGP_preprocess_pdu(a);
+		var octetstring a_preprocessed := f_BSSGP_expand_len(a);
 		log ("Preprocessed: ", a_preprocessed);
 
 		if (a_preprocessed != b) {
@@ -21,7 +21,7 @@
 
 	function f_dec_and_log(in octetstring inp) {
 		log("Input: ", inp);
-		var octetstring inp_p := f_BSSGP_preprocess_pdu(inp);
+		var octetstring inp_p := f_BSSGP_expand_len(inp);
 		log ("Preprocessed: ", inp_p);
 		var BssgpPdu dec := dec_BssgpPdu(inp_p);
 		log("Decoded: ", dec);