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 <laforge@gnumonks.org>
 * 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
};

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, {34, 0, 0}, 7);

type record IuUP_Config {
	/* actively send INIT (true) or only passively respond (false) */
	boolean active_init,
	boolean data_pdu_type_0,
	/* RAB Flow Combinations */
	record of IuUP_RabFlowCombination rab_flow_combs
};

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(boolean act_init) := {
	cfg := {
		active_init := act_init,
		data_pdu_type_0 := true,
		rab_flow_combs := { t_IuUP_RFC_AMR_12_2(0), t_IuUP_RFC_AMR_SID(1) }
	},
	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.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;
}

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 := valueof(ts_IuUP_INIT('160051673C01270000820000001710000100'O));
					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;
	}
}


}
