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}) {} } } }