diff OrthancServer/DicomProtocol/DicomUserConnection.cpp @ 947:c2c28dd17e87 query-retrieve

integration mainline -> query-retrieve
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 25 Jun 2014 12:09:38 +0200
parents 67e6400fca03 b3f6fb1130cd
children 111e23bb4904
line wrap: on
line diff
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Apr 16 16:34:09 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Jun 25 12:09:38 2014 +0200
@@ -30,6 +30,55 @@
  **/
 
 
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+#include "../PrecompiledHeadersServer.h"
 #include "DicomUserConnection.h"
 
 #include "../../Core/OrthancException.h"
@@ -58,8 +107,29 @@
 #endif 
 
 
+#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX)
+/**
+ * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that
+ * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an
+ * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect
+ * that the result will fit."
+ * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html
+ **/
+#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
+
+
 static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax;
 
+/**
+ * "If we have more than 64 storage SOP classes, tools such as
+ * storescu will fail because they attempt to negotiate two
+ * presentation contexts for each SOP class, and there is a total
+ * limit of 128 contexts for one association."
+ **/
+static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64;
+
+
 namespace Orthanc
 {
   struct DicomUserConnection::PImpl
@@ -103,55 +173,38 @@
   }
 
 
-  void DicomUserConnection::CopyParameters(const DicomUserConnection& other)
+  static void RegisterStorageSOPClass(T_ASC_Parameters* params,
+                                      unsigned int& presentationContextId,
+                                      const std::string& sopClass,
+                                      const char* asPreferred[],
+                                      std::vector<const char*>& asFallback)
   {
-    Close();
-    localAet_ = other.localAet_;
-    distantAet_ = other.distantAet_;
-    distantHost_ = other.distantHost_;
-    distantPort_ = other.distantPort_;
-    manufacturer_ = other.manufacturer_;
-    preferredTransferSyntax_ = other.preferredTransferSyntax_;
+    Check(ASC_addPresentationContext(params, presentationContextId, 
+                                     sopClass.c_str(), asPreferred, 1));
+    presentationContextId += 2;
+
+    if (asFallback.size() > 0)
+    {
+      Check(ASC_addPresentationContext(params, presentationContextId, 
+                                       sopClass.c_str(), &asFallback[0], asFallback.size()));
+      presentationContextId += 2;
+    }
   }
-
-
+  
+    
   void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax)
   {
-    // Fallback transfer syntaxes
+    // Flatten an array with the preferred transfer syntax
+    const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
+
+    // Setup the fallback transfer syntaxes
     std::set<std::string> fallbackSyntaxes;
     fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax);
     fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax);
     fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax);
-
-    // Transfer syntaxes for C-ECHO, C-FIND and C-MOVE
-    std::vector<std::string> transferSyntaxes;
-    transferSyntaxes.push_back(UID_VerificationSOPClass);
-    transferSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
-    transferSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
-    transferSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
-
-    // TODO: Allow the set below to be configured
-    std::set<std::string> uselessSyntaxes;
-    uselessSyntaxes.insert(UID_BlendingSoftcopyPresentationStateStorage);
-    uselessSyntaxes.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
-    uselessSyntaxes.insert(UID_ColorSoftcopyPresentationStateStorage);
-    uselessSyntaxes.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
-
-    // Add the transfer syntaxes for C-STORE
-    for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
-    {
-      // Test to make some room to allow the ECHO and FIND requests
-      if (uselessSyntaxes.find(dcmShortSCUStorageSOPClassUIDs[i]) == uselessSyntaxes.end())
-      {
-        transferSyntaxes.push_back(dcmShortSCUStorageSOPClassUIDs[i]);
-      }
-    }
-
-    // Flatten the fallback transfer syntaxes array
-    const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
-
     fallbackSyntaxes.erase(preferredTransferSyntax);
 
+    // Flatten an array with the fallback transfer syntaxes
     std::vector<const char*> asFallback;
     asFallback.reserve(fallbackSyntaxes.size());
     for (std::set<std::string>::const_iterator 
@@ -160,19 +213,28 @@
       asFallback.push_back(it->c_str());
     }
 
+    CheckStorageSOPClassesInvariant();
     unsigned int presentationContextId = 1;
-    for (size_t i = 0; i < transferSyntaxes.size(); i++)
+
+    for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
+         it != reservedStorageSOPClasses_.end(); ++it)
     {
-      Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, 
-                                       transferSyntaxes[i].c_str(), asPreferred, 1));
-      presentationContextId += 2;
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
+    }
 
-      if (asFallback.size() > 0)
-      {
-        Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, 
-                                         transferSyntaxes[i].c_str(), &asFallback[0], asFallback.size()));
-        presentationContextId += 2;
-      }
+    for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
+         it != storageSOPClasses_.end(); ++it)
+    {
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
+    }
+
+    for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin();
+         it != defaultStorageSOPClasses_.end(); ++it)
+    {
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
     }
   }
 
@@ -192,8 +254,16 @@
     DcmFileFormat dcmff;
     Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength));
 
+    // Determine the storage SOP class UID for this instance
+    static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016);
+    OFString sopClassUid;
+    if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good())
+    {
+      connection.AddStorageSOPClass(sopClassUid.c_str());
+    }
+
     // Determine whether a new presentation context must be
-    // negociated, depending on the transfer syntax of this instance
+    // negotiated, depending on the transfer syntax of this instance
     DcmXfer xfer(dcmff.getDataset()->getOriginalXfer());
     const std::string syntax(xfer.getXferID());
     bool isGeneric = IsGenericTransferSyntax(syntax);
@@ -201,8 +271,8 @@
     if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()))
     {
       // Making a generic-to-specific or specific-to-generic change of
-      // the transfer syntax. Renegociate the connection.
-      LOG(INFO) << "Renegociating a C-Store association due to a change in the transfer syntax";
+      // the transfer syntax. Renegotiate the connection.
+      LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated";
 
       if (isGeneric)
       {
@@ -212,7 +282,11 @@
       {
         connection.SetPreferredTransferSyntax(syntax);
       }
+    }
 
+    if (!connection.IsOpen())
+    {
+      LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters";
       connection.Open();
     }
 
@@ -232,7 +306,7 @@
       if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);
       if (!modalityName) modalityName = "unknown SOP class";
       throw OrthancException("DicomUserConnection: No presentation context for modality " + 
-                              std::string(modalityName));
+                             std::string(modalityName));
     }
 
     // Prepare the transmission of data
@@ -288,88 +362,88 @@
     std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields));
     switch (model)
     {
-    case FindRootModel_Patient:
-      DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT");
-      sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
+      case FindRootModel_Patient:
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT");
+        sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
       
-      // Accession number
-      if (!fields.HasTag(0x0008, 0x0050))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
 
-      // Patient ID
-      if (!fields.HasTag(0x0010, 0x0020))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), "");
+        // Patient ID
+        if (!fields.HasTag(0x0010, 0x0020))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), "");
 
-      break;
+        break;
 
-    case FindRootModel_Study:
-      DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY");
-      sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+      case FindRootModel_Study:
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY");
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
 
-      // Accession number
-      if (!fields.HasTag(0x0008, 0x0050))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
 
-      // Study instance UID
-      if (!fields.HasTag(0x0020, 0x000d))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
+        // Study instance UID
+        if (!fields.HasTag(0x0020, 0x000d))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
 
-      break;
+        break;
 
-    case FindRootModel_Series:
-      DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES");
-      sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+      case FindRootModel_Series:
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES");
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
 
-      // Accession number
-      if (!fields.HasTag(0x0008, 0x0050))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
 
-      // Study instance UID
-      if (!fields.HasTag(0x0020, 0x000d))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
+        // Study instance UID
+        if (!fields.HasTag(0x0020, 0x000d))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
 
-      // Series instance UID
-      if (!fields.HasTag(0x0020, 0x000e))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), "");
+        // Series instance UID
+        if (!fields.HasTag(0x0020, 0x000e))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), "");
 
-      break;
+        break;
 
-    case FindRootModel_Instance:
-      if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
-          manufacturer_ == ModalityManufacturer_Dcm4Chee)
-      {
-        // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
-        // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
-        // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE");
-      }
-      else
-      {
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
-      }
+      case FindRootModel_Instance:
+        if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
+            manufacturer_ == ModalityManufacturer_Dcm4Chee)
+        {
+          // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
+          // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
+          // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE");
+        }
+        else
+        {
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
+        }
 
-      sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
 
-      // Accession number
-      if (!fields.HasTag(0x0008, 0x0050))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
 
-      // Study instance UID
-      if (!fields.HasTag(0x0020, 0x000d))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
+        // Study instance UID
+        if (!fields.HasTag(0x0020, 0x000d))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
 
-      // Series instance UID
-      if (!fields.HasTag(0x0020, 0x000e))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), "");
+        // Series instance UID
+        if (!fields.HasTag(0x0020, 0x000e))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), "");
 
-      // SOP Instance UID
-      if (!fields.HasTag(0x0008, 0x0018))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), "");
+        // SOP Instance UID
+        if (!fields.HasTag(0x0008, 0x0018))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), "");
 
-      break;
+        break;
 
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
     // Figure out which of the accepted presentation contexts should be used
@@ -501,6 +575,35 @@
   }
 
 
+  void DicomUserConnection::ResetStorageSOPClasses()
+  {
+    CheckStorageSOPClassesInvariant();
+
+    storageSOPClasses_.clear();
+    defaultStorageSOPClasses_.clear();
+
+    // Copy the short list of storage SOP classes from DCMTK, making
+    // room for the 4 SOP classes reserved for C-ECHO, C-FIND, C-MOVE.
+
+    std::set<std::string> uncommon;
+    uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_ColorSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
+
+    // Add the storage syntaxes for C-STORE
+    for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
+    {
+      if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end())
+      {
+        defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]);
+      }
+    }
+
+    CheckStorageSOPClassesInvariant();
+  }
+
+
   DicomUserConnection::DicomUserConnection() : 
     pimpl_(new PImpl),
     preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX),
@@ -514,6 +617,14 @@
     pimpl_->net_ = NULL;
     pimpl_->params_ = NULL;
     pimpl_->assoc_ = NULL;
+
+    // SOP classes for C-ECHO, C-FIND and C-MOVE
+    reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass);
+    reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
+
+    ResetStorageSOPClasses();
   }
 
   DicomUserConnection::~DicomUserConnection()
@@ -521,6 +632,16 @@
     Close();
   }
 
+
+  void DicomUserConnection::Connect(const RemoteModalityParameters& parameters)
+  {
+    SetDistantApplicationEntityTitle(parameters.GetApplicationEntityTitle());
+    SetDistantHost(parameters.GetHost());
+    SetDistantPort(parameters.GetPort());
+    SetDistantManufacturer(parameters.GetManufacturer());
+  }
+
+
   void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet)
   {
     if (localAet_ != aet)
@@ -594,6 +715,11 @@
       return;
     }
 
+    LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() 
+              << "\" to AET \"" << GetDistantApplicationEntityTitle() << "\" on host "
+              << GetDistantHost() << ":" << GetDistantPort() 
+              << " (manufacturer: " << EnumerationToString(GetDistantManufacturer()) << ")";
+
     Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_));
     Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
 
@@ -607,11 +733,11 @@
     char distantHostAndPort[HOST_NAME_MAX];
 
 #ifdef _MSC_VER
-	_snprintf
+    _snprintf
 #else
-	snprintf
+      snprintf
 #endif
-		(distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_);
+      (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_);
 
     Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort));
 
@@ -742,4 +868,60 @@
     dcmConnectionTimeout.set(seconds);
   }
 
+
+  void DicomUserConnection::CheckStorageSOPClassesInvariant() const
+  {
+    assert(storageSOPClasses_.size() + 
+           defaultStorageSOPClasses_.size() + 
+           reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES);
+  }
+
+  void DicomUserConnection::AddStorageSOPClass(const char* sop)
+  {
+    CheckStorageSOPClassesInvariant();
+
+    if (storageSOPClasses_.find(sop) != storageSOPClasses_.end())
+    {
+      // This storage SOP class is already explicitly registered. Do
+      // nothing.
+      return;
+    }
+
+    if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end())
+    {
+      // This storage SOP class is not explicitly registered, but is
+      // used by default. Just register it explicitly.
+      defaultStorageSOPClasses_.erase(sop);
+      storageSOPClasses_.insert(sop);
+
+      CheckStorageSOPClassesInvariant();
+      return;
+    }
+
+    // This storage SOP class is neither explicitly, nor implicitly
+    // registered. Close the connection and register it explicitly.
+
+    Close();
+
+    if (reservedStorageSOPClasses_.size() + 
+        storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)  // (*)
+    {
+      // The maximum number of SOP classes is reached
+      ResetStorageSOPClasses();
+      defaultStorageSOPClasses_.erase(sop);
+    }
+    else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + 
+             defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)
+    {
+      // Make room in the default storage syntaxes
+      assert(defaultStorageSOPClasses_.size() > 0);  // Necessarily true because condition (*) is false
+      defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin());
+    }
+
+    // Explicitly register the new storage syntax
+    storageSOPClasses_.insert(sop);
+
+    CheckStorageSOPClassesInvariant();
+  }
+
 }