/* * * Copyright (C) 2017, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * Open Connections GmbH * Stau 33 * D-26122 Oldenburg, Germany * * * Module: dcmnet * * Author: Michael Onken * * Purpose: Test DcmSPC and DcmSCU classes (only certain aspects so far) * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #ifdef WITH_THREADS #include "dcmtk/ofstd/oftest.h" #include "dcmtk/dcmnet/scp.h" #include "dcmtk/dcmnet/scu.h" static OFLogger t_scuscp_logger= OFLog::getLogger("dcmtk.test.tscuscp"); /** SCP derived from DcmSCP in order to test two types of virtual methods: * * Additionally the SCP derives from OFThread in order to construct the * test case (i.e. send data with SCU at the same time and check results). */ struct TestSCP: DcmSCP, OFThread { /** Constructor */ TestSCP() : DcmSCP(), m_listen_result(EC_NotYetImplemented), // value indicating "not set" m_set_stop_after_assoc(OFFalse), m_set_stop_after_timeout(OFFalse), m_stop_after_assoc_result(OFFalse), m_stop_after_timeout_result(OFFalse), m_notify_connection_timeout_result(OFFalse), m_notify_assoc_termination_result(OFFalse) { } /** Clear settings */ void clear() { m_listen_result = EC_NotYetImplemented; m_set_stop_after_assoc = OFFalse; m_set_stop_after_timeout = OFFalse; m_stop_after_assoc_result = OFFalse; m_stop_after_timeout_result = OFFalse; m_notify_connection_timeout_result = OFFalse; m_notify_assoc_termination_result = OFFalse; } /** Overwrite method from DcmSCP in order to test feature to stop after current * association. * @return Returns value of m_set_stop_after_assoc */ virtual OFBool stopAfterCurrentAssociation() { if (m_set_stop_after_assoc) { m_stop_after_assoc_result = OFTrue; } return m_set_stop_after_assoc; } /** Overwrite method from DcmSCP in order to test feature to stop after * the SCP's connection timeout occurred in non-blocking mode. * @return Returns value of m_set_stop_after_timeout */ virtual OFBool stopAfterConnectionTimeout() { if (m_set_stop_after_timeout) { m_stop_after_timeout_result = OFTrue; } return m_set_stop_after_timeout; } /** Overwrite method from DcmSCP in order to test feature that SCP reports * connection timeout in non-blocking mode. */ virtual void notifyConnectionTimeout() { m_notify_connection_timeout_result = OFTrue; } /** Overwrite method from DcmSCP in order to test feature that SCP reports * the termination of an association. */ virtual void notifyAssociationTermination() { m_notify_assoc_termination_result = OFTrue; } /// The result returned my SCP's listen() method OFCondition m_listen_result; /// If set, the SCP should stop after the currently running association OFBool m_set_stop_after_assoc; /// If set, the SCP should stop after TCP timeout occurred in non-blocking mode OFBool m_set_stop_after_timeout; /// Indicator whether related virtual function was called and returned accordingly OFBool m_stop_after_assoc_result; /// Indicator whether related virtual function was called and returned accordingly OFBool m_stop_after_timeout_result; /// Indicator whether related virtual notifier function was called OFBool m_notify_connection_timeout_result; /// Indicator whether related virtual notifier function was called OFBool m_notify_assoc_termination_result; /** Method called by OFThread to start SCP operation. Starts listen() loop of DcmSCP. */ virtual void run() { m_listen_result = listen(); } }; /** Configure Verification SOP class on server * @param cfg The SCP configuration to modify */ void configure_scp_for_echo(DcmSCPConfig& cfg, T_ASC_SC_ROLE roleOfRequestor = ASC_SC_ROLE_DEFAULT) { cfg.setPort(11112); OFList xfers; xfers.push_back(UID_LittleEndianImplicitTransferSyntax); OFCHECK(cfg.addPresentationContext(UID_VerificationSOPClass, xfers, roleOfRequestor).good()); } /** Send ECHO to SCP * @param called_ae_title The Called AE Title to be used * @param do_release If OFTrue, SCU should release the association (otherwise * we expect the SCP to do that. * @param secs_after_echo Seconds to wait after sending echo */ void scu_sends_echo( const OFString& called_ae_title, const OFBool do_release = OFTrue, const int secs_after_echo = 0) { // make sure server is up OFStandard::sleep(2); DcmSCU scu; scu.setAETitle("TEST_SCU"); scu.setPeerAETitle(called_ae_title); scu.setPeerHostName("localhost"); scu.setPeerPort(11112); OFList xfers; xfers.push_back(UID_LittleEndianImplicitTransferSyntax); OFCondition result = scu.addPresentationContext(UID_VerificationSOPClass, xfers); OFCHECK(result.good()); result = scu.initNetwork(); OFCHECK(result.good()); result = scu.negotiateAssociation(); OFCHECK(result.good()); result = scu.sendECHORequest(1); OFCHECK(result.good()); OFStandard::sleep(secs_after_echo); if (do_release) { result = scu.releaseAssociation(); OFCHECK(result.good()); } return; } // Test case that checks whether server returns after association if enabled OFTEST_FLAGS(dcmnet_scp_stop_after_current_association, EF_Slow) { TestSCP scp; DcmSCPConfig& config = scp.getConfig(); configure_scp_for_echo(config); config.setAETitle("STOP_AFTER_ASSOC"); config.setConnectionBlockingMode(DUL_BLOCK); scp.start(); scu_sends_echo("STOP_AFTER_ASSOC"); OFStandard::sleep(1); // make sure server would have time to return // Check whether all test variables have the correct values OFCHECK(scp.m_listen_result == EC_NotYetImplemented); // still listening OFCHECK(scp.m_stop_after_assoc_result == OFFalse); OFCHECK(scp.m_stop_after_timeout_result == OFFalse); OFCHECK(scp.m_notify_connection_timeout_result == OFFalse); OFCHECK(scp.m_notify_assoc_termination_result == OFTrue); // scu released association // Tell SCP to stop after association and run SCU again scp.clear(); scp.m_set_stop_after_assoc = OFTrue; scu_sends_echo("STOP_AFTER_ASSOC"); OFStandard::sleep(1); // Check whether all test variables have the correct values OFCHECK(scp.m_listen_result == NET_EC_StopAfterAssociation); OFCHECK(scp.m_stop_after_timeout_result == OFFalse); OFCHECK(scp.m_notify_connection_timeout_result == OFFalse); OFCHECK(scp.m_notify_assoc_termination_result == OFTrue); // scu released association scp.join(); OFCHECK(scp.m_listen_result == NET_EC_StopAfterAssociation); } OFTEST_FLAGS(dcmnet_scp_stop_after_timeout, EF_Slow) { TestSCP scp; DcmSCPConfig& config = scp.getConfig(); configure_scp_for_echo(config); config.setAETitle("STOP_AFTER_TMOUT"); config.setConnectionBlockingMode(DUL_NOBLOCK); config.setConnectionTimeout(3); scp.m_set_stop_after_timeout = OFTrue; scp.start(); scu_sends_echo("STOP_AFTER_TMOUT"); OFStandard::sleep(5); // make sure server has enough time to run into timeout // Check whether all test variables have the correct values OFCHECK(scp.m_stop_after_assoc_result == OFFalse); OFCHECK(scp.m_stop_after_timeout_result == OFTrue); // virtual "decision" method actually called OFCHECK(scp.m_notify_connection_timeout_result == OFTrue); // virtual notifier method actually called OFCHECK(scp.m_notify_assoc_termination_result == OFTrue); // scu released association scp.join(); OFCHECK(scp.m_listen_result == NET_EC_StopAfterConnectionTimeout); // SCP returns, using specific return code } // Check whether SCP does not stop without request in blocking mode OFTEST_FLAGS(dcmnet_scp_no_stop_wo_request_noblock, EF_Slow) { TestSCP scp; DcmSCPConfig& config = scp.getConfig(); configure_scp_for_echo(config); config.setAETitle("NO_STOP_NOBLOCK"); config.setConnectionBlockingMode(DUL_NOBLOCK); config.setConnectionTimeout(1); scp.m_set_stop_after_timeout = OFFalse; scp.start(); scu_sends_echo("NO_STOP_NOBLOCK"); OFStandard::sleep(3); // should enough for the SCP to run into a timeout (1 sec) // Check whether all test variables have the correct values OFCHECK(scp.m_listen_result == EC_NotYetImplemented); // still listening OFCHECK(scp.m_stop_after_assoc_result == OFFalse); OFCHECK(scp.m_stop_after_timeout_result == OFFalse); OFCHECK(scp.m_notify_connection_timeout_result == OFTrue); // Timeout occurred, but we do not stop OFCHECK(scp.m_notify_assoc_termination_result == OFTrue); // SCU released association // Stop (already tested in former test cases) scp.m_set_stop_after_timeout = OFTrue; scp.join(); } // Check whether SCP does not stop without request in blocking mode OFTEST_FLAGS(dcmnet_scp_no_stop_wo_request_block, EF_Slow) { TestSCP scp; DcmSCPConfig& config = scp.getConfig(); configure_scp_for_echo(config); config.setAETitle("NO_STOP_BLOCK"); config.setConnectionBlockingMode(DUL_BLOCK); scp.start(); scu_sends_echo("NO_STOP_BLOCK"); OFStandard::sleep(3); // should enough for the SCP to run into a timeout (1 sec) // Check whether all test variables have the correct values // SCP did not return yet (check whether it is still connected, // since no test for m_listen_result possible) OFCHECK(scp.m_listen_result == EC_NotYetImplemented); // still listening OFCHECK(scp.m_stop_after_assoc_result == OFFalse); OFCHECK(scp.m_stop_after_timeout_result == OFFalse); OFCHECK(scp.m_notify_connection_timeout_result == OFFalse); OFCHECK(scp.m_notify_assoc_termination_result == OFTrue); // SCU released association // Stop (already tested in former test cases), therefore, send another echo scp.m_set_stop_after_assoc = OFTrue; scu_sends_echo("NO_STOP_BLOCK"); scp.join(); } // Check association termination notifier does not fire when there was no // association at all OFTEST_FLAGS(dcmnet_scp_no_term_notify_without_association, EF_Slow) { TestSCP scp; DcmSCPConfig& config = scp.getConfig(); configure_scp_for_echo(config); config.setAETitle("NO_TERM_WO_ASSOC"); config.setConnectionBlockingMode(DUL_NOBLOCK); config.setConnectionTimeout(1); scp.m_set_stop_after_timeout = OFTrue; scp.start(); OFStandard::sleep(3); // make sure server runs into timeout // Check whether all test variables have the correct values. OFCHECK(scp.m_stop_after_assoc_result == OFFalse); OFCHECK(scp.m_stop_after_timeout_result == OFFalse); OFCHECK(scp.m_notify_connection_timeout_result == OFFalse); OFCHECK(scp.m_notify_assoc_termination_result == OFFalse); // no notification scp.join(); OFCHECK(scp.m_listen_result == NET_EC_StopAfterConnectionTimeout); // SCP ran into timeout } /** Helper function for testing role selection, test "dcmnet_scp_role_selection". * @param r_req The role selection setting from the association requestor * @param r_acc The role selection setting configured for the association acceptor * @param expected_result The expected result of the role negotiation, i.e. what the * acceptor will return to the client. * @param expect_assoc_reject If OFTrue, it is expected that the negotiation fails, * default is OFFalse */ void test_role_selection(const T_ASC_SC_ROLE r_req, const T_ASC_SC_ROLE r_acc, const T_ASC_SC_ROLE expected_result, const OFBool expect_assoc_reject = OFFalse, const OFBool acceptDefaultInsteadOfSCP = OFFalse) { // Make it possible to loop over roles, used for checking the // negotiated presentation contexts. static OFVector roles; if (roles.empty()) { roles.push_back(ASC_SC_ROLE_DEFAULT); roles.push_back(ASC_SC_ROLE_NONE); roles.push_back(ASC_SC_ROLE_SCP); roles.push_back(ASC_SC_ROLE_SCU); roles.push_back(ASC_SC_ROLE_SCUSCP); } TestSCP scp; DcmSCPConfig& config = scp.getConfig(); configure_scp_for_echo(config, r_acc); config.setAETitle("ACCEPTOR"); config.setConnectionBlockingMode(DUL_NOBLOCK); config.setConnectionTimeout(3); config.setAlwaysAcceptDefaultRole(acceptDefaultInsteadOfSCP); scp.start(); OFStandard::sleep(1); DcmSCU scu; scu.setPeerAETitle("ACCEPTOR"); scu.setAETitle("REQUESTOR"); scu.setPeerHostName("localhost"); scu.setPeerPort(11112); OFList ts; ts.push_back(UID_LittleEndianImplicitTransferSyntax); OFCHECK(scu.addPresentationContext(UID_VerificationSOPClass, ts, r_req).good()); OFCHECK(scu.initNetwork().good()); if (!expect_assoc_reject) OFCHECK(scu.negotiateAssociation().good()); else { OFCHECK(scu.negotiateAssociation() == DUL_ASSOCIATIONREJECTED); } // Loop over roles and check for each role whether has been negotiated or not. // Only a single role (the expected_result) should be successfully negotiated. OFVector::iterator it = roles.begin(); OFCondition result; while (it != roles.end()) { T_ASC_PresentationContextID id = scu.findPresentationContextID(UID_VerificationSOPClass, UID_LittleEndianImplicitTransferSyntax, (*it)); if ( ((*it) == expected_result) && !expect_assoc_reject) { if (id == 0) { OFCHECK_FAIL("Error while testing requestor role " << ASC_role2String(r_req) << " versus acceptor role " << ASC_role2String(r_acc) << ", expected result: " << ASC_role2String(expected_result) << ", but did not find related presentation context with that role"); } } else { if (id != 0) { OFCHECK_FAIL("Error while testing requestor role " << ASC_role2String(r_req) << " versus acceptor role " << ASC_role2String(r_acc) << ", expected result: " << ASC_role2String(expected_result) << ", but found unexpected presentation context for role " << ASC_role2String(*it)); } } it++; } scp.m_set_stop_after_assoc = OFTrue; scp.m_set_stop_after_timeout = OFTrue; // also handles the association rejection case // Only release association if we're connected (could happen we're not in case // of test failures of if expect_assoc_reject is true) if (scu.isConnected()) OFCHECK(scu.releaseAssociation().good()); scp.join(); } // Test case that checks whether server returns after association if enabled OFTEST_FLAGS(dcmnet_scp_role_selection, EF_Slow) { // The ollowing role selection behaviour should be implemented and // is exercised in this test (copied from dul.h): // * +--------------------+------------------+---------+ // * | Requestor Proposal | Acceptor Setting | Result | // * +--------------------+------------------+---------+ // * | SCU | SCP | NONE | // * | SCU | SCU | SCU | // * | SCU | SCU/SCP | SCU | // * | SCU | DEFAULT | DEFAULT | // * | SCP | SCP | SCP | // * | SCP | SCU | NONE | // * | SCP | SCU/SCP | SCP | // * | SCP | DEFAULT | DEFAULT | // * | SCU/SCP | SCP | SCP | // * | SCU/SCP | SCU | SCU | // * | SCU/SCP | SCU/SCP | SCU/SCP | // * | SCU/SCP | DEFAULT | DEFAULT | // * | DEFAULT | SCP | Reject | // * | DEFAULT | SCU | DEFAULT | // * | DEFAULT | SCU/SCP | DEFAULT | // * | DEFAULT | DEFAULT | DEFAULT | // * +--------------------+------------------+---------+ // // The Reject case can be turned into returning DEFAULT role when // setting a specific option also being tested below. test_role_selection(ASC_SC_ROLE_SCU, ASC_SC_ROLE_SCP, ASC_SC_ROLE_NONE); test_role_selection(ASC_SC_ROLE_SCU, ASC_SC_ROLE_SCU, ASC_SC_ROLE_SCU); test_role_selection(ASC_SC_ROLE_SCU, ASC_SC_ROLE_SCUSCP, ASC_SC_ROLE_SCU); test_role_selection(ASC_SC_ROLE_SCU, ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_DEFAULT); test_role_selection(ASC_SC_ROLE_SCP, ASC_SC_ROLE_SCP, ASC_SC_ROLE_SCP); test_role_selection(ASC_SC_ROLE_SCP, ASC_SC_ROLE_SCU, ASC_SC_ROLE_NONE); test_role_selection(ASC_SC_ROLE_SCP, ASC_SC_ROLE_SCUSCP, ASC_SC_ROLE_SCP); test_role_selection(ASC_SC_ROLE_SCP, ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_DEFAULT); test_role_selection(ASC_SC_ROLE_SCUSCP, ASC_SC_ROLE_SCP, ASC_SC_ROLE_SCP); test_role_selection(ASC_SC_ROLE_SCUSCP, ASC_SC_ROLE_SCU, ASC_SC_ROLE_SCU); test_role_selection(ASC_SC_ROLE_SCUSCP, ASC_SC_ROLE_SCUSCP, ASC_SC_ROLE_SCUSCP); test_role_selection(ASC_SC_ROLE_SCUSCP, ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_DEFAULT); test_role_selection(ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_SCP, ASC_SC_ROLE_NONE /* not evaluated */, OFTrue /* expect rejection */); // Repeat the test but this time work around faulty clients by also accepting Default role // instead of the originally configured SCP role. test_role_selection(ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_SCP, ASC_SC_ROLE_DEFAULT, OFFalse, OFTrue); test_role_selection(ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_SCU, ASC_SC_ROLE_DEFAULT); test_role_selection(ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_SCUSCP, ASC_SC_ROLE_DEFAULT); test_role_selection(ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_DEFAULT, ASC_SC_ROLE_DEFAULT); } #endif // WITH_THREADS