This function is to be used in conjunction with the LDicomNet::LDicomNet(*pszPath, nMode, bReserved) constructor in order to change security options from the defaults. This function is available in the Medical Imaging Suite toolkits.
#include "ltdic.h"
L_INT LDicomNet::Initialize(pszPath, nMode, pCtxCreate)
Character string that contains the location of the temporary files. This should be the same string that was used in the LDicomNet constructor.
Flag that indicates the security mode to use when initializing the network structure. This should be the same flag that was used in the LDicomNet constructor. Possible values are:
Value | Meaning |
---|---|
DICOM_SECURE_NONE | No security mode. |
DICOM_SECURE_ISCL | Integrated Secure Communication Layer security mode. |
DICOM_SECURE_TLS | Transport Layer Security security mode. |
Pointer to the L_SSL_CTX_CREATE structure that is used to modify the security defaults. This structure is used only if the nMode flag is DICOM_SECURE_TLS. Pass NULL to get the default values.
Value | Meaning |
---|---|
SUCCESS | The function was successful. |
> 0 | An error occurred. Refer to Return Codes. |
NOTE: When using the LDicomNet::LDicomNet(*pszPath, nMode, bReserved) version of the constructor, in addition to calling LDicomNet::Startup it is also necessary to call LDicomNet::Initialize in order to prepare the LDicomNet object for use. Use the pCtxCreate parameter when the nMode flag is set to DICOM_SECURE_TLS.
NOTE: The following uses of the LDicomNet constructors are functionally equivalent:
1.
LDicomNet \*pNet = new LDicomNet(pszPath, nMode);
2.
LDicomNet \*pNet = new LDicomNet(pszPath, nMode, 0);
if (pNet)
pNet->**Initialize**(pszPath, nMode, NULL);
Win32, x64
This is a basic, but complete example that shows a DICOM client sending a C-Echo-REQ to a server, and the server processing the request using TLS Security.
namespace LDicomNet_Initialize_Namespace
{
#ifndef CA_CERT_NAME
#define CA_CERT_NAME MAKE_IMAGE_PATH(TEXT("CA.pem"))
#endif
#ifndef SERVER_CERT_NAME
#define SERVER_CERT_NAME MAKE_IMAGE_PATH(TEXT("Server.pem"))
#endif
#ifndef CLIENT_CERT_NAME
#define CLIENT_CERT_NAME MAKE_IMAGE_PATH(TEXT("Client.pem"))
#endif
// Logs a message
// This implementation logs to the console, and the debug window
L_VOID LogMessage(TCHAR *szMsg)
{
wprintf(TEXT("\n"));
wprintf(szMsg);
OutputDebugStringW(TEXT("\n"));
OutputDebugStringW(szMsg);
}
L_VOID LogMessage(TCHAR *s, L_INT n)
{
TCHAR szLog[200] = {0};
wsprintf(szLog, TEXT("%s [%d]"), s, n);
LogMessage(szLog);
}
L_INT PrivateKeyPassword(L_TCHAR *pszPassword, L_INT nSize, L_INT nFlag)
{
UNREFERENCED_PARAMETER(nFlag);
LPCTSTR pszMyPassword= TEXT("test");
L_INT nRet = 0;
if ((L_INT)_tcslen(pszMyPassword) < nSize)
{
_tcsncpy_s(pszPassword, nSize, pszMyPassword, nSize);
nRet = (L_INT)_tcslen(pszMyPassword);
}
return nRet;
}
L_VOID SetupTlsContext(LDicomNet *pNet, L_TCHAR *pszCertName)
{
if (pNet != NULL)
{
L_SSL_CTX_CREATE ctxCreate;
memset(&ctxCreate, 0, sizeof(L_SSL_CTX_CREATE));
ctxCreate.uStructSize = sizeof(L_SSL_CTX_CREATE);
ctxCreate.uFlags = FLAG_SSL_CTX_CREATE_METHOD_TYPE | FLAG_SSL_CTX_CREATE_VERIFY_MODE |
FLAG_SSL_CTX_CREATE_VERIFY_DEPTH | FLAG_SSL_CTX_CREATE_OPTIONS | FLAG_SSL_CTX_CREATE_CAFILE;
ctxCreate.nMethodTypeSSL= TYPE_SSLV23_METHOD;
ctxCreate.pszCAfile = CA_CERT_NAME;
ctxCreate.uVerifyMode = L_SSL_VERIFY_PEER | L_SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
ctxCreate.nVerifyDepth = 2;
ctxCreate.nOptions = L_SSL_OP_NO_SSLv2|L_SSL_OP_ALL;
ctxCreate.nReserved1 = 0;
ctxCreate.nReserved2 = 0;
L_INT nRet = pNet->Initialize(NULL, DICOM_SECURE_TLS, &ctxCreate);
if (nRet == DICOM_SUCCESS)
{
L_TCHAR szMsg[200] = {0};
// Assign the server the certificate
// Note that SERVER_CERT_NAME contains both the password and an encrypted private key
// When loading the private key, the OnPrivateKeyPassword virtual function is called
// so that the encryption password "test" can be supplied
nRet = pNet->SetServerCertificateTLS (pszCertName, L_TLS_FILETYPE_PEM, NULL);
if (nRet == DICOM_SUCCESS)
wsprintf(szMsg, TEXT("%s loaded successfully"), pszCertName);
else
wsprintf(szMsg, TEXT("%s could not be loaded successfully -- error[%d]"), pszCertName, nRet);
LogMessage(szMsg);
}
}
}
L_VOID DumpTlsInformation(LDicomNet *pNet)
{
L_CIPHERSUITE ciphersuite;
ciphersuite = pNet->GetCiphersuiteTLS();
switch(ciphersuite)
{
case TLS_DHE_RSA_WITH_DES_CBC_SHA:
LogMessage( TEXT(" Secure connected, cipher is TLS_DHE_RSA_WITH_DES_CBC_SHA"));
break;
case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
LogMessage( TEXT(" Secure connected, cipher is TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"));
break;
case TLS_DHE_RSA_AES256_SHA:
LogMessage( TEXT(" Secure connected, cipher is TLS_DHE_RSA_AES256_SHA"));
break;
}
L_UINT32 nEncryptionAlgorithm = pNet->GetEncryptionAlgorithmTLS(ciphersuite);
switch(nEncryptionAlgorithm)
{
case L_CRYPT_NONE:
LogMessage(TEXT(" Encryption Algorithm: L_CRYPT_NONE"));
break;
case L_CRYPT_DES:
LogMessage(TEXT(" Encryption Algorithm: L_CRYPT_DES"));
break;
case L_CRYPT_3DES:
LogMessage(TEXT(" Encryption Algorithm: L_CRYPT_3DES"));
break;
case L_CRYPT_RC4:
LogMessage(TEXT(" Encryption Algorithm: L_CRYPT_RC4"));
break;
case L_CRYPT_RC2:
LogMessage(TEXT(" Encryption Algorithm: L_CRYPT_RC2"));
break;
case L_CRYPT_IDEA:
LogMessage(TEXT(" Encryption Algorithm: L_CRYPT_IDEA"));
break;
case L_CRYPT_FORTEZZA:
LogMessage(TEXT(" Encryption Algorithm: L_CRYPT_FORTEZZA"));
break;
case L_CRYPT_AES:
LogMessage(TEXT(" Encryption Algorithm: L_CRYPT_AES"));
break;
}
L_UINT32 auth = pNet->GetAuthenticationAlgorithmTLS(ciphersuite);
switch(auth)
{
case L_MUTUALAUTH_NONE:
LogMessage(TEXT(" Authentication Algorithm: L_MUTUALAUTH_NONE"));
break;
case L_MUTUALAUTH_RSA:
LogMessage(TEXT(" Authentication Algorithm: L_MUTUALAUTH_RSA"));
break;
case L_MUTUALAUTH_DSS:
LogMessage(TEXT(" Authentication Algorithm: L_MUTUALAUTH_DSS"));
break;
case L_MUTUALAUTH_DH:
LogMessage(TEXT(" Authentication Algorithm: L_MUTUALAUTH_DH"));
break;
}
L_UINT32 integrity = pNet->GetIntegrityAlgorithmTLS(ciphersuite);
switch(integrity)
{
case L_MAC_NONE:
LogMessage(TEXT(" Message Authentication code type: L_MAC_NONE"));
break;
case L_MAC_SHA1:
LogMessage(TEXT(" Message Authentication code type: L_MAC_SHA1"));
break;
case L_MAC_MD5:
LogMessage(TEXT(" Message Authentication code type: L_MAC_MD5"));
break;
}
L_UINT32 keyExchange = pNet->GetKeyExchangeAlgorithmTLS(ciphersuite);
switch(keyExchange)
{
case L_KEYEXCHANGE_NONE:
LogMessage(TEXT(" Key Exchange Algorithm: L_KEYEXCHANGE_NONE"));
break;
case L_KEYEXCHANGE_RSA_SIGNED_DHE:
LogMessage(TEXT(" Key Exchange Algorithm: L_KEYEXCHANGE_RSA_SIGNED_DHE"));
break;
case L_KEYEXCHANGE_RSA:
LogMessage(TEXT(" Key Exchange Algorithm: L_KEYEXCHANGE_RSA"));
break;
case L_KEYEXCHANGE_DH:
LogMessage(TEXT(" Key Exchange Algorithm: L_KEYEXCHANGE_DH"));
break;
case L_KEYEXCHANGE_DH_DSS:
LogMessage(TEXT(" Key Exchange Algorithm: L_KEYEXCHANGE_DH_DSS"));
break;
case L_KEYEXCHANGE_FORTEZZA:
LogMessage(TEXT(" Key Exchange Algorithm: L_KEYEXCHANGE_FORTEZZA"));
break;
}
L_UINT32 uKeyLength = pNet->GetEncryptKeyLengthTLS(ciphersuite);
LogMessage(TEXT(" Encrypt Key Length"), uKeyLength);
L_UINT32 uMutualAuthKeyLength = pNet->GetMutualAuthKeyLengthTLS(ciphersuite);
LogMessage(TEXT(" Mutual authentication key length"), uMutualAuthKeyLength);
}
// *******************************************************************************************
// Client Class
//
// Class that is used to connect to the server
// *******************************************************************************************
class CMyClient : public LDicomNet
{
public:
CMyClient(L_INT32 nMode): LDicomNet(NULL, nMode)
{
m_waitEvent = CreateEvent( NULL, TRUE, TRUE, TEXT("ClientEvent"));
ResetEvent(m_waitEvent);
this->SetCipherToIndexTLS(0, TLS_DHE_RSA_WITH_DES_CBC_SHA);
L_CIPHERSUITE ciphersuite;
ciphersuite = this->GetCipherFromIndexTLS(0);
assert(ciphersuite == TLS_DHE_RSA_WITH_DES_CBC_SHA);
this->SetClientCertificateTLS( MAKE_IMAGE_PATH(TEXT("Client.pem")), L_TLS_FILETYPE_PEM, NULL);
}
~CMyClient(void)
{
CloseHandle(m_waitEvent);
}
// Client
L_VOID OnConnect (L_INT nError);
L_VOID OnSecureLinkReady (L_UINT32 nError);
L_VOID OnReceiveAssociateAccept (LDicomAssociate *pPDU);
L_VOID OnReceiveReleaseResponse ();
L_VOID OnReceiveCEchoResponse (L_UCHAR nPresentationID, L_UINT16 nMessageID, L_TCHAR *pszClass, L_UINT16 nStatus);
L_INT OnPrivateKeyPassword (L_TCHAR *pszPassword, L_INT nSize, L_INT nFlag);
L_INT OnVerify (L_INT ok, L_TCHAR *pszCertificateString, L_INT nError, L_TCHAR *pszErrorString);
L_BOOL Wait(DWORD timeout = 5000);
private:
HANDLE m_waitEvent;
};
// Continues dispatching messages until hEvent is signaled, our timeout
// Returns TRUE if hEvent is signaled
// Returns FALSE if timeout
L_BOOL MessageLoop (
HANDLE hEvent, // handles that need to be waited on
DWORD timeout // timeout in milliseconds
)
{
DWORD dwStart = GetTickCount();
MSG msg = {0};
volatile L_BOOL bRunForever = TRUE;
while (bRunForever)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (WaitForSingleObject(hEvent, 0) == WAIT_OBJECT_0)
{
ResetEvent(hEvent);
return TRUE;
}
DWORD dwCurrent = GetTickCount();
if ((dwCurrent - dwStart) > timeout)
{
return FALSE;
}
}
return TRUE;
}
L_VOID CMyClient::OnConnect(L_INT nError)
{
L_TCHAR szMsg[200] = {0};
wsprintf(szMsg, TEXT("CMyClient::OnConnect: nError[%d]"), nError);
LogMessage(szMsg);
}
L_VOID CMyClient::OnSecureLinkReady(L_UINT32 nError)
{
UNREFERENCED_PARAMETER(nError);
SetEvent(m_waitEvent);
L_UINT32 uSecureMode = this->GetSecureMode();
switch(uSecureMode)
{
case DICOM_SECURE_NONE:
LogMessage(TEXT("CMyClient::OnSecureLinkReady: DICOM_SECURE_NONE"));
break;
case DICOM_SECURE_ISCL:
LogMessage(TEXT("CMyClient::OnSecureLinkReady: DICOM_SECURE_ISCL"));
break;
case DICOM_SECURE_TLS:
{
LogMessage(TEXT("CMyClient::OnSecureLinkReady: DICOM_SECURE_TLS"));
if (nError == DICOM_SUCCESS)
{
DumpTlsInformation(this);
}
else
{
L_TCHAR szMsg[200] = {0};
wsprintf(szMsg, TEXT( "ClientTLS: Secure link error %d"), nError);
LogMessage(szMsg);
}
}
break;
}
}
L_VOID CMyClient::OnReceiveAssociateAccept (LDicomAssociate *pPDU)
{
UNREFERENCED_PARAMETER(pPDU);
SetEvent(m_waitEvent);
LogMessage(TEXT("CMyClient::OnReceiveAssociateAccept"));
}
L_VOID CMyClient::OnReceiveCEchoResponse(L_UCHAR nPresentationID, L_UINT16 nMessageID, L_TCHAR *pszClass, L_UINT16 nStatus)
{
SetEvent(m_waitEvent);
L_TCHAR szMsg[200] = {0};
if (pszClass == NULL)
{
pszClass = TEXT("");
}
wsprintf(szMsg, TEXT("CMyClient::OnReceiveCEchoResponse: \n\tnPresentationID[%d], \n\tnMessageID[%d], \n\tpszClass[%s], \n\tnStatus[%d]"), nPresentationID, nMessageID, pszClass, nStatus);
LogMessage(szMsg);
}
L_INT CMyClient::OnPrivateKeyPassword(L_TCHAR *pszPassword, L_INT nSize, L_INT nFlag)
{
return PrivateKeyPassword(pszPassword, nSize, nFlag);
}
L_INT CMyClient::OnVerify(L_INT ok, L_TCHAR *pszCertificateString, L_INT nError, L_TCHAR *pszErrorString)
{
L_TCHAR szMsg[200] = { 0 };
wsprintf(szMsg, TEXT("CMyClient::OnVerify: \n\tok[%d], \n\tnError[%d], \n\tpszErrorString[%s]"), ok, nError, pszErrorString);
LogMessage(szMsg);
LogMessage(TEXT("n\tpszCertificateString:\n\t"));
LogMessage(pszCertificateString);
return ok;
}
L_VOID CMyClient::OnReceiveReleaseResponse()
{
SetEvent(m_waitEvent);
LogMessage(TEXT("CMyClient::OnReceiveReleaseResponse"));
}
L_BOOL CMyClient::Wait(DWORD timeout)
{
L_BOOL bRet = MessageLoop(m_waitEvent, timeout);
return bRet;
}
// *******************************************************************************************
// Server Connection Class
//
// When a client connects, CMyServer creates a new instance of the CMyServerConnection class
// and accepts the connection.
// *******************************************************************************************
class CMyServerConnection : public LDicomNet
{
public:
CMyServerConnection(L_INT32 nMode): LDicomNet(NULL, nMode)
{
}
~CMyServerConnection(void)
{
}
// Server
L_VOID OnReceiveAssociateRequest (LDicomAssociate *pPDU);
L_VOID OnReceiveAssociateReject (L_UCHAR nResult, L_UCHAR nSource, L_UCHAR nReason);
L_VOID OnReceiveCEchoRequest (L_UCHAR nPresentationID, L_UINT16 nMessageID, L_TCHAR *pszClass);
L_VOID OnReceiveReleaseRequest ();
L_INT OnPrivateKeyPassword (L_TCHAR *pszPassword, L_INT nSize, L_INT nFlag);
};
#define SIZEINWORD(p) sizeof(p)/sizeof(L_TCHAR)
L_VOID CMyServerConnection::OnReceiveAssociateRequest(LDicomAssociate *pPDU)
{
LogMessage(TEXT("\tCMyServerConnection::OnReceiveAssociateRequest"));
//check the version, if not 1, reject it
if (pPDU->GetVersion() != 1)
{
LogMessage(TEXT("\tCMyServerConnection::SendAssociateReject"));
SendAssociateReject(
PDU_REJECT_RESULT_PERMANENT,
PDU_REJECT_SOURCE_USER,
PDU_REJECT_REASON_UNKNOWN
);
}
else
{
LDicomAssociate DicomAssociate(FALSE);
L_TCHAR clientAE[20] = {0};
pPDU->GetCalling(clientAE, 20);
if (lstrcmp(clientAE, TEXT("LEAD_CLIENT")) != 0)
{
LogMessage(TEXT("\tCMyServerConnection::SendAssociateReject"));
SendAssociateReject(
PDU_REJECT_RESULT_PERMANENT,
PDU_REJECT_SOURCE_USER,
PDU_REJECT_REASON_UNKNOWN
);
return;
}
//Copy presentation objects from received
//Reply that we only support the first Transfer Syntax from the received hPDU
L_TCHAR szTransfer[PDU_MAX_UID_SIZE+1] = {0};
L_TCHAR szAbstract[PDU_MAX_UID_SIZE+1] = {0};
L_INT iPresentationCount = pPDU->GetPresentationCount();
for (L_UCHAR i = 0; i<iPresentationCount; i++)
{
L_UCHAR nId = pPDU->GetPresentation(i);
pPDU->GetTransfer(nId, 0, szTransfer, PDU_MAX_UID_SIZE+1);
L_UCHAR nResult = PDU_ACCEPT_RESULT_SUCCESS;
pPDU->GetAbstract(nId, szAbstract, PDU_MAX_UID_SIZE+1);
DicomAssociate.AddPresentation( nId, nResult, szAbstract);
DicomAssociate.AddTransfer( nId, szTransfer);
}
LogMessage(TEXT("\tCMyServerConnection::SendAssociateAccept"));
SendAssociateAccept(&DicomAssociate);
}
}
L_VOID CMyServerConnection::OnReceiveAssociateReject(L_UCHAR nResult, L_UCHAR nSource, L_UCHAR nReason)
{
L_TCHAR szMsg[200] = {0};
wsprintf(szMsg, TEXT("\tOnReceiveAssociateReject\nResult[%d]\nSource[%d]\nReason[%d]"), nResult, nSource, nReason);
LogMessage(szMsg);
}
L_VOID CMyServerConnection::OnReceiveCEchoRequest(L_UCHAR nPresentationID, L_UINT16 nMessageID, L_TCHAR *pszClass)
{
LogMessage(TEXT("\tCMyServerConnection::OnReceiveCEchoRequest"));
LogMessage(TEXT("\tCMyServerConnection::SendCEchoResponse"));
SendCEchoResponse(nPresentationID, nMessageID, pszClass, COMMAND_STATUS_SUCCESS);
}
L_VOID CMyServerConnection::OnReceiveReleaseRequest()
{
LogMessage(TEXT("\tCMyServerConnection::OnReceiveReleaseRequest"));
LogMessage(TEXT("\tCMyServerConnection::SendReleaseResponse"));
SendReleaseResponse();
}
L_INT CMyServerConnection::OnPrivateKeyPassword(L_TCHAR *pszPassword, L_INT nSize, L_INT nFlag)
{
return PrivateKeyPassword(pszPassword, nSize, nFlag);
}
// *******************************************************************************************
// Server Class
//
// Listens for connections
// When a client connects, this class creates a CMyServerConnection and accepts the connection
// *******************************************************************************************
class CMyServer : public LDicomNet
{
public:
CMyServer(L_INT32 nMode): LDicomNet(NULL, nMode)
{
m_pServerConnection = NULL;
}
~CMyServer(void)
{
if (m_pServerConnection != NULL)
{
delete m_pServerConnection;
}
}
L_VOID OnAccept (L_INT nError);
L_VOID OnClose (L_INT nError, LDicomNet *pServerConnection);
L_INT OnPrivateKeyPassword (L_TCHAR *pszPassword, L_INT nSize, L_INT nFlag);
CMyServerConnection *m_pServerConnection;
};
L_VOID CMyServer::OnAccept(L_INT nError)
{
LogMessage(TEXT("\tCMyServer::OnAccept"));
if (nError != DICOM_SUCCESS)
{
return;
}
if (m_pServerConnection != NULL)
{
delete m_pServerConnection;
m_pServerConnection = NULL;
}
m_pServerConnection = new CMyServerConnection(DICOM_SECURE_TLS);
if (m_pServerConnection == NULL)
{
return;
}
SetupTlsContext(m_pServerConnection, SERVER_CERT_NAME);
nError = LDicomNet::Accept(m_pServerConnection);
if (nError != DICOM_SUCCESS)
{
delete m_pServerConnection;
return;
}
}
L_VOID CMyServer::OnClose(L_INT nError, LDicomNet *pServerConnection)
{
UNREFERENCED_PARAMETER(nError);
LogMessage(TEXT("\tCMyServer::OnClose"));
if (m_pServerConnection == pServerConnection)
{
m_pServerConnection = NULL;
}
delete (CMyServerConnection *)pServerConnection;
}
L_INT CMyServer::OnPrivateKeyPassword(L_TCHAR *pszPassword, L_INT nSize, L_INT nFlag)
{
return PrivateKeyPassword(pszPassword, nSize, nFlag);
}
// *******************************************************************************************
// Sample starts here
// *******************************************************************************************
#define WaitForProcessing() \
{ \
if (!client.Wait()) \
{ \
LogMessage(TEXT("Timeout: client.Connect")); \
nRet = DICOM_ERROR_NET_TIME_OUT; \
goto Cleanup; \
} \
}
L_INT LDicomNet_InitializeExample()
{
LogMessage(TEXT("\n\n *** Initialize ***"));
L_TCHAR *pszServerAddress = TEXT("127.0.0.1");
L_UINT uServerPort = 105;
L_INT nRet = DICOM_SUCCESS;
LDicomNet::StartUp();
CMyClient client(DICOM_SECURE_TLS);
CMyServer server(DICOM_SECURE_TLS);
SetupTlsContext(&client, CLIENT_CERT_NAME);
SetupTlsContext(&server, SERVER_CERT_NAME);
LogMessage(TEXT("\tCMyServer::Listen"));
nRet = server.Listen(pszServerAddress, uServerPort, 5);
LogMessage(TEXT("CMyClient::Connect"));
client.Connect(NULL, 0, pszServerAddress, uServerPort);
if (!client.Wait(2000))
{
if (!client.IsConnected())
{
LogMessage(TEXT("Timeout: client.Connect"));
nRet = DICOM_ERROR_NET_TIME_OUT;
goto Cleanup;
}
}
if (nRet == DICOM_SUCCESS)
{
//create the Associate Class as Request
LDicomAssociate dicomAssociateRequest(TRUE);
dicomAssociateRequest.Default();
// Send A-Associate-RQ message
dicomAssociateRequest.SetCalled(TEXT("LEAD_SERVER"));
dicomAssociateRequest.SetCalling(TEXT("LEAD_CLIENT"));
LogMessage(TEXT("CMyClient::SendAssociateRequest"));
nRet = client.SendAssociateRequest(&dicomAssociateRequest);
if (!client.Wait(5000))
{
LogMessage(TEXT("Timeout: client.Connect"));
nRet = DICOM_ERROR_NET_TIME_OUT;
goto Cleanup;
}
}
L_UCHAR nPresentationID = client.GetAssociate()->FindAbstract(UID_VERIFICATION_CLASS);
L_UINT16 uUniqueID = 99;
LogMessage(TEXT("CMyClient::SendCEchoRequest"));
client.SendCEchoRequest( nPresentationID, uUniqueID, UID_VERIFICATION_CLASS);
WaitForProcessing();
LogMessage(TEXT("CMyClient::SendReleaseRequest"));
client.SendReleaseRequest();
WaitForProcessing();
Cleanup:
LogMessage(TEXT("CMyClient::Close"));
client.Close();
client.Wait(1000);
LogMessage(TEXT("\tCMyServer::Close"));
server.Close();
LDicomNet::ShutDown();
return nRet;
}
}