diff --git a/asterisk/AMI_Functions.ttcn b/asterisk/AMI_Functions.ttcn index d5139f55d..72420e1fc 100644 --- a/asterisk/AMI_Functions.ttcn +++ b/asterisk/AMI_Functions.ttcn @@ -25,10 +25,14 @@ modulepar { } const charstring AMI_FIELD_ACTION := "Action"; +const charstring AMI_FIELD_ACTION_ID := "ActionId"; const charstring AMI_FIELD_USERNAME := "Username"; const charstring AMI_FIELD_SECRET := "Secret"; const charstring AMI_FIELD_RESPONSE := "Response"; +/* Extensions: */ +const charstring AMI_FIELD_REGISTRATION := "Registration"; + type record AMI_Field { charstring key, charstring val @@ -69,28 +73,46 @@ tr_AMI_Field(template (present) charstring key := ?, template (value) AMI_Field ts_AMI_Field_Action(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION, val); template (value) AMI_Field +ts_AMI_Field_ActionId(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_ACTION_ID, val); +template (value) AMI_Field ts_AMI_Field_Username(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_USERNAME, val); template (value) AMI_Field ts_AMI_Field_Secret(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_SECRET, val); +/* Extensions: */ +template (value) AMI_Field +ts_AMI_Field_Registration(template (value) charstring val) := ts_AMI_Field(AMI_FIELD_REGISTRATION, val); template (present) AMI_Field tr_AMI_Field_Action(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_ACTION, val); template (present) AMI_Field +tr_AMI_Field_ActionId(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_ACTION_ID, val); +template (present) AMI_Field tr_AMI_Field_Username(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_USERNAME, val); template (present) AMI_Field tr_AMI_Field_Secret(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_SECRET, val); template (present) AMI_Field tr_AMI_Field_Response(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_RESPONSE, val); +/* Extensions: */ +template (present) AMI_Field +tr_AMI_Field_Registration(template (present) charstring val := ?) := tr_AMI_Field(AMI_FIELD_REGISTRATION, val); template (present) AMI_Field tr_AMI_Field_ResponseSuccess := tr_AMI_Field(AMI_FIELD_RESPONSE, "Success"); -/* +/*********************** * Message Templates: + ***********************/ + +/* + * ACTIONS */ +/* Action: Login + * Username: + * Secret: + */ template (value) AMI_Msg ts_AMI_Action_Login(charstring username, charstring secret) := { ts_AMI_Field_Action("Login"), @@ -106,15 +128,101 @@ tr_AMI_Action_Login(template(present) charstring username := ?, tr_AMI_Field_Secret(secret) ); +/* Action: PJSIPRegister + * ActionID: + * Registration: volte_ims + */ +template (value) AMI_Msg +ts_AMI_Action_PJSIPRegister(template (value) charstring registration := "volte_ims", + template (value) charstring action_id := "0001") := { + ts_AMI_Field_Action("PJSIPRegister"), + ts_AMI_Field_ActionId(action_id), + ts_AMI_Field_Registration(registration) +}; +template (present) AMI_Msg +tr_AMI_Action_PJSIPRegister(template (present) charstring registration := ?, + template (present) charstring action_id := ?) := { + tr_AMI_Field_Action("PJSIPRegister"), + tr_AMI_Field_ActionId(action_id), + tr_AMI_Field_Registration(registration) +}; + +/* + * RESPONSES + */ + +/* Response: Success + */ template (present) AMI_Msg tr_AMI_Response_Success := superset( tr_AMI_Field_ResponseSuccess ); +/* Response: Success + * ActionId: + */ +template (present) AMI_Msg +tr_AMI_Response_Success_ActionId(template (present) charstring action_id := ?) := superset( + tr_AMI_Field_ResponseSuccess, + tr_AMI_Field_ActionId(action_id) +); + /* * Functions: */ +/* Generate a random "ActionId" value: */ +function f_gen_action_id() return charstring { + return hex2str(f_rnd_hexstring(16)); +} + +function f_ami_msg_find(AMI_Msg msg, + template (present) charstring key := ?) +return template (omit) AMI_Field { + var integer i; + + for (i := 0; i < lengthof(msg); i := i + 1) { + if (not ispresent(msg[i])) { + continue; + } + if (match(msg[i].key, key)) { + return msg[i]; + } + } + return omit; +} + +function f_ami_msg_find_or_fail(AMI_Msg msg, + template (present) charstring key := ?) +return AMI_Field { + var template (omit) AMI_Field field; + field := f_ami_msg_find(msg, key); + if (istemplatekind(field, "omit")) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Key ", key, " not found in ", msg)); + } + return valueof(field); +} + +function f_ami_msg_get_value(AMI_Msg msg, + template (present) charstring key := ?) +return template (omit) charstring { + var template (omit) AMI_Field field; + field := f_ami_msg_find(msg, key); + if (istemplatekind(field, "omit")) { + return omit; + } + return field.val; +} + +function f_ami_msg_get_value_or_fail(AMI_Msg msg, + template (present) charstring key := ?) +return template charstring { + var AMI_Field field; + field := f_ami_msg_find_or_fail(msg, key); + return field.val; +} + private function f_ami_wait_for_prompt_str(TELNETasp_PT pt, charstring log_label := "(?)") return charstring { var charstring rx, buf := ""; @@ -172,11 +280,23 @@ function f_ami_transceive_match(TELNETasp_PT pt, function f_ami_transceive_match_response_success(TELNETasp_PT pt, template (value) AMI_Msg tx_msg) { - f_ami_transceive_match(pt, tx_msg, tr_AMI_Response_Success); + var template (present) AMI_Msg exp_resp; + var template (omit) charstring action_id := f_ami_msg_get_value(valueof(tx_msg), AMI_FIELD_ACTION_ID); + if (isvalue(action_id)) { + exp_resp := tr_AMI_Response_Success_ActionId(action_id); + } else { + exp_resp := tr_AMI_Response_Success; + } + f_ami_transceive_match(pt, tx_msg, exp_resp); } function f_ami_action_login(TELNETasp_PT pt, charstring username, charstring secret) { f_ami_transceive_match_response_success(pt, ts_AMI_Action_Login(username, secret)); } +function f_ami_action_PJSIPRegister(TELNETasp_PT pt, charstring register) { + var charstring reg_action_id := f_gen_action_id(); + f_ami_transceive_match_response_success(pt, ts_AMI_Action_PJSIPRegister(register, reg_action_id)); +} + } diff --git a/asterisk/Asterisk_Tests.ttcn b/asterisk/Asterisk_Tests.ttcn index c94f97f4b..723587324 100644 --- a/asterisk/Asterisk_Tests.ttcn +++ b/asterisk/Asterisk_Tests.ttcn @@ -37,6 +37,7 @@ modulepar { charstring mp_local_ims_host := "127.0.0.3"; integer mp_local_ims_port := 5060; + charstring mp_ims_imsi := "001010000000002"; /* Asterisk AMI: */ charstring mp_ami_user := "test_user"; @@ -68,6 +69,15 @@ function f_init_ConnHdlrPars(integer idx := 1) runs on test_CT return SIPConnHdl return valueof(pars); } +function f_init_IMS_ConnHdlrPars(integer idx := 1) runs on test_CT return IMS_ConnHdlrPars { + var template (value) IMS_CallPars cp := t_IMS_CallPars(mp_local_sip_host, 1234 + 2*idx); + var template (value) IMS_ConnHdlrPars pars := t_IMS_Pars(mp_local_ims_host, + mp_local_ims_port, + mp_ims_imsi, + cp := cp); + return valueof(pars); +} + /* Initialize connection towards Asterisk AMI */ private function f_init_ami() runs on test_CT { map(self:AMI, system:AMI); @@ -111,6 +121,22 @@ runs on test_CT return SIPConnHdlr { return vc_conn; } +function f_start_handler_IMS(ims_void_fn fn, IMS_ConnHdlrPars pars) +runs on test_CT return IMS_ConnHdlr { + var IMS_ConnHdlr vc_conn; + var charstring id := testcasename() & "-IMS_ConnHdlr-" & pars.user; + + vc_conn := IMS_ConnHdlr.create(id) alive; + + connect(vc_conn:SIP, vc_SIP:CLIENT); + connect(vc_conn:SIP_PROC, vc_SIP:CLIENT_PROC); + + connect(vc_conn:COORD, self:IMS_COORD); + + vc_conn.start(f_ims_handler_init(fn, id, pars)); + return vc_conn; +} + /* Test SIP registration of local clients */ private function f_TC_internal_registration(charstring id) runs on SIPConnHdlr { @@ -290,12 +316,46 @@ testcase TC_selftest() runs on test_CT { setverdict(pass); } +/* Test SIP registration of local clients */ +private function f_TC_ims_registration(charstring id) runs on IMS_ConnHdlr { + + as_IMS_register(); + setverdict(pass); +} +testcase TC_ims_registration() runs on test_CT { + var IMS_ConnHdlrPars pars; + var IMS_ConnHdlr vc_conn; + f_init(); + pars := f_init_IMS_ConnHdlrPars(); + vc_conn := f_start_handler_IMS(refers(f_TC_ims_registration), pars); + + /* Trigger registration: */ + f_ami_action_PJSIPRegister(AMI, "volte_ims"); + /* TODO: Rx "Event: AuthRequest" */ + /* TODO: Tx "Action: AuthResponse" */ + /* TODO: Rx "Response: Success" */ + /* TODO: once registration is successful, rx: + * Event: Registry + * ChannelType: pjsip + * Username: + * Domain: + * Status: + * Cause: */ + + /* TODO: test "Action: PJSIPUnregister" */ + + /* TODO: in another test emulating a call, test "Action: DedicatedBearerStatus" */ + + vc_conn.done; +} + control { execute( TC_internal_registration() ); execute( TC_internal_call_momt() ); execute( TC_internal_call_all_2registered() ); execute( TC_internal_call_all_3registered() ); execute( TC_internal_call_all_4registered() ); + execute( TC_ims_registration() ); } } diff --git a/asterisk/IMS_ConnectionHandler.ttcn b/asterisk/IMS_ConnectionHandler.ttcn index a1baeb4e9..cded5fcb3 100644 --- a/asterisk/IMS_ConnectionHandler.ttcn +++ b/asterisk/IMS_ConnectionHandler.ttcn @@ -43,8 +43,8 @@ type record of IMS_ConnHdlr IMS_ConnHdlrList; type record IMS_ConnHdlrPars { float t_guard, - charstring remote_sip_host, - uint16_t remote_sip_port, + charstring remote_sip_host optional, + uint16_t remote_sip_port optional, charstring user, charstring display_name, charstring password, @@ -66,6 +66,10 @@ type record IMS_CallParsMT { /* Whether to expect CANCEL instead of ACK as answer to our OK */ boolean exp_cancel } +template (value) IMS_CallParsMT t_IMS_CallParsMT := { + wait_coord_cmd_pickup := false, + exp_cancel := false +} type record IMS_CallPars { SipAddr calling optional, @@ -85,4 +89,127 @@ type record IMS_CallPars { IMS_CallParsMT mt } +template (value) IMS_CallPars t_IMS_CallPars(charstring local_rtp_addr, + uint16_t local_rtp_port := 0, + template (omit) SipAddr calling := omit, + template (omit) SipAddr called := omit) := { + calling := calling, + called := called, + from_addr := omit, + to_addr := omit, + sip_call_id := hex2str(f_rnd_hexstring(15)), + sip_seq_nr := f_sip_rand_seq_nr(), + sip_body := omit, + local_rtp_addr := local_rtp_addr, + local_rtp_port := local_rtp_port, + peer_sdp := omit, + mt := t_IMS_CallParsMT +} + +template (value) IMS_ConnHdlrPars t_IMS_Pars(charstring local_sip_host, + uint16_t local_sip_port, + charstring user, + charstring display_name := "Anonymous", + charstring password := "secret", + template (omit) IMS_CallPars cp := omit) := { + t_guard := 30.0, + remote_sip_host := omit, + remote_sip_port := omit, + user := user, + display_name := f_sip_str_quote(display_name), + password := password, + registrar_sip_req_uri := valueof(ts_SipUrlHost(local_sip_host)), + registrar_sip_record := ts_SipAddr(ts_HostPort(local_sip_host), + ts_UserInfo(user), + f_sip_str_quote(display_name)), + registrar_sip_call_id := hex2str(f_rnd_hexstring(15)) & "@" & local_sip_host, + registrar_sip_seq_nr := f_sip_rand_seq_nr(), + local_via := ts_Via_from(ts_HostPort(local_sip_host, local_sip_port)), + local_sip_url_ext := ts_SipUrl(ts_HostPort(local_sip_host, local_sip_port), + ts_UserInfo(user)), + local_sip_record := ts_SipAddr(ts_HostPort(local_sip_host), + ts_UserInfo(user)), + local_contact := valueof(ts_Contact({ + ts_ContactAddress( + ts_Addr_Union_SipUrl(ts_SipUrl(ts_HostPort( + local_sip_host, + local_sip_port), + ts_UserInfo(user))), + omit) + })), + cp := cp +} + +private altstep as_Tguard() runs on IMS_ConnHdlr { + [] g_Tguard.timeout { + setverdict(fail, "Tguard timeout"); + mtc.stop; + } +} + +type function ims_void_fn(charstring id) runs on IMS_ConnHdlr; +function f_ims_handler_init(ims_void_fn fn, charstring id, IMS_ConnHdlrPars pars) +runs on IMS_ConnHdlr { + g_name := id; + g_pars := pars; + g_Tguard.start(pars.t_guard); + activate(as_Tguard()); + + /* call the user-supied test case function */ + fn.apply(id); +} + +private altstep as_SIP_fail_req(charstring exp_msg_str := "") runs on IMS_ConnHdlr +{ + var PDU_SIP_Request sip_req; + [] SIP.receive(PDU_SIP_Request:?) -> value sip_req { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(g_name & ": Received unexpected SIP Req message := ", sip_req, "\nvs exp := ", exp_msg_str)); + } +} + +private altstep as_SIP_fail_resp(charstring exp_msg_str := "") runs on IMS_ConnHdlr +{ + var PDU_SIP_Response sip_resp; + [] SIP.receive(PDU_SIP_Response:?) -> value sip_resp { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(g_name & ": Received unexpected SIP Resp message := ", sip_resp, "\nvs exp := ", exp_msg_str)); + } +} + +/* Peer is calling us, accept it: */ +altstep as_IMS_register(boolean exp_update_to_direct_rtp := true, + boolean fail_others := true) runs on IMS_ConnHdlr +{ + var template (present) PDU_SIP_Request exp_req := + tr_SIP_REGISTER(g_pars.registrar_sip_req_uri, + ?, + tr_SipAddr(), + tr_SipAddr(), + tr_Via_from(?)); + var charstring sip_expect_str := log2str(exp_req); + + [] SIP.receive(exp_req) -> value g_rx_sip_req { + var template (value) PDU_SIP_Response tx_resp; + var Via via; + var charstring tx_sdp; + + via := g_rx_sip_req.msgHeader.via; + + /* Tx 200 OK + * TODO: Tx Unauthorized instead, with IMS params */ + tx_resp := ts_SIP_Response(g_pars.cp.sip_call_id, + g_pars.cp.from_addr, + g_pars.cp.to_addr, + "REGISTER", 200, + g_pars.cp.sip_seq_nr, + "OK", + via); + SIP.send(tx_resp); + } + [fail_others] as_SIP_fail_resp(sip_expect_str); + [fail_others] as_SIP_fail_req(sip_expect_str); + +} + }