module MGCP_Templates {

/* MGCP Templates, building on top of MGCP_Types (Osmocom) and SDP_Types from Ericsson.
 *
 * (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.
 */


import from MGCP_Types all;
import from SDP_Types all;
import from SDP_Templates all;

function f_mgcp_par_append(inout template MgcpParameterList list, template MgcpParameter par) {
	var integer len := lengthof(list);
	list[len] := par;
}

/* 3.2.2.6 Connection Mode (sendonly, recvonly, sendrecv, confrnce, inactive, loopback,
	* conttest, netwloop, netwtest) */
template MgcpParameter t_MgcpParConnMode(template MgcpConnectionMode mode) := { "M", mode };

/* 3.2.2.2 CallId: maximum 32 hex chars */
template MgcpParameter ts_MgcpParCallId(MgcpCallId cid) := {
	code := "C",
	val := hex2str(cid)
};

/* 3.2.2.18 RequestIdentifier: Maximum 32 hex chars */
template MgcpParameter ts_MgcpParReqId(MgcpRequestId rid) := {
	code := "X",
	val := hex2str(rid)
};

/* 3.2.1.3 SpecificEndpointId */
template MgcpParameter ts_MgcpParSpecEP(MgcpEndpoint ep) := {
	code := "Z",
	val := ep
};

/* 3.2.2.10: LocalConnectionOptions (codec, packetization, bandwidth, ToS, eco, gain, silence, ...) */
template MgcpParameter t_MgcpParLocConnOpt(template charstring lco) := { "L", lco };

/* 3.2.2.5: ConnectionId: maximum 32 hex chars */
template MgcpParameter ts_MgcpParConnectionId(MgcpConnectionId cid) := {
	code := "I",
	val := hex2str(cid)
};

/* Osmocom extension: X-Osmux: {*,%u} */
template MgcpParameter ts_MgcpParOsmuxCID(MgcpOsmuxCID osmux_cid) := {
	code := "X-OSMUX",
	val := f_mgcp_osmux_cid_encode(osmux_cid)
};

/* Osmocom extension: X-Osmux: {*,%u} */
template MgcpParameter t_MgcpParOsmoIGN(template charstring val) := {
	code := "X-OSMO-IGN",
	val := val
};

/* osmo-bsc_mgcp implements L/C/M/X only, osmo-mgw adds 'I' */
/* SDP: osmo-bsc_mgcp implements Tx of v,o,s,c,t,m,a */

template (value) MgcpResponse
ts_MgcpResp_Err(template (value) MgcpTransId trans_id,
		template (value) MgcpResponseCode code,
		template (value) charstring string := "FAIL") := {
	line := {
		code := code,
		trans_id := trans_id,
		string := string
	},
	params := {},
	sdp := omit
}
template MgcpResponse
tr_MgcpResp_Err(template (present) MgcpResponseCode code) := {
	line := {
		code := code,
		trans_id := ?,
		string := ?
	},
	params := {},
	sdp := omit
}

template MgcpCommandLine t_MgcpCmdLine(template charstring verb, template MgcpTransId trans_id, template charstring ep) := {
	verb := verb,
	trans_id := trans_id,
	ep := ep,
	ver := "1.0"
};

template MgcpCommand ts_AUEP(MgcpTransId trans_id, charstring ep) := {
	line := t_MgcpCmdLine("AUEP", trans_id, ep),
	params := omit,
	sdp := omit
}

template MgcpResponse tr_AUEP_ACK := {
	line := {
		code := "200",
		trans_id := ?,
		string := "OK"
	},
	params:= *,
	sdp := omit
}

template MgcpCommand ts_CRCX(MgcpTransId trans_id, charstring ep, MgcpConnectionMode mode, MgcpCallId call_id, template SDP_Message sdp := omit) := {
	line := t_MgcpCmdLine("CRCX", trans_id, ep),
	params := {
		t_MgcpParConnMode(mode),
		ts_MgcpParCallId(call_id),
		//t_MgcpParReqId(omit),
		t_MgcpParLocConnOpt("p:20, a:AMR")
	},
	sdp := sdp
}

template MgcpCommand ts_CRCX_no_lco(MgcpTransId trans_id, charstring ep, MgcpConnectionMode mode, MgcpCallId call_id, template SDP_Message sdp := omit) := {
	line := t_MgcpCmdLine("CRCX", trans_id, ep),
	params := {
		t_MgcpParConnMode(mode),
		ts_MgcpParCallId(call_id)
	},
	sdp := sdp
}

template MgcpCommand ts_CRCX_osmux(MgcpTransId trans_id, charstring ep, MgcpConnectionMode mode, MgcpCallId call_id, MgcpOsmuxCID osmux_cid, template SDP_Message sdp := omit) := {
	line := t_MgcpCmdLine("CRCX", trans_id, ep),
	params := {
		t_MgcpParConnMode(mode),
		ts_MgcpParCallId(call_id),
		//t_MgcpParReqId(omit),
		t_MgcpParLocConnOpt("p:20, a:AMR"),
		ts_MgcpParOsmuxCID(osmux_cid)
	},
	sdp := sdp
}

template MgcpCommand tr_CRCX(template MgcpEndpoint ep := ?, template SDP_Message sdp := *) := {
	line := t_MgcpCmdLine("CRCX", ?, ep),
	params := *,
	sdp := sdp
}

template MgcpResponse tr_CRCX_ACK := {
	line := {
		code := "200",
		trans_id := ?,
		string := "OK"
	},
	params:= { { "I", ? }, *},
	sdp := ?
}

template MgcpResponse tr_CRCX_ACK_osmux := {
	line := {
		code := "200",
		trans_id := ?,
		string := "OK"
	},
	params:= { { "I", ? }, {"X-OSMUX", ?}, *},
	sdp := ?
}

template MgcpResponse ts_CRCX_ACK(MgcpTransId trans_id, MgcpConnectionId conn_id, template SDP_Message sdp := omit) := {
	line := {
		code := "200",
		trans_id := trans_id,
		string := "OK"
	},
	params:= { ts_MgcpParConnectionId(conn_id) },
	sdp := sdp
}

template MgcpResponse ts_CRCX_ACK_osmux(MgcpTransId trans_id, MgcpConnectionId conn_id, MgcpOsmuxCID osmux_cid, template SDP_Message sdp := omit) := {
	line := {
		code := "200",
		trans_id := trans_id,
		string := "OK"
	},
	params:= {
		ts_MgcpParConnectionId(conn_id),
		ts_MgcpParOsmuxCID(osmux_cid)
	},
	sdp := sdp
}

template MgcpCommand ts_MDCX(MgcpTransId trans_id, charstring ep, MgcpConnectionMode mode, MgcpCallId call_id, MgcpConnectionId conn_id, template SDP_Message sdp := omit) := {
	line := t_MgcpCmdLine("MDCX", trans_id, ep),
	params := {
		t_MgcpParConnMode(mode),
		ts_MgcpParCallId(call_id),
		ts_MgcpParConnectionId(conn_id),
		//t_MgcpParReqId(omit),
		t_MgcpParLocConnOpt("p:20, a:AMR")
	},
	sdp := sdp
}

template MgcpCommand ts_MDCX_osmux(MgcpTransId trans_id, charstring ep, MgcpConnectionMode mode, MgcpCallId call_id, MgcpConnectionId conn_id, MgcpOsmuxCID osmux_cid, template SDP_Message sdp := omit) := {
	line := t_MgcpCmdLine("MDCX", trans_id, ep),
	params := {
		t_MgcpParConnMode(mode),
		ts_MgcpParCallId(call_id),
		ts_MgcpParConnectionId(conn_id),
		//t_MgcpParReqId(omit),
		t_MgcpParLocConnOpt("p:20, a:AMR"),
		ts_MgcpParOsmuxCID(osmux_cid)
	},
	sdp := sdp
}

template MgcpCommand tr_MDCX(template SDP_Message sdp := *) := {
	line := t_MgcpCmdLine("MDCX", ?, ?),
	params := *,
	sdp := sdp
}

template MgcpResponse tr_MDCX_ACK := {
	line := {
		code := "200",
		trans_id := ?,
		string := "OK"
	},
	params := *,
	sdp := ?
}

template MgcpResponse ts_MDCX_ACK(MgcpTransId trans_id, MgcpConnectionId conn_id, template SDP_Message sdp := omit) := ts_CRCX_ACK(trans_id, conn_id, sdp);
template MgcpResponse ts_MDCX_ACK_osmux(MgcpTransId trans_id, MgcpConnectionId conn_id, MgcpOsmuxCID osmux_cid, template SDP_Message sdp := omit) := ts_CRCX_ACK_osmux(trans_id, conn_id, osmux_cid, sdp);

/* have a function that generates a template, rather than a template in order to handle
	* optional parameters */
function ts_DLCX(MgcpTransId trans_id, charstring ep, template MgcpCallId call_id := omit,
		 template MgcpConnectionId conn_id := omit) return template MgcpCommand {
	var template MgcpCommand cmd;
	cmd.line := t_MgcpCmdLine("DLCX", trans_id, ep);
	cmd.params := {};
	cmd.sdp := omit;
	if (isvalue(call_id)) {
		f_mgcp_par_append(cmd.params, ts_MgcpParCallId(valueof(call_id)));
		if (isvalue(conn_id)) {
			f_mgcp_par_append(cmd.params, ts_MgcpParConnectionId(valueof(conn_id)));
		}
	}
	return cmd;
}

template MgcpCommand tr_DLCX(template MgcpEndpoint ep := ?) := {
	line := t_MgcpCmdLine("DLCX", ?, ep),
	params := *,
	sdp := *
}

template MgcpResponse tr_DLCX_ACK := {
	line := {
		code := ("200", "250"),
		trans_id := ?,
		string := "OK"
	},
	params:= *,
	sdp := *
}

template MgcpResponse ts_DLCX_ACK2(MgcpTransId trans_id) := {
	line := {
		code := "250",
		trans_id := trans_id,
		string := "OK"
	},
	params:= { /* list of ConnectionIDs */ },
	sdp := omit
}



template MgcpResponse ts_DLCX_ACK(MgcpTransId trans_id, MgcpConnectionId conn_id, template SDP_Message sdp := omit) := ts_CRCX_ACK(trans_id, conn_id, sdp);

template MgcpCommand tr_RSIP := {
	line := t_MgcpCmdLine("RSIP", ?, ?),
	params := *,
	sdp := *
}

function f_mgcp_addr2addrtype(charstring addr) return charstring {
	for (var integer i := 0; i < lengthof(addr); i := i + 1) {
		if (addr[i] == ":") {
			return "IP6";
		}
	}
	return "IP4";
}

/* -1 is wildcard, positive is translated as string */
function f_mgcp_osmux_cid_encode(MgcpOsmuxCID osmux_cid) return charstring {
	if (osmux_cid == -1) {
		return "*";
	}
	return int2str(osmux_cid);
}

function f_mgcp_osmux_cid_decode(charstring osmux_cid) return MgcpOsmuxCID {
	if (osmux_cid == "*") {
		return -1;
	}
	return str2int(osmux_cid);
}

function f_mgcp_contains_par(MgcpMessage msg, MgcpInfoCode code) return boolean {
	var MgcpParameterList pars;
	if (ischosen(msg.command)) {
		pars := msg.command.params;
	} else {
		pars := msg.response.params;
	}
	for (var integer i := 0; i < lengthof(pars); i := i + 1) {
		var MgcpParameter par := pars[i];
		if (par.code == code) {
			return true;
		}
	}
	return false;
}

function f_mgcp_extract_par(MgcpMessage msg, MgcpInfoCode code) return charstring {
	var MgcpParameterList pars;
	if (ischosen(msg.command)) {
		pars := msg.command.params;
	} else {
		pars := msg.response.params;
	}
	for (var integer i := 0; i < lengthof(pars); i := i + 1) {
		var MgcpParameter par := pars[i];
		if (par.code == code) {
			return par.val;
		}
	}
	setverdict(fail, "Could not extract parameters for code ", code);
	return "";
}

function f_MgcpResp_extract_par(MgcpResponse resp, MgcpInfoCode code) return charstring {
	var MgcpMessage msg := {
		response := resp
	}
	return f_mgcp_extract_par(msg, code);
}

function f_MgcpCmd_extract_par(MgcpCommand cmd, MgcpInfoCode code) return charstring {
	var MgcpMessage msg := {
		command := cmd
	}
	return f_mgcp_extract_par(msg, code);
}

function f_MgcpCmd_contains_par(MgcpCommand cmd, MgcpInfoCode code) return boolean {
	var MgcpMessage msg := {
		command := cmd
	}
	return f_mgcp_contains_par(msg, code);
}

function f_MgcpResp_extract_conn_id(MgcpResponse resp) return MgcpConnectionId {
	return str2hex(f_MgcpResp_extract_par(resp, "I"));
}

function f_MgcpCmd_extract_call_id(MgcpCommand cmd) return MgcpCallId {
	return str2hex(f_MgcpCmd_extract_par(cmd, "C"));
}

function f_MgcpCmd_extract_conn_id(MgcpCommand cmd) return MgcpConnectionId {
	return str2hex(f_MgcpCmd_extract_par(cmd, "I"));
}

function f_MgcpCmd_extract_osmux_cid(MgcpCommand cmd) return MgcpOsmuxCID {
	return f_mgcp_osmux_cid_decode(f_MgcpCmd_extract_par(cmd, "X-OSMUX"));
}


function f_mgcp_alloc_tid() return MgcpTransId {
	return int2str(float2int(rnd()*2147483647.0));
}

function f_mgcp_alloc_call_id() return MgcpCallId {
	return int2hex(float2int(rnd()*2147483647.0), 8);
}

function f_mgcp_alloc_conn_id() return MgcpConnectionId {
	return int2hex(float2int(rnd()*2147483647.0), 8);
}

/* those verbs that related to a connection (and hence have ConnectionId) */
template MgcpVerb tr_MgcpVerb_ConnectionOriented := ("CRCX", "MDCX", "DLCX", "AUCX");
/* entire command template matching only connection oriented verbs */
template MgcpCommand tr_MgcpCommand_CO := {
	line := {
		verb := tr_MgcpVerb_ConnectionOriented,
		trans_id := ?,
		ep := ?,
		ver := ?
	},
	params := *,
	sdp := *
}

function f_mgcp_find_param_entry(MgcpParameterList pars, MgcpInfoCode code, out charstring ret)
return boolean {
	for (var integer i := 0; i < sizeof(pars); i := i+1) {
		if (pars[i].code == code) {
			ret := pars[i].val;
			return true;
		}
	}
	return false;
}

function f_mgcp_find_param(MgcpMessage msg, MgcpInfoCode code, out charstring ret)
return boolean {
	var MgcpParameterList pars;
	if (ischosen(msg.command)) {
		pars := msg.command.params;
	} else {
		pars := msg.response.params;
	}
	return f_mgcp_find_param_entry(pars, code, ret);
}

/* template to determine if a MGCP endpoint is a wildcard endpoint */
template charstring t_MGCP_EP_wildcard := (pattern "\*@*", pattern "rtpbridge/\*@*");


}