fo-dicom / fo-dicom

Fellow Oak DICOM for .NET, .NET Core, Universal Windows, Android, iOS, Mono and Unity
https://fo-dicom.github.io/stable/v5/index.html
Other
1.06k stars 640 forks source link

Cannot Set the AE Title for Server #1713

Closed mohantyytasre closed 8 months ago

mohantyytasre commented 10 months ago

Expected behavior

I am new to DICOM and using fo-dicom library for DICOM communication. I have used the following code to simulate DICOM Client and DICOM Server on separate systems to test DICOM Asssociation and send DicomCEchoRequest. If I set some other AE Title on Server system the association should get rejected .

namespace DicomClient { internal class Program { static FellowOakDicom.Network.Client.DicomClient Client;

    static DicomAssociation association = new DicomAssociation();
    public const string IPv4Any = "192.168.0.55";          //-- "127.0.0.1",     "192.168.0.55"
    const int port = 137;                             //--  8000,137

    static DicomCEchoRequest dicomCEchoRequest = new DicomCEchoRequest();

    static async Task Main(string[] args)
    {
        try
        {
            // Set your desired application title for the client
            string clientAETitle = "SCU";

            //attach an event handler when Echo request is sent 
            dicomCEchoRequest.OnRequestSent += OnEchoRequestSent;

           // attach an event handler when remote peer not responds to echo request for certain Time
            dicomCEchoRequest.OnTimeout += OnEchoResponseSentTimeOut;

            //attach an event handler when remote peer responds to echo request
            dicomCEchoRequest.OnResponseReceived += OnEchoResponseReceivedFromRemoteHost;

          DicomDataset scu_dataset = new DicomDataset();

            // Set Calling AE Title in the dataset
            scu_dataset.AddOrUpdate(0x00020016, clientAETitle);
            dicomCEchoRequest.Dataset = scu_dataset;

            //-----------------------------------------SCU---------------------------------------------//

            //create DICOM  client with handlers      

            Client = CreateDicomClient(IPv4Any, port, "SCU", "SCP");

            //-------------------------------SCU REEQUEST SEND------------------------------------------------//

            //Set Negotiation Asynchronous Operations
            Client.NegotiateAsyncOps();

            //Add Request to SCU Asynchronously
            await Client.AddRequestAsync(dicomCEchoRequest);

            //Start SCU Asynchronously
            await Client.SendAsync();

            var tls = Client.TlsInitiator;

            var pc = association.PresentationContexts;
            var a = Client.CalledAe;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);

        }

        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

    private static void OnEchoRequestSent(object sender, DicomRequest.OnRequestSentEventArgs e)
    {
        Console.WriteLine($"DICOM Echo Verification request was sent to remote host");
        Console.WriteLine($"Verification request was: {e.ToString()}");
    }

    private static void OnEchoResponseSentTimeOut(object sender, DicomRequest.OnTimeoutEventArgs e)
    {           
        Console.WriteLine($"Verification request response was not recieved for: {e.ToString()}");
    }

    private static void OnEchoResponseReceivedFromRemoteHost(DicomCEchoRequest request, DicomCEchoResponse response)
    {
        Console.WriteLine($"DICOM Echo Verification request was received by remote host");
        Console.WriteLine($"Response was received from remote host...");
        Console.WriteLine($"Verification response status returned was:{response.Status.ToString()}");
    }

    private static FellowOakDicom.Network.Client.DicomClient CreateDicomClient(string IPAdress, int port, string CallingAE, string CalledAE)
    {
        // SCU (Service Class User) configuration
        var dicomClient = DicomClientFactory.Create(IPAdress, port, false, CallingAE, CalledAE);
        var client = (FellowOakDicom.Network.Client.DicomClient)dicomClient;

        // Set up association callback for SCU
        client.AssociationAccepted += (sender, e) =>
        {
            association = new DicomAssociation();
            association = e.Association;
            Console.WriteLine("Association accepted by SCP");
        };

        // Set up association callback for SCU
        client.AssociationRejected += (sender, e) =>
        {
            Console.WriteLine($"Association rejected.Reason: {e.Reason}");
        };

        // Set up association callback for SCU
        client.AssociationReleased += (sender, e) =>
        {
            Console.WriteLine($"Association released by SCP ");
        };

        return client;
    }
}

}


namespace DicomServer { internal class Program { const int port = 8000; static DicomServer Server;

    static void Main(string[] args)
    {

        //-----------------------------------------SCP---------------------------------------------//

        string serverAETitle = "AAA";

        //DicomDataset scp_dataset = new DicomDataset();

        // Set Calling AE Title in the dataset
        //DicomStatus status = new DicomStatus(null, DicomState.Success, null);
        //scp_dataset.AddOrUpdate(0x00020017, serverAETitle);
        //var dicomCEchoResponse = new DicomCEchoResponse(dicomCEchoRequest, );
        //dicomCEchoResponse.Dataset = scp_dataset;

        //  create DICOM  server 
        Server = CreateDicomServer(port);

        var registration = Server.Registration;
        var server = registration.DicomServer;
        var task = registration.Task;

        if (Server.IsListening)
        {
            Console.WriteLine("Server is Listening");
        }

        var server_log = Server.Logger;
        Console.ReadKey();
    }

    private static DicomServer<DicomCEchoProvider> CreateDicomServer(int port)
    {
        // Set up SCP (Service Class Provider)
        var dicomserver = DicomServerFactory.Create<DicomCEchoProvider>(port, null);

        var server = (DicomServer<DicomCEchoProvider>)dicomserver;
        var a = server.Port;

        return server;
    }        

}

}

--> Client should validate the Server AE Tiltle.

Actual behavior

--> For any Server AE Title in
Client = CreateDicomClient(IPv4Any, port, "SCU", "SCP"); the association is getting established.

--> Client is not validating the Server AE Tiltle. -->For Client am I setting Client AE Title correctly or not? --> Not clear for how to set the Server AE Title. --> The association is getting terminated immediately.

Steps to reproduce the behavior

Run the code

fo-dicom version and OS/platform

5.1.1 and Windows 10

gofal commented 10 months ago

The AE validation is not done on the client side, but on the server side. The client is the one who is initiating the whole association, so it is assumed that the client actually wants to communicate. The server on the other hand receives many association and is the one to validate and decide if the association should be accepted or not.

So the client builds up the AssocationRequest message, which already contains the clients AETitle and the AETitle of the server to which the client wants to talk. Then on the server side in method OnReceiveAssocationRequestAsync the server then receives the AssociationRequest message, checks if the server feels responsible for the called AETitle and if the server allows the client with the calling AETitle to open the association. Depending on that, the server accepts or rejects.

The idea of the AETitle is not validation, this would be way too unsecure and easy to "hack", its rather about configuration. If the client connects to a port and requests an association to a server with aet STORESCP, then the server may forward this request to a storage service, and if the request is for a server aet QUERYSCP, then the server forwards it to an other internal process. And if a worklist-server receives an association request of a client with aet MRT_SCU then the worklist server may return only the mri-data, but if the request comes from a client with aet CT_SCU, then the ct-data is returned.

gofal commented 10 months ago

if you want to write a server that checks the AETitles on AssociationRequest, then you have to write your own. Or you could create a class inheriting from DicomCEchoProvider and override the virtual method OnReceiveAssociationRequestAsync. Like so:

 public class MyCEchoProvider : DicomCEchoProvider
 {
     public MyCEchoProvider(INetworkStream stream, Encoding fallbackEncoding, ILogger log, DicomServiceDependencies dependencies) : base(stream, fallbackEncoding, log, dependencies)
     {
     }

     public override Task OnReceiveAssociationRequestAsync(DicomAssociation association)
     {
         // first check the aetitle
         if (association.CalledAE != "AAA")
         {
             return SendAssociationRejectAsync(DicomRejectResult.Permanent, DicomRejectSource.ServiceProviderPresentation, DicomRejectReason.CalledAENotRecognized);
         }

         // else just handle the c-echo requests as in base class
         return base.OnReceiveAssociationRequestAsync(association);
     }
 }

and build up your server with

// Set up SCP (Service Class Provider)
        var dicomserver = DicomServerFactory.Create<MyCEchoProvider>(port, null);
mohantyytasre commented 10 months ago

OK got it...Thanks for explaining in simplified way in this complex world of DICOM :)

mohantyytasre commented 10 months ago

As the Association gets terminated at the end of each request can I ask a basic question: To send multiple requests like C-Echo and C-STORE requests from client to server is it possible in single Association what changes i code can help me to do so or for each request do client and server need to associate separately.

gofal commented 10 months ago

Sure, you can call await client.AddRequestAsync(...); several times with various requests before calling await client.SenAsync(...)

mohantyytasre commented 10 months ago

I am implementing Print SCU and Print SCP from Samples/Desktop. I am facing issues for it as

--> Print SCU : want logs for each N-Event Request (N-Create, N-Set, N-Action) . I have logged the events for association but is not able to get log using dicomClient.OnNEventReportRequest .

--> Print SCP : in the following code in PrintJob.cs

  private void OnPrintPage(object sender, PrintPageEventArgs e)
    {
        _currentFilmBox.Print(e.Graphics, e.MarginBounds, 100);
        _currentFilmBox = null;
        _currentPage++;

        e.HasMorePages = _currentPage < FilmBoxFolderList.Count;
    }         
        _currentFilmBox is of type FilmBox and not have the .Print method rather it has .PrintStandard  and such methods. So it is giving error on it.

Please help me to resolve this to complete this implementation.

gofal commented 8 months ago

fo-dicom.core is completely platform independent. Printing and graphics still has some platform dependencies in .net. It relates to GDI and therefore only works on windows. The Print method here on FilmBox instance is an extension method. You have to add a nuget reference to fo-dicom.Imaging.Destkop. There in the namespce FellowOakDicom.Printing this method is included.

gofal commented 8 months ago

... but this now is off topic of this issue. To I will close this issue for now. If there is still some issue related to AETitle on Server, please feel free to reopen this issue.