ggsn: Initial testing of open5gs Gy interface

Related: SYS#5276
Change-Id: I10027d4f8adc6b47ce97b90514d1f13e9aa3d40d
diff --git a/ggsn_tests/GGSN_Tests.ttcn b/ggsn_tests/GGSN_Tests.ttcn
index 203dcbf..7f6f67c 100644
--- a/ggsn_tests/GGSN_Tests.ttcn
+++ b/ggsn_tests/GGSN_Tests.ttcn
@@ -39,6 +39,7 @@
 	const integer GTP1C_PORT := 2123;
 	const integer GTP1U_PORT := 2152;
 	const integer PCRF_PORT := 3868;
+	const integer OCS_PORT := 3869;
 
 	type enumerated GGSN_Impl {
 		GGSN_IMPL_OSMOCOM,
@@ -128,6 +129,10 @@
 		var DIAMETER_Emulation_CT vc_Gx;
 		port DIAMETER_PT Gx_UNIT;
 		port DIAMETEREM_PROC_PT Gx_PROC;
+		var DIAMETER_Emulation_CT vc_Gy;
+		port DIAMETER_PT Gy_UNIT;
+		port DIAMETEREM_PROC_PT Gy_PROC;
+		var integer g_gy_validity_time := 0; /* In seconds. 0 => disabled, !0 => grant over CC-Time period */
 	}
 
 	private function f_init_vty() runs on GT_CT {
@@ -203,7 +208,10 @@
 			unitdata_cb := refers(DiameterForwardUnitdataCallback),
 			raw := true /* handler mode (single component for all IMSI)) */
 		};
-		var DIAMETER_conn_parameters pars := {
+		var DIAMETER_conn_parameters pars;
+
+		/* Gx setup: */
+		pars := {
 			remote_ip := m_ggsn_ip_gtpc,
 			remote_sctp_port := -1,
 			local_ip := m_bind_ip_gtpc,
@@ -219,7 +227,25 @@
 		connect(vc_Gx:DIAMETER_PROC, self:Gx_PROC);
 		vc_Gx.start(DIAMETER_Emulation.main(ops, pars, id));
 
+		/* Gy setup: */
+		pars := {
+			remote_ip := m_ggsn_ip_gtpc,
+			remote_sctp_port := -1,
+			local_ip := m_bind_ip_gtpc,
+			local_sctp_port := OCS_PORT,
+			origin_host := "ocs.localdomain",
+			origin_realm := "localdomain",
+			auth_app_id := c_DIAMETER_CREDIT_CONTROL_AID,
+			vendor_app_id := omit
+		};
+		vc_Gy := DIAMETER_Emulation_CT.create(id);
+		map(vc_Gy:DIAMETER, system:DIAMETER_CODEC_PT);
+		connect(vc_Gy:DIAMETER_UNIT, self:Gy_UNIT);
+		connect(vc_Gy:DIAMETER_PROC, self:Gy_PROC);
+		vc_Gy.start(DIAMETER_Emulation.main(ops, pars, id));
+
 		f_diameter_wait_capability(Gx_UNIT);
+		f_diameter_wait_capability(Gy_UNIT);
 		/* Give some time for our emulation to get out of SUSPECT list of SUT (3 watchdong ping-pongs):
 		 * RFC6733 sec 5.1
 		 * RFC3539 sec 3.4.1 [5]
@@ -425,9 +451,9 @@
 		}
 	}
 
-	private altstep as_DIA_CCR(DCC_NONE_CC_Request_Type req_type) runs on GT_CT {
+	private altstep as_DIA_Gx_CCR(DCC_NONE_CC_Request_Type req_type) runs on GT_CT {
 		var PDU_DIAMETER rx_dia;
-		[] Gx_UNIT.receive(tr_DIA_CCR(req_type := req_type)) -> value rx_dia {
+		[] Gx_UNIT.receive(tr_DIA_Gx_CCR(req_type := req_type)) -> value rx_dia {
 			var template (omit) AVP avp;
 			var octetstring sess_id;
 			var AVP_Unsigned32 req_num;
@@ -438,7 +464,7 @@
 			avp := f_DIAMETER_get_avp(rx_dia, c_AVP_Code_DCC_NONE_CC_Request_Number);
 			req_num := valueof(avp.avp_data.avp_DCC_NONE_CC_Request_Number);
 
-			Gx_UNIT.send(ts_DIA_CCA(rx_dia.hop_by_hop_id, rx_dia.end_to_end_id, sess_id,
+			Gx_UNIT.send(ts_DIA_Gx_CCA(rx_dia.hop_by_hop_id, rx_dia.end_to_end_id, sess_id,
 						 req_type, req_num));
 		}
 		[] Gx_UNIT.receive(PDU_DIAMETER:?) -> value rx_dia {
@@ -447,6 +473,34 @@
 		}
 	}
 
+	private altstep as_DIA_Gy_CCR(DCC_NONE_CC_Request_Type req_type) runs on GT_CT {
+		var PDU_DIAMETER rx_dia;
+		[] Gy_UNIT.receive(tr_DIA_Gy_CCR(req_type := req_type)) -> value rx_dia {
+			var template (value) PDU_DIAMETER tx_dia;
+			var template (omit) AVP avp;
+			var octetstring sess_id;
+			var AVP_Unsigned32 req_num;
+
+			avp := f_DIAMETER_get_avp(rx_dia, c_AVP_Code_BASE_NONE_Session_Id);
+			sess_id := valueof(avp.avp_data.avp_BASE_NONE_Session_Id);
+
+			avp := f_DIAMETER_get_avp(rx_dia, c_AVP_Code_DCC_NONE_CC_Request_Number);
+			req_num := valueof(avp.avp_data.avp_DCC_NONE_CC_Request_Number);
+			if (g_gy_validity_time > 0) {
+				tx_dia := ts_DIA_Gy_CCA_ValidityTime(rx_dia.hop_by_hop_id, rx_dia.end_to_end_id, sess_id,
+							 req_type, req_num, g_gy_validity_time);
+			} else {
+				tx_dia := ts_DIA_Gy_CCA(rx_dia.hop_by_hop_id, rx_dia.end_to_end_id, sess_id,
+							 req_type, req_num);
+			}
+			Gy_UNIT.send(tx_dia);
+		}
+		[] Gy_UNIT.receive(PDU_DIAMETER:?) -> value rx_dia {
+			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+						log2str("Received unexpected DIAMETER Gy", rx_dia));
+		}
+	}
+
 	/* send a PDP context activation */
 	function f_pdp_ctx_act(inout PdpContext ctx, template OCT1 exp_cause := '80'O) runs on GT_CT return CreatePDPContextResponse {
 		var Gtp1cUnitdata ud;
@@ -460,7 +514,10 @@
 		T_default.start;
 		d := activate(pingpong());
 		if (Gx_PROC.checkstate("Connected")) {
-			as_DIA_CCR(INITIAL_REQUEST);
+			as_DIA_Gx_CCR(INITIAL_REQUEST);
+		}
+		if (Gy_PROC.checkstate("Connected")) {
+			as_DIA_Gy_CCR(INITIAL_REQUEST);
 		}
 		alt {
 			[] GTPC.receive(tr_GTPC_MsgType(g_peer_c, createPDPContextResponse, ctx.teic)) -> value ud {
@@ -522,7 +579,10 @@
 		T_default.start;
 		d := activate(pingpong());
 		if (Gx_PROC.checkstate("Connected") and expect_diameter) {
-			as_DIA_CCR(TERMINATION_REQUEST);
+			as_DIA_Gx_CCR(TERMINATION_REQUEST);
+		}
+		if (Gy_PROC.checkstate("Connected") and expect_diameter) {
+			as_DIA_Gy_CCR(TERMINATION_REQUEST);
 		}
 		alt {
 			[] GTPC.receive(tr_GTPC_MsgType(g_peer_c, deletePDPContextResponse, expect_teid)) -> value ud {
@@ -1938,7 +1998,8 @@
 
 		T_next.start;
 		alt {
-		[Gx_PROC.checkstate("Connected")] as_DIA_CCR(INITIAL_REQUEST) { repeat; }
+		[Gx_PROC.checkstate("Connected")] as_DIA_Gx_CCR(INITIAL_REQUEST) { repeat; }
+		[Gy_PROC.checkstate("Connected")] as_DIA_Gy_CCR(INITIAL_REQUEST) { repeat; }
 		[] pingpong();
 		[] T_next.timeout {
 			f_send_gtpc(ts_GTPC_CreatePDP(g_peer_c, g_c_seq_nr, ctx[next_req_ctx].imsi, g_restart_ctr,
@@ -1992,7 +2053,8 @@
 		rx_resp_ctx := 0;
 		T_next.start;
 		alt {
-		[Gx_PROC.checkstate("Connected")] as_DIA_CCR(TERMINATION_REQUEST) { repeat; }
+		[Gx_PROC.checkstate("Connected")] as_DIA_Gx_CCR(TERMINATION_REQUEST) { repeat; }
+		[Gy_PROC.checkstate("Connected")] as_DIA_Gy_CCR(TERMINATION_REQUEST) { repeat; }
 		[] pingpong();
 		[] T_next.timeout {
 			f_send_gtpc(ts_GTPC_DeletePDP(g_peer_c, g_c_seq_nr, ctx[next_req_ctx].teic_remote, ctx[next_req_ctx].nsapi, '1'B));
@@ -2044,7 +2106,8 @@
 
 		T_next.start;
 		alt {
-		[Gx_PROC.checkstate("Connected")] as_DIA_CCR(INITIAL_REQUEST) { repeat; }
+		[Gx_PROC.checkstate("Connected")] as_DIA_Gx_CCR(INITIAL_REQUEST) { repeat; }
+		[Gy_PROC.checkstate("Connected")] as_DIA_Gy_CCR(INITIAL_REQUEST) { repeat; }
 		[] pingpong();
 		[] T_next.timeout {
 			if (cont_req) {
@@ -2093,7 +2156,7 @@
 		rx_resp_ctx := 0;
 		T_next.start;
 		alt {
-		[Gx_PROC.checkstate("Connected")] as_DIA_CCR(TERMINATION_REQUEST) { repeat; }
+		[Gx_PROC.checkstate("Connected")] as_DIA_Gx_CCR(TERMINATION_REQUEST) { repeat; }
 		[] pingpong();
 		[] T_next.timeout {
 			f_send_gtpc(ts_GTPC_DeletePDP(g_peer_c, g_c_seq_nr, teic_list[next_req_ctx], '0001'B, '1'B));
@@ -2124,6 +2187,56 @@
 		f_shutdown_helper();
 	}
 
+	/* Test charging over Gy interface. */
+	testcase TC_gy_charging_cc_time() runs on GT_CT {
+		var default d;
+
+		g_gy_validity_time := 3; /* Grant access for 3 seconds, needs to be re-validated afterwards */
+		f_init();
+		var PdpContext ctx := valueof(t_DefinePDP(f_rnd_imsi('26242'H), '1234'O, c_ApnInternet, valueof(t_EuaIPv4Dyn)));
+		ctx.pco_req := valueof(ts_PCO_IPv4_DNS_CONT);
+		f_pdp_ctx_act(ctx);
+
+		var OCT4 dns1_addr := f_PCO_extract_proto(ctx.pco_neg, '000d'O);
+
+		/* Send some UL traffic: */
+		var OCT4 saddr := ctx.eua.endUserAddress.endUserAddressIPv4.ipv4_address;
+		f_send_gtpu(ctx, f_gen_icmpv4_echo(saddr, dns1_addr));
+		f_wait_icmp4_echo_reply(ctx);
+
+		T_default.start(10.0);
+		d := activate(pingpong());
+
+		g_gy_validity_time := 2;
+		/* First update reports octests/pkt on both UL/DL (see icmp ping-pong above) */
+		as_DIA_Gy_CCR(UPDATE_REQUEST);
+
+		/* Second update: 0 ul/dl pkt/octet should be reported, since nothing was sent */
+		as_DIA_Gy_CCR(UPDATE_REQUEST);
+
+		/* Third update: make sure report contains again octets/pkts for both UL/DL: */
+		f_send_gtpu(ctx, f_gen_icmpv4_echo(saddr, dns1_addr));
+		f_wait_icmp4_echo_reply(ctx);
+		f_send_gtpu(ctx, f_gen_icmpv4_echo(saddr, dns1_addr));
+		f_wait_icmp4_echo_reply(ctx);
+		as_DIA_Gy_CCR(UPDATE_REQUEST);
+
+		/* Let the CCA reach the GGSN */
+		f_sleep(0.5);
+		deactivate(d);
+		T_default.stop;
+
+		/* Send some data and validate it is reported in the TERMINATION_REQUEST
+		 * (triggered by PFCP Session Deletion Response): */
+		f_send_gtpu(ctx, f_gen_icmpv4_echo(saddr, dns1_addr));
+		f_wait_icmp4_echo_reply(ctx);
+
+		f_pdp_ctx_del(ctx, '1'B);
+
+
+		f_shutdown_helper();
+	}
+
 	control {
 		execute(TC_pdp4_act_deact());
 		execute(TC_pdp4_act_deact_ipcp());
@@ -2170,5 +2283,10 @@
 		execute(TC_lots_of_concurrent_pdp_ctx());
 		/* Keep at the end, crashes older osmo-ggsn versions (OS#5469): */
 		execute(TC_addr_pool_exhaustion());
+
+		/* open5gs specific tests: */
+		if (m_ggsn_impl == GGSN_IMPL_OPEN5GS) {
+			execute(TC_gy_charging_cc_time());
+		}
 	}
 }