module RemsimClient_Tests { /* Integration Tests for osmo-remsim-client * (C) 2019-2020 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 * * This test suite tests osmo-remsim-client by attaching to the external interfaces. */ import from Native_Functions all; import from Osmocom_Types all; import from IPA_Emulation all; import from Misc_Helpers all; /* the PIPEasp port allows us to interact with osmo-remsim-client-shell via stdin/stdout */ import from PIPEasp_PortType all; import from PIPEasp_Types all; import from RSPRO all; import from RSPRO_Types all; import from RSPRO_Server all; import from REMSIM_Tests all; modulepar { boolean mp_have_local_client := false; /* backwards compatible default */ charstring mp_client_shell_cmd := "osmo-remsim-client-shell"; }; type component client_test_CT extends rspro_server_CT { port PIPEasp_PT PIPE; var ComponentIdentity g_srv_comp_id, g_bankd_comp_id; timer g_T_guard := 60.0; }; private altstep as_Tguard() runs on client_test_CT { [] g_T_guard.timeout { setverdict(fail, "Timeout of T_guard"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } private function f_init() runs on client_test_CT { g_srv_comp_id := valueof(ts_CompId(remsimServer, "ttcn-server")); g_bankd_comp_id := valueof(ts_CompId(remsimBankd, "ttcn-bankd")); /* Start the guard timer */ g_T_guard.start; activate(as_Tguard()); f_rspro_srv_init(0, mp_server_ip, mp_server_port, g_srv_comp_id); f_rspro_srv_init(1, mp_bankd_ip, mp_bankd_port, g_bankd_comp_id, exp_connect := false); } /* ConnectClientReq from client to remsim-server */ testcase TC_srv_connectClient() runs on client_test_CT { f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); setverdict(pass); f_sleep(1.0); } /* ConnectClientReq from client to remsim-server */ testcase TC_srv_connectClient_reject() runs on client_test_CT { f_init(); /* expect inbound connectClientReq */ as_connectClientReq(res := illegalClientId); /* expect disconnect by client */ RSPRO_SRV[0].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)); setverdict(pass); f_sleep(1.0); } /* ConnectClientReq from client to remsim-server */ testcase TC_srv_connectClient_configClientBank() runs on client_test_CT { var BankSlot bslot := { 1, 0 }; f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); /* configure client to connect to [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4(mp_bankd_ip), mp_bankd_port)); /* expect inbound connect on simulated bankd */ f_rspro_srv_exp_connect(1); /* expect inbound connectClientReq on simulated bankd */ as_connectClientReq(i := 1); setverdict(pass); f_sleep(1.0); } /* Test if client re-connects to server after connection is lost */ testcase TC_srv_reconnect() runs on client_test_CT { var BankSlot bslot := { 1, 0 }; f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); /* disconnect the client from server and expect re-establish + re-connect */ f_rspro_srv_fini(0); f_rspro_srv_init(0, mp_server_ip, mp_server_port, g_srv_comp_id, exp_connect := true); /* expect inbound connectClientReq */ as_connectClientReq(i := 0); setverdict(pass); f_sleep(1.0); } /* Test if client re-connects to bank after connection is lost */ testcase TC_bank_reconnect() runs on client_test_CT { var BankSlot bslot := { 1, 0 }; f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); /* configure client to connect to [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4(mp_bankd_ip), mp_bankd_port)); /* expect inbound connect on simulated bankd */ f_rspro_srv_exp_connect(1); /* expect inbound connectClientReq on simulated bankd */ as_connectClientReq(i := 1); /* disconnect the client from bankd and expect re-establish + re-connect */ f_rspro_srv_fini(1); f_rspro_srv_init(1, mp_bankd_ip, mp_bankd_port, g_bankd_comp_id, exp_connect := true); /* expect inbound connectClientReq on simulated bankd */ as_connectClientReq(i := 1); setverdict(pass); f_sleep(1.0); } /* Test if client disconnects from bankd after slotmap delete on server */ testcase TC_bank_disconnect() runs on client_test_CT { var BankSlot bslot := { 1, 0 }; f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); /* configure client to connect to [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4(mp_bankd_ip), mp_bankd_port)); /* expect inbound connect on simulated bankd */ f_rspro_srv_exp_connect(1); /* expect inbound connectClientReq on simulated bankd */ as_connectClientReq(i := 1); f_sleep(1.0); /* configure client to disconnect from [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4("0.0.0.0"), 0)); /* expect disconnect of client on simulated bankd side */ RSPRO_SRV[1].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)); setverdict(pass); } /* Test if client connects to bankd after disconnects from bankd after slotmap delete on server */ testcase TC_bank_disconnect_reconnect() runs on client_test_CT { var BankSlot bslot := { 1, 0 }; f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); /* configure client to connect to [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4(mp_bankd_ip), mp_bankd_port)); /* expect inbound connect on simulated bankd */ f_rspro_srv_exp_connect(1); /* expect inbound connectClientReq on simulated bankd */ as_connectClientReq(i := 1); f_sleep(1.0); /* configure client to disconnect from [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4("0.0.0.0"), 0)); /* expect disconnect of client on simulated bankd side */ RSPRO_SRV[1].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)); /* re-start the IPA emulation (which terminated itself on the TCP disconnect */ f_rspro_srv_init(1, mp_bankd_ip, mp_bankd_port, g_bankd_comp_id, exp_connect := false); /* configure client to connect to [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4(mp_bankd_ip), mp_bankd_port)); /* expect inbound connect on simulated bankd */ f_rspro_srv_exp_connect(1); /* expect inbound connect on simulated bankd */ as_connectClientReq(i := 1); setverdict(pass); } /*********************************************************************** * Tests interacting with local remsim-bankd-client via PIPE port ***********************************************************************/ template (value) ASP_PExecuteBackground ts_ExecBg(charstring cmd) := { command := cmd } template (present) ASP_PStdout tr_Stdout(template (present) charstring line) := { stdout := line } template (present) ASP_PStderr tr_Stderr(template (present) charstring line) := { stderr := line } template (value) ASP_PStdin ts_Stdin(template (value) charstring line) := { stdin :=line } /* osmo-remsim-client logs to stderr, so we need to ignore that log output */ private altstep as_ignore_stderr() runs on client_test_CT { [] PIPE.receive(tr_Stderr(?)) { repeat; } } /* start a local osmo-remsim-client as sub-process and attach it to PIPE port */ private function f_init_client_pipe(ClientSlot cs) runs on client_test_CT { var charstring cmdline := mp_client_shell_cmd & " -i 127.0.0.1"; map(self:PIPE, system:PIPE); PIPE.send(ts_ExecBg(cmdline & " -c " & int2str(cs.clientId) & " -n " & int2str(cs.slotNr))); activate(as_ignore_stderr()); } /* pretty-print an octet string as series of ASCII encoded hex bytes with spaces */ function f_osmo_hexdump(octetstring input) return charstring { var charstring ret := ""; for (var integer i := 0; i < lengthof(input); i := i+1) { ret := ret & f_str_tolower(oct2str(input[i])) & " "; } return ret; } /* simulated bankd instructs client to "set ATR"; expect it to show up on stdout */ function f_set_atr(template (value) ClientSlot cs, octetstring atr, template ResultCode exp_res := ok, integer i := 0) runs on client_test_CT { RSPRO_SRV[i].send(ts_RSPRO_SetAtrReq(cs, atr)); interleave { [] RSPRO_SRV[i].receive(tr_RSPRO_SetAtrRes(exp_res)); [] PIPE.receive(tr_Stdout("SET_ATR: " & f_osmo_hexdump(atr))); } } /* send a C-APDU from simulated application to client stdin; expect it on simulated bankd */ function f_client2bank(template (present) ClientSlot cs, template (present) BankSlot bs, octetstring c_apdu, integer i:=0) runs on client_test_CT { /* send C-APDU via STDIN of osmo-remsim-client-shell */ PIPE.send(ts_Stdin(oct2str(c_apdu))); /* expect the same C-APDU to arrive via RSPRO tpduModemToCard */ f_rspro_srv_exp(tr_RSPRO_TpduModemToCard(cs, bs, ?, c_apdu), i); } /* send a R-APDU from simulated bankd to client; expect it on simualated app (client stdout) */ function f_bank2client(template (present) BankSlot bs, template (present) ClientSlot cs, octetstring r_apdu, boolean exp_fail := false, integer i:=0) runs on client_test_CT { var TpduFlags flags := { tpduHeaderPresent:=true, finalPart:=true, procByteContinueTx:=false, procByteContinueRx:=false } timer T := 10.0; RSPRO_SRV[i].send(ts_RSPRO_TpduCardToModem(bs, cs, flags, r_apdu)); T.start; alt { [] PIPE.receive(tr_Stdout("R-APDU: " & f_osmo_hexdump(r_apdu))) { if (exp_fail) { setverdict(fail, "Expected R-APDU to fail but still received it"); self.stop; } else { setverdict(pass); } } [] PIPE.receive(tr_Stdout(?)) { setverdict(fail, "Unexpected R-APDU on stdout of remsim-client-shell"); self.stop; } [] T.timeout { if (exp_fail) { setverdict(pass); } else { setverdict(fail, "Timeout waiting for R-APDU on remsim-client-shell"); self.stop; } } } } /* Transceive a C+R APDU from client (via pipe) to simulated bankd and back */ function f_xceive_apdus(ClientSlot cslot, BankSlot bslot, integer count := 100, integer i := 0) runs on client_test_CT { for (var integer j := 0; j < count; j := j+1) { var octetstring c_apdu := f_rnd_octstring_rnd_len(270); var octetstring r_apdu := f_rnd_octstring_rnd_len(270); f_client2bank(cslot, bslot, c_apdu, i:=i); f_bank2client(bslot, cslot, r_apdu, i:=i); } } /* transceive APDUs using forked osmo-remsim-client-shell + stdio */ testcase TC_pipe_xceive_apdus() runs on client_test_CT { var BankSlot bslot := { 1, 0 }; var ClientSlot cslot := { 321, 123 }; f_init_client_pipe(cslot); f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); /* configure client to connect to [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4(mp_bankd_ip), mp_bankd_port)); /* expect inbound connect on simulated bankd */ f_rspro_srv_exp_connect(1); /* expect inbound connectClientReq on simulated bankd */ as_connectClientReq(i := 1); f_set_atr(cslot, '3B9F96801FC78031A073BE21136743200718000001A5'O, i:=1); f_xceive_apdus(cslot, bslot, count := 100, i:=1); } /* Send R-APDU from correct bankId/slotNr but to wrong ClientId */ testcase TC_pipe_apdu_wrong_cslot() runs on client_test_CT { var BankSlot bslot := { 1, 0 }; var ClientSlot cslot := { 321, 123 }; f_init_client_pipe(cslot); f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); /* configure client to connect to [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4(mp_bankd_ip), mp_bankd_port)); /* expect inbound connect on simulated bankd */ f_rspro_srv_exp_connect(1); /* expect inbound connectClientReq on simulated bankd */ as_connectClientReq(i := 1); f_set_atr(cslot, '3B9F96801FC78031A073BE21136743200718000001A5'O, i:=1); var octetstring c_apdu := f_rnd_octstring_rnd_len(270); var octetstring r_apdu := f_rnd_octstring_rnd_len(270); /* Send C-APDU from correct ClientId/Slot to simulated bankd */ f_client2bank(cslot, bslot, c_apdu, i:=1); /* respond with R-APDU from correct bankId/Slot but stating wrong ClientId */ cslot.clientId := 1023; f_bank2client(bslot, cslot, r_apdu, exp_fail:=true, i:=1); } /* Send R-APDU from wrong bankId/slotNr but to correct ClientId/Slot */ testcase TC_pipe_apdu_wrong_bslot() runs on client_test_CT { var BankSlot bslot := { 1, 0 }; var ClientSlot cslot := { 321, 123 }; f_init_client_pipe(cslot); f_init(); /* expect inbound connectClientReq */ as_connectClientReq(); /* configure client to connect to [simulated] bankd */ f_rspro_config_client_bank(bslot, ts_IpPort(ts_IPv4(mp_bankd_ip), mp_bankd_port)); /* expect inbound connect on simulated bankd */ f_rspro_srv_exp_connect(1); /* expect inbound connectClientReq on simulated bankd */ as_connectClientReq(i := 1); f_set_atr(cslot, '3B9F96801FC78031A073BE21136743200718000001A5'O, i:=1); var octetstring c_apdu := f_rnd_octstring_rnd_len(270); var octetstring r_apdu := f_rnd_octstring_rnd_len(270); /* Send C-APDU from correct ClientId/Slot to simulated bankd */ f_client2bank(cslot, bslot, c_apdu, i:=1); /* respond with R-APDU from wrong bankId but stating correct ClientId */ bslot.bankId := 1023; f_bank2client(bslot, cslot, r_apdu, exp_fail:=true, i:=1); } /* TODO: * send a configClientBankIpReq and change the bank of an active client * send a configClientBankSlotReq and chagne the bank slot of an active client * test keepalive mechanism: do we get IPA PING? * test keepalive mechanism: do we see disconnect+reconnect if we don't respond to IPA PING? * test messages in invalid state, e.g. APDUs before we're connected to a bank * test messages on server connection which are only permitted on bankd connection */ control { execute( TC_srv_connectClient() ); execute( TC_srv_connectClient_reject() ); execute( TC_srv_connectClient_configClientBank() ); execute( TC_srv_reconnect() ); execute( TC_bank_reconnect() ); execute( TC_bank_disconnect() ); execute( TC_bank_disconnect_reconnect() ); if (mp_have_local_client) { execute( TC_pipe_xceive_apdus() ); execute( TC_pipe_apdu_wrong_cslot() ); execute( TC_pipe_apdu_wrong_bslot() ); } } }