Add new LAPDm RAW port

This is a Test Component which sits on top of L1CTL (which in turn is on
top of the Unix Domain Socket Test Porrt).  It performs LAPDm message
encoding/decoding, so we can use the regular send() / receive() methods
and associated template matching to send/receive/match LAPDm in a
comfortable way.
diff --git a/lapd/L1CTL_Test.ttcn b/lapd/L1CTL_Test.ttcn
index 3ff063d..e3d149e 100644
--- a/lapd/L1CTL_Test.ttcn
+++ b/lapd/L1CTL_Test.ttcn
@@ -1,119 +1,164 @@
 module L1CTL_Test {
 	import from GSM_Types all;
 	import from Osmocom_Types all;
-	import from L1CTL_Types all;
-	import from L1CTL_PortType all;
+	import from LAPDm_RAW_PT all;
 	import from LAPDm_Types all;
 
-	const octetstring c_ul_param_req := '1300000000000000001d0000'O;
-	const octetstring c_ul_data_req := '060a0128284018001d000103490615004001c0000000000000000000000000'O;
-	const octetstring c_ul_ccch_mode_req := '1000000002000000'O;
-	const octetstring c_ul_reset_req := '0d00000002000000'O;
-	const octetstring c_ul_dm_est_req := '050000002800000007000367000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005'O;
-	const octetstring c_ul_rach_req := '040000000000000012010008'O;
-
-	const octetstring c_dl_data_ind := '03000000900003670015f5613f3f00002d063f0328e36712ead000002b2b2b2b2b2b2b2b2b2b2b'O;
-
 	type component dummy_CT {
-		var charstring l1ctl_sock_path := "/tmp/osmocom_l2";
-		port L1CTL_PT L1CTL;
-		var ChannelDescription chan_desc;
+		port LAPDm_PT LAPDM;
+		var lapdm_CT lapdm_component;
 	};
 
-
 	function f_init() runs on dummy_CT {
-		map(self:L1CTL, system:L1CTL);
-		L1CTL.send(L1CTL_connect:{path:=l1ctl_sock_path});
-		L1CTL.receive(L1CTL_connect_result:{result_code := SUCCESS, err:=omit});
+		/* create the LAPDm component */
+		lapdm_component := lapdm_CT.create;
+		/* connect our own LAPDM port to the LAPDM Service Provider of the LAPDm component */
+		connect(self:LAPDM, lapdm_component:LAPDM_SP);
+		/* connect the LAPDm compoent's lower-side port to the system L1CTL port (which is internally
+		 * connected to the Unix Domain Socket test port */
+		map(lapdm_component:L1CTL, system:L1CTL);
 
-		L1CTL.send(t_L1ctlResetReq(L1CTL_RES_T_SCHED));
-		L1CTL.receive;
+		/* start the LAPDm parallel component calling it's local function LAPDmStart */
+		lapdm_component.start(LAPDmStart());
 	}
 
-	/* a very ppor man's job to check if we can actually still decode the L1CTL binary messages */
-	testcase TC_selftest() runs on dummy_CT {
-		log("L1CTL_PARAM_REQ: ", dec_L1ctlUlMessage(c_ul_param_req));
-		log("L1CTL_DATA_REQ: ", dec_L1ctlUlMessage(c_ul_data_req));
-		log("L1CTL_CCCH_MODE_REQ: ", dec_L1ctlUlMessage(c_ul_ccch_mode_req));
-		log("L1CTL_RESET_REQ: ", dec_L1ctlUlMessage(c_ul_reset_req));
-		log("L1CTL_DM_EST_REQ: ", dec_L1ctlUlMessage(c_ul_dm_est_req));
-		log("L1CTL_RACH_REQ: ", dec_L1ctlUlMessage(c_ul_rach_req));
-		log("L1CTL_DATA_IND: ", dec_L1ctlDlMessage(c_dl_data_ind));
-		setverdict(pass);
-	}
-
-	/* master function establishing a dedicated radio channel */
+	/* master function establishing a dedicated radio channel (takes care of RACH/IMM.ASS handling) */
 	function f_establish_dcch() runs on dummy_CT {
-		var ImmediateAssignment imm_ass;
-		var GsmFrameNumber rach_fn;
-		var uint8_t ra := 23;
+		var BCCH_tune_req tune_req := { arfcn := { false, 871 }, combined_ccch := true };
+		var DCCH_establish_req est_req := { ra := 23 };
 
-		/* send FB/SB req to sync to cell */
-		f_L1CTL_FBSB(L1CTL, { false, 871 }, CCCH_MODE_COMBINED);
-		/* send RACH request and obtain FN at which it was sent */
-		rach_fn := f_L1CTL_RACH(L1CTL, ra);
-		/* wait for receiving matching IMM ASS */
-		imm_ass := f_L1CTL_WAIT_IMM_ASS(L1CTL, ra, rach_fn)
-		/* send DM_EST_REQ */
-		f_L1CTL_DM_EST_REQ_IA(L1CTL, imm_ass);
-
-		chan_desc := imm_ass.chan_desc;
+		LAPDM.send(tune_req);
+		LAPDM.send(est_req);
+		LAPDM.receive(DCCH_establish_res:?);
 	}
 
-	/* send some data over an established dedicated radio channel */
-	function f_send_l2(template RslLinkId link_id, octetstring l2_data) runs on dummy_CT { 
-		L1CTL.send(t_L1CTL_DATA_REQ(chan_desc.chan_nr, link_id, l2_data));
-	}
-
-	/* send some data over an established dedicated radio channel */
-	function f_send_l2_lapdm_b(template RslLinkId link_id, template LapdmFrameB b) runs on dummy_CT { 
-		var octetstring l2_data := enc_LapdmFrameB(valueof(b));
-		L1CTL.send(t_L1CTL_DATA_REQ(chan_desc.chan_nr, link_id, l2_data));
-	}
-
-
-	function f_recv_l2() runs on dummy_CT return octetstring {
-		var L1ctlDlMessage dl;
-		var octetstring l2_data;
-		timer T := 5.0;
-		T.start
-		alt {
-			[] L1CTL.receive(t_L1CTL_DATA_IND(chan_desc.chan_nr)) -> value dl { l2_data := dl.payload.data_ind.payload };
-			[] L1CTL.receive { repeat; };
-			[] T.timeout { setverdict(fail, "Timeout waiting for DL data"); };
-		}
-		return l2_data;
-	}
-
-	/* release the dedicated radio channel */
+	/* helper function releasing dedicated radio channel physically (no Um signaling!) */
 	function f_release_dcch() runs on dummy_CT {
-		L1CTL.send(t_L1CTL_DM_REL_REQ(chan_desc.chan_nr));
+		var DCCH_release_req rel_req := {};
+		LAPDM.send(rel_req);
 	}
 
-	template LapdmFrameB LAPDm_SABM(template GsmSapi sapi, template octetstring payload)  := {
-		addr := tr_LapdmAddr(sapi, false),
-		ctrl := t_LapdmCtrlSABM(true),
-		len := t_LapdmLengthIndicator(lengthof(payload)),
-		payload := payload
+	template LAPDm_ph_data t_PH_DATA(template GsmSapi sapi, template boolean sacch, template LapdmFrame frame) := {
+		sacch := sacch,
+		sapi := sapi,
+		lapdm := frame
+	}
+	/* template for a valid SABM frame */
+	template LapdmFrame LAPDm_B_SABM(template GsmSapi sapi, template octetstring payload)  := {
+		b := {
+			addr := tr_LapdmAddr(sapi, false),
+			ctrl := t_LapdmCtrlSABM(true),
+			len := lengthof(payload),
+			m := false,
+			el := 1,
+			payload := payload
+		}
 	}
 
-	testcase TC_l1ctl() runs on dummy_CT {
-		f_init();
+	/* template for a valid UA frame */
+	template LapdmFrame LAPDm_B_UA(template GsmSapi sapi, template octetstring payload)  := {
+		b := {
+			addr := tr_LapdmAddr(sapi, false),
+			ctrl := t_LapdmCtrlUA(true),
+			len := lengthof(payload),
+			m := false,
+			el := 1,
+			payload := payload
+		}
+	}
+
+	/* template for a valid UI frame */
+	template LapdmFrame LAPDm_B_UI(template GsmSapi sapi, template octetstring payload)  := {
+		b := {
+			addr := tr_LapdmAddr(sapi, true),
+			ctrl := t_LapdmCtrlUI(false),
+			len := lengthof(payload),
+			m := false,
+			el := 1,
+			payload := payload
+		}
+	}
+
+	function f_test_sabm_results_in_ua(uint8_t sapi, boolean use_sacch, octetstring payload) runs on dummy_CT return boolean {
+		var LAPDm_ph_data phd;
+		var boolean result := false;
+		timer T := 5.0;
 
 		f_establish_dcch();
-
-		f_send_l2_lapdm_b(ts_RslLinkID_DCCH(0), LAPDm_SABM(0, 'FEFE'O));
-		f_recv_l2();
-
-		//f_send_l2(ts_RslLinkID_DCCH(0), '000102030405060708090a0b0c0d0e0f10111213141516'O);
-
+		LAPDM.send(t_PH_DATA(sapi, use_sacch, LAPDm_B_SABM(sapi, payload)));
+		log("====> expecting ", t_PH_DATA(sapi, use_sacch, LAPDm_B_UA(sapi, payload)));
+		T.start
+		alt {
+			[] LAPDM.receive(t_PH_DATA(sapi, use_sacch, LAPDm_B_UA(sapi, payload))) { result := true; }
+			[] LAPDM.receive(t_PH_DATA(sapi, use_sacch, ?)) -> value phd { log("Other msg on DCH: ", phd); repeat; }
+			[] LAPDM.receive(t_PH_DATA(?, ?, ?)) -> value phd { log("Other PH-DATA: ", phd); repeat; }
+			[] T.timeout { setverdict(fail); }
+		}
 		f_release_dcch();
+		return result;
+	}
 
+	testcase TC_sabm_ua_dcch_sapi0() runs on dummy_CT {
+		f_init();
+		if (not f_test_sabm_results_in_ua(0, false, 'FEFE'O)) {
+			setverdict(fail);
+		}
 		setverdict(pass);
 	}
 
+	testcase TC_sabm_ua_dcch_sapi0_nopayload() runs on dummy_CT {
+		f_init();
+		if (not f_test_sabm_results_in_ua(0, false, ''O)) {
+			setverdict(fail);
+		}
+		setverdict(pass);
+	}
+
+	testcase TC_sabm_ua_dcch_sapi3() runs on dummy_CT {
+		f_init();
+		if (not f_test_sabm_results_in_ua(3, false, 'FEFE'O)) {
+			setverdict(fail);
+		}
+		setverdict(pass);
+	}
+
+	testcase TC_sabm_ua_dcch_sapi4() runs on dummy_CT {
+		f_init();
+		if (not f_test_sabm_results_in_ua(4, false, 'FEFE'O)) {
+			setverdict(fail);
+		}
+		setverdict(pass);
+	}
+
+
+	testcase TC_foo() runs on dummy_CT {
+/*
+		var LapdmFrame lf := valueof(LAPDm_B_UA(0, ''O));
+		log("ENC UA: ", enc_LapdmFrame(lf));
+		lf := valueof(LAPDm_B_UI(0, ''O));
+		log("ENC UI B: ", enc_LapdmFrame(lf));
+		log("ENC UI B: ", enc_LapdmFrameB(lf.b));
+
+		log("DEC UI AF: ", dec_LapdmAddressField('03'O));
+*/
+		log("DEC UI CU: ", dec_LapdmCtrlU('03'O));
+		log("DEC UI CT: ", dec_LapdmCtrl('03'O));
+
+		log("DEC UA: ", dec_LapdmFrameB('017301'O));
+		log("DEC UI: ", dec_LapdmFrameA('030301'O));
+		log("DEC I: ", dec_LapdmFrameA('030001'O));
+		log("DEC S: ", dec_LapdmFrameA('030101'O));
+		log("DEC: ", dec_LapdmFrameB('030301'O));
+		log("DEC: ", dec_LapdmFrameB('0303012B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B'O));
+	}
+
 	control {
-		execute(TC_selftest());
-		execute(TC_l1ctl());
+		execute(TC_foo());
+		execute(TC_sabm_ua_dcch_sapi0());
+/*
+		execute(TC_sabm_ua_dcch_sapi0_nopayload());
+		execute(TC_sabm_ua_dcch_sapi3());
+		execute(TC_sabm_ua_dcch_sapi4());
+*/
 	}
 }
diff --git a/lapd/LAPDm_RAW_PT.ttcn b/lapd/LAPDm_RAW_PT.ttcn
new file mode 100644
index 0000000..37824d3
--- /dev/null
+++ b/lapd/LAPDm_RAW_PT.ttcn
@@ -0,0 +1,248 @@
+/* Test Port that stacks on top of L1CTL test port and performs LAPDm encoding/decoding, so the user can send
+ * and receive LAPDm frames in decoded TTCN-3 data types.  This is particularly useful for sending/receiving
+ * all kinds of hand-crafted LAPDm frames for testing of the remote LAPDm layer */
+module LAPDm_RAW_PT {
+	import from GSM_Types all;
+	import from Osmocom_Types all;
+	import from L1CTL_Types all;
+	import from L1CTL_PortType all;
+	import from LAPDm_Types all;
+
+	/* request to tune to a given ARFCN and start BCCH decoding */
+	type record BCCH_tune_req {
+		Arfcn arfcn,
+		boolean combined_ccch
+	}
+
+	/* ask for a dedicated channel to be established */
+	type record DCCH_establish_req {
+		uint8_t ra
+	}
+
+	type record DCCH_establish_res {
+		ChannelDescription chan_desc optional,
+		charstring err optional
+	}
+
+	type record DCCH_release_req {
+	}
+
+	/* PH-DATA.ind / PH-DATA.req */
+	type record LAPDm_ph_data {
+		boolean sacch,
+		GsmSapi sapi,
+		LapdmFrame lapdm
+	}
+
+	/* port from our (internal) point of view */
+	type port LAPDm_SP_PT message {
+		in	BCCH_tune_req,
+			DCCH_establish_req,
+			DCCH_release_req,
+			LAPDm_ph_data;
+		out	DCCH_establish_res,
+			LAPDm_ph_data;
+	} with {extension "internal"};
+
+	/* port from user (external) point of view */
+	type port LAPDm_PT message {
+		in	DCCH_establish_res,
+			LAPDm_ph_data;
+		out	BCCH_tune_req,
+			DCCH_establish_req,
+			DCCH_release_req,
+			LAPDm_ph_data;
+	} with {extension "internal"};
+
+	function LAPDmStart() runs on lapdm_CT {
+		f_init();
+		ScanEvents();
+	}
+
+	/* TS 44.004 Figure 5.1 */
+	type enumerated ph_state_enum {
+		PH_STATE_NULL,
+		PH_STATE_BCH,
+		PH_STATE_SEARCHING_BCH,
+		PH_STATE_TUNING_DCH,
+		PH_STATE_DCH
+	}
+
+	type component lapdm_CT {
+		var charstring l1ctl_sock_path := "/tmp/osmocom_l2";
+
+		/* L1CTL port towards the bottom */
+		port L1CTL_PT L1CTL;
+		/* Port towards L2 */
+		port LAPDm_SP_PT LAPDM_SP;
+
+		/* physical layer state */
+		var ph_state_enum ph_state := PH_STATE_NULL;
+
+		/* channel description of the currently active DCH */
+		var ChannelDescription chan_desc;
+	};
+
+	/* wrapper function to log state transitions */
+	private function set_ph_state(ph_state_enum new_state) runs on lapdm_CT {
+		log("PH-STATE ", ph_state, " -> ", new_state);
+		ph_state := new_state;
+	}
+
+	private function f_init() runs on lapdm_CT {
+		L1CTL.send(L1CTL_connect:{path:=l1ctl_sock_path});
+		L1CTL.receive(L1CTL_connect_result:{result_code := SUCCESS, err:=omit});
+
+		L1CTL.send(t_L1ctlResetReq(L1CTL_RES_T_SCHED));
+		L1CTL.receive;
+		set_ph_state(PH_STATE_NULL);
+	}
+
+	/* release the dedicated radio channel */
+	private function f_release_dcch() runs on lapdm_CT {
+		L1CTL.send(t_L1CTL_DM_REL_REQ(chan_desc.chan_nr));
+		set_ph_state(PH_STATE_NULL);
+	}
+
+	/* tune to given ARFCN and start BCCH/CCCH decoding */
+	private function f_tune_bcch(Arfcn arfcn, boolean combined) runs on lapdm_CT {
+		var L1ctlCcchMode mode := CCCH_MODE_NON_COMBINED;
+		if (combined) {
+			mode := CCCH_MODE_COMBINED;
+		}
+
+		if (ph_state == PH_STATE_DCH) {
+			/* release any previous DCH */
+			f_release_dcch();
+		}
+
+		set_ph_state(PH_STATE_SEARCHING_BCH);
+
+		/* send FB/SB req to sync to cell */
+		f_L1CTL_FBSB(L1CTL, arfcn, mode);
+		set_ph_state(PH_STATE_BCH);
+	}
+
+	/* master function establishing a dedicated radio channel */
+	private function f_establish_dcch(uint8_t ra) runs on lapdm_CT {
+		var ImmediateAssignment imm_ass;
+		var GsmFrameNumber rach_fn;
+
+		/* send RACH request and obtain FN at which it was sent */
+		rach_fn := f_L1CTL_RACH(L1CTL, ra);
+		//if (not rach_fn) { return; }
+
+		/* wait for receiving matching IMM ASS */
+		imm_ass := f_L1CTL_WAIT_IMM_ASS(L1CTL, ra, rach_fn)
+		//if (not imm_ass) { return; }
+		set_ph_state(PH_STATE_TUNING_DCH);
+
+		/* store/save channel description */
+		chan_desc := imm_ass.chan_desc;
+
+		/* send DM_EST_REQ */
+		f_L1CTL_DM_EST_REQ_IA(L1CTL, imm_ass);
+		set_ph_state(PH_STATE_DCH);
+	}
+
+	function ScanEvents() runs on lapdm_CT {
+		var L1ctlDlMessage dl;
+		var BCCH_tune_req bt;
+		var LAPDm_ph_data lpd;
+		var DCCH_establish_req est_req;
+		var DCCH_establish_res est_res;
+
+		while (true) {
+		if (ph_state == PH_STATE_NULL) {
+			alt {
+			[] LAPDM_SP.receive(BCCH_tune_req:?) -> value bt {
+				f_tune_bcch(bt.arfcn, bt.combined_ccch);
+			}
+
+			[] LAPDM_SP.receive {}
+			[] L1CTL.receive {}
+
+			}
+		} else if (ph_state == PH_STATE_BCH or ph_state == PH_STATE_SEARCHING_BCH) {
+			alt {
+			[] LAPDM_SP.receive(BCCH_tune_req:?) -> value bt {
+				f_tune_bcch(bt.arfcn, bt.combined_ccch);
+			}
+
+			/* forward CCCH SAPI from L1CTL to User */
+			[] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_BCCH(0))) -> value dl {
+				lpd.sacch := false;
+				lpd.sapi := 0;
+				lpd.lapdm.bbis := dec_LapdmFrameBbis(dl.payload.data_ind.payload);
+				LAPDM_SP.send(lpd);
+			}
+
+			/* forward BCCH SAPI from L1CTL to User */
+			[] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl {
+				lpd.sacch := false;
+				lpd.sapi := 0;
+				lpd.lapdm.bbis := dec_LapdmFrameBbis(dl.payload.data_ind.payload);
+				LAPDM_SP.send(lpd);
+			}
+
+			/* Establish dedicated channel */
+			[] LAPDM_SP.receive(DCCH_establish_req:?) -> value est_req {
+				var DCCH_establish_res res;
+				f_establish_dcch(est_req.ra);
+				if (ph_state == PH_STATE_DCH) {
+					res := { chan_desc, omit };
+				} else {
+					res := { omit, "Unable to esetablish DCCH" };
+				}
+				LAPDM_SP.send(res);
+			}
+
+			[] LAPDM_SP.receive {}
+			[] L1CTL.receive {}
+
+			}
+
+		} else if (ph_state == PH_STATE_TUNING_DCH or ph_state == PH_STATE_DCH) {
+			alt {
+
+			/* decode any received DATA frames for the dedicated channel and pass them up */
+			[] L1CTL.receive(t_L1CTL_DATA_IND(chan_desc.chan_nr)) -> value dl {
+				if (dl.dl_info.link_id.c == SACCH) {
+					lpd.sacch := true;
+					/* FIXME: how to deal with UI frames in B4 format (lo length!) */
+				} else {
+					lpd.sacch := false;
+				}
+				lpd.sapi := dl.dl_info.link_id.sapi;
+				lpd.lapdm.b := dec_LapdmFrameB(dl.payload.data_ind.payload);
+				LAPDM_SP.send(lpd);
+			}
+
+			/* encode any LAPDm record from user and pass it on to L1CTL */
+			[] LAPDM_SP.receive(LAPDm_ph_data:?) -> value lpd {
+				var octetstring buf;
+				var RslLinkId link_id;
+				if (lpd.sacch) {
+					link_id := valueof(ts_RslLinkID_SACCH(lpd.sapi));
+				} else {
+					link_id := valueof(ts_RslLinkID_DCCH(lpd.sapi));
+				}
+				buf := enc_LapdmFrame(lpd.lapdm);
+				L1CTL.send(t_L1CTL_DATA_REQ(chan_desc.chan_nr, link_id, buf));
+			}
+
+			/* Release dedicated channel */
+			[] LAPDM_SP.receive(DCCH_release_req:?) {
+				/* go back to BCCH */
+				f_release_dcch();
+			}
+
+			[] LAPDM_SP.receive {}
+			[] L1CTL.receive {}
+
+
+			}
+		}
+		} /* while (1) */
+	}
+}
diff --git a/lapd/LAPDm_Types.ttcn b/lapd/LAPDm_Types.ttcn
index 522aa4d..988e015 100644
--- a/lapd/LAPDm_Types.ttcn
+++ b/lapd/LAPDm_Types.ttcn
@@ -116,7 +116,7 @@
 	};
 
 	template LapdmCtrl t_LapdmCtrlUA(template boolean f) modifies t_LapdmCtrlU := {
-		u := { u2 := '01'B, p_f := f, u := '011'B }
+		u := { u2 := '00'B, p_f := f, u := '011'B }
 	};
 
 	external function dec_LapdmAddressField(in octetstring stream) return LapdmAddressField
@@ -154,16 +154,18 @@
 	type record LapdmFrameB {
 		LapdmAddressField	addr,
 		LapdmCtrl		ctrl,
-		LapdmLengthIndicator	len,
+		uint6_t			len,
+		boolean			m,
+		uint1_t			el (1),
 		octetstring		payload
-	} with { variant "" };
+	} with { variant (len) "LENGTHTO(payload)"
+		 variant "FIELDORDER(msb)" };
 
 	external function enc_LapdmFrameB(in LapdmFrameB si) return octetstring
 		with { extension "prototype(convert) encode(RAW)" };
 	external function dec_LapdmFrameB(in octetstring stream) return LapdmFrameB
 		with { extension "prototype(convert) decode(RAW)" };
 
-
 	/* Format B4 */
 	type record LapdmFrameB4 {
 		LapdmAddressField	addr,