module IuUP_Emulation { /* IuUP emulation, uses the encoding/decoding from IuUP_Types. * * rather than running in a separate component with related primitives, * we just implement a set of functions and data types which can be used * by other code (such as an RTP endpoint) to implement IuUP support. * * (C) 2017 by Harald Welte * All rights reserved. * * Released under the terms of GNU General Public License, Version 2 or * (at your option) any later version. * * SPDX-License-Identifier: GPL-2.0-or-later */ import from Osmocom_Types all; import from IuUP_Types all; type record IuUP_RabFlowCombination { IuUP_RFCI rfci, /* number of bits per sub-flow */ RecOfU8 sub_flow_bits, /* IPTI value in number of ITIs for the corresponding RFCI */ uint8_t ipti }; type record of IuUP_RabFlowCombination IuUP_RabFlowCombinationList; template (value) IuUP_RabFlowCombination t_IuUP_RFC(IuUP_RFCI rfci, RecOfU8 subflow_bits, uint8_t ipti) := { rfci := rfci, sub_flow_bits := subflow_bits, ipti := ipti } template (value) IuUP_RabFlowCombination t_IuUP_RFC_AMR_12_2(IuUP_RFCI rfci) := t_IuUP_RFC(rfci, {81, 103, 60}, 1); template (value) IuUP_RabFlowCombination t_IuUP_RFC_AMR_SID(IuUP_RFCI rfci) := t_IuUP_RFC(rfci, {39, 0, 0}, 7); template (value) IuUP_RabFlowCombination t_IuUP_RFC_AMR_NO_DATA(IuUP_RFCI rfci) := t_IuUP_RFC(rfci, {0, 0, 0}, 1); const IuUP_RabFlowCombinationList c_IuUP_Config_RabFlowCombination_def := { { rfci := 0, sub_flow_bits := {81, 103, 60}, ipti := 1 }, { rfci := 1, sub_flow_bits := {39, 0, 0}, ipti := 7 }, { rfci := 2, sub_flow_bits := {0, 0, 0}, ipti := 1 } }; type record IuUP_Config { /* actively send INIT (true) or only passively respond (false) */ boolean active_init, boolean data_pdu_type_0, /* RAB Flow Combinations */ IuUP_RabFlowCombinationList rab_flow_combs }; template (value) IuUP_Config t_IuUP_Config(boolean active_init := true, boolean data_pdu_type_0 := true, template (value) IuUP_RabFlowCombinationList rab_flow_combs := c_IuUP_Config_RabFlowCombination_def) := { active_init := active_init, data_pdu_type_0 := true, rab_flow_combs := rab_flow_combs } const IuUP_Config c_IuUP_Config_def := { active_init := true, data_pdu_type_0 := true, rab_flow_combs := c_IuUP_Config_RabFlowCombination_def } type enumerated IuUP_Em_State { ST_INIT, ST_DATA_TRANSFER_READY }; type record IuUP_Entity { IuUP_Config cfg, IuUP_Em_State state, IuUP_FrameNr tx_next_frame_nr, IuUP_FrameNr rx_last_frame_nr optional, IuUP_PDU pending_tx_pdu optional }; template (value) IuUP_Entity t_IuUP_Entity(template (value) IuUP_Config cfg) := { cfg := cfg, state := ST_INIT, tx_next_frame_nr := 0, rx_last_frame_nr := omit, pending_tx_pdu := omit } function f_IuUP_Em_rx_decaps(inout IuUP_Entity st, octetstring inp) return octetstring { var IuUP_PDU pdu := dec_IuUP_PDU(inp); if (ischosen(pdu.type_0)) { if (st.cfg.data_pdu_type_0) { /* FIXME: check header / CRC */ st.rx_last_frame_nr := pdu.type_0.frame_nr; return pdu.type_0.payload; } else { setverdict(fail, "PDU Type 0 received but 1 configured"); mtc.stop; } } else if (ischosen(pdu.type_1)) { if (st.cfg.data_pdu_type_0 == false) { /* FIXME: check header / CRC */ st.rx_last_frame_nr := pdu.type_1.frame_nr; return pdu.type_1.payload; } else { setverdict(fail, "PDU Type 1 received but 0 configured"); mtc.stop; } } else if (ischosen(pdu.type_14)) { if (match(pdu, tr_IuUP_INIT)) { if (st.cfg.active_init == true) { setverdict(fail, "INIT received in ACTIVE role"); mtc.stop; } else { /* store an INIT_ACK to be transmitted later */ st.pending_tx_pdu := valueof(ts_IuUP_INIT_ACK(pdu.type_14.frame_nr, pdu.type_14.u.proc.hdr.iuup_version)); } } else if (match(pdu, tr_IuUP_INIT_ACK)) { if (st.cfg.active_init == true) { log("IuUP INIT_ACK Received"); st.pending_tx_pdu := omit; /* Drop pending Init retrans */ st.state := ST_DATA_TRANSFER_READY; } else { setverdict(fail, "INIT_ACK received in PASSIVE role"); mtc.stop; } } return ''O; } else { setverdict(fail, "Impossible IuUP PDU decoded from ", inp); mtc.stop; } self.stop; } private function f_ts_IuUP_INIT(inout IuUP_Entity st) return IuUP_PDU { var IuUP_PDU pdu; var uint4_t data_pdu_type; var template (omit) IuUP_InitRfci rfci := omit; var IuUP_IPTI_List IPTIs := {}; var uint4_t num_rfci := lengthof(st.cfg.rab_flow_combs); if (st.cfg.data_pdu_type_0 == true) { data_pdu_type := 0; } else { data_pdu_type := 1; } /* Build RFCI list: */ for (var integer remain := num_rfci; remain > 0; remain := remain - 1) { var IuUP_RabFlowCombination comb := st.cfg.rab_flow_combs[remain - 1]; var boolean lri := false; if (remain == num_rfci) { lri := true; } rfci := ts_IuUP_InitRfci(lri, false, comb.rfci, comb.sub_flow_bits, omit, rfci) } /* Build IPTI list: */ for (var integer i := 0; i < num_rfci; i := i + 1) { IPTIs := IPTIs & { st.cfg.rab_flow_combs[i].ipti }; } template (value) IuUP_PDU14_ProcSending_INIT tpl := ts_IuUP_PDU14_ProcSending_INIT( ti := true, subflows_per_rfci := num_rfci, chain_ind := false, rfci := rfci, IPTIs := IPTIs, versions_supported := '0000000000000001'B, data_pdu_type := data_pdu_type ); pdu := valueof(ts_IuUP_INIT(tpl)); return pdu; } function f_IuUP_Em_tx_encap(inout IuUP_Entity st, in octetstring payload) return octetstring { var IuUP_PDU pdu; select (st.state) { case (ST_INIT) { if (st.cfg.active_init) { if (not isvalue(st.pending_tx_pdu)) { /* send INIT */ pdu := f_ts_IuUP_INIT(st); st.pending_tx_pdu := pdu; } /* else: wait for INIT-ACK return ''O at the end */ } else { /* wait for INIT */ if (isvalue(st.pending_tx_pdu)) { /* we're waiting to transmit the INIT_ACK in response to an * init (passive) */ pdu := st.pending_tx_pdu; st.pending_tx_pdu := omit; st.state := ST_DATA_TRANSFER_READY; } } } case (ST_DATA_TRANSFER_READY) { if (st.cfg.data_pdu_type_0) { pdu := valueof(ts_IuUP_Type0(st.tx_next_frame_nr, 0, payload)); } else { pdu := valueof(ts_IuUP_Type1(st.tx_next_frame_nr, 0, payload)); } st.tx_next_frame_nr := (st.tx_next_frame_nr + 1) mod 16; } } if (isvalue(pdu)) { return f_enc_IuUP_PDU(pdu); } else { return ''O; } } }