Skip to content

Commit

Permalink
[dcmnet] Implement TCP connect cancellation support in DcmSCU
Browse files Browse the repository at this point in the history
  • Loading branch information
100029962 committed Apr 17, 2023
1 parent 1bd0e8b commit d32854f
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 1 deletion.
8 changes: 7 additions & 1 deletion dcmnet/include/dcmtk/dcmnet/scu.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,14 @@ class DCMTK_DCMNET_EXPORT DcmSCU
/** Negotiate association by using presentation contexts and parameters as defined by
* earlier function calls. If negotiation fails, there is no need to close the association
* or to do anything else with this class.
* @param tcpCancelToken [in] Optional cancellation token which is checked periodically
* for cancellation. If the token is cancelled during TCP
* connect phase, the TCP connect attempts are stopped and this
* function returns failure.
* @return EC_Normal if negotiation was successful, otherwise error code.
* NET_EC_AlreadyConnected if SCU is already connected.
* NET_EC_AlreadyConnected if SCU is already connected. DULC_TCPINITERROR is returned
* if the TCP connect attempt was cancelled before the connection was established
*
*/
virtual OFCondition negotiateAssociation(IDcmCancelToken* tcpCancelToken = NULL);

Expand Down
15 changes: 15 additions & 0 deletions dcmnet/libsrc/scu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
#include <zlib.h> /* for zlibVersion() */
#endif

static bool CancelTcpConnect(void* context)
{
if (!context)
return false;

const IDcmCancelToken* token = OFstatic_cast(const IDcmCancelToken*, context);
return token->IsCanceled();
}

DcmSCU::DcmSCU()
: m_assoc(NULL)
, m_net(NULL)
Expand Down Expand Up @@ -129,6 +138,9 @@ OFCondition DcmSCU::initNetwork()
return cond;
}

m_params->DULparams.tcpPollInterval = m_tcpPollInterval;
m_params->DULparams.tcpConnectCanceled = CancelTcpConnect;

/* sets this application's title and the called application's title in the params */
/* structure. The default values are "ANY-SCU" and "ANY-SCP". */
ASC_setAPTitles(m_params, m_ourAETitle.c_str(), m_peerAETitle.c_str(), NULL);
Expand Down Expand Up @@ -272,6 +284,9 @@ OFCondition DcmSCU::negotiateAssociation(IDcmCancelToken* tcpCancelToken)
else
DCMNET_DEBUG("Request Parameters:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_RQ));


m_params->DULparams.tcpCancelContext = tcpCancelToken;

/* create association, i.e. try to establish a network connection to another */
/* DICOM application. This call creates an instance of T_ASC_Association*. */
DCMNET_INFO("Requesting Association");
Expand Down
3 changes: 3 additions & 0 deletions dcmnet/tests/tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_fails_when_requestedsopinstance_is_em
OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_succeeds_and_modifies_instance_when_scp_has_instance);
OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_succeeds_and_sets_responsestatuscode_from_scp_when_scp_sets_error_status);

OFTEST_REGISTER(dcmnet_scu_negotiateAssociation_fails_when_token_is_initially_cancelled);
OFTEST_REGISTER(dcmnet_scu_negotiateAssociation_fails_when_token_gets_cancelled_after_3_calls);
OFTEST_REGISTER(dcmnet_scu_negotiateAssociation_fails_when_connectionTimeout_elapses);
#endif // WITH_THREADS

OFTEST_MAIN("dcmnet")
112 changes: 112 additions & 0 deletions dcmnet/tests/tscuscp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,118 @@ OFTEST(dcmnet_scu_getConectionTimeout_returns_scu_tcp_connection_timeout)
OFCHECK(scu.getConnectionTimeout() == 42);
}

struct CancelToken : IDcmCancelToken
{
CancelToken(Uint32 cancelAfterNumCalls)
: m_canceled(false)
, m_cancelAfterNumCalls(cancelAfterNumCalls)
, m_callCount(0)
{}

bool IsCanceled() const /* override */
{
++m_callCount;
if (m_callCount > m_cancelAfterNumCalls)
m_canceled = true;

return m_canceled;
}

mutable bool m_canceled;
const Uint32 m_cancelAfterNumCalls;
mutable Uint32 m_callCount;
};


struct TcpCancelFixture
{
TcpCancelFixture()
{
m_scu.setPeerAETitle("ACCEPTOR");
m_scu.setAETitle("REQUESTOR");
m_scu.setPeerHostName("localhost");
m_scu.setPeerPort(GetUnusedPort());

OFList<OFString> ts;
ts.push_back(UID_LittleEndianImplicitTransferSyntax);
OFCHECK(m_scu.addPresentationContext(UID_VerificationSOPClass, ts, ASC_SC_ROLE_DEFAULT).good());
}

// Temporarily start an SCP to get an unused port
// Note: The port is not reserved after this call returns
static Uint16 GetUnusedPort()
{
TestSCP m_scp;
DcmSCPConfig& config = m_scp.getConfig();
configure_scp_for_echo(config, 0, ASC_SC_ROLE_SCP);
config.setAETitle("ACCEPTOR");
config.setConnectionBlockingMode(DUL_NOBLOCK);
config.setConnectionTimeout(4);
OFCHECK(m_scp.openListenPort().good());
const Uint16 port = config.getPort();
m_scp.join();
return port;
}

DcmSCU m_scu;

};

OFTEST(dcmnet_scu_negotiateAssociation_fails_when_token_is_initially_cancelled)
{
TcpCancelFixture m_fixture;
DcmSCU& scu = m_fixture.m_scu;

scu.setTcpPollInterval(1);
scu.setConnectionTimeout(60);

OFCHECK(scu.initNetwork().good());

CancelToken tok(0);
tok.m_canceled = true;

const OFCondition result = scu.negotiateAssociation(&tok);

OFCHECK(result.code() == DULC_TCPINITERROR);
OFCHECK(tok.m_callCount == 1);
}

OFTEST(dcmnet_scu_negotiateAssociation_fails_when_token_gets_cancelled_after_3_calls)
{
TcpCancelFixture m_fixture;
DcmSCU& scu = m_fixture.m_scu;

scu.setTcpPollInterval(1);
scu.setConnectionTimeout(60);

OFCHECK(scu.initNetwork().good());

CancelToken tok(3);

const OFCondition result = scu.negotiateAssociation(&tok);

OFCHECK(result.code() == DULC_TCPINITERROR);
OFCHECK(tok.m_callCount == 4);
}

OFTEST(dcmnet_scu_negotiateAssociation_fails_when_connectionTimeout_elapses)
{

TcpCancelFixture m_fixture;
DcmSCU& scu = m_fixture.m_scu;

scu.setTcpPollInterval(1);
scu.setConnectionTimeout(5);

CancelToken tok(15); // Should never be called 15 times since connection timeout is reached first

OFCHECK(scu.initNetwork().good());
const OFCondition result = scu.negotiateAssociation(&tok);

OFCHECK(result.code() == DULC_TCPINITERROR);
OFCHECK(!tok.m_canceled);
}


/** Helper function for testing role selection, test "dcmnet_scp_role_selection".
* @param r_req The role selection setting from the association requestor
Expand Down

0 comments on commit d32854f

Please sign in to comment.