osmo-ttcn3-hacks/library/LAPDm_RAW_PT.ttcn

407 lines
10 KiB
Plaintext

/* 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 GSM_RR_Types all;
import from Osmocom_Types all;
import from L1CTL_Types all;
import from L1CTL_PortType all;
import from LAPDm_Types all;
import from RLCMAC_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 TBF_establish_res {
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
}
type record TBF_establish_req {
uint8_t ra
}
/* PH-DATA.ind / PH-DATA.req */
type record RLCMAC_ph_data_ind {
GprsCodingScheme cs,
RlcmacDlBlock block
}
type record RLCMAC_ph_data_req {
uint8_t tbf_id,
GprsCodingScheme cs,
RlcmacUlBlock block
}
/* port from our (internal) point of view */
type port LAPDm_SP_PT message {
in BCCH_tune_req,
DCCH_establish_req,
DCCH_release_req,
TBF_establish_req,
RLCMAC_ph_data_req,
LAPDm_ph_data;
out DCCH_establish_res,
TBF_establish_res,
RLCMAC_ph_data_ind,
LAPDm_ph_data;
} with {extension "internal"};
/* port from user (external) point of view */
type port LAPDm_PT message {
in DCCH_establish_res,
TBF_establish_res,
RLCMAC_ph_data_ind,
LAPDm_ph_data;
out BCCH_tune_req,
DCCH_establish_req,
DCCH_release_req,
TBF_establish_req,
RLCMAC_ph_data_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,
PH_STATE_TBF
}
type component lapdm_CT {
/* 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 {
f_connect_reset(L1CTL);
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_BCH);
}
/* 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();
} else if (ph_state == PH_STATE_TBF) {
f_release_tbf();
}
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);
}
/* initialize a tfi_usf array with "not used" value 255 for all TN */
function f_TfiUsfArrInit() return TfiUsfArr {
var TfiUsfArr tua := { 255, 255, 255, 255, 255, 255, 255, 255 };
return tua;
}
/* set TFI/USF value for one given timeslot number (index) */
function f_TfiUsfArrSet(inout TfiUsfArr a, in uint8_t idx, in uint8_t tfi_usf) {
a[idx] := tfi_usf;
}
/* Match an IMM.ASS for an Uplink TBF with a dynamic allocation */
template ImmediateAssignment t_IMM_ASS_TBF_UL_DYN(uint8_t ra, GsmFrameNumber fn) modifies t_IMM_ASS := {
ded_or_tbf := { spare := ?, tma := ?, downlink := false, tbf := true},
chan_desc := omit,
pkt_chan_desc := ?,
rest_octets := {
presence := '11'B,
ll := omit,
lh := omit,
hl := omit,
hh := {
presence := '00'B,
ul := {
presence := '1'B,
dynamic := {
tfi_assignment := ?,
polling := ?,
spare := '0'B,
usf := ?,
usf_granularity := ?,
p0_present := ?,
p0 := *,
pr_mode := *,
ch_coding_cmd := ?,
tlli_block_chan_coding:= ?,
alpha_present := ?,
alpha := *,
gamma := ?,
ta_index_present := ?,
ta_index := *,
tbf_starting_time_present := ?,
tbf_starting_time := *
},
single := omit
},
dl := omit
}
}
};
private function f_establish_tbf(uint8_t ra) runs on lapdm_CT {
var ImmediateAssignment imm_ass;
var GsmFrameNumber rach_fn;
var TfiUsfArr tua := f_TfiUsfArrInit();
/* 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);
if (match(imm_ass, t_IMM_ASS_TBF_UL_DYN(ra, rach_fn))) {
set_ph_state(PH_STATE_TBF);
/* store/save channel description */
//chan_desc := imm_ass.chan_desc;
/* Important: ARFCN, TN, TSC, USF, USF_GRANULARITY, CH_CODING_CMD */
f_TfiUsfArrSet(tua, imm_ass.pkt_chan_desc.tn, imm_ass.rest_octets.hh.ul.dynamic.usf);
f_L1CTL_TBF_CFG(L1CTL, true, tua);
} else {
/* FIXME: single block uplink allocation */
log("Failed to match ", t_IMM_ASS_TBF_UL_DYN(ra, rach_fn));
log("Non-dynamic UL TBF assignment not supported yet");
}
}
private function f_release_tbf() runs on lapdm_CT {
var TfiUsfArr tua := f_TfiUsfArrInit();
/* send "all timeslots unused" for both UL and DL */
f_L1CTL_TBF_CFG(L1CTL, true, tua);
f_L1CTL_TBF_CFG(L1CTL, false, tua);
/* L1 will then fall back to BCCH/CCCH */
set_ph_state(PH_STATE_BCH);
}
function ScanEvents() runs on lapdm_CT {
var L1ctlDlMessage dl;
var BCCH_tune_req bt;
var LAPDm_ph_data lpd;
var RLCMAC_ph_data_ind rpdi;
var RLCMAC_ph_data_req rpdr;
var DCCH_establish_req est_req;
var DCCH_establish_res est_res;
var TBF_establish_req tbf_req;
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);
}
/* Establish TBF / packet transfer mode */
[] LAPDM_SP.receive(TBF_establish_req:?) -> value tbf_req {
var TBF_establish_res res;
f_establish_tbf(tbf_req.ra);
if (ph_state == PH_STATE_TBF) {
res := { err := omit };
} else {
res := { err := "Unable to establish TBF" };
}
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 {}
}
} else if (ph_state == PH_STATE_TBF) {
alt {
/* decode + forward any blocks from L1 to L23*/
[] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_PDCH(?))) -> value dl {
rpdi.block := dec_RlcmacDlBlock(dl.payload.data_ind.payload);
rpdi.cs := CS1; /* FIXME */
log("RPDI: ", rpdi);
LAPDM_SP.send(rpdi);
}
[] L1CTL.receive { }
/* encode + forward any blocks from L23 to L1 */
[] LAPDM_SP.receive(RLCMAC_ph_data_req:?) -> value rpdr {
var octetstring buf;
buf := enc_RlcmacUlBlock(rpdr.block);
L1CTL.send(t_L1CTL_DATA_TBF_REQ(buf, L1CTL_CS1, rpdr.tbf_id));
}
/* FIXME: release TBF mode */
[] LAPDM_SP.receive(DCCH_release_req:?) {
/* go back to BCCH */
f_release_tbf();
}
}
}
} /* while (1) */
}
}