LAUREL BRIDGE

LaurelBridge.DCFExamples.VerificationSCPExtended Namespace

DICOM Connectivity Framework V3.4
The VerificationSCPExtended example demonstrates how to use the DCF to implement a simple Verification (Echo) service class provider that extends the stock VerificationSCP, overriding methods to handle the CEchoRequest DIMSE messages.
Classes

  ClassDescription
Public classOptions
Command line options class for parsing user options for VerificationSCPExtended.
Public classProgram
Basic verification service class provider example which should be run with the VerificationSCU or EchoSCU for unencrypted messages. This class also supports TLS for processing encrypted messages which should be run with EchoSCU. View the --help command line option to see a summary of the TLS command line options.
Public classProgramExtendedVerificationServer
ExtendedVerificationServer handles associations by creating a custom Verification SCP that extends VerificationSCP to handle the C-ECHO requests on the association.
Public classProgramMyVerificationScp
MyVerificationScp extends VerificationSCP to override the CEchoRq method
Remarks

Supported OS Platforms:

  • Windows - .Net Framework 4.7.2 64-bit and 32-bit
  • Windows - .Net Core 2.1 64-bit and 32-bit
  • Linux - .Net Core 2.1 64-bit

Examples

VerificationSCPExtended Sample Code
public class Program
{
    /// <summary>
    /// Main entry point for VerificationSCPExtended.
    /// </summary>
    /// <param name="args">command line arguments</param>
    [STAThread]
    public static void Main(string[] args)
    {
        Console.Title = "LaurelBridge.DCFExamples.VerificationSCPExtended";
        try
        {
            Options options;
            if (!Options.TryParse(args, out options))
            {
                throw new ArgumentException("bad command line parameters");
            }

            // Create an ExtendedVerificationServer using command line options
            ExtendedVerificationServer extendedVerificationServerTls = new ExtendedVerificationServer(options);
            extendedVerificationServerTls.BeginListening();
            Console.WriteLine("listening on {0}{1}...", options.EnabledTlsProtocols == SslProtocols.None ? "" : "TLS ", options.Port);
        }
        catch (Exception e)
        {
            Console.WriteLine("Error during execution: {0}", e.Message);
            Environment.ExitCode = 1;
        }
    }

    /// <summary>
    /// ExtendedVerificationServer handles associations by creating a custom Verification SCP that extends VerificationSCP to handle the C-ECHO requests on the association.
    /// </summary>
    public class ExtendedVerificationServer : AssociationListenerAdapter, IAssociationConfigPolicyManager
    {
        private readonly AssociationManager _manager;

        private readonly IList<AllowedPresentationContext> _presentationContexts;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="port">Server port</param>
        public ExtendedVerificationServer(int port)
            : this(port, null)
        {
        }

        /// <summary>
        /// Constructor with allowed presentation contexts.
        /// </summary>
        /// <param name="port">Server port</param>
        /// <param name="allowedPresentationContexts">list of allowed presentation contexts</param>
        public ExtendedVerificationServer(int port, IList<AllowedPresentationContext> allowedPresentationContexts)
        {
            _presentationContexts = allowedPresentationContexts;
            _manager = new AssociationManager();
            _manager.ServerTcpPort = port;
            _manager.AddAssociationListener(this);
        }

        /// <summary>
        /// Constructor with command line options and allowed presentation contexts
        /// </summary>
        /// <param name="options">command line options</param>
        /// <param name="allowedPresentationContexts">list of allowed presentation contexts</param>
        public ExtendedVerificationServer(Options options, IList<AllowedPresentationContext> allowedPresentationContexts = null)
        {
            if (options.EnabledTlsProtocols == SslProtocols.None)
            {
                _manager = new AssociationManager
                {
                    ServerTcpPort = options.Port
                };
            }
            else
            {
                TlsConnection tlsConnection = CreateTlsConnection(options);
                _manager = CreateAssociationManager(options, new TlsDicomSocketFactory(tlsConnection,
                    new X509Certificate2(options.Certificate),
                    options.TlsAllowMissingClient));
            }

            _presentationContexts = allowedPresentationContexts;
            _manager.AddAssociationListener(this);
        }

        /// <summary>
        /// Create TlsConnection from properties in options
        /// </summary>
        /// <param name="options">command line options</param>
        /// <returns></returns>
        private TlsConnection CreateTlsConnection(Options options)
        {
            TlsConnection tlsConnection = new TlsConnection(
                null,
                options.EnabledTlsProtocols,
                true,
                true,
                options.TlsAllowSelfSigned,
                options.TlsIgnoreMismatch)
            {
                AuthenticationTimeoutMs = options.TlsAuthTimeout,
                WriteTimeoutMs = options.TlsTimeout,
            };

            return tlsConnection;
        }

        /// <summary>
        /// Create association manager with options and socket factory
        /// </summary>
        /// <param name="options">command line options</param>
        /// <param name="socketFactory">instance of DicomSocketFactory or TlsDicomSocketFactory</param>
        /// <returns></returns>
        private AssociationManager CreateAssociationManager(Options options, DicomSocketFactory socketFactory)
        {
            CFGGroup mgrConfig = new CFGGroup("associationManager");
            mgrConfig.setAttributeValue("server_host_address", "0.0.0.0");
            mgrConfig.setAttributeIntValue("server_tcp_port", options.Port);
            mgrConfig.setAttributeIntValue("max_concurrent_associations", AssociationManager.MAX_CONCURRENT_ASSOCIATIONS);
            mgrConfig.setAttributeIntValue("max_total_associations", 1);
            mgrConfig.setAttributeIntValue("max_total_connections", -1);
            if (TlsNetworkSocket.LAZY_SERVER_SIDE_AUTHENTICATION_MODE && socketFactory is TlsDicomSocketFactory)
            {
                // When are authenticating on a separate thread that AssociationManager creates to handle an association, the authentication
                // is triggered when DCF attempts to read the first PDU; so we give additional time to read the first PDU
                mgrConfig.setAttributeIntValue("first_pdu_read_timeout", 15 + options.TlsAuthTimeout);
            }
            else
            {
                mgrConfig.setAttributeIntValue("first_pdu_read_timeout", 15);
            }
            mgrConfig.setAttributeValue("default_session_config_name", string.Empty);
            mgrConfig.setAttributeBoolValue("listen_cfg_group", false);
            mgrConfig.setAttributeIntValue("select_timeout", 1);
            mgrConfig.setAttributeIntValue("receive_buffer_size", GetBufferSize(false));
            mgrConfig.setAttributeIntValue("send_buffer_size", 0); // I don't think server send buffer size is an issue, responses are very small
            mgrConfig.setAttributeBoolValue("call_app_ready_in_run", false);
            mgrConfig.setAttributeBoolValue("reuse_address", false);
            mgrConfig.setAttributeBoolValue("exclusive_address_use", true);

            // this needs to be created with the same cfg policy manager as the other one
            //AssociationManager am = new AssociationManager(mgrConfig, new WorklistAssociationAcceptorFactory(this, Config));
            AssociationManager am = new AssociationManager(mgrConfig, new AssociationAcceptorFactory(), this, socketFactory);

            // register as an AssociationListener
            am.AddAssociationListener(this);

            return am;
        }

        /// <summary>
        /// Get default value for receive buffer size
        /// </summary>
        /// <param name="optimizeForWan"></param>
        /// <returns></returns>
        private static int GetBufferSize(bool optimizeForWan)
        {
            if (!optimizeForWan)
                return 0;

            return 0x10000;
        }

        /// <summary>
        /// Start listening for associations via the AssociationManager.
        /// </summary>
        public void BeginListening()
        {
            Thread t = new Thread(_manager.Run);
            t.Start();
            if (!_manager.WaitForRunning(2000))
            {
                throw new TimeoutException("AssociationManager did not start in an acceptable amount of time");
            }
        }

        /// <summary>
        /// Stop receiving any new associations.
        /// </summary>
        public void Stop()
        {
            _manager.Stop();
        }

        #region IAssociationConfigPolicyManager implementation
        /// <summary>
        /// Get the session settings for the AssociationAcceptor.
        /// </summary>
        /// <param name="assoc">the AssociationAcceptor</param>
        /// <returns>the session settings</returns>
        public DicomSessionSettings GetSessionSettings(AssociationAcceptor assoc)
        {
            return new DicomSessionSettings();
        }
        #endregion

        #region AssociationListenerAdapter overrides
        /// <summary>
        /// Callback for beginning the association.
        /// </summary>
        /// <param name="assoc">the AssociationAcceptor</param>
        public override void BeginAssociation(AssociationAcceptor assoc)
        {
            assoc.RegisterServiceClassProvider(new MyVerificationScp(assoc, _presentationContexts));
        }
        #endregion
    }

    /// <summary>
    /// MyVerificationScp extends VerificationSCP to override the CEchoRq method
    /// </summary>
    public class MyVerificationScp : VerificationSCP
    {
        /// <summary>
        /// Construct MyVerificationSCP with association acceptor and list of allowed presentation contexts
        /// </summary>
        /// <param name="acceptor">The association acceptor for this association.</param>
        /// <param name="allowedPresentationContexts">The list of allowed presentation contexts.</param>
        public MyVerificationScp(AssociationAcceptor acceptor, IList<AllowedPresentationContext> allowedPresentationContexts)
            : base(acceptor, allowedPresentationContexts, null)
        {
        }

        /// <summary>
        /// Override CEchoRq to generate and return the CEchoResponse to the verification SCU.
        /// </summary>
        /// <param name="request">The CEcho request.</param>
        /// <returns>The CEcho response for the request.</returns>
        public override CEchoResponse CEchoRq(CEchoRequest request)
        {
            CEchoResponse response = new CEchoResponse(request);

            Console.WriteLine("MyVerificationScp: cEcho request from {0} to port {1}",
                Acceptor.AssociationInfo.CallingTitle, Acceptor.AssociationInfo.CalledPresentationAddress);

            return response;
        }
    }
}