Network association#

The connection between two DICOM peers is handled by odil::Association and odil::AssociationParameters. The former encapsulates the connection itself (TCP/IP layer and DICOM layer) while the latter handles the various options that parameterize the association (AE titles, presentation contexts, authentication, etc.).

Establishing an association#

An association will at least require to define the application entity titles (both local and remote) the presentation contexts (i.e. representation of the data) that are acceptable for the application. The following example shows how to create a simple odil::AssociationParameters object.

#include <odil/AssociationParameters.h>
#include <odil/registry.h>

odil::AssociationParameters
create_parameters()
{
    std::vector<std::string> transfer_syntaxes{
        odil::registry::ExplicitVRLittleEndian,
        odil::registry::ExplicitVRLittleEndian
    };

    odil::AssociationParameters parameters;
    parameters
        .set_calling_ae_title("WORKSTATION")
        .set_called_ae_title("SERVER")
        .set_presentation_contexts({
            {
                odil::registry::Verification,
                transfer_syntaxes,
                odil::AssociationParameters::PresentationContext::Role::SCU
            },
            {
                odil::registry::PatientRootQueryRetrieveInformationModelFind,
                transfer_syntaxes,
                odil::AssociationParameters::PresentationContext::Role::SCU
            }
        });
    return parameters;
}

Note that the odil::AssociationParameters object uses parameter chaining. The objects passed to set_presentation_contexts are of type odil::AssociationParameters::PresentationContext. In this example, the id of the presentation context is not specified: it is instead set to an special value by the constructor, and a valid id is then assigned in set_presentation_contexts.

After setting the address and port of the remote peer, the association is established through the associate member function. Once established, the negotiated parameters (i.e. acceptable to both peers) may be obtained by the get_negotiated_parameters member function.

#include <odil/Association.h>

int main()
{
    odil::Association association;
    association.set_peer_host("www.dicomserver.co.uk");
    association.set_peer_port(11112);
    association.set_parameters(create_parameters());
    association.associate();

    auto const & contexts =
        association.get_negotiated_parameters().get_presentation_contexts();
    std::cout << "Presentation contexts (" << contexts.size() << ")\n";
    for(auto const & context: contexts)
    {
        std::cout
            << "\t"
            << odil::registry::uids_dictionary.at(context.abstract_syntax).name
            << ": "
            << odil::registry::uids_dictionary.at(context.transfer_syntaxes[0]).name
            << ", "
            << (context.scu_role_support?"SCU":"")
            << ((context.scu_role_support & context.scp_role_support)?"/":"")
            << (context.scp_role_support?"SCP":"")
            << std::endl;
    }
}

Receiving an association#

Receiving an association is performed by the receive_association member function. It will listen on the given IP address and TCP port and block until an acceptable association request is received or until an error occurs. By default all association requests are deemed acceptable, and the first transfer syntax of each presentation context is accepted. Errors (unacceptable association, low-level protocol error) will throw an exception. After acceptance, the association object contains the negotiated parameters:

#include <odil/Association.h>

int main()
{
    odil::Association association;

    association.receive_association(boost::asio::ip::tcp::v4(), 11112);

    std::cout
          << "Received association from "
          << association.get_peer_host() << ":" << association.get_peer_port()
          << std::endl ;

      auto const & contexts =
          association.get_negotiated_parameters().get_presentation_contexts();
      std::cout << "Presentation contexts (" << contexts.size() << ")\n";
      for(auto const & context: contexts)
      {
          std::cout
              << "\t"
              << odil::registry::uids_dictionary.at(context.abstract_syntax).name
              << ": "
              << odil::registry::uids_dictionary.at(context.transfer_syntaxes[0]).name
              << ", "
              << (context.scu_role_support?"SCU":"")
              << ((context.scu_role_support & context.scp_role_support)?"/":"")
              << (context.scp_role_support?"SCP":"")
              << std::endl;
      }
  }

Optionally, a callback which indicates whether the association request is acceptable or may be specified. If the request is acceptable, the callback must return the accepted parameters, otherwise it must throw an exception of type odil::AssociationRejected. As an example, the default acceptation callback, which accepts the first transfer syntax for all abstract syntaxes, would be:

#include <odil/Association.h>

odil::AssociationParameters
acceptor(odil::AssociationParameters const & input)
{
    odil::AssociationParameters output;

    output.set_called_ae_title(input.get_called_ae_title());
    output.set_calling_ae_title(input.get_calling_ae_title());

    auto presentation_contexts = input.get_presentation_contexts();
    for(auto & presentation_context: presentation_contexts)
    {
        presentation_context.transfer_syntaxes =
            { presentation_context.transfer_syntaxes[0] };
        presentation_context.result =
            odil::AssociationParameters::PresentationContext::Result::Acceptance;
    }
    output.set_presentation_contexts(presentation_contexts);

    output.set_maximum_length(input.get_maximum_length());

    return output;
}