titan.TestPorts.AF_PACKET/src/FrameRelay_Emulation.ttcn

506 lines
16 KiB
Plaintext

module FrameRelay_Emulation {
/* Frame Relay emulation layer on top of FrameRelay_CodecPort
*
* This implements de-multiplexing between per-DLCI client components and the
* underlying FrameRelay_CodecPort / HLDC / AF_PACKET stack. It also implements
* LMI as per ITU-T Q.933 on DLCI 0
*/
import from General_Types all;
import from Osmocom_Types all;
import from FrameRelay_CodecPort all;
import from FrameRelay_Types all;
import from Q933_Types all;
/* Link status notification */
type enumerated FRemu_LinkStatus {
FR_LINK_STS_AVAILABLE,
FR_LINK_STS_UNAVAILABLE
};
/* PVC status notification */
type record FRemu_PvcStatus {
integer dlci,
boolean new,
boolean delete,
boolean active
};
/* Frame Relay Emulation notifies the user about some event */
type union FRemu_Event {
FRemu_LinkStatus link_status,
FRemu_PvcStatus pvc_status
};
template (present) FRemu_Event tr_FRemu_PvcStatusAct(template (present) integer dlci,
template (present) boolean active := ?) := {
pvc_status := {
dlci := dlci,
new := ?,
delete := false,
active := active
}
};
/* port type between FR emulation and per-DLCI components (like NS) */
type port FRemu_PT message {
inout FrameRelayFrame;
in FRemu_Event;
} with { extension "internal" }
type port FRemu_asp_PT message {
inout FrameRelayFrame;
out FRemu_Event;
} with { extension "internal" extension "provider" };
/* port between FR emulation and per-DLCI client components (like NS) */
type port FRemu_PROC_PT procedure {
inout FRemu_register, FRemu_unregister;
} with { extension "internal" };
signature FRemu_register(integer dlci);
signature FRemu_unregister(integer dlci);
/* FR Emulation main component/dispatcher. Exists once per FR_CODEC_PT */
type component FR_Emulation_CT {
/* port towards the bottom (HDLC device) */
port FR_CODEC_PT FR;
/* port towards the user[s] (PVC specific) */
port FRemu_asp_PT CLIENT;
port FRemu_PROC_PT PROC;
var ConnectionData ConnectionTable[1024];
var Q933em_State q933em;
/* Polling verification timer */
timer T391;
/* Link integrity verification polling timer */
timer T392;
};
/* FR Emulation user/client component/dispatcher. Exists once per DLC */
type component FR_Client_CT {
/* message port towards the Frame Relay Emulation */
port FRemu_PT FR;
/* procedure port towards the Frame Relay Emulation */
port FRemu_PROC_PT FR_PROC;
};
type record ConnectionData {
/* component reference to the client component for the above DLCI */
FR_Client_CT vc_conn,
/* was the PVC reported as active by Q.933 yet? */
FRemu_PvcStatus q933_status
};
/***********************************************************************
* Q.933
***********************************************************************/
type record Q933em_State {
/* configuration */
Q933em_Config cfg,
/* last transmitted sequence number */
uint8_t tx_seq_nr,
/* last received sequence number */
uint8_t last_rx_seq_nr,
/* error counter buckets; we use elements 0..N393 */
boolean err_buckets[10],
integer num_cycles,
/* do we currently have a 'service affecting condition (true) or not? */
boolean service_affecting_condition,
/* did we receive a "full status" in the current T391 cycle? */
boolean rx_status_in_cycle
};
type record Q933em_Config {
/* Full status (status of all PVCs) polling counter (default: 6) */
uint8_t N391,
/* Error threshold */
integer N392,
/* Monitored events count */
integer N393,
float T391,
float T392,
/* is the ATS the user equipment (true) or network (false) */
boolean ats_is_user,
/* optional bidirectional network procedures */
boolean bidirectional
};
template (value) Q933em_Config ts_Q933em_Config(boolean ats_is_user, boolean bidirectional) := {
N391 := 6,
N392 := 4,
N393 := 10,
T391 := 10.0,
T392 := 15.0,
ats_is_user := ats_is_user,
bidirectional := bidirectional
};
private template (value) Q933em_State ts_Q933em_State(Q933em_Config cfg) := {
cfg := cfg,
tx_seq_nr := 0,
last_rx_seq_nr := 0,
err_buckets := { true, true, true, true, true, true, true, true, true, true },
num_cycles := 0,
service_affecting_condition := true,
rx_status_in_cycle := false
}
/* obtain the next sequence number following the one given as argument */
private function q933_next_seq(uint8_t cur_seq) return uint8_t {
/* The network equipment increments the send sequence counter using modulo 256. The value zero is skipped. */
if (cur_seq >= 255) {
return 1;
} else {
return cur_seq + 1;
}
}
private function fill_err_bucket(boolean has_error) runs on FR_Emulation_CT {
var integer i, n_errors := 0;
var integer err_bucket_idx := q933em.num_cycles mod q933em.cfg.N393;
/* add current error status to bucket */
q933em.err_buckets[err_bucket_idx] := has_error;
/* check if thresholds are met */
if (not q933em.service_affecting_condition) {
if (q933em.num_cycles >= q933em.cfg.N393) {
for (i := 0; i < q933em.cfg.N393; i := i + 1) {
if (q933em.err_buckets[i]) {
n_errors := n_errors + 1;
}
}
if (n_errors >= q933em.cfg.N392) {
q933em.service_affecting_condition := true;
log("Detecting service affecting condition after N392 errors during last N393 cycles");
notify_all_clients(FRemu_Event:{link_status:=FR_LINK_STS_UNAVAILABLE});
T392.stop;
}
}
} else {
/* if N392 consecutive 'good' cycles -> clear service_affecting_condition */
var integer start_idx := (err_bucket_idx + 1 + q933em.cfg.N393 - q933em.cfg.N392) mod q933em.cfg.N393;
var integer consecutive_good := 0;
for (i := 0; i < q933em.cfg.N392; i := i+1) {
if (q933em.err_buckets[(start_idx + i) mod q933em.cfg.N393] == false) {
consecutive_good := consecutive_good + 1;
}
}
if (consecutive_good == q933em.cfg.N392) {
q933em.service_affecting_condition := false;
log("Detecting no more service affecting condition after N392 consecutive good cycles");
notify_all_clients(FRemu_Event:{link_status:=FR_LINK_STS_AVAILABLE});
if (not q933em.cfg.ats_is_user) {
/* on the network side, all DLCs are active immediately */
notify_all_clients_pvc_state();
}
}
}
/* increment index for next cycle */
q933em.num_cycles := q933em.num_cycles + 1;
}
/* handle incoming Link Integrity Verification IE */
private function q933_handle_rx_link_int(Q933_LinkIntegrityIE link_int) runs on FR_Emulation_CT {
if (link_int.recv_seq_nr == 0) {
/* The value '0' shall never be sent by a standards-conforming implementation,
* See Q.933 section A.4.2 (NOTE). In Osmocom we use it as "magic" value to
* artificially indicate a 'service affecting condition' */
q933em.service_affecting_condition := true;
log("Detecting service affecting condition after zero receive sequence number");
notify_all_clients(FRemu_Event:{link_status:=FR_LINK_STS_UNAVAILABLE});
q933em.num_cycles := 0;
T392.stop;
} else if (q933em.tx_seq_nr != link_int.recv_seq_nr) {
log("Link Integrity IE with discontiguous sequence numbers: expected=",
q933em.tx_seq_nr, " received=", link_int.recv_seq_nr);
fill_err_bucket(true);
/* FIXME */
} else {
fill_err_bucket(false);
}
q933em.last_rx_seq_nr := link_int.send_seq_nr;
}
/* generate outbound Link Integrity Verification IE */
private function q933_gen_tx_link_int() runs on FR_Emulation_CT return Q933_LinkIntegrityIE {
q933em.tx_seq_nr := q933_next_seq(q933em.tx_seq_nr);
return valueof(ts_Q933_LinkIntIE(q933em.tx_seq_nr, q933em.last_rx_seq_nr));
}
/* generate outbound PVC Status Record */
private function q933_gen_pvc_status_rec() runs on FR_Emulation_CT return Q933_PvcStatusRec {
var Q933_PvcStatusRec ret := {};
var integer i;
for (i := 0; i < lengthof(ConnectionTable); i := i+1) {
if (ConnectionTable[i].vc_conn == null) {
continue;
}
var boolean active := not q933em.service_affecting_condition;
/* TODO: set new? */
ret := ret & {valueof(ts_Q933_PvcStatus(i, false, false, active))};
}
return ret;
}
/* transmit a Q.933 STATUS_ENQ and start T391 */
private function q933_tx_status_enq() runs on FR_Emulation_CT {
var Q933_TypeOfReport rep_type := Q933_REP_T_LINK_INTEG_VF_ONLY;
var Q933_LinkIntegrityIE link_int := q933_gen_tx_link_int();
/* every N391 cycles, request full status and not just link integrity */
if ((link_int.send_seq_nr mod q933em.cfg.N391) == 0) {
rep_type := Q993_REP_T_FULL_STATUS;
}
/* transmit STATUS ENQUIRY */
q933_tx(ts_Q933_STATUS_ENQ(rep_type, link_int));
/* re-start timer */
q933em.rx_status_in_cycle := false;
T391.start(q933em.cfg.T391);
}
/* handle an incoming Q.933 message */
private function handle_rx_q933(Q933_PDU rx_pdu) runs on FR_Emulation_CT {
if (not q933em.cfg.ats_is_user or q933em.cfg.bidirectional) {
/* network or bi-directional */
select (rx_pdu) {
case (tr_Q933_STATUS_ENQ(Q933_REP_T_LINK_INTEG_VF_ONLY)) {
T392.stop;
q933_handle_rx_link_int(rx_pdu.body.status_enq.link_int);
q933_tx(ts_Q933_STATUS(Q933_REP_T_LINK_INTEG_VF_ONLY, q933_gen_tx_link_int()));
T392.start(q933em.cfg.T392);
return;
}
case (tr_Q933_STATUS_ENQ(Q993_REP_T_FULL_STATUS)) {
T392.stop;
q933_handle_rx_link_int(rx_pdu.body.status_enq.link_int);
/* create response message */
var Q933_PvcStatusRec pvc_status_rec := q933_gen_pvc_status_rec();
q933_tx(ts_Q933_STATUS(Q993_REP_T_FULL_STATUS, q933_gen_tx_link_int(), ts_Q933_PvcStatusIE(pvc_status_rec)));
T392.start(q933em.cfg.T392);
return;
}
}
} else if (q933em.cfg.ats_is_user or q933em.cfg.bidirectional) {
select (rx_pdu) {
case (tr_Q933_STATUS(Q933_REP_T_LINK_INTEG_VF_ONLY)) {
q933_handle_rx_link_int(rx_pdu.body.status.link_int);
q933em.rx_status_in_cycle := true;
return;
}
case (tr_Q933_STATUS(Q993_REP_T_FULL_STATUS)) {
q933_handle_rx_link_int(rx_pdu.body.status.link_int);
q933em.rx_status_in_cycle := true;
/* process inbound PVC status and dispatch to users */
q933_handle_rx_pvc_status(rx_pdu.body.status.pvc_status.pvc_status);
return;
}
}
}
/* if we reach here, something unsupported was received */
setverdict(fail, "Unexpected Q933 received: ", rx_pdu);
}
private function status_pvc2fremu(Q933_PvcStatus inp) return FRemu_PvcStatus {
var FRemu_PvcStatus ps := {
dlci := bit2int(inp.dlci_high & inp.dlci_low),
new := inp.new,
delete := inp.delete,
active := inp.active
}
return ps;
}
private function q933_handle_rx_pvc_status(Q933_PvcStatusRec pvc_str) runs on FR_Emulation_CT {
for (var integer i := 0; i < sizeof(pvc_str); i := i+1) {
var Q933_PvcStatus pvc_st := pvc_str[i];
var FRemu_PvcStatus frps := status_pvc2fremu(pvc_st);
var FR_Client_CT vc_conn := ConnectionTable[frps.dlci].vc_conn;
if (vc_conn != null) {
if (ConnectionTable[frps.dlci].q933_status != frps) {
CLIENT.send(FRemu_Event:{pvc_status:=frps}) to vc_conn;
ConnectionTable[frps.dlci].q933_status := frps;
}
}
}
}
/* Encode + Transmit a Q.933 message over DLCI 0 */
private function q933_tx(template (value) Q933_PDU tx) runs on FR_Emulation_CT {
var octetstring q933_bin := enc_Q933_PDU(valueof(tx));
/* Add Q.921 LAPD UI frame header */
FR.send(ts_FR(0, '03'O & q933_bin, false));
}
/***********************************************************************
* Main
***********************************************************************/
private function notify_client(FR_Client_CT vc_conn, template (value) FRemu_Event evt)
runs on FR_Emulation_CT {
CLIENT.send(evt) to vc_conn;
}
private function notify_all_clients(template (value) FRemu_Event evt)
runs on FR_Emulation_CT {
for (var integer i:= 0; i < sizeof(ConnectionTable); i := i + 1) {
if (ConnectionTable[i].vc_conn != null) {
notify_client(ConnectionTable[i].vc_conn, evt);
}
}
}
private function notify_all_clients_pvc_state()
runs on FR_Emulation_CT {
for (var integer i:= 0; i < sizeof(ConnectionTable); i := i + 1) {
if (ConnectionTable[i].vc_conn != null) {
notify_client(ConnectionTable[i].vc_conn,
FRemu_Event:{pvc_status:=ConnectionTable[i].q933_status});
}
}
}
function main(Q933em_Config q933_cfg) runs on FR_Emulation_CT {
var FrameRelayFrame rx_fr;
var integer dlci;
var FR_Client_CT vc_conn;
q933em := valueof(ts_Q933em_State(q933_cfg));
if (q933em.cfg.ats_is_user or q933em.cfg.bidirectional) {
q933_tx_status_enq();
}
for (var integer i:= 0; i < sizeof(ConnectionTable); i := i + 1) {
ConnectionTable[i] := {
vc_conn := null,
q933_status := {
dlci := i,
new := false,
delete := false,
active := false
}
};
}
while (true) {
alt {
/* FR PORT */
/* Handle DLCI=0 wihh UI frame and ITU-T LMI */
[] FR.receive(tr_FR(0, '03*'O)) -> value rx_fr {
/* strip one-byte FR header */
var Q933_PDU rx_q933 := dec_Q933_PDU(substr(rx_fr.payload, 1, lengthof(rx_fr.payload)-1));
handle_rx_q933(rx_q933);
}
[] FR.receive(tr_FR(0, ?)) -> value rx_fr {
setverdict(fail, "Unsupported DLCI 0 frame received: ", rx_fr);
mtc.stop;
}
[q933em.cfg.ats_is_user or q933em.cfg.bidirectional] T391.timeout {
if (not q933em.rx_status_in_cycle) {
/* increase error count */
fill_err_bucket(true);
}
/* again request status; re-start timer */
q933_tx_status_enq();
}
[not q933em.cfg.ats_is_user or q933em.cfg.bidirectional] T392.timeout {
/* increase error count */
fill_err_bucket(true);
/* re-start timer */
T392.start(q933em.cfg.T392);
}
/* Handle all other DLCIs */
[not q933em.service_affecting_condition] FR.receive(tr_FR(?, ?)) -> value rx_fr {
/* find user for DLCI; dispatch */
vc_conn := ConnectionTable[rx_fr.hdr.dlci].vc_conn;
if (vc_conn == null) {
log("Dropping Rx FR for unequipped user DLCI ", rx_fr.hdr.dlci);
repeat;
}
CLIENT.send(rx_fr) to vc_conn;
}
[q933em.service_affecting_condition] FR.receive(tr_FR(?, ?)) -> value rx_fr {
log("Dropping Rx FR frame while service affecting condition exists");
}
/* CLIENT PORT */
[not q933em.service_affecting_condition] CLIENT.receive(tr_FR(?,?)) -> value rx_fr {
FR.send(rx_fr);
}
[q933em.service_affecting_condition] CLIENT.receive(tr_FR(?,?)) -> value rx_fr {
log("Dropping Tx FR frame while service affecting condition exists");
}
/* PROCEDURE PORT */
[] PROC.getcall(FRemu_register:{?}) -> param(dlci) sender vc_conn {
if (ConnectionTable[dlci].vc_conn != null and ConnectionTable[dlci].vc_conn != vc_conn) {
setverdict(fail, "DLCI ", dlci, " already registred by ", ConnectionTable[dlci].vc_conn,
" when ", vc_conn, " tries to register for it");
mtc.stop;
}
/* on the network side, every DLC is immediately active */
ConnectionTable[dlci] := {
vc_conn := vc_conn,
q933_status := {
dlci := dlci,
new := true,
delete := false,
active := true
}
};
PROC.reply(FRemu_register:{dlci}) to vc_conn;
if (not q933em.cfg.ats_is_user) {
notify_client(vc_conn,
FRemu_Event:{pvc_status:=ConnectionTable[dlci].q933_status});
}
/* optionally send async Q.933 STATUS? */
}
[] PROC.getcall(FRemu_unregister:{?}) -> param(dlci) sender vc_conn {
if (ConnectionTable[dlci].vc_conn != vc_conn) {
setverdict(fail, "Component ", vc_conn, " tries to unregister DLCI ", dlci,
" registered by ", ConnectionTable[dlci].vc_conn);
mtc.stop;
}
ConnectionTable[dlci].vc_conn := null;
/* optionally send async Q.933 STATUS? */
PROC.reply(FRemu_unregister:{dlci}) to vc_conn;
}
}
}
}
/***********************************************************************
* Client convenience helper functions
***********************************************************************/
function f_fremu_register(integer dlci) runs on FR_Client_CT {
FR_PROC.call(FRemu_register:{dlci}) {
[] FR_PROC.getreply(FRemu_register:{dlci}) {}
}
}
function f_fremu_unregister(integer dlci) runs on FR_Client_CT {
FR_PROC.call(FRemu_unregister:{dlci}) {
[] FR_PROC.getreply(FRemu_unregister:{dlci}) {}
}
}
}