edge: Support MCS data block encoding

Currently only GPRS data block encoding is supported.

This commit adds rlc_data_to_dl_append_egprs which does the EGPRS
specific extension and chunk handling. It extends
Encoding::rlc_data_to_dl_append to use that function for MCS coding
schemes.

Sponsored-by: On-Waves ehf
diff --git a/tests/edge/EdgeTest.cpp b/tests/edge/EdgeTest.cpp
index c405b4b..704a735 100644
--- a/tests/edge/EdgeTest.cpp
+++ b/tests/edge/EdgeTest.cpp
@@ -730,6 +730,249 @@
 	OSMO_ASSERT(data[0] == ((10 << 2) | (1 << 1) | (1 << 0)));
 	OSMO_ASSERT(data[1] == 0);
 
+	/* TS 44.060, B.8.1 */
+	cs = GprsCodingScheme::MCS4;
+
+	/* Block 1 */
+	gprs_rlc_data_block_info_init(&rdbi, cs);
+	num_chunks = 0;
+	write_offset = 0;
+	memset(data, 0, sizeof(data));
+
+	llc.reset();
+	llc.put_frame(llc_data, 11);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_SPACE_LEFT);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(write_offset == 1 + 11);
+	OSMO_ASSERT(num_chunks == 1);
+
+	llc.reset();
+	llc.put_frame(llc_data, 26);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_SPACE_LEFT);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(write_offset == 2 + 11 + 26);
+	OSMO_ASSERT(num_chunks == 2);
+
+	llc.reset();
+	llc.put_frame(llc_data, 99);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_NEED_MORE_BLOCKS);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(rdbi.cv != 0);
+	OSMO_ASSERT(write_offset == (int)rdbi.data_len);
+	OSMO_ASSERT(num_chunks == 3);
+
+	OSMO_ASSERT(data[0] == ((11 << 1) | (0 << 0)));
+	OSMO_ASSERT(data[1] == ((26 << 1) | (1 << 0)));
+	OSMO_ASSERT(data[2] == 0);
+
+	/* TS 44.060, B.8.2 */
+
+	/* Note that the spec confuses the byte numbering here, since it
+	 * includes the FBI/E header bits into the N2 octet count which
+	 * is not consistent with Section 10.3a.1 & 10.3a.2. */
+
+	cs = GprsCodingScheme::MCS2;
+
+	/* Block 1 */
+	gprs_rlc_data_block_info_init(&rdbi, cs);
+	num_chunks = 0;
+	write_offset = 0;
+	memset(data, 0, sizeof(data));
+
+	llc.reset();
+	llc.put_frame(llc_data, 15);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_SPACE_LEFT);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(write_offset == 1 + 15);
+	OSMO_ASSERT(num_chunks == 1);
+
+	llc.reset();
+	llc.put_frame(llc_data, 12);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_NEED_MORE_BLOCKS);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(rdbi.cv != 0);
+	OSMO_ASSERT(write_offset == (int)rdbi.data_len);
+	OSMO_ASSERT(num_chunks == 2);
+
+	OSMO_ASSERT(data[0] == ((15 << 1) | (1 << 0)));
+	OSMO_ASSERT(data[1] == 0);
+
+	/* Block 2 */
+	gprs_rlc_data_block_info_init(&rdbi, cs);
+	num_chunks = 0;
+	write_offset = 0;
+	memset(data, 0, sizeof(data));
+
+	OSMO_ASSERT(llc.chunk_size() == 0);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_SPACE_LEFT);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(write_offset == 1 + 0);
+	OSMO_ASSERT(num_chunks == 1);
+
+	llc.reset();
+	llc.put_frame(llc_data, 7);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_SPACE_LEFT);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(rdbi.cv != 0);
+	OSMO_ASSERT(write_offset == 2 + 0 + 7);
+	OSMO_ASSERT(num_chunks == 2);
+
+	llc.reset();
+	llc.put_frame(llc_data, 18);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_BLOCK_FILLED);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(rdbi.cv != 0);
+	OSMO_ASSERT(write_offset == (int)rdbi.data_len);
+	OSMO_ASSERT(num_chunks == 3);
+
+	OSMO_ASSERT(data[0] == ((0 << 1) | (0 << 0)));
+	OSMO_ASSERT(data[1] == ((7 << 1) | (0 << 0)));
+	OSMO_ASSERT(data[2] == ((18 << 1) | (1 << 0)));
+	OSMO_ASSERT(data[3] == 0);
+
+	/* Block 3 */
+	gprs_rlc_data_block_info_init(&rdbi, cs);
+	num_chunks = 0;
+	write_offset = 0;
+	memset(data, 0, sizeof(data));
+
+	llc.reset();
+	llc.put_frame(llc_data, 6);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, false);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_SPACE_LEFT);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(write_offset == 1 + 6);
+	OSMO_ASSERT(num_chunks == 1);
+
+	llc.reset();
+	llc.put_frame(llc_data, 12);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, true);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_BLOCK_FILLED);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(rdbi.cv == 0);
+	OSMO_ASSERT(write_offset == (int)rdbi.data_len);
+	OSMO_ASSERT(num_chunks == 3);
+
+	OSMO_ASSERT(data[0] == ((6 << 1) | (0 << 0)));
+	OSMO_ASSERT(data[1] == ((12 << 1) | (0 << 0)));
+	OSMO_ASSERT(data[2] == ((127 << 1) | (1 << 0)));
+	OSMO_ASSERT(data[3] == 0);
+
+	/* TS 44.060, B.8.3 */
+
+	/* Note that the spec confuses the byte numbering here, too (see above) */
+
+	cs = GprsCodingScheme::MCS2;
+
+	/* Block 1 */
+	gprs_rlc_data_block_info_init(&rdbi, cs);
+	num_chunks = 0;
+	write_offset = 0;
+	memset(data, 0, sizeof(data));
+
+	llc.reset();
+	llc.put_frame(llc_data, rdbi.data_len);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, true);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_BLOCK_FILLED);
+	OSMO_ASSERT(rdbi.e == 1);
+	OSMO_ASSERT(rdbi.cv == 0);
+	OSMO_ASSERT(write_offset == (int)rdbi.data_len);
+	OSMO_ASSERT(num_chunks == 1);
+
+	OSMO_ASSERT(data[0] == 0);
+
+	/* Final block with an LLC of size data_len-1 */
+
+	cs = GprsCodingScheme::MCS2;
+
+	/* Block 1 */
+	gprs_rlc_data_block_info_init(&rdbi, cs);
+	num_chunks = 0;
+	write_offset = 0;
+	memset(data, 0, sizeof(data));
+
+	llc.reset();
+	llc.put_frame(llc_data, rdbi.data_len - 1);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, true);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_BLOCK_FILLED);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(rdbi.cv == 0);
+	OSMO_ASSERT(write_offset == (int)rdbi.data_len);
+	OSMO_ASSERT(num_chunks == 1);
+
+	OSMO_ASSERT(data[0] == (((rdbi.data_len-1) << 1) | (1 << 0)));
+	OSMO_ASSERT(data[1] == 0);
+
+	/* Final block with an LLC of size data_len-2 */
+
+	cs = GprsCodingScheme::MCS2;
+
+	/* Block 1 */
+	gprs_rlc_data_block_info_init(&rdbi, cs);
+	num_chunks = 0;
+	write_offset = 0;
+	memset(data, 0, sizeof(data));
+
+	llc.reset();
+	llc.put_frame(llc_data, rdbi.data_len - 2);
+
+	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
+		&llc, &write_offset, &num_chunks, data, true);
+
+	OSMO_ASSERT(ar == Encoding::AR_COMPLETED_BLOCK_FILLED);
+	OSMO_ASSERT(rdbi.e == 0);
+	OSMO_ASSERT(rdbi.cv == 0);
+	OSMO_ASSERT(write_offset == (int)rdbi.data_len);
+	OSMO_ASSERT(num_chunks == 2);
+
+	OSMO_ASSERT(data[0] == (((rdbi.data_len-2) << 1) | (0 << 0)));
+	OSMO_ASSERT(data[1] == ((127 << 1) | (1 << 0)));
+	OSMO_ASSERT(data[2] == 0);
+
 	printf("=== end %s ===\n", __func__);
 }