[med-svn] [dcmtkpp] 01/09: Imported Upstream version 0.4.0

Julien Lamy lamy-guest at moszumanska.debian.org
Thu Jan 14 08:45:45 UTC 2016


This is an automated email from the git hooks/post-receive script.

lamy-guest pushed a commit to branch master
in repository dcmtkpp.

commit 40b3289c52a1cc216217df6cb54a7db6d7edebfe
Author: Julien Lamy <lamy at unistra.fr>
Date:   Tue Jan 12 17:05:10 2016 +0100

    Imported Upstream version 0.4.0
---
 .travis.yml                                        |    6 +-
 CMakeLists.txt                                     |   24 +-
 Doxyfile                                           |    4 +-
 FindDCMTK.cmake                                    |   32 +
 FindICU.cmake                                      |   26 +-
 FindJsonCpp.cmake                                  |   24 +
 README.md                                          |   26 +-
 appveyor.full.yml                                  |   52 +
 appveyor.yml                                       |   43 +
 examples/CMakeLists.txt                            |   12 +-
 examples/dicomdir.cpp                              |   14 +-
 examples/dump.cpp                                  |   18 +-
 examples/find.cpp                                  |   77 +-
 examples/genericscp.cpp                            |  142 ++
 examples/get.cpp                                   |  136 +-
 examples/move.cpp                                  |  138 +-
 examples/store.cpp                                 |   76 +-
 generate_registry                                  |    4 +-
 registry.cpp.tmpl                                  |   18 +-
 registry.h.tmpl                                    |   10 +-
 src/CMakeLists.txt                                 |   43 +-
 src/dcmtkpp/Association.cpp                        |  535 ----
 src/dcmtkpp/Association.h                          |  184 --
 src/dcmtkpp/GetSCU.cpp                             |  105 -
 src/dcmtkpp/MoveSCU.cpp                            |  162 --
 src/dcmtkpp/Network.cpp                            |  201 --
 src/dcmtkpp/Network.h                              |   91 -
 src/dcmtkpp/Response.cpp                           |   43 -
 src/dcmtkpp/Response.h                             |   44 -
 src/dcmtkpp/SCP.cpp                                |   44 -
 src/dcmtkpp/SCP.h                                  |   31 -
 src/dcmtkpp/ServiceRole.cpp                        |  438 ----
 src/dcmtkpp/ServiceRole.h                          |  113 -
 src/dcmtkpp/ServiceRole.txx                        |   38 -
 src/dcmtkpp/StoreSCP.cpp                           |   94 -
 src/dcmtkpp/StoreSCP.h                             |   43 -
 src/dcmtkpp/uid.cpp                                |   88 -
 src/odil/Association.cpp                           |  496 ++++
 src/odil/Association.h                             |  222 ++
 src/odil/AssociationAcceptor.cpp                   |   98 +
 src/odil/AssociationAcceptor.h                     |   64 +
 src/odil/AssociationParameters.cpp                 |  457 ++++
 src/odil/AssociationParameters.h                   |  156 ++
 src/{dcmtkpp => odil}/BasicDirectoryCreator.cpp    |   27 +-
 src/{dcmtkpp => odil}/BasicDirectoryCreator.h      |   21 +-
 src/{dcmtkpp => odil}/DataSet.cpp                  |   22 +-
 src/{dcmtkpp => odil}/DataSet.h                    |   36 +-
 src/odil/EchoSCP.cpp                               |   82 +
 src/odil/EchoSCP.h                                 |   54 +
 src/{dcmtkpp => odil}/Element.cpp                  |   10 +-
 src/{dcmtkpp => odil}/Element.h                    |   32 +-
 src/{dcmtkpp => odil}/Element.txx                  |    8 +-
 src/{dcmtkpp => odil}/ElementsDictionary.cpp       |    6 +-
 src/{dcmtkpp => odil}/ElementsDictionary.h         |    6 +-
 .../UIDsDictionary.cpp => odil/Exception.cpp}      |   29 +-
 src/odil/Exception.h                               |   37 +
 src/odil/FindSCP.cpp                               |   94 +
 src/odil/FindSCP.h                                 |   52 +
 src/{dcmtkpp => odil}/FindSCU.cpp                  |   40 +-
 src/{dcmtkpp => odil}/FindSCU.h                    |   11 +-
 src/odil/GetSCP.cpp                                |  141 ++
 src/odil/GetSCP.h                                  |   59 +
 src/odil/GetSCU.cpp                                |  115 +
 src/{dcmtkpp => odil}/GetSCU.h                     |   28 +-
 src/odil/MoveSCP.cpp                               |  147 ++
 src/odil/MoveSCP.h                                 |   65 +
 src/odil/MoveSCU.cpp                               |  169 ++
 src/{dcmtkpp => odil}/MoveSCU.h                    |   17 +-
 src/{dcmtkpp => odil}/Reader.cpp                   |  118 +-
 src/{dcmtkpp => odil}/Reader.h                     |   23 +-
 src/odil/SCP.cpp                                   |   46 +
 src/odil/SCP.h                                     |   55 +
 src/odil/SCPDispatcher.cpp                         |   78 +
 src/odil/SCPDispatcher.h                           |   47 +
 src/{dcmtkpp => odil}/SCU.cpp                      |   35 +-
 src/{dcmtkpp => odil}/SCU.h                        |   16 +-
 src/odil/StoreSCP.cpp                              |   84 +
 src/odil/StoreSCP.h                                |   54 +
 src/{dcmtkpp => odil}/StoreSCU.cpp                 |   33 +-
 src/{dcmtkpp => odil}/StoreSCU.h                   |   18 +-
 src/{dcmtkpp => odil}/Tag.cpp                      |   10 +-
 src/{dcmtkpp => odil}/Tag.h                        |   10 +-
 src/{dcmtkpp => odil}/UIDsDictionary.cpp           |    6 +-
 src/{dcmtkpp => odil}/UIDsDictionary.h             |    4 +-
 src/{dcmtkpp => odil}/VR.cpp                       |   30 +-
 src/{dcmtkpp => odil}/VR.h                         |   10 +-
 src/{dcmtkpp => odil}/VRFinder.cpp                 |   18 +-
 src/{dcmtkpp => odil}/VRFinder.h                   |   10 +-
 src/{dcmtkpp => odil}/Value.cpp                    |   10 +-
 src/{dcmtkpp => odil}/Value.h                      |   26 +-
 src/{dcmtkpp => odil}/Value.txx                    |    8 +-
 src/{dcmtkpp => odil}/Writer.cpp                   |  111 +-
 src/{dcmtkpp => odil}/Writer.h                     |   29 +-
 src/{dcmtkpp/UIDsDictionary.cpp => odil/asio.cpp}  |   22 +-
 src/odil/base64.cpp                                |   36 +
 src/odil/base64.h                                  |   42 +
 src/odil/base64.txx                                |  131 +
 src/{dcmtkpp => odil/dcmtk}/ElementAccessor.cpp    |   11 +-
 src/{dcmtkpp => odil/dcmtk}/ElementAccessor.h      |   15 +-
 src/{dcmtkpp => odil/dcmtk}/ElementAccessor.txx    |   11 +-
 src/{dcmtkpp => odil/dcmtk}/ElementTraits.cpp      |   13 +-
 src/{dcmtkpp => odil/dcmtk}/ElementTraits.h        |   11 +-
 src/{dcmtkpp => odil/dcmtk}/Exception.cpp          |   27 +-
 src/{dcmtkpp => odil/dcmtk}/Exception.h            |   60 +-
 src/{dcmtkpp => odil/dcmtk}/VRTraits.h             |   16 +-
 src/{dcmtkpp => odil/dcmtk}/conversion.cpp         |   23 +-
 src/{dcmtkpp => odil/dcmtk}/conversion.h           |   35 +-
 src/{dcmtkpp => odil/dcmtk}/conversion.txx         |   17 +-
 src/odil/dul/EventData.h                           |   39 +
 src/odil/dul/StateMachine.cpp                      |  764 ++++++
 src/odil/dul/StateMachine.h                        |  274 +++
 src/odil/dul/Transport.cpp                         |  295 +++
 src/odil/dul/Transport.h                           |  107 +
 src/odil/endian.h                                  |   87 +
 src/{dcmtkpp => odil}/json_converter.cpp           |   44 +-
 src/{dcmtkpp => odil}/json_converter.h             |    8 +-
 src/{dcmtkpp => odil/message}/CEchoRequest.cpp     |   15 +-
 src/{dcmtkpp => odil/message}/CEchoRequest.h       |   17 +-
 src/{dcmtkpp => odil/message}/CEchoResponse.cpp    |   17 +-
 src/{dcmtkpp => odil/message}/CEchoResponse.h      |   17 +-
 src/{dcmtkpp => odil/message}/CFindRequest.cpp     |   17 +-
 src/{dcmtkpp => odil/message}/CFindRequest.h       |   19 +-
 src/{dcmtkpp => odil/message}/CFindResponse.cpp    |   23 +-
 src/{dcmtkpp => odil/message}/CFindResponse.h      |   36 +-
 src/{dcmtkpp => odil/message}/CGetRequest.cpp      |   19 +-
 src/{dcmtkpp => odil/message}/CGetRequest.h        |   21 +-
 src/{dcmtkpp => odil/message}/CGetResponse.cpp     |   31 +-
 src/{dcmtkpp => odil/message}/CGetResponse.h       |   45 +-
 src/{dcmtkpp => odil/message}/CMoveRequest.cpp     |   19 +-
 src/{dcmtkpp => odil/message}/CMoveRequest.h       |   23 +-
 src/{dcmtkpp => odil/message}/CMoveResponse.cpp    |   31 +-
 src/{dcmtkpp => odil/message}/CMoveResponse.h      |   46 +-
 src/{dcmtkpp => odil/message}/CStoreRequest.cpp    |   18 +-
 src/{dcmtkpp => odil/message}/CStoreRequest.h      |   27 +-
 src/{dcmtkpp => odil/message}/CStoreResponse.cpp   |   23 +-
 src/{dcmtkpp => odil/message}/CStoreResponse.h     |   34 +-
 src/{dcmtkpp => odil/message}/Cancellation.cpp     |   21 +-
 src/{dcmtkpp => odil/message}/Cancellation.h       |   19 +-
 src/{dcmtkpp => odil/message}/Message.cpp          |   17 +-
 src/{dcmtkpp => odil/message}/Message.h            |   41 +-
 src/{dcmtkpp => odil/message}/Request.cpp          |   15 +-
 src/{dcmtkpp => odil/message}/Request.h            |   17 +-
 src/odil/message/Response.cpp                      |   97 +
 src/odil/message/Response.h                        |  103 +
 src/odil/pdu/AAbort.cpp                            |   93 +
 src/odil/pdu/AAbort.h                              |   49 +
 src/odil/pdu/AAssociate.cpp                        |  272 ++
 src/odil/pdu/AAssociate.h                          |   91 +
 src/odil/pdu/AAssociateAC.cpp                      |   93 +
 src/odil/pdu/AAssociateAC.h                        |   47 +
 src/odil/pdu/AAssociateRJ.cpp                      |  112 +
 src/odil/pdu/AAssociateRJ.h                        |   54 +
 src/odil/pdu/AAssociateRQ.cpp                      |   93 +
 src/odil/pdu/AAssociateRQ.h                        |   47 +
 src/odil/pdu/AReleaseRP.cpp                        |   48 +
 src/odil/pdu/AReleaseRP.h                          |   37 +
 src/odil/pdu/AReleaseRQ.cpp                        |   48 +
 src/odil/pdu/AReleaseRQ.h                          |   37 +
 src/odil/pdu/ApplicationContext.cpp                |   68 +
 src/odil/pdu/ApplicationContext.h                  |   46 +
 src/odil/pdu/ImplementationClassUID.cpp            |   68 +
 src/odil/pdu/ImplementationClassUID.h              |   48 +
 src/odil/pdu/ImplementationVersionName.cpp         |   72 +
 src/odil/pdu/ImplementationVersionName.h           |   48 +
 src/odil/pdu/Item.cpp                              |  454 ++++
 src/odil/pdu/Item.h                                |  266 ++
 src/odil/pdu/MaximumLength.cpp                     |   65 +
 src/odil/pdu/MaximumLength.h                       |   47 +
 src/odil/pdu/Object.cpp                            |  116 +
 src/odil/pdu/Object.h                              |   55 +
 src/odil/pdu/PDataTF.cpp                           |  186 ++
 src/odil/pdu/PDataTF.h                             |   67 +
 src/odil/pdu/PresentationContext.cpp               |  218 ++
 src/odil/pdu/PresentationContext.h                 |   70 +
 src/odil/pdu/PresentationContextAC.cpp             |   91 +
 src/odil/pdu/PresentationContextAC.h               |   55 +
 src/odil/pdu/PresentationContextRQ.cpp             |  101 +
 src/odil/pdu/PresentationContextRQ.h               |   58 +
 src/odil/pdu/RoleSelection.cpp                     |  108 +
 src/odil/pdu/RoleSelection.h                       |   65 +
 src/odil/pdu/UserIdentityAC.cpp                    |   75 +
 src/odil/pdu/UserIdentityAC.h                      |   47 +
 src/odil/pdu/UserIdentityRQ.cpp                    |  169 ++
 src/odil/pdu/UserIdentityRQ.h                      |   75 +
 src/odil/pdu/UserInformation.cpp                   |  113 +
 src/odil/pdu/UserInformation.h                     |   66 +
 src/odil/pdu/UserInformation.txx                   |  157 ++
 src/{dcmtkpp => odil}/registry.cpp                 |   18 +-
 src/{dcmtkpp => odil}/registry.h                   |   10 +-
 src/odil/uid.cpp                                   |   43 +
 src/{dcmtkpp => odil}/uid.h                        |   19 +-
 src/{dcmtkpp => odil}/unicode.cpp                  |   11 +-
 src/{dcmtkpp => odil}/unicode.h                    |    6 +-
 src/{dcmtkpp => odil}/xml_converter.cpp            |   40 +-
 src/{dcmtkpp => odil}/xml_converter.h              |    8 +-
 tests/CMakeLists.txt                               |   33 +-
 tests/MessageFixtureBase.h                         |   26 +-
 tests/PeerFixtureBase.h                            |   71 +-
 tests/code/Association.cpp                         |  268 +-
 tests/code/AssociationAcceptor.cpp                 |   13 +
 tests/code/AssociationParameters.cpp               |  178 ++
 tests/code/BasicDirectoryCreator.cpp               |  159 ++
 tests/code/CEchoRequest.cpp                        |   56 -
 tests/code/CEchoResponse.cpp                       |   58 -
 tests/code/CFindResponse.cpp                       |   72 -
 tests/code/CGetResponse.cpp                        |   93 -
 tests/code/CMoveResponse.cpp                       |   93 -
 tests/code/CStoreResponse.cpp                      |   68 -
 tests/code/Cancellation.cpp                        |   40 -
 tests/code/DataSet.cpp                             |  295 ++-
 tests/code/DcmtkException.cpp                      |   26 +
 tests/code/EchoSCP.cpp                             |  122 +
 tests/code/Element.cpp                             |   96 +-
 tests/code/ElementAccessor.cpp                     |   44 +-
 tests/code/Exception.cpp                           |   36 +-
 tests/code/FindSCP.cpp                             |  166 ++
 tests/code/FindSCU.cpp                             |   31 +-
 tests/code/GetSCP.cpp                              |  201 ++
 tests/code/GetSCU.cpp                              |   38 +-
 tests/code/Message.cpp                             |   57 -
 tests/code/MoveSCP.cpp                             |  237 ++
 tests/code/MoveSCU.cpp                             |   45 +-
 tests/code/Network.cpp                             |  156 --
 tests/code/Reader.cpp                              |  250 +-
 tests/code/Response.cpp                            |   31 -
 tests/code/SCPDispatcher.cpp                       |  132 +
 tests/code/SCU.cpp                                 |   26 +-
 tests/code/ServiceRole.cpp                         |   62 -
 tests/code/StoreSCU.cpp                            |   66 +-
 tests/code/Tag.cpp                                 |   82 +-
 tests/code/VR.cpp                                  |   18 +-
 tests/code/VRFinder.cpp                            |  177 ++
 tests/code/Value.cpp                               |  224 +-
 tests/code/Writer.cpp                              |  268 +-
 tests/code/base64.cpp                              |   75 +
 tests/code/conversion.cpp                          |  272 +-
 tests/code/dul/StateMachine.cpp                    |   22 +
 tests/code/dul/Transport.cpp                       |   45 +
 tests/code/endian.cpp                              |   89 +
 tests/code/json_converter.cpp                      |  126 +-
 tests/code/message/CEchoRequest.cpp                |   60 +
 tests/code/message/CEchoResponse.cpp               |   64 +
 tests/code/{ => message}/CFindRequest.cpp          |   46 +-
 tests/code/message/CFindResponse.cpp               |  107 +
 tests/code/{ => message}/CGetRequest.cpp           |   48 +-
 tests/code/message/CGetResponse.cpp                |  129 +
 tests/code/{ => message}/CMoveRequest.cpp          |   47 +-
 tests/code/message/CMoveResponse.cpp               |  130 +
 tests/code/{ => message}/CStoreRequest.cpp         |   44 +-
 tests/code/message/CStoreResponse.cpp              |  106 +
 tests/code/message/Cancellation.cpp                |   44 +
 tests/code/message/Message.cpp                     |   63 +
 tests/code/{ => message}/Request.cpp               |   16 +-
 tests/code/message/Response.cpp                    |  112 +
 tests/code/pdu/AAbort.cpp                          |   51 +
 tests/code/pdu/AAssociateAC.cpp                    |  279 +++
 tests/code/pdu/AAssociateRJ.cpp                    |   59 +
 tests/code/pdu/AAssociateRQ.cpp                    |  290 +++
 tests/code/pdu/AReleaseRP.cpp                      |   34 +
 tests/code/pdu/AReleaseRQ.cpp                      |   34 +
 tests/code/pdu/ApplicationContext.cpp              |   44 +
 tests/code/pdu/ImplementationClassUID.cpp          |   43 +
 tests/code/pdu/ImplementationVersionName.cpp       |   59 +
 tests/code/pdu/Item.cpp                            |  128 +
 tests/code/pdu/MaximumLength.cpp                   |   56 +
 tests/code/pdu/PDataTF.cpp                         |   74 +
 tests/code/pdu/PresentationContextAC.cpp           |   62 +
 tests/code/pdu/PresentationContextRQ.cpp           |   82 +
 tests/code/pdu/RoleSelection.cpp                   |   74 +
 tests/code/pdu/UserIdentityAC.cpp                  |   58 +
 tests/code/pdu/UserIdentityRQ.cpp                  |  124 +
 tests/code/pdu/UserInformation.cpp                 |  160 ++
 tests/code/registry.cpp                            |   16 +-
 tests/code/uid.cpp                                 |    4 +-
 tests/code/unicode.cpp                             |  113 +-
 tests/code/xml_converter.cpp                       |  219 +-
 tests/run.sh                                       |   12 +-
 tests/tools/CMakeLists.txt                         |   11 +
 tests/tools/dndefine.h                             |   80 +
 tests/tools/getscu.cc                              |  657 +++++
 tests/tools/scu.cc                                 | 2590 ++++++++++++++++++++
 tests/tools/scu.h                                  | 1197 +++++++++
 282 files changed, 20946 insertions(+), 5558 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 3741652..41f89da 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
-sudo: false
+sudo: required
+dist: trusty
 language: cpp
 compiler:
   - gcc
-  - clang
 addons:
   apt:
     packages:
@@ -10,7 +10,6 @@ addons:
     - libwrap0-dev
     - libjsoncpp-dev
     - libicu-dev
-    - uuid-dev
     - zlib1g-dev
     - libboost-dev
     - libboost-filesystem-dev
@@ -28,6 +27,7 @@ before_script:
   - cmake -D CMAKE_CXX_FLAGS:STRING="${CMAKE_CXX_FLAGS}" -D CMAKE_BUILD_TYPE:STRING=Debug ../
 script:
   - make -j $(nproc)
+  - export PATH=$PWD/tests/tools:$PATH
   - ../tests/run.sh
 after_success:
   - if [ "${CC}" = "gcc" ]; then ${HOME}/.local/bin/coveralls --exclude examples --exclude tests --exclude-pattern '.*CMake[^/]+\.c(?:pp)?' --exclude-pattern "/usr/.*" --root=${SRC_DIR} --build-root ${BIN_DIR} | grep -vP "^File '.*'$" | grep -vP ":creating '.*'$" | grep -vP "^Lines executed:.*" | sed '/^$/d'; fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0cc8bea..1fe85f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,11 +1,11 @@
 cmake_minimum_required(VERSION 2.8)
 
-project("dcmtkpp")
-set(dcmtkpp_MAJOR_VERSION 0)
-set(dcmtkpp_MINOR_VERSION 3)
-set(dcmtkpp_PATCH_VERSION 1)
-set(dcmtkpp_VERSION 
-    ${dcmtkpp_MAJOR_VERSION}.${dcmtkpp_MINOR_VERSION}.${dcmtkpp_PATCH_VERSION})
+project("odil")
+set(odil_MAJOR_VERSION 0)
+set(odil_MINOR_VERSION 4)
+set(odil_PATCH_VERSION 0)
+set(odil_VERSION 
+    ${odil_MAJOR_VERSION}.${odil_MINOR_VERSION}.${odil_PATCH_VERSION})
 
 option(BUILD_EXAMPLES "Build the examples directory." ON)
 
@@ -21,3 +21,15 @@ endif()
 if(BUILD_TESTING)
     add_subdirectory("tests")
 endif()
+
+add_custom_target(
+    CIIntegration ${CMAKE_COMMAND} -E echo "CI Integration"
+    SOURCES appveyor.yml .travis.yml)
+
+add_custom_target(
+    Documentation ${CMAKE_COMMAND} -E echo "Documentation"
+    SOURCES Doxyfile LICENSE.txt README.md)
+
+add_custom_target(
+    Registry ${CMAKE_COMMAND} -E echo "Registry"
+    SOURCES generate_registry registry.cpp.tmpl registry.h.tmpl)
diff --git a/Doxyfile b/Doxyfile
index 885d4f2..9b2a083 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -26,7 +26,7 @@ DOXYFILE_ENCODING      = UTF-8
 # identify the project. Note that if you do not use Doxywizard you need 
 # to put quotes around the project name if it contains spaces.
 
-PROJECT_NAME           = "dcmtkpp"
+PROJECT_NAME           = "odil"
 
 # The PROJECT_NUMBER tag can be used to enter a project or revision number. 
 # This could be handy for archiving the generated documentation or 
@@ -657,7 +657,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories 
 # with spaces.
 
-INPUT                  = ./src/dcmtkpp
+INPUT                  = ./src/odil
 
 # This tag can be used to specify the character encoding of the source files 
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is 
diff --git a/FindDCMTK.cmake b/FindDCMTK.cmake
new file mode 100644
index 0000000..22f205d
--- /dev/null
+++ b/FindDCMTK.cmake
@@ -0,0 +1,32 @@
+# - Try to find DCMTK
+# Once done this will define
+#  DCMTK_FOUND - System has DCMTK
+#  DCMTK_INCLUDE_DIRS - The DCMTK include directories
+#  DCMTK_LIBRARY_DIRS - The DCMTK link directories
+#  DCMTK_LIBRARIES - The libraries needed to use DCMTK
+#  DCMTK_DEFINITIONS - Compiler switches required for using DCMTK
+
+set(DCMTK_DEFINITIONS "-D HAVE_CONFIG_H")
+
+find_path(DCMTK_INCLUDE_DIR "dcmtk/dcmdata/dctk.h")
+find_library(DCMTK_LIBRARY dcmdata)
+
+set(DCMTK_INCLUDE_DIRS ${DCMTK_INCLUDE_DIR})
+
+set(DCMTK_LIBRARIES ${DCMTK_LIBRARY} dcmnet dcmdata oflog ofstd)
+foreach(library iconv pthread wrap z)
+    find_library(${library}_LIBRARY ${library})
+    if(${library}_LIBRARY)
+        set(DCMTK_LIBRARIES ${DCMTK_LIBRARIES} ${${library}_LIBRARY})
+    endif()
+endforeach()
+
+get_filename_component(DCMTK_LIBRARY_DIRS "${DCMTK_LIBRARY}" PATH)
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set DCMTK_FOUND to TRUE
+# if all listed variables are TRUE
+find_package_handle_standard_args(
+    DCMTK DEFAULT_MSG DCMTK_INCLUDE_DIR DCMTK_LIBRARY)
+
+mark_as_advanced(DCMTK_INCLUDE_DIR DCMTK_LIBRARY)
diff --git a/FindICU.cmake b/FindICU.cmake
index 57cf7ed..61e9d72 100644
--- a/FindICU.cmake
+++ b/FindICU.cmake
@@ -1,7 +1,23 @@
-find_path(ICU_INCLUDE_DIR "unicode/ucnv.h")
-# icucore is for OS X
-find_library(ICU_LIBRARIES NAMES icuuc icucore)
+# - Try to find ICU
+# Once done this will define
+#  ICU_FOUND - System has ICU
+#  ICU_INCLUDE_DIRS - The ICU include directories
+#  ICU_LIBRARIES - The libraries needed to use ICU
+#  ICU_DEFINITIONS - Compiler switches required for using ICU
+
+find_package(PkgConfig)
+pkg_check_modules(PC_ICU QUIET icu-uc)
+set(ICU_DEFINITIONS ${PC_ICU_CFLAGS_OTHER})
+
+find_path(ICU_INCLUDE_DIR "unicode/ucnv.h" HINTS ${PC_ICU_INCLUDE_DIRS})
+find_library(ICU_LIBRARY NAMES icuuc HINTS ${PC_ICU_LIBRARY_DIRS} )
+
+set(ICU_LIBRARIES ${ICU_LIBRARY} )
+set(ICU_INCLUDE_DIRS ${ICU_INCLUDE_DIR} )
 
 include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(ICU DEFAULT_MSG
-    ICU_INCLUDE_DIR ICU_LIBRARIES)
+# handle the QUIETLY and REQUIRED arguments and set ICU_FOUND to TRUE
+# if all listed variables are TRUE
+find_package_handle_standard_args(ICU DEFAULT_MSG ICU_LIBRARY ICU_INCLUDE_DIR)
+
+mark_as_advanced(ICU_INCLUDE_DIR ICU_LIBRARY)
diff --git a/FindJsonCpp.cmake b/FindJsonCpp.cmake
new file mode 100644
index 0000000..5f6c57c
--- /dev/null
+++ b/FindJsonCpp.cmake
@@ -0,0 +1,24 @@
+# - Try to find JsonCpp
+# Once done this will define
+#  JsonCpp_FOUND - System has JsonCpp
+#  JsonCpp_INCLUDE_DIRS - The JsonCpp include directories
+#  JsonCpp_LIBRARIES - The libraries needed to use JsonCpp
+#  JsonCpp_DEFINITIONS - Compiler switches required for using JsonCpp
+
+find_package(PkgConfig)
+pkg_check_modules(PC_JsonCpp QUIET jsoncpp)
+set(JsonCpp_DEFINITIONS ${PC_JsonCpp_CFLAGS_OTHER})
+
+find_path(JsonCpp_INCLUDE_DIR "json/json.h" HINTS ${PC_JsonCpp_INCLUDE_DIRS})
+find_library(JsonCpp_LIBRARY NAMES jsoncpp HINTS ${PC_JsonCpp_LIBRARY_DIRS} )
+
+set(JsonCpp_LIBRARIES ${JsonCpp_LIBRARY} )
+set(JsonCpp_INCLUDE_DIRS ${JsonCpp_INCLUDE_DIR} )
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set JsonCpp_FOUND to TRUE
+# if all listed variables are TRUE
+find_package_handle_standard_args(
+    JsonCpp DEFAULT_MSG JsonCpp_LIBRARY JsonCpp_INCLUDE_DIR)
+
+mark_as_advanced(JsonCpp_INCLUDE_DIR JsonCpp_LIBRARY)
diff --git a/README.md b/README.md
index 086f9e0..dc3dc37 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,20 @@
-DCMTK++
+Odil
 =======
 
-DCMTK++ is a C++ wrapper library for the [DCMTK](http://dicom.offis.de/dcmtk.php.en)
-DICOM toolkit.
+Odil is a C++11 library for the [DICOM](http://dicom.nema.org/) standard.
 
-DCMTK++ is a C++ wrapper library for [DCMTK](http://dicom.offis.de/dcmtk.php.en), 
-a C++ toolkit handling the DICOM medical imaging standard. DCMTK++ 
-leverages C++ constructs to provide a more user-friendly API, notably 
-for the networking part. Included in DCMTK++ are exception-based error 
-handling, generic access to datasets elements, standard JSON 
-representation of datasets, and generic implementation of messages, 
+Odil leverages C++ constructs to provide a user-friendly API of the different
+parts of the DICOM standard. Included in Odil are exception-based error
+handling, generic access to datasets elements, standard JSON and XML
+representation of datasets, and generic implementation of messages,
 clients and servers for the various DICOM protocols.
 
-[![Build Status](https://travis-ci.org/lamyj/dcmtkpp.svg?branch=master)](https://travis-ci.org/lamyj/dcmtkpp)
-[![Coverage Status](https://coveralls.io/repos/lamyj/dcmtkpp/badge.svg)](https://coveralls.io/r/lamyj/dcmtkpp)
+Odil also provides conversion to and from
+[DCMTK](http://dicom.offis.de/dcmtk.php.en) data structures.
+
+Odil builds and run on:
+* Linux (Debian 7, Debian 8, Ubuntu 12.04, Ubuntu 14.04, both 32 and 64 bits)
+* OS X
+
+[![Build Status](https://travis-ci.org/lamyj/odil.svg?branch=master)](https://travis-ci.org/lamyj/odil)
+[![Coverage Status](https://coveralls.io/repos/lamyj/odil/badge.svg)](https://coveralls.io/r/lamyj/odil)
diff --git a/appveyor.full.yml b/appveyor.full.yml
new file mode 100644
index 0000000..092d607
--- /dev/null
+++ b/appveyor.full.yml
@@ -0,0 +1,52 @@
+version: "{build}"
+
+os: Visual Studio 2015
+
+clone_folder: c:\projects\dcmtkpp
+
+environment:
+  BOOST_ROOT: C:/Libraries/boost_1_59_0
+  BOOST_LIBRARYDIR: C:/Libraries/boost_1_59_0/lib64-msvc-14.0
+  ICU_INCLUDE_DIR: C:/Libraries/icu/include
+  ICU_LIBRARY: C:/Libraries/icu/lib64/icuuc.lib
+  JsonCpp_INCLUDE_DIR: c:/Libraries/jsoncpp_0_10_5/include
+  JsonCpp_LIBRARY: c:/Libraries/jsoncpp_0_10_5/lib/jsoncpp.lib
+  DCMTK_INCLUDE_DIR: C:/Libraries/dcmtk-3.6.1_20150924/include
+  DCMTK_LIBRARY: C:/Libraries/dcmtk-3.6.1_20150924/lib/dcmdata.lib
+
+#init:
+#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
+
+install:
+  # ICU4C
+- ps: Start-FileDownload http://download.icu-project.org/files/icu4c/56.1/icu4c-56_1-Win64-msvc10.zip
+- 7z x -oC:\Libraries icu4c-56_1-Win64-msvc10.zip
+  # JsonCpp
+- ps: Start-FileDownload https://github.com/open-source-parsers/jsoncpp/archive/0.10.5.zip
+- 7z x -oC:\projects 0.10.5.zip
+- cd C:\projects\jsoncpp-0.10.5
+- mkdir build
+- cd build
+- cmake -D CMAKE_INSTALL_PREFIX=c:\Libraries\jsoncpp_0_10_5 ..
+- cmake --build . --config release --target install
+  # DCMTK
+- ps: Start-FileDownload http://dicom.offis.de/download/dcmtk/snapshot/dcmtk-3.6.1_20150924.tar.gz
+- 7z x -so dcmtk-3.6.1_20150924.tar.gz | 7z x -si -oC:\projects -ttar
+- cd C:\projects\dcmtk-3.6.1_20150924
+- mkdir build
+- cd build
+- cmake -D CMAKE_INSTALL_PREFIX=c:\Libraries\dcmtk-3.6.1_20150924 ..
+- cmake --build . --config release --target install
+
+before_build:
+  - cd c:\projects\dcmtkpp
+  - md build
+  - cd build
+  - set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH%
+  - cmake -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_LIBRARYDIR%" -DICU_INCLUDE_DIR="%ICU_INCLUDE_DIR%" -DICU_LIBRARY="%ICU_LIBRARY%" -DJsonCpp_INCLUDE_DIR="%JsonCpp_INCLUDE_DIR%" -DJsonCpp_LIBRARY="%JsonCpp_LIBRARY%" -DDCMTK_INCLUDE_DIR="%DCMTK_INCLUDE_DIR%" -DDCMTK_LIBRARY="%DCMTK_LIBRARY%" ..
+
+build:
+  project: C:\projects\dcmtkpp\build\dcmtkpp.sln
+
+#on_finish:
+#- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..f791cea
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,43 @@
+version: "{build}"
+
+os: Windows Server 2012 R2
+
+clone_folder: c:\projects\odil
+
+environment:
+  BOOST_ROOT: C:/Libraries/boost_1_59_0
+  BOOST_LIBRARYDIR: C:/Libraries/boost_1_59_0/lib64-msvc-14.0
+  ICU_INCLUDE_DIR: C:/Libraries/icu/include
+  ICU_LIBRARY: C:/Libraries/icu/lib64/icuuc.lib
+  JsonCpp_INCLUDE_DIR: c:/Libraries/jsoncpp_0_10_5/include
+  JsonCpp_LIBRARY: c:/Libraries/jsoncpp_0_10_5/lib/jsoncpp.lib
+  DCMTK_INCLUDE_DIR: C:/Libraries/dcmtk-3.6.1_20150924/include
+  DCMTK_LIBRARY: C:/Libraries/dcmtk-3.6.1_20150924/lib/dcmdata.lib
+
+configuration:
+  - Release
+
+install:
+  # ICU4C
+- ps: Start-FileDownload http://download.icu-project.org/files/icu4c/56.1/icu4c-56_1-Win64-msvc10.zip
+- 7z x -bd -oC:\Libraries icu4c-56_1-Win64-msvc10.zip
+  # JsonCpp
+- ps: Start-FileDownload https://github.com/lamyj/jsoncpp/releases/download/0.10.5/jsoncpp_0_10_5_Win64_msvc14.zip
+- 7z x -bd -oC:\Libraries jsoncpp_0_10_5_Win64_msvc14.zip
+  # DCMTK
+- ps: Start-FileDownload https://github.com/lamyj/dcmtk/releases/download/DCMTK-3.6.1_20150924/dcmtk-3.6.1_20150924_Win64_msvc14.zip
+- 7z x -bd -oC:\Libraries dcmtk-3.6.1_20150924_Win64_msvc14.zip
+
+before_build:
+  - cd c:\projects\odil
+  - md build
+  - cd build
+  - cmake
+    -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_LIBRARYDIR%"
+    -DICU_INCLUDE_DIR="%ICU_INCLUDE_DIR%" -DICU_LIBRARY="%ICU_LIBRARY%"
+    -DJsonCpp_INCLUDE_DIR="%JsonCpp_INCLUDE_DIR%" -DJsonCpp_LIBRARY="%JsonCpp_LIBRARY%"
+    -DDCMTK_INCLUDE_DIR="%DCMTK_INCLUDE_DIR%" -DDCMTK_LIBRARY="%DCMTK_LIBRARY%"
+    ..
+
+build:
+  project: C:\projects\odil\build\odil.sln
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index fa27216..0c5827e 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -1,11 +1,17 @@
-add_definitions("-D HAVE_CONFIG_H")
+find_package(DCMTK REQUIRED)
+find_package(JsonCpp REQUIRED)
 
-include_directories(${CMAKE_SOURCE_DIR}/src)
+include_directories(${CMAKE_SOURCE_DIR}/src ${JsonCpp_INCLUDE_DIRS})
+add_definitions(
+    ${DCMTK_DEFINITIONS}
+    -D BOOST_ASIO_DYN_LINK
+    -D ODIL_MAJOR_VERSION=${odil_MAJOR_VERSION}
+)
 
 file(GLOB_RECURSE examples *.cpp)
 
 foreach(example_file ${examples})
     get_filename_component(example ${example_file} NAME_WE)
     add_executable(${example} ${example_file})
-    target_link_libraries(${example} dcmtkpp dcmdata)
+    target_link_libraries(${example} odil)
 endforeach()
diff --git a/examples/dicomdir.cpp b/examples/dicomdir.cpp
index 426a488..5274ebd 100644
--- a/examples/dicomdir.cpp
+++ b/examples/dicomdir.cpp
@@ -1,20 +1,20 @@
 #include <string>
 #include <vector>
 
-#include <dcmtkpp/BasicDirectoryCreator.h>
-#include <dcmtkpp/registry.h>
+#include <odil/BasicDirectoryCreator.h>
+#include <odil/registry.h>
 
 int main(int argc, char** argv)
 {
     std::string const root(argv[1]);
     std::vector<std::string> const files(argv+2, argv+argc);
 
-    dcmtkpp::BasicDirectoryCreator creator(root, files,
+    odil::BasicDirectoryCreator creator(root, files,
         {
-            {"PATIENT", { {dcmtkpp::registry::PatientBirthDate, 3} }},
-            {"STUDY", { {dcmtkpp::registry::PatientAge, 3} }},
-            {"SERIES", { {dcmtkpp::registry::SeriesDescription, 3} }},
-            {"IMAGE", { {dcmtkpp::registry::ImageType, 3} }},
+            {"PATIENT", { {odil::registry::PatientBirthDate, 3} }},
+            {"STUDY", { {odil::registry::PatientAge, 3} }},
+            {"SERIES", { {odil::registry::SeriesDescription, 3} }},
+            {"IMAGE", { {odil::registry::ImageType, 3} }},
         });
 
     creator();
diff --git a/examples/dump.cpp b/examples/dump.cpp
index 86200ce..c7c987d 100644
--- a/examples/dump.cpp
+++ b/examples/dump.cpp
@@ -4,9 +4,9 @@
 #include <string>
 #include <utility>
 
-#include <dcmtkpp/DataSet.h>
-#include <dcmtkpp/Reader.h>
-#include <dcmtkpp/Value.h>
+#include <odil/DataSet.h>
+#include <odil/Reader.h>
+#include <odil/Value.h>
 
 struct Printer
 {
@@ -30,7 +30,7 @@ struct Printer
         }
     }
 
-    void operator()(dcmtkpp::Value::DataSets const & value) const
+    void operator()(odil::Value::DataSets const & value) const
     {
         this->stream << "\n";
 
@@ -46,17 +46,17 @@ struct Printer
         }
     }
 
-    void operator()(dcmtkpp::Value::Binary const & value) const
+    void operator()(odil::Value::Binary const & value) const
     {
         this->stream << this->indent << "(binary)";
     }
 
-    void operator()(dcmtkpp::DataSet const & data_set) const
+    void operator()(odil::DataSet const & data_set) const
     {
         for(auto const & item: data_set)
         {
             this->stream << this->indent << item.first << " " << as_string(item.second.vr) << " ";
-            dcmtkpp::apply_visitor(*this, item.second.get_value());
+            odil::apply_visitor(*this, item.second.get_value());
             this->stream << "\n";
         }
     }
@@ -68,10 +68,10 @@ int main(int argc, char** argv)
     {
         std::ifstream stream(argv[i], std::ios::in | std::ios::binary);
 
-        std::pair<dcmtkpp::DataSet, dcmtkpp::DataSet> file;
+        std::pair<odil::DataSet, odil::DataSet> file;
         try
         {
-            file = dcmtkpp::Reader::read_file(stream);
+            file = odil::Reader::read_file(stream);
         }
         catch(std::exception & e)
         {
diff --git a/examples/find.cpp b/examples/find.cpp
index 9acb22a..4c5b1e0 100644
--- a/examples/find.cpp
+++ b/examples/find.cpp
@@ -1,73 +1,72 @@
 #include <iostream>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/FindSCU.h"
-#include "dcmtkpp/registry.h"
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/FindSCU.h"
+#include "odil/registry.h"
 
-void print_informations(dcmtkpp::DataSet const & response)
+void print_informations(odil::DataSet const & response)
 {
-    std::cout << response.as_string("PatientName", 0) << ": "
-              << response.as_string("StudyDescription", 0)
-              << " on " << response.as_string("StudyDate", 0) << ", "
-              << response.as_int(dcmtkpp::registry::NumberOfStudyRelatedSeries, 0) << " series\n";
+    auto const name = response.has("PatientName")?
+        response.as_string("PatientName", 0):"(no name)";
+    auto const study = response.has("StudyDescription")?
+        response.as_string("StudyDescription", 0):"(no description)";
+    auto const date = response.has("StudyDate")?
+        response.as_string("StudyDate", 0):"(no date)";
+    std::cout
+        << "\"" << name << "\": \"" << study << "\" on " << date << "\n";
 }
 
 int main()
 {
-    dcmtkpp::Network network;
-    network.initialize();
-    
-    dcmtkpp::Association association;
-    
-    association.set_own_ae_title("myself");
-    
-    association.set_peer_host_name("pacs.example.com");
+    odil::Association association;
+    association.set_peer_host("184.73.255.26");
     association.set_peer_port(11112);
-    association.set_peer_ae_title("pacs");
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::StudyRootQueryRetrieveInformationModelFIND,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::VerificationSOPClass,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.associate(network);
+    association.update_parameters()
+        .set_calling_ae_title("myself")
+        .set_called_ae_title("AWSPIXELMEDPUB")
+        .set_presentation_contexts({
+            {
+                1, odil::registry::StudyRootQueryRetrieveInformationModelFIND,
+                { odil::registry::ExplicitVRLittleEndian }, true, false
+            },
+            {
+                3, odil::registry::VerificationSOPClass,
+                { odil::registry::ExplicitVRLittleEndian }, true, false
+            }
+        });
     
-    dcmtkpp::FindSCU scu;
-    scu.set_network(&network);
-    scu.set_association(&association);
+    association.associate();
     
+    odil::FindSCU scu(association);
+
     scu.echo();
-    
-    dcmtkpp::DataSet query;
-    query.add("PatientName", { "DOE^John" });
+
+    odil::DataSet query;
+    query.add("PatientName", { "*" });
     query.add("QueryRetrieveLevel", { "STUDY" });
     query.add("StudyDescription");
-    query.add(dcmtkpp::registry::NumberOfStudyRelatedSeries);
     query.add("StudyDate");
     
-    scu.set_affected_sop_class(dcmtkpp::registry::StudyRootQueryRetrieveInformationModelFIND);
+    scu.set_affected_sop_class(odil::registry::StudyRootQueryRetrieveInformationModelFIND);
     
     std::cout << "--------\n";
     std::cout << "Callback\n";
     std::cout << "--------\n\n";
     
     scu.find(query, print_informations);
-    
+
     std::cout << "\n";
         
     std::cout << "------\n";
     std::cout << "vector\n";
     std::cout << "------\n\n";
     
-    std::vector<dcmtkpp::DataSet> result = scu.find(query);
-    for(auto dataset: result)
+    auto const result = scu.find(query);
+    for(auto const & dataset: result)
     {
         print_informations(dataset);
     }
     
     association.release();
-    network.drop();
 }
diff --git a/examples/genericscp.cpp b/examples/genericscp.cpp
new file mode 100644
index 0000000..3bd0a3c
--- /dev/null
+++ b/examples/genericscp.cpp
@@ -0,0 +1,142 @@
+#include <boost/asio.hpp>
+
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/EchoSCP.h"
+#include "odil/FindSCP.h"
+#include "odil/StoreSCP.h"
+#include "odil/registry.h"
+#include "odil/SCPDispatcher.h"
+#include "odil/SCP.h"
+
+#include "odil/message/CEchoRequest.h"
+#include "odil/message/CFindRequest.h"
+#include "odil/message/CFindResponse.h"
+#include "odil/message/CStoreRequest.h"
+
+class FindGenerator: public odil::SCP::DataSetGenerator
+{
+public:
+    FindGenerator()
+    {
+        // Nothing to do
+    }
+
+    virtual ~FindGenerator()
+    {
+        // Nothing to do.
+    }
+
+    virtual void initialize(odil::message::Request const & )
+    {
+        odil::DataSet data_set_1;
+        data_set_1.add(odil::registry::PatientName, {"Hello^World"});
+        data_set_1.add(odil::registry::PatientID, {"1234"});
+        this->_responses.push_back(data_set_1);
+
+        odil::DataSet data_set_2;
+        data_set_2.add(odil::registry::PatientName, {"Doe^John"});
+        data_set_2.add(odil::registry::PatientID, {"5678"});
+        this->_responses.push_back(data_set_2);
+
+        this->_response_iterator = this->_responses.begin();
+    }
+
+    virtual bool done() const
+    {
+        return (this->_response_iterator == this->_responses.end());
+    }
+
+    virtual odil::DataSet get() const
+    {
+        return *this->_response_iterator;
+    }
+
+    virtual void next()
+    {
+        ++this->_response_iterator;
+    }
+
+
+private:
+    std::vector<odil::DataSet> _responses;
+    std::vector<odil::DataSet>::const_iterator _response_iterator;
+};
+
+odil::Value::Integer echo(odil::message::CEchoRequest const & request)
+{
+    std::cout << "Received echo\n";
+    std::cout << "  ID: " << request.get_message_id() << "\n";
+    std::cout << "  Affected SOP Class UID: " << request.get_affected_sop_class_uid() << "\n";
+    return odil::message::Response::Success;
+}
+
+odil::Value::Integer store(odil::message::CStoreRequest const & request)
+{
+    auto const patient_name =
+        request.get_data_set().as_string(odil::registry::PatientName)[0];
+    std::cout << "Storing " << patient_name << "\n";
+    return odil::message::Response::Success;
+}
+
+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()
+        << "\n";
+
+    auto const & contexts =
+        association.get_negotiated_parameters().get_presentation_contexts();
+    std::cout << "Presentation contexts (" << contexts.size() << ")\n";
+    for(auto const & context: contexts)
+    {
+        std::cout
+            << "    "
+            << 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;
+    }
+
+    auto echo_scp = std::make_shared<odil::EchoSCP>(association, echo);
+    auto find_scp = std::make_shared<odil::FindSCP>(
+        association, std::make_shared<FindGenerator>());
+    auto store_scp = std::make_shared<odil::StoreSCP>(association, store);
+
+    odil::SCPDispatcher dispatcher(association);
+    dispatcher.set_scp(odil::message::Message::Command::C_ECHO_RQ, echo_scp);
+    dispatcher.set_scp(odil::message::Message::Command::C_FIND_RQ, find_scp);
+    dispatcher.set_scp(
+        odil::message::Message::Command::C_STORE_RQ, store_scp);
+
+    bool done = false;
+    while(!done)
+    {
+        try
+        {
+            dispatcher.dispatch();
+        }
+        catch(odil::AssociationReleased const &)
+        {
+            std::cout << "Peer released association" << std::endl;
+            done = true;
+        }
+        catch(odil::AssociationAborted const & e)
+        {
+            std::cout
+                << "Peer aborted association, "
+                << "source: " << int(e.source) << ", "
+                << "reason: " << int(e.reason)
+                << std::endl;
+            done = true;
+        }
+    }
+}
diff --git a/examples/get.cpp b/examples/get.cpp
index 19ea720..a082989 100644
--- a/examples/get.cpp
+++ b/examples/get.cpp
@@ -1,69 +1,100 @@
 #include <iostream>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/GetSCU.h"
-#include "dcmtkpp/registry.h"
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/FindSCU.h"
+#include "odil/GetSCU.h"
+#include "odil/registry.h"
 
-void print_informations(dcmtkpp::DataSet const & response)
+void print_informations(odil::DataSet const & response)
 {
+    auto const name = response.has("PatientName")?
+        response.as_string("PatientName", 0):"(no name)";
+    auto const study = response.has("StudyDescription")?
+        response.as_string("StudyDescription", 0):"(no study description)";
+    auto const series = response.has("SeriesDescription")?
+        response.as_string("SeriesDescription", 0):"(no series description)";
+    auto const instance = response.has("InstanceNumber")?
+        response.as_int("InstanceNumber", 0):(-1);
     std::cout
-        << response.as_string("PatientName", 0)
-        << ": "
-        << response.as_string("StudyDescription", 0)
-        << " / "
-        << response.as_string("SeriesDescription", 0)
-        << ": "
-        << response.as_string("InstanceNumber", 0)
-        << "\n";
+        << name << ": " << study << " / " << series << ": " << instance << "\n";
 }
 
 int main()
 {
-    dcmtkpp::Network network;
-    network.initialize();
-    
-    dcmtkpp::Association association;
-    
-    association.set_own_ae_title("myself");
-    
-    association.set_peer_host_name("pacs.example.com");
+    odil::Association association;
+    association.set_peer_host("184.73.255.26");
     association.set_peer_port(11112);
-    association.set_peer_ae_title("pacs");
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::StudyRootQueryRetrieveInformationModelGET,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::MRImageStorage,
-        { dcmtkpp::registry::ImplicitVRLittleEndian },
-        ASC_SC_ROLE_SCP);
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::VerificationSOPClass,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.associate(network);
-    
-    dcmtkpp::GetSCU scu;
-    scu.set_network(&network);
-    scu.set_association(&association);
-    
-    scu.echo();
+    association.update_parameters()
+        .set_calling_ae_title("myself")
+        .set_called_ae_title("AWSPIXELMEDPUB")
+        .set_presentation_contexts({
+            {
+                1, odil::registry::StudyRootQueryRetrieveInformationModelFIND,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
+            },
+            {
+                3, odil::registry::StudyRootQueryRetrieveInformationModelGET,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
+            },
+            {
+                5, odil::registry::MRImageStorage,
+                { odil::registry::ImplicitVRLittleEndian }, false, true
+            },
+            {
+                7, odil::registry::VerificationSOPClass,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
+            }
+        });
+    
+    association.associate();
+
+    odil::FindSCU find_scu(association);
+    find_scu.set_affected_sop_class(
+        odil::registry::StudyRootQueryRetrieveInformationModelFIND);
+
+    odil::DataSet query;
+    query.add("QueryRetrieveLevel", { "STUDY" });
+    query.add("StudyInstanceUID");
+
+    auto const studies = find_scu.find(query);
+    odil::DataSet series;
+    for(auto const & study: studies)
+    {
+        if(!study.has("StudyInstanceUID"))
+        {
+            continue;
+        }
+
+        query = odil::DataSet();
+        query.add("QueryRetrieveLevel", {"SERIES"});
+        query.add("Modality", {"MR"});
+        query.add("StudyInstanceUID", study.as_string("StudyInstanceUID"));
+        query.add("SeriesInstanceUID");
+
+        auto const study_series = find_scu.find(query);
+
+        if(!study_series.empty())
+        {
+            series = study_series[0];
+            break;
+        }
+    }
+
+    odil::GetSCU get_scu(association);
+    get_scu.set_affected_sop_class(
+        odil::registry::StudyRootQueryRetrieveInformationModelGET);
     
-    dcmtkpp::DataSet query;
-    query.add("PatientID",{ "1234" });
+    query = odil::DataSet();
     query.add("QueryRetrieveLevel", { "SERIES" });
-    query.add("StudyInstanceUID", { "1.2.3.4.5" });
-    query.add("SeriesInstanceUID", { "1.2.3.4.5.1" });
-    
-    scu.set_affected_sop_class(dcmtkpp::registry::StudyRootQueryRetrieveInformationModelGET);
-    
+    query.add("StudyInstanceUID", series["StudyInstanceUID"]);
+    query.add("SeriesInstanceUID", series["SeriesInstanceUID"]);
+
     std::cout << "--------\n";
     std::cout << "Callback\n";
     std::cout << "--------\n\n";
     
-    scu.get(query, print_informations);
+    get_scu.get(query, print_informations);
     
     std::cout << "\n";
     
@@ -71,12 +102,11 @@ int main()
     std::cout << "vector\n";
     std::cout << "------\n\n";
     
-    std::vector<dcmtkpp::DataSet> result = scu.get(query);
-    for(auto dataset: result)
+    auto const result = get_scu.get(query);
+    for(auto const & dataset: result)
     {
         print_informations(dataset);
     }
     
     association.release();
-    network.drop();
 }
diff --git a/examples/move.cpp b/examples/move.cpp
index 589ce1d..b96e2c7 100644
--- a/examples/move.cpp
+++ b/examples/move.cpp
@@ -1,74 +1,103 @@
 #include <iostream>
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmdata/dctk.h>
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/FindSCU.h"
+#include "odil/MoveSCU.h"
+#include "odil/registry.h"
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/MoveSCU.h"
-#include "dcmtkpp/registry.h"
-
-void print_informations(dcmtkpp::DataSet const & response)
+void print_informations(odil::DataSet const & response)
 {
+    auto const name = response.has("PatientName")?
+        response.as_string("PatientName", 0):"(no name)";
+    auto const study = response.has("StudyDescription")?
+        response.as_string("StudyDescription", 0):"(no study description)";
+    auto const series = response.has("SeriesDescription")?
+        response.as_string("SeriesDescription", 0):"(no series description)";
+    auto const instance = response.has("InstanceNumber")?
+        response.as_int("InstanceNumber", 0):(-1);
     std::cout
-        << response.as_string("PatientName", 0)
-        << ": "
-        << response.as_string("StudyDescription", 0)
-        << " / "
-        << response.as_string("SeriesDescription", 0)
-        << ": "
-        << response.as_string("InstanceNumber", 0)
-        << "\n";
+        << name << ": " << study << " / " << series << ": " << instance << "\n";
 }
 
 int main()
 {
-    dcmtkpp::Network network;
-    network.set_role(NET_ACCEPTORREQUESTOR);
-    network.set_port(11112);
-    network.initialize();
-    
-    dcmtkpp::Association association;
-    association.set_own_ae_title("myself");
-    
-    association.set_peer_host_name("pacs.example.com");
+    odil::Association association;
+    association.set_peer_host("184.73.255.26");
     association.set_peer_port(11112);
-    association.set_peer_ae_title("pacs");
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::StudyRootQueryRetrieveInformationModelMOVE,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.add_presentation_context(
-         dcmtkpp::registry::MRImageStorage,
-        { dcmtkpp::registry::ImplicitVRLittleEndian },
-        ASC_SC_ROLE_SCP);
-    
-    association.add_presentation_context(
-         dcmtkpp::registry::VerificationSOPClass,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.associate(network);
-    
-    dcmtkpp::MoveSCU scu;
-    scu.set_network(&network);
-    scu.set_association(&association);
-    scu.set_move_destination(association.get_own_ae_title());
+    association.update_parameters()
+        .set_calling_ae_title("myself")
+        .set_called_ae_title("AWSPIXELMEDPUB")
+        .set_presentation_contexts({
+            {
+                1, odil::registry::StudyRootQueryRetrieveInformationModelFIND,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
+            },
+            {
+                3, odil::registry::StudyRootQueryRetrieveInformationModelMOVE,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
+            },
+            {
+                5, odil::registry::MRImageStorage,
+                { odil::registry::ImplicitVRLittleEndian }, false, true
+            },
+            {
+                7, odil::registry::VerificationSOPClass,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
+            }
+        });
+    
+    association.associate();
+
+    odil::FindSCU find_scu(association);
+    find_scu.set_affected_sop_class(
+        odil::registry::StudyRootQueryRetrieveInformationModelFIND);
+
+    odil::DataSet query;
+    query.add("QueryRetrieveLevel", { "STUDY" });
+    query.add("StudyInstanceUID");
+    query.add("NumberOfStudyRelatedSeries");
+
+    auto const studies = find_scu.find(query);
+    odil::DataSet series;
+    for(auto const & study: studies)
+    {
+        if(!study.has("StudyInstanceUID"))
+        {
+            continue;
+        }
+
+        query = odil::DataSet();
+        query.add("QueryRetrieveLevel", {"SERIES"});
+        query.add("Modality", {"MR"});
+        query.add("StudyInstanceUID", study.as_string("StudyInstanceUID"));
+        query.add("SeriesInstanceUID");
+
+        auto const study_series = find_scu.find(query);
+
+        if(!study_series.empty())
+        {
+            series = study_series[0];
+            break;
+        }
+    }
     
-    scu.echo();
+    odil::MoveSCU move_scu(association);
+    move_scu.set_affected_sop_class(
+        odil::registry::StudyRootQueryRetrieveInformationModelMOVE);
+    move_scu.set_move_destination(
+        association.get_parameters().get_calling_ae_title());
     
-    dcmtkpp::DataSet query;
-    query.add("PatientID",{ "1234" });
+    query = odil::DataSet();
     query.add("QueryRetrieveLevel", { "SERIES" });
-    query.add("StudyInstanceUID", { "1.2.3.4.5" });
-    query.add("SeriesInstanceUID", { "1.2.3.4.5.1" });
-    
-    scu.set_affected_sop_class(dcmtkpp::registry::StudyRootQueryRetrieveInformationModelMOVE);
+    query.add("StudyInstanceUID", series["StudyInstanceUID"]);
+    query.add("SeriesInstanceUID", series["SeriesInstanceUID"]);
     
     std::cout << "--------\n";
     std::cout << "Callback\n";
     std::cout << "--------\n\n";
     
-    scu.move(query, print_informations);
+    move_scu.move(query, print_informations);
     
     std::cout << "\n";
     
@@ -76,12 +105,11 @@ int main()
     std::cout << "vector\n";
     std::cout << "------\n\n";
     
-    std::vector<dcmtkpp::DataSet> result = scu.move(query);
+    std::vector<odil::DataSet> result = move_scu.move(query);
     for(auto dataset: result)
     {
         print_informations(dataset);
     }
     
     association.release();
-    network.drop();
 }
diff --git a/examples/store.cpp b/examples/store.cpp
index 515821c..4e3d170 100644
--- a/examples/store.cpp
+++ b/examples/store.cpp
@@ -1,63 +1,41 @@
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmdata/dctk.h>
-
-#include "dcmtkpp/conversion.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/StoreSCU.h"
-
-void progress_callback(void * data, unsigned long bytes_count)
-{
-    long file_size = *reinterpret_cast<long*>(data);
-    std::cout << bytes_count << "/" << file_size << std::endl;
-}
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/Reader.h"
+#include "odil/StoreSCU.h"
 
 int main(int argc, char** argv)
 {
-    dcmtkpp::Network network;
-    network.initialize();
-    
-    dcmtkpp::Association association;
-    
-    association.set_own_ae_title("myself");
-    
-    association.set_peer_host_name("pacs.example.com");
+    odil::Association association;
+    association.set_peer_host("184.73.255.26");
     association.set_peer_port(11112);
-    association.set_peer_ae_title("pacs");
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::MRImageStorage,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::EnhancedMRImageStorage,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.add_presentation_context(
-        dcmtkpp::registry::VerificationSOPClass,
-        { dcmtkpp::registry::ImplicitVRLittleEndian });
-    
-    association.associate(network);
-    
-    dcmtkpp::StoreSCU scu;
-    scu.set_network(&network);
-    scu.set_association(&association);
+    association.update_parameters()
+        .set_calling_ae_title("myself")
+        .set_called_ae_title("AWSPIXELMEDPUB")
+        .set_presentation_contexts({
+            {
+                1, odil::registry::MRImageStorage,
+                { odil::registry::ImplicitVRLittleEndian }, false, true
+            },
+            {
+                3, odil::registry::VerificationSOPClass,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
+            }
+        });
+    
+    association.associate();
+    
+    odil::StoreSCU scu(association);
     
     scu.echo();
     
     for(int i=1; i<argc; ++i)
     {
-        std::cout << "Storing " << argv[i] << std::endl;
-        
-        long file_size = OFStandard::getFileSize(argv[i]);
-        DcmFileFormat file;
-        file.loadFile(argv[i]);
-
-        dcmtkpp::DataSet const data_set = dcmtkpp::convert(file.getDataset());
+        std::ifstream stream(argv[i], std::ios::binary);
+        auto const header_and_data_set = odil::Reader::read_file(stream);
         
-        scu.set_affected_sop_class(data_set);
-        scu.store(data_set, progress_callback, &file_size);
+        scu.set_affected_sop_class(header_and_data_set.first);
+        scu.store(header_and_data_set.first);
     }
     
     association.release();
-    network.drop();
 }
diff --git a/generate_registry b/generate_registry
index 3ed077f..53e836f 100755
--- a/generate_registry
+++ b/generate_registry
@@ -23,8 +23,8 @@ def main():
         trim_blocks=True)
     
     templates = {
-        "src/dcmtkpp/registry.h": jinja_environment.get_template("registry.h.tmpl"),
-        "src/dcmtkpp/registry.cpp": jinja_environment.get_template("registry.cpp.tmpl"),
+        "src/odil/registry.h": jinja_environment.get_template("registry.h.tmpl"),
+        "src/odil/registry.cpp": jinja_environment.get_template("registry.cpp.tmpl"),
     }
 
     sources = [
diff --git a/registry.cpp.tmpl b/registry.cpp.tmpl
index 0169112..5a78062 100644
--- a/registry.cpp.tmpl
+++ b/registry.cpp.tmpl
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,14 +8,14 @@
 
 #include <stdint.h>
 
-#include "dcmtkpp/registry.h"
+#include "odil/registry.h"
 
-#include "dcmtkpp/ElementsDictionary.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/VR.h"
-#include "dcmtkpp/UIDsDictionary.h"
+#include "odil/ElementsDictionary.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
+#include "odil/UIDsDictionary.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 namespace registry
@@ -91,5 +91,5 @@ UIDsDictionary create_uids_dictionary()
 
 }
 
-dcmtkpp::ElementsDictionary dcmtkpp::registry::public_dictionary=dcmtkpp::registry::create_public_dictionary();
-dcmtkpp::UIDsDictionary dcmtkpp::registry::uids_dictionary=dcmtkpp::registry::create_uids_dictionary();
+odil::ElementsDictionary odil::registry::public_dictionary=odil::registry::create_public_dictionary();
+odil::UIDsDictionary odil::registry::uids_dictionary=odil::registry::create_uids_dictionary();
diff --git a/registry.h.tmpl b/registry.h.tmpl
index 9a5a216..670e0d1 100644
--- a/registry.h.tmpl
+++ b/registry.h.tmpl
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,11 @@
 #ifndef _afc7b2d7_0869_4fea_9a9b_7fe6228baca9
 #define _afc7b2d7_0869_4fea_9a9b_7fe6228baca9
 
-#include "dcmtkpp/ElementsDictionary.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/UIDsDictionary.h"
+#include "odil/ElementsDictionary.h"
+#include "odil/Tag.h"
+#include "odil/UIDsDictionary.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 namespace registry
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e3ae50c..417282b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,28 +1,35 @@
 find_package(Boost REQUIRED COMPONENTS filesystem system)
+find_package(DCMTK REQUIRED)
 find_package(ICU REQUIRED)
-
-set(DCMTK_LIBRARIES dcmnet dcmdata oflog ofstd pthread z)
-find_library(USE_WRAP wrap)
-if(USE_WRAP)
-    set(DCMTK_LIBRARIES ${DCMTK_LIBRARIES} wrap)
-endif()
+find_package(JsonCpp REQUIRED)
 
 file(GLOB_RECURSE headers "*.h")
 file(GLOB_RECURSE templates "*.txx")
 file(GLOB_RECURSE files "*.cpp")
+
 include_directories(
-    ${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIRS} ${ICU_INCLUDE_DIR})
-add_definitions("-D HAVE_CONFIG_H")
-add_definitions("-D DCMTKPP_MAJOR_VERSION=${dcmtkpp_MAJOR_VERSION}")
+    ${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIRS} ${DCMTK_INCLUDE_DIRS}
+    ${ICU_INCLUDE_DIRS} ${JsonCpp_INCLUDE_DIRS})
+add_definitions(
+    ${DCMTK_DEFINITIONS}
+    -D BOOST_ASIO_SEPARATE_COMPILATION
+    -D ODIL_MAJOR_VERSION=${odil_MAJOR_VERSION}
+)
+link_directories(${Boost_LIBRARY_DIRS} ${DCMTK_LIBRARY_DIRS})
+
+add_library(odil SHARED ${files} ${headers} ${templates})
+
+target_link_libraries(odil
+    ${Boost_LIBRARIES} ${DCMTK_LIBRARIES} ${ICU_LIBRARIES} ${JsonCpp_LIBRARIES})
+set_target_properties(odil PROPERTIES
+    VERSION ${odil_VERSION}
+    SOVERSION ${odil_MAJOR_VERSION})
 
-link_directories(${Boost_LIBRARY_DIRS})
-add_library(dcmtkpp SHARED ${files})
-install(TARGETS dcmtkpp LIBRARY DESTINATION lib)
-install(FILES ${headers} ${templates} DESTINATION include/dcmtkpp)
+install(FILES ${headers} ${templates} DESTINATION include/odil)
+install(
+    TARGETS odil
+    ARCHIVE DESTINATION lib
+    LIBRARY DESTINATION lib
+    RUNTIME DESTINATION bin)
 
-target_link_libraries(dcmtkpp
-    ${Boost_LIBRARIES} ${DCMTK_LIBRARIES} jsoncpp ${ICU_LIBRARIES} uuid)
-set_target_properties(dcmtkpp PROPERTIES 
-    VERSION ${dcmtkpp_VERSION}
-    SOVERSION ${dcmtkpp_MAJOR_VERSION})
 
diff --git a/src/dcmtkpp/Association.cpp b/src/dcmtkpp/Association.cpp
deleted file mode 100644
index d60dec2..0000000
--- a/src/dcmtkpp/Association.cpp
+++ /dev/null
@@ -1,535 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "Association.h"
-
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/assoc.h>
-#include <dcmtk/dcmnet/cond.h>
-
-#include "dcmtkpp/Exception.h"
-
-namespace dcmtkpp
-{
-
-Association
-::Association()
-: _own_ae_title(""),
-  _peer_host_name(""), _peer_port(104), _peer_ae_title(""),
-  _user_identity_type(UserIdentityType::None), 
-  _user_identity_primary_field(""), _user_identity_secondary_field(""),
-  _association(NULL)
-{
-    // Nothing else
-}
-
-Association
-::Association(Association const & other)
-: _own_ae_title(other.get_own_ae_title()), 
-  _peer_host_name(other.get_peer_host_name()), _peer_port(other.get_peer_port()),
-  _peer_ae_title(other.get_peer_ae_title()),
-  _user_identity_type(other.get_user_identity_type()), 
-  _user_identity_primary_field(other.get_user_identity_primary_field()), 
-  _user_identity_secondary_field(other.get_user_identity_secondary_field()),
-  _association(NULL)
-{
-}
-
-Association
-::~Association()
-{
-    if(this->is_associated())
-    {
-        this->release();
-    }
-}
-
-Association &
-Association
-::operator=(Association const & other)
-{
-    if(this != &other)
-    {
-        this->set_own_ae_title(other.get_own_ae_title());
-        this->set_peer_host_name(other.get_peer_host_name());
-        this->set_peer_port(other.get_peer_port());
-        this->set_peer_ae_title(other.get_peer_ae_title());
-        this->set_user_identity_type(other.get_user_identity_type());
-        this->set_user_identity_primary_field(other.get_user_identity_primary_field());
-        this->set_user_identity_secondary_field(other.get_user_identity_secondary_field());
-    }
-    
-    return *this;
-}
-
-std::string const &
-Association
-::get_own_ae_title() const
-{
-    return this->_own_ae_title;
-}
-
-void
-Association
-::set_own_ae_title(std::string const & ae_title)
-{
-    if(this->is_associated())
-    {
-        throw Exception("Cannot set member while associated");
-    }
-    
-    this->_own_ae_title = ae_title;
-}
-
-std::string const &
-Association
-::get_peer_host_name() const
-{
-    return this->_peer_host_name;
-}
-
-void
-Association
-::set_peer_host_name(std::string const & host_name)
-{
-    if(this->is_associated())
-    {
-        throw Exception("Cannot set member while associated");
-    }
-    
-    this->_peer_host_name = host_name;
-}
-
-uint16_t
-Association
-::get_peer_port() const
-{
-    return this->_peer_port;
-}
-
-void
-Association
-::set_peer_port(uint16_t port)
-{
-    if(this->is_associated())
-    {
-        throw Exception("Cannot set member while associated");
-    }
-    
-    this->_peer_port = port;
-}
-
-std::string const &
-Association
-::get_peer_ae_title() const
-{
-    return this->_peer_ae_title;
-}
-
-void
-Association
-::set_peer_ae_title(std::string const & ae_title)
-{
-    if(this->is_associated())
-    {
-        throw Exception("Cannot set member while associated");
-    }
-    
-    this->_peer_ae_title = ae_title;
-}
-
-void
-Association
-::add_presentation_context(std::string const & abstract_syntax,
-    std::vector<std::string> const & transfer_syntaxes, T_ASC_SC_ROLE role)
-{
-    if(this->is_associated())
-    {
-        throw Exception("Cannot set member while associated");
-    }
-    
-    this->_presentation_contexts.push_back(
-        {abstract_syntax, transfer_syntaxes, role});
-}
-
-UserIdentityType
-Association
-::get_user_identity_type() const
-{
-    return this->_user_identity_type;
-}
-
-void
-Association
-::set_user_identity_type(UserIdentityType type)
-{
-    if(this->is_associated())
-    {
-        throw Exception("Cannot set member while associated");
-    }
-    
-    this->_user_identity_type = type;
-}
-
-std::string const &
-Association
-::get_user_identity_primary_field() const
-{
-    return this->_user_identity_primary_field;
-}
-
-void
-Association
-::set_user_identity_primary_field(std::string const & value)
-{
-    if(this->is_associated())
-    {
-        throw Exception("Cannot set member while associated");
-    }
-    
-    this->_user_identity_primary_field = value;
-}
-
-std::string const &
-Association
-::get_user_identity_secondary_field() const
-{
-    return this->_user_identity_secondary_field;
-}
-
-void
-Association
-::set_user_identity_secondary_field(std::string const & value)
-{
-    if(this->is_associated())
-    {
-        throw Exception("Cannot set member while associated");
-    }
-    
-    this->_user_identity_secondary_field = value;
-}
-
-void
-Association
-::set_user_identity_to_none()
-{
-    this->set_user_identity_type(UserIdentityType::None);
-    this->set_user_identity_primary_field("");
-    this->set_user_identity_secondary_field("");
-}
-
-void
-Association
-::set_user_identity_to_username(std::string const & username)
-{
-    this->set_user_identity_type(UserIdentityType::Username);
-    this->set_user_identity_primary_field(username);
-    this->set_user_identity_secondary_field("");
-}
-
-void
-Association
-::set_user_identity_to_username_and_password(
-    std::string const & username, std::string const & password)
-{
-    this->set_user_identity_type(UserIdentityType::UsernameAndPassword);
-    this->set_user_identity_primary_field(username);
-    this->set_user_identity_secondary_field(password);
-}
-    
-void
-Association
-::set_user_identity_to_kerberos(std::string const & ticket)
-{
-    this->set_user_identity_type(UserIdentityType::Kerberos);
-    this->set_user_identity_primary_field(ticket);
-    this->set_user_identity_secondary_field("");
-}
-
-void
-Association
-::set_user_identity_to_saml(std::string const & assertion)
-{
-    this->set_user_identity_type(UserIdentityType::SAML);
-    this->set_user_identity_primary_field(assertion);
-    this->set_user_identity_secondary_field("");
-}
-
-bool
-Association
-::is_associated() const
-{
-    return (this->_association != NULL);
-}
-
-void
-Association
-::associate(Network & network)
-{
-    if(!network.is_initialized())
-    {
-        throw Exception("Network is not initialized");
-    }
-    
-    if(this->is_associated())
-    {
-        throw Exception("Already associated");
-    }
-    
-    OFCondition condition;
-    
-    T_ASC_Parameters * params;
-    condition = ASC_createAssociationParameters(&params, ASC_MAXIMUMPDUSIZE);
-    if(condition.bad())
-    {
-        throw Exception(condition);
-    }
-    
-    condition = ASC_setAPTitles(params, 
-        this->_own_ae_title.c_str(), this->_peer_ae_title.c_str(), NULL);
-    if(condition.bad())
-    {
-        ASC_destroyAssociationParameters(&params);
-        throw Exception(condition);
-    }
-    
-    std::string localhost(128, '\0');
-    gethostname(&localhost[0], localhost.size()-1);
-    
-    std::ostringstream peer;
-    peer << this->_peer_host_name << ":" << this->_peer_port;
-    
-    condition = ASC_setPresentationAddresses(params,
-        "localhost", peer.str().c_str());
-    if(condition.bad())
-    {
-        ASC_destroyAssociationParameters(&params);
-        throw Exception(condition);
-    }
-    
-    unsigned int context_id = 1;
-    for(auto const & context: this->_presentation_contexts)
-    {
-        char const ** transfer_syntaxes = new char const *[context.transfer_syntaxes.size()];
-        for(std::size_t i = 0; i < context.transfer_syntaxes.size(); ++i)
-        {
-            transfer_syntaxes[i] = context.transfer_syntaxes[i].c_str();
-        }
-        
-        condition = ASC_addPresentationContext(params, 
-            context_id, context.abstract_syntax.c_str(),
-            transfer_syntaxes, context.transfer_syntaxes.size(), context.role);
-        if(condition.bad())
-        {
-            ASC_destroyAssociationParameters(&params);
-            throw Exception(condition);
-        }
-        
-        context_id += 2;
-    }
-    
-    if(this->_user_identity_type == UserIdentityType::None)
-    {
-        // Nothing to do.
-    }
-    else if(this->_user_identity_type == UserIdentityType::Username)
-    {
-        condition = ASC_setIdentRQUserOnly(params, 
-            this->_user_identity_primary_field.c_str());
-    }
-    else if(this->_user_identity_type == UserIdentityType::UsernameAndPassword)
-    {
-        condition = ASC_setIdentRQUserOnly(params, 
-            this->_user_identity_primary_field.c_str(),
-            this->_user_identity_secondary_field.c_str());
-    }
-    else if(this->_user_identity_type == UserIdentityType::Kerberos)
-    {
-        condition = ASC_setIdentRQKerberos(params, 
-            this->_user_identity_primary_field.c_str(),
-            this->_user_identity_primary_field.size());
-    }
-    else if(this->_user_identity_type == UserIdentityType::SAML)
-    {
-        condition = ASC_setIdentRQSaml(params, 
-            this->_user_identity_primary_field.c_str(),
-            this->_user_identity_primary_field.size());
-    }
-    else
-    {
-        ASC_destroyAssociationParameters(&params);
-        throw Exception("Unknown identity type");
-    }
-    
-    if(condition.bad())
-    {
-        ASC_destroyAssociationParameters(&params);
-        throw Exception(condition);
-    }
-    
-    condition = ASC_requestAssociation(
-        network.get_network(), params, &this->_association);
-    if(condition.bad())
-    {
-        OFString empty;
-        
-        if(condition == DUL_ASSOCIATIONREJECTED)
-        {
-            T_ASC_RejectParameters rej;
-            ASC_getRejectParameters(params, &rej);
-            
-            ASC_destroyAssociationParameters(&params);
-            throw Exception(ASC_printRejectParameters(empty, &rej).c_str());
-        } 
-        else 
-        {
-            ASC_destroyAssociationParameters(&params);
-            throw Exception(DimseCondition::dump(empty, condition).c_str());
-        }
-    }
-}
-
-void 
-Association
-::receive(Network & network, bool accept_all)
-{
-    if(!network.is_initialized())
-    {
-        throw Exception("Network is not initialized");
-    }
-    
-    if(this->is_associated())
-    {
-        throw Exception("Already associated");
-    }
-    
-    OFCondition condition;
-    
-    condition = ASC_receiveAssociation(
-        network.get_network(), &this->_association, ASC_DEFAULTMAXPDU);
-    if(condition.bad())
-    {
-        throw Exception(condition);
-    }
-    
-    T_ASC_Parameters * const params = this->_association->params;
-    DUL_ASSOCIATESERVICEPARAMETERS const dul = params->DULparams;
-    // No peer port should be defined when receiving
-    this->_peer_host_name = dul.callingPresentationAddress;
-    this->_peer_port = 0;
-    this->_peer_ae_title = dul.callingAPTitle;
-    
-    if(accept_all)
-    {
-        unsigned int const pc_count = ASC_countPresentationContexts(params);
-        for(unsigned int pc_index=0; pc_index<pc_count; ++pc_index)
-        {
-            T_ASC_PresentationContext pc;
-            memset(&pc, 0, sizeof(pc));
-            ASC_getPresentationContext(params, pc_index, &pc);
-            
-            for(unsigned int ts_index=0; ts_index<pc.transferSyntaxCount; ++ts_index)
-            {
-                std::string const abstract_syntax = pc.abstractSyntax;
-                char const * abstract_syntax_data = abstract_syntax.c_str();
-                
-                condition = ASC_acceptContextsWithTransferSyntax(
-                    this->_association->params, 
-                    pc.proposedTransferSyntaxes[ts_index],
-                    1, &abstract_syntax_data);
-                if(condition.bad())
-                {
-                    this->abort();
-                    throw Exception(condition);
-                }
-            }
-        }
-    }
-    else
-    {
-        for(auto const & context: this->_presentation_contexts)
-        {
-            for(std::size_t i = 0; i < context.transfer_syntaxes.size(); ++i)
-            {
-                char const * abstract_syntax = context.abstract_syntax.c_str();
-                char const * transfer_syntax = context.transfer_syntaxes[i].c_str();
-                condition = ASC_acceptContextsWithTransferSyntax(
-                    this->_association->params, transfer_syntax,
-                    1, &abstract_syntax);
-                if(condition.bad())
-                {
-                    this->abort();
-                    throw Exception(condition);
-                }
-            }
-        }
-    }
-    
-    condition = ASC_acknowledgeAssociation(this->_association);
-    if(condition.bad())
-    {
-        throw Exception(condition);
-    }
-}
-
-T_ASC_Association *
-Association
-::get_association()
-{
-    return this->_association;
-}
-
-void
-Association
-::release()
-{
-    if(!this->is_associated())
-    {
-        throw Exception("Not associated");
-    }
-    
-    ASC_releaseAssociation(this->_association);
-    ASC_destroyAssociation(&this->_association);
-    this->_association = NULL;
-}
-
-void
-Association
-::abort()
-{
-    if(!this->is_associated())
-    {
-        throw Exception("Not associated");
-    }
-    
-    ASC_abortAssociation(this->_association);
-    ASC_destroyAssociation(&this->_association);
-    this->_association = NULL;
-}
-
-void
-Association
-::drop()
-{
-    if(!this->is_associated())
-    {
-        throw Exception("Not associated");
-    }
-    
-    ASC_acknowledgeRelease(this->_association);
-    ASC_dropSCPAssociation(this->_association);
-    ASC_destroyAssociation(&this->_association);
-    this->_association = NULL;
-}
-
-}
diff --git a/src/dcmtkpp/Association.h b/src/dcmtkpp/Association.h
deleted file mode 100644
index 8b2fcb8..0000000
--- a/src/dcmtkpp/Association.h
+++ /dev/null
@@ -1,184 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#ifndef _a52696bc_5c6e_402d_a343_6cb085eb0138
-#define _a52696bc_5c6e_402d_a343_6cb085eb0138
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/assoc.h>
-
-#include "dcmtkpp/Network.h"
-
-namespace dcmtkpp
-{
-
-/// @brief User identity types as defined by PS3.8 2013 D.3.3.7
-enum class UserIdentityType
-{
-    None = 0,
-    Username = 1,
-    UsernameAndPassword = 2,
-    Kerberos = 3,
-    SAML = 4
-};
-
-/**
- * @brief Wrapper around the T_ASC_Association class.
- * 
- * No member can be set while the object is associated.
- */
-class Association
-{
-public:
-    /// @brief Create a default, un-associated, association.
-    Association();
-    
-    /// @brief Create an un-associated association.
-    Association(Association const & other);
-    
-    /// @brief Destroy the association, release it if necessary.
-    ~Association();
-    
-    /// @brief Assing an un-associated association; it remains un-associated.
-    Association & operator=(Association const & other);
-    
-    /// @brief Return the AE title of the caller. Defaults to "".
-    std::string const & get_own_ae_title() const;
-    /// @brief Set the AE title of the caller.
-    void set_own_ae_title(std::string const & ae_title);
-    
-    /// @name Peer
-    /// @{
-    
-    /// @brief Return the host name of the peer. Defaults to "".
-    std::string const & get_peer_host_name() const;
-    /// @brief Set the host name of the peer.
-    void set_peer_host_name(std::string const & host_name);
-    
-    /// @brief Return the port of the peer. Defaults to 104.
-    uint16_t get_peer_port() const;
-    /// @brief Set the port of the peer.
-    void set_peer_port(uint16_t port);
-    
-    /// @brief Return the AE title of the peer. Defaults to "".
-    std::string const & get_peer_ae_title() const;
-    /// @brief Set the AE title of the peer.
-    void set_peer_ae_title(std::string const & ae_title);
-    
-    /// @}
-    
-    /// @name Presentation contexts
-    /// @{
-    
-    void add_presentation_context(std::string const & abstract_syntax,
-        std::vector<std::string> const & transfer_syntaxes,
-        T_ASC_SC_ROLE role=ASC_SC_ROLE_DEFAULT);
-    
-    /// @}
-    
-    /// @name User identity
-    /// @{
-        
-    /// @brief Return the user identity type. Defaults to None.
-    UserIdentityType get_user_identity_type() const;
-    /// @brief Set the user identity type.
-    void set_user_identity_type(UserIdentityType type);
-    
-    /// @brief Return the user identity primary field. Defaults to "".
-    std::string const & get_user_identity_primary_field() const;
-    /// @brief Set the user identity primary field. 
-    void set_user_identity_primary_field(std::string const & value);
-    
-    /// @brief Return the user identity secondary field. Defaults to "".
-    std::string const & get_user_identity_secondary_field() const;
-    /// @brief Set the user identity secondary field.
-    void set_user_identity_secondary_field(std::string const & value);
-    
-    /// @brief Do no authenticate user.
-    void set_user_identity_to_none();
-    
-    /// @brief Authenticate user using only a username.
-    void set_user_identity_to_username(std::string const & username);
-    
-    /// @brief Authenticate user using a username and a password.
-    void set_user_identity_to_username_and_password(
-        std::string const & username, std::string const & password);
-        
-    /// @brief Authenticate user using a Kerberos ticket.
-    void set_user_identity_to_kerberos(std::string const & ticket);
-    
-    /// @brief Authenticate user using a SAML assertion.
-    void set_user_identity_to_saml(std::string const & assertion);
-    
-    /// @}
-    
-    /// @name Association
-    /// @{
-    
-    /// @brief Test whether the object is currently associated to its peer.
-    bool is_associated() const;
-    
-    /**
-     * @brief Request an association with the peer. 
-     * @param network network to use for the data transmission.
-     * 
-     * Throws an exception if already associated.
-     */
-    void associate(Network & network);
-    
-    /** 
-     * @brief Receive an association for a peer.
-     * @param network network to use for the data transmission.
-     * @param accept_all if true, accept all presentations contexts proposed
-     *  by peer.
-     */
-    void receive(Network & network, bool accept_all=false);
-    
-    /// @brief Return the association object.
-    T_ASC_Association * get_association();
-    
-    /// @brief Gracefully release the association. Throws an exception if not associated.
-    void release();
-    /// @brief Forcefully release the association. Throws an exception if not associated.
-    void abort();
-    
-    /// @brief Drop an association that has been released by the peer.
-    void drop();
-    
-    /// @}
-
-private:
-    std::string _own_ae_title;
-    
-    std::string _peer_host_name;
-    uint16_t _peer_port;
-    std::string _peer_ae_title;
-    
-    struct PresentationContext
-    {
-        std::string abstract_syntax;
-        std::vector<std::string> transfer_syntaxes;
-        T_ASC_SC_ROLE role;
-    };
-
-    std::vector<PresentationContext> _presentation_contexts;
-    
-    UserIdentityType _user_identity_type;
-    std::string _user_identity_primary_field;
-    std::string _user_identity_secondary_field;
-    
-    T_ASC_Association * _association;
-};
-
-}
-
-#endif // _a52696bc_5c6e_402d_a343_6cb085eb0138
diff --git a/src/dcmtkpp/GetSCU.cpp b/src/dcmtkpp/GetSCU.cpp
deleted file mode 100644
index 52fa1fb..0000000
--- a/src/dcmtkpp/GetSCU.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "dcmtkpp/GetSCU.h"
-
-#include <functional>
-#include <sstream>
-#include <vector>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CGetRequest.h"
-#include "dcmtkpp/CGetResponse.h"
-#include "dcmtkpp/CStoreRequest.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-
-namespace dcmtkpp
-{
-
-GetSCU
-::~GetSCU()
-{
-    // Nothing to do
-}
-
-void 
-GetSCU
-::get(DataSet const & query, Callback callback) const
-{
-    // Send the request
-    CGetRequest request(this->_association->get_association()->nextMsgID++,
-        this->_affected_sop_class, Message::Priority::MEDIUM, query);
-    this->_send(request, this->_affected_sop_class);
-    
-    // Receive the responses
-    bool done = false;
-    while(!done)
-    {
-        Message const message = this->_receive();
-
-        if(message.get_command_field() == Message::Command::C_GET_RSP)
-        {
-            done = this->_get_response(CGetResponse(message));
-        }
-        else if(message.get_command_field() == Message::Command::C_STORE_RQ)
-        {
-            try
-            {
-                this->_store_request(CStoreRequest(message), callback);
-            }
-            catch(...)
-            {
-                // FIXME: logging
-                done = true;
-            }
-        }
-        else
-        {
-            std::ostringstream exception_message;
-            exception_message << "DIMSE: Unexpected Response Command Field: 0x"
-                              << std::hex << message.get_command_field();
-            throw Exception(exception_message.str());
-        }
-    }
-}
-
-std::vector<DataSet>
-GetSCU
-::get(DataSet const & query) const
-{
-    std::vector<DataSet> result;
-    auto callback = [&result](DataSet const & dataset) {
-        result.push_back(dataset);
-    };
-    this->get(query, callback);
-    
-    return result;
-}
-
-bool
-GetSCU
-::_get_response(CGetResponse const & response) const
-{
-    bool const done = (response.get_status() != STATUS_Pending);
-    return done;
-}
-
-void
-GetSCU
-::_store_request(CStoreRequest const & request, Callback callback) const
-{
-    StoreSCP scp;
-    scp.set_network(this->get_network());
-    scp.set_association(this->get_association());
-    scp.store(request, callback);
-}
-
-}
diff --git a/src/dcmtkpp/MoveSCU.cpp b/src/dcmtkpp/MoveSCU.cpp
deleted file mode 100644
index 9181523..0000000
--- a/src/dcmtkpp/MoveSCU.cpp
+++ /dev/null
@@ -1,162 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "dcmtkpp/MoveSCU.h"
-
-#include <vector>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CMoveRequest.h"
-#include "dcmtkpp/CMoveResponse.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/StoreSCP.h"
-
-namespace dcmtkpp
-{
-
-MoveSCU
-::MoveSCU()
-: SCU(), _move_destination("")
-{
-    // Nothing else.
-}
-
-MoveSCU
-::~MoveSCU()
-{
-    // Nothing to do
-}
-
-std::string const & 
-MoveSCU
-::get_move_destination() const
-{
-    return this->_move_destination;
-}
-
-void
-MoveSCU
-::set_move_destination(std::string const & move_destination)
-{
-    this->_move_destination = move_destination;
-}
-
-void
-MoveSCU
-::move(DataSet const & query, Callback callback) const
-{
-    // Send the request
-    CMoveRequest const request(this->_association->get_association()->nextMsgID++,
-        this->_affected_sop_class, Message::Priority::MEDIUM,
-        this->_move_destination, query);
-    this->_send(request, this->_affected_sop_class);
-    
-    // Receive the responses
-    Association store_association;
-    bool done = false;
-    while(!done)
-    {
-        // Use a small timeout to avoid blocking for a long time.
-        if(!store_association.is_associated() && 
-           this->_network->is_association_waiting(1))
-        {
-            store_association.receive(*this->_network, true);
-        }
-        
-        done = this->_dispatch(store_association, callback);
-    }
-}
-
-std::vector<DataSet>
-MoveSCU
-::move(DataSet const & query) const
-{
-    std::vector<DataSet> result;
-    auto callback = [&result](DataSet const & dataset) {
-        result.push_back(dataset);
-    };
-    this->move(query, callback);
-    
-    return result;
-}
-
-bool 
-MoveSCU
-::_dispatch(Association & association, Callback callback) const
-{
-    T_ASC_Association *associations[2];
-    int size = 0;
-
-    associations[0] = this->_association->get_association();
-    size = 1;
-    associations[1] = association.get_association();
-    if(association.is_associated())
-    {
-        ++size;
-    }
-    
-    // At this point, we should have a readable association.
-    if(!ASC_selectReadableAssociation(associations, size, 1)) 
-    {
-        throw Exception("No readable association");
-    }
-    
-    bool move_finished;
-    
-    if(associations[0] != NULL) 
-    {
-        move_finished = this->_handle_main_association();
-    }
-    else if(associations[1] != NULL) 
-    {
-        bool const release = 
-            this->_handle_store_association(association, callback);
-        if(release)
-        {
-            association.drop();
-        }
-    }
-    
-    return move_finished;
-}
-
-bool
-MoveSCU
-::_handle_main_association() const
-{
-    Message const message = this->_receive();
-    
-    if(message.get_command_field() != Message::Command::C_MOVE_RSP)
-    {
-        std::ostringstream exception_message;
-        exception_message << "DIMSE: Unexpected Response Command Field: 0x"
-                << std::hex << message.get_command_field();
-        throw Exception(exception_message.str());
-    }
-    
-    CMoveResponse const response(message);
-    bool const done = (response.get_status() != STATUS_Pending);
-    
-    return done;
-}
-
-bool
-MoveSCU
-::_handle_store_association(Association & association, Callback callback) const
-{
-    StoreSCP scp;
-    scp.set_network(this->_network);
-    scp.set_association(&association);
-    return scp.store(callback);
-}
-
-}
diff --git a/src/dcmtkpp/Network.cpp b/src/dcmtkpp/Network.cpp
deleted file mode 100644
index 218b4c3..0000000
--- a/src/dcmtkpp/Network.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "dcmtkpp/Network.h"
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/assoc.h>
-#include <dcmtk/dcmnet/cond.h>
-
-#include "dcmtkpp/Exception.h"
-
-namespace dcmtkpp
-{
-
-Network
-::Network()
-: _role(NET_REQUESTOR), _port(0), _timeout(30), _options(0), _network(NULL)
-{
-    // Nothing else.
-}
-
-Network
-::Network(T_ASC_NetworkRole role, int port, int timeout, unsigned long options)
-: _role(role), _port(port), _timeout(timeout), _options(options), _network(NULL)
-{
-    // Nothing else.
-}
-
-Network
-::Network(Network const & other)
-: _role(other.get_role()), _port(other.get_port()), 
-  _timeout(other.get_timeout()), _options(other.get_options()), _network(NULL)
-{
-    // Nothing else
-}
-
-Network
-::~Network()
-{
-    if(this->is_initialized())
-    {
-        this->drop();
-    }
-}
-
-Network &
-Network
-::operator=(Network const & other)
-{
-    if(this != &other)
-    {
-        this->set_role(other.get_role());
-        this->set_port(other.get_port());
-        this->set_timeout(other.get_timeout());
-        this->set_options(other.get_options());
-    }
-    
-    return *this;
-}
-
-T_ASC_NetworkRole
-Network
-::get_role() const
-{
-    return this->_role;
-}
-
-void
-Network
-::set_role(T_ASC_NetworkRole role)
-{
-    if(this->is_initialized())
-    {
-        throw Exception("Cannot set member while initialized");
-    }
-    
-    this->_role = role;
-}
-
-int
-Network
-::get_port() const
-{
-    return this->_port;
-}
-
-void
-Network
-::set_port(int port)
-{
-    if(this->is_initialized())
-    {
-        throw Exception("Cannot set member while initialized");
-    }
-    
-    this->_port = port;
-}
-
-int
-Network
-::get_timeout() const
-{
-    return this->_timeout;
-}
-
-void
-Network
-::set_timeout(int timeout)
-{
-    if(this->is_initialized())
-    {
-        throw Exception("Cannot set member while initialized");
-    }
-    
-    this->_timeout = timeout;
-}
-
-unsigned long
-Network
-::get_options() const
-{
-    return this->_options;
-}
-
-void
-Network
-::set_options(unsigned long options)
-{
-    if(this->is_initialized())
-    {
-        throw Exception("Cannot set member while initialized");
-    }
-    
-    this->_options = options;
-}
-
-void
-Network
-::initialize()
-{
-    if(this->is_initialized())
-    {
-        throw Exception("Already initialized");
-    }
-    
-    OFCondition const condition = ASC_initializeNetwork(
-        this->_role, this->_port, this->_timeout, &this->_network, 
-        this->_options);
-    
-    if(condition.bad())
-    {
-        throw Exception(condition);
-    }
-}
-
-T_ASC_Network *
-Network
-::get_network()
-{
-    return this->_network;
-}
-
-bool 
-Network
-::is_association_waiting(int const timeout)
-{
-    return ASC_associationWaiting(this->_network, timeout);
-}
-
-void
-Network
-::drop()
-{
-    if(!this->is_initialized())
-    {
-        throw Exception("Not initialized");
-    }
-    
-    OFCondition const condition = ASC_dropNetwork(&this->_network);
-    
-    if(condition.bad())
-    {
-        throw Exception(condition);
-    }
-    
-    this->_network = NULL;
-}
-
-bool
-Network
-::is_initialized() const
-{
-    return (this->_network != NULL);
-}
-
-}
diff --git a/src/dcmtkpp/Network.h b/src/dcmtkpp/Network.h
deleted file mode 100644
index 7cf8acf..0000000
--- a/src/dcmtkpp/Network.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#ifndef _fad49120_3f5c_447d_b0e9_72719087640c
-#define _fad49120_3f5c_447d_b0e9_72719087640c
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/assoc.h>
-
-namespace dcmtkpp
-{
-
-/**
- * @brief Wrapper around the T_ASC_Network class.
- * 
- * No member can be set while the network is initialized.
- */
-class Network
-{
-public:
-    /// @brief Create a default, un-initialized, network.
-    Network();
-    
-    /// @brief Create an un-initialized network.
-    Network(T_ASC_NetworkRole role, int port, int timeout, unsigned long options=0);
-    
-    /// @brief Create an un-initialized network.
-    Network(Network const & other);
-    
-    /// @brief Destroy the network, dropping it if necessary.
-    ~Network();
-    
-    /// @brief Assing an un-initialized network; it remains un-initialized.
-    Network & operator=(Network const & other);
-    
-    /// @brief Return the role of the network, defaults to NET_REQUESTOR.
-    T_ASC_NetworkRole get_role() const;
-    
-    /// @brief Set the role of the network.
-    void set_role(T_ASC_NetworkRole role);
-    
-    /// @brief Return the port for acceptor role, defaults to 0.
-    int get_port() const;
-    
-    /// @brief Set the port for acceptor role.
-    void set_port(int port);
-    
-    /// @brief Return the timeout in seconds, defaults to 30.
-    int get_timeout() const;
-    
-    /// @brief Set the timeout in seconds.
-    void set_timeout(int timeout);
-    
-    /// @brief Return the options, defaults to 0.
-    unsigned long get_options() const;
-    
-    /// @brief the set options.
-    void set_options(unsigned long options);
-    
-    /// @brief Initialize the network.
-    void initialize();
-    
-    /// @brief Return the underlying DCMTK object.
-    T_ASC_Network * get_network();
-    
-    /// @brief Test whether an association request is waiting.
-    bool is_association_waiting(int const timeout);
-    
-    /// @brief Drop (i.e. un-initialize) the network.
-    void drop();
-    
-    /// @brief Test whether the network is initialized.
-    bool is_initialized() const;
-
-private:
-    T_ASC_NetworkRole _role;
-    int _port;
-    int _timeout;
-    unsigned long _options;
-    
-    T_ASC_Network * _network;
-};
-
-}
-
-#endif // _fad49120_3f5c_447d_b0e9_72719087640c
diff --git a/src/dcmtkpp/Response.cpp b/src/dcmtkpp/Response.cpp
deleted file mode 100644
index f3c2e20..0000000
--- a/src/dcmtkpp/Response.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "Response.h"
-
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
-
-namespace dcmtkpp
-{
-
-Response
-::Response(Value::Integer message_id_being_responded_to, Value::Integer status)
-: Message()
-{
-    this->set_message_id_being_responded_to(message_id_being_responded_to);
-    this->set_status(status);
-}
-
-Response
-::Response(Message const & message)
-: Message()
-{
-    this->set_message_id_being_responded_to(
-        message.get_command_set().as_int(
-            registry::MessageIDBeingRespondedTo, 0));
-
-    this->set_status(message.get_command_set().as_int(registry::Status, 0));
-}
-
-Response
-::~Response()
-{
-    // Nothing to do.
-}
-
-}
diff --git a/src/dcmtkpp/Response.h b/src/dcmtkpp/Response.h
deleted file mode 100644
index a1a66c4..0000000
--- a/src/dcmtkpp/Response.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#ifndef _0dd2e31e_212a_494a_a8d3_93b235336658
-#define _0dd2e31e_212a_494a_a8d3_93b235336658
-
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
-
-namespace dcmtkpp
-{
-
-/// @brief Base class for all DIMSE response messages.
-class Response: public Message
-{
-public:
-    /// @brief Create a response with given message id and status;
-    Response(Value::Integer message_id_being_responded_to, Value::Integer status);
-
-    /**
-     * @brief Create a response from the Message ID Being Responded To and the
-     * Status stored in the message command set.
-     *
-     * Raise an exception is either of those elements is missing.
-     */
-    Response(Message const & message);
-
-    /// @brief Destructor.
-    virtual ~Response();
-    
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(
-        message_id_being_responded_to, registry::MessageIDBeingRespondedTo)
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(status, registry::Status)
-};
-
-}
-
-#endif // _0dd2e31e_212a_494a_a8d3_93b235336658
diff --git a/src/dcmtkpp/SCP.cpp b/src/dcmtkpp/SCP.cpp
deleted file mode 100644
index d24e861..0000000
--- a/src/dcmtkpp/SCP.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "SCP.h"
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CEchoRequest.h"
-#include "dcmtkpp/CEchoResponse.h"
-#include "dcmtkpp/ServiceRole.h"
-
-namespace dcmtkpp
-{
-
-SCP
-::SCP()
-: ServiceRole()
-{
-    // Nothing else.
-}
-
-SCP
-::~SCP()
-{
-    // Nothing to do.
-}
-
-void
-SCP
-::_send_echo_response(CEchoRequest const & request) const
-{
-    CEchoResponse response(
-        request.get_message_id(), STATUS_Success,
-        request.get_affected_sop_class_uid());
-    this->_send(response, request.get_affected_sop_class_uid());
-}
-
-}
diff --git a/src/dcmtkpp/SCP.h b/src/dcmtkpp/SCP.h
deleted file mode 100644
index d6130a3..0000000
--- a/src/dcmtkpp/SCP.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#ifndef _f4680d8c_18a8_4317_956d_3ae238cb39cc
-#define _f4680d8c_18a8_4317_956d_3ae238cb39cc
-
-#include "dcmtkpp/CEchoRequest.h"
-#include "dcmtkpp/ServiceRole.h"
-
-namespace dcmtkpp
-{
-
-/// @brief Base class for all Service Class Providers.
-class SCP: public ServiceRole
-{
-public:
-    SCP();
-    virtual ~SCP();
-protected:
-    /// @brief Send a C-ECHO response.
-    void _send_echo_response(CEchoRequest const & request) const;
-};
-
-}
-
-#endif // _f4680d8c_18a8_4317_956d_3ae238cb39cc
diff --git a/src/dcmtkpp/ServiceRole.cpp b/src/dcmtkpp/ServiceRole.cpp
deleted file mode 100644
index d2a076b..0000000
--- a/src/dcmtkpp/ServiceRole.cpp
+++ /dev/null
@@ -1,438 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "ServiceRole.h"
-
-#include <unistd.h>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/assoc.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Reader.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Writer.h"
-
-namespace dcmtkpp
-{
-
-ServiceRole
-::ServiceRole()
-: _association(NULL), _network(NULL)
-{
-    // Nothing else
-}
-
-ServiceRole
-::ServiceRole(ServiceRole const & other)
-: _association(other._association), _network(other._network)
-{
-    // Nothing else
-}
-
-ServiceRole const &
-ServiceRole
-::operator=(ServiceRole const & other)
-{
-    if(this != &other)
-    {
-        this->set_association(other.get_association());
-        this->set_network(other.get_network());
-    }
-    
-    return *this;
-}
-
-ServiceRole
-::~ServiceRole()
-{
-    // Nothing to do.
-}
-
-Network *
-ServiceRole
-::get_network() const
-{
-    return this->_network;
-}
-
-void
-ServiceRole
-::set_network(Network * network)
-{
-    this->_network = network;
-}
-
-Association *
-ServiceRole
-::get_association() const
-{
-    return this->_association;
-}
-
-void
-ServiceRole
-::set_association(Association * association)
-{
-    this->_association = association;
-}
-
-void 
-ServiceRole
-::_progress_callback_wrapper(void * data, unsigned long bytes_count)
-{
-    ProgressCallbackData * encapsulated = 
-        reinterpret_cast<ProgressCallbackData*>(data);
-    encapsulated->callback(encapsulated->data, bytes_count);
-}
-
-T_ASC_PresentationContextID
-ServiceRole
-::_find_presentation_context(std::string const & abstract_syntax) const
-{
-    T_ASC_PresentationContextID const presentation_id = 
-        ASC_findAcceptedPresentationContextID(
-            this->_association->get_association(), 
-            abstract_syntax.c_str());
-    if(presentation_id == 0) 
-    {
-        throw Exception("No Presentation Context for Get Operation");
-    }
-    
-    return presentation_id;
-}
-void
-ServiceRole
-::_send(
-    Message const & message, std::string const & abstract_syntax,
-    ProgressCallback callback, void* callback_data) const
-{
-    T_ASC_PresentationContextID const presentation_context = 
-        this->_find_presentation_context(abstract_syntax);
-
-    this->_send(
-        message.get_command_set(),
-        presentation_context, registry::ImplicitVRLittleEndian,
-        DUL_COMMANDPDV, NULL, NULL);
-    if(message.has_data_set() && !message.get_data_set().empty())
-    {
-        // FIXME: transfer syntax
-        this->_send(
-            message.get_data_set(),
-            presentation_context, registry::ImplicitVRLittleEndian,
-            DUL_DATASETPDV, callback, callback_data);
-    }
-}
-
-Message
-ServiceRole
-::_receive(ProgressCallback callback, void* callback_data) const
-{
-    // Receive command set
-    auto const command = this->_receive_dataset(callback, callback_data);
-    if(command.second != DUL_COMMANDPDV)
-    {
-        throw Exception("Did not receive command set");
-    }
-    DataSet const & command_set = command.first;
-    
-    // Receive potential data set
-    DataSet data_set;
-    bool has_data_set;
-    if(command_set.as_int(registry::CommandDataSetType, 0) != DIMSE_DATASET_NULL)
-    {
-        auto const data = this->_receive_dataset(callback, callback_data);
-        if(data.second != DUL_DATASETPDV)
-        {
-            throw Exception("Did not receive data set");
-        }
-        data_set = data.first;
-        has_data_set = true;
-    }
-    else
-    {
-        has_data_set = false;
-    }
-    
-    return has_data_set?Message(command_set, data_set):Message(command_set);
-}
-
-void
-ServiceRole
-::_send(
-    DataSet const & obj, T_ASC_PresentationContextID presID,
-    std::string const & transfer_syntax, DUL_DATAPDV pdvType,
-    ProgressCallback callback, void *callbackContext) const
-    /*
-     * This function sends all information which is included in a DcmDataset object over
-     * the network which is provided in assoc.
-     *
-     * Parameters:
-     *   obj             - [in] Contains the information which shall be sent over the network.
-     *   presId          - [in] The ID of the presentation context which shall be used
-     *   xferSyntax      - [in] The transfer syntax which shall be used.
-     *   pdvType         - [in] Specifies if the information in this DcmDataset object belongs to
-     *                          a DIMSE command (as for example C-STORE) (DUL_COMMANDPDV) or if
-     *                          the information is actual instance information (DUL_DATASETPDV).
-     *   callback        - [in] Pointer to a function which shall be called to indicate progress.
-     *   callbackContext - []
-     */
-{
-    bool const use_group_length = (pdvType == DUL_COMMANDPDV);
-    std::stringstream stream;
-    Writer writer(
-        stream, transfer_syntax, Writer::ItemEncoding::ExplicitLength,
-        use_group_length);
-    writer.write_data_set(obj);
-    std::string const data = stream.str();
-
-    /* we may wish to restrict output PDU size */
-    /* max PDV size is max PDU size minus 12 bytes PDU/PDV header */
-    unsigned long bufLen = this->_association->get_association()->sendPDVLength;
-    if (bufLen + 12 > dcmMaxOutgoingPDUSize.get())
-    {
-        bufLen = dcmMaxOutgoingPDUSize.get() - 12;
-    }
-
-    bool done = false;
-    unsigned long bytesTransmitted=0;
-    while(!done)
-    {
-        std::string const buffer = data.substr(bytesTransmitted, bufLen);
-
-        /* count the bytes and the amount of PDVs which were transmitted */
-        bytesTransmitted += buffer.size();
-        done = (bytesTransmitted>=data.size());
-
-        /* initialize a DUL_PDV variable with the buffer's data */
-        DUL_PDV pdv;
-        pdv.fragmentLength = buffer.size();
-        pdv.presentationContextID = presID;
-        pdv.pdvType = pdvType;
-        pdv.lastPDV = done;
-        pdv.data = reinterpret_cast<void*>(const_cast<char*>(&buffer[0]));
-
-        /* append this PDV to a PDV list structure, set the counter variable */
-        /* to 1 since this structure contains only 1 element */
-        DUL_PDVLIST pdvList;
-        pdvList.count = 1;
-        pdvList.pdv = &pdv;
-
-        /* send information over the network to the other DICOM application */
-        OFCondition const dulCond = DUL_WritePDVs(
-            &this->_association->get_association()->DULassociation, &pdvList);
-        if (dulCond.bad())
-        {
-            throw Exception(
-                makeDcmnetSubCondition(
-                    DIMSEC_SENDFAILED, OF_error, "DIMSE Failed to send message",
-                    dulCond));
-        }
-
-        /* execute callback function to indicate progress */
-        if(callback)
-        {
-            callback(callbackContext, bytesTransmitted);
-        }
-    }
-}
-
-
-std::pair<DataSet, DUL_DATAPDV>
-ServiceRole::_receive_dataset(
-    ProgressCallback callback, void *callbackContext) const
-{
-    DataSet data_set;
-
-    std::stringstream stream;
-
-    /* start a loop in which we want to read a DIMSE command from the incoming socket stream. */
-    /* Since the command could stretch over more than one PDU, the use of a loop is mandatory. */
-    DUL_DATAPDV type;
-    bool last = false;
-    DIC_UL pdvCount = 0;
-    T_ASC_PresentationContextID pid = 0;
-    while(!last)
-    {
-        DUL_PDV const pdv = this->_read_next_pdv();
-
-        /* if this is the first loop iteration, get the presentation context ID
-         * which is captured in the current PDV. If this is not the first loop
-         * iteration, check if the presentation context IDs in the current PDV
-         * and in the last PDV are identical. If they are not, return an error.
-         */
-        if (pdvCount == 0)
-        {
-            pid = pdv.presentationContextID;
-        }
-        else if (pdv.presentationContextID != pid)
-        {
-            std::ostringstream message;
-            message << "Different presentation IDs inside Command Set: "
-                    << pid << " != " << pdv.presentationContextID;
-            throw Exception(message.str());
-        }
-
-        /* check if the fragment length of the current PDV is odd. This should */
-        /* never happen (see DICOM standard (year 2000) part 7, annex F) (or */
-        /* the corresponding section in a later version of the standard.) */
-        if ((pdv.fragmentLength % 2) != 0)
-        {
-            /* This should NEVER happen.  See Part 7, Annex F. */
-            std::ostringstream message;
-            message << "Odd fragment length: " << pdv.fragmentLength;
-            throw Exception(message.str());
-        }
-
-        /* if information is contained the PDVs fragment, we want to insert
-         * this information into the buffer
-         */
-        stream.write(reinterpret_cast<char *>(pdv.data), pdv.fragmentLength);
-
-        std::string transfer_syntax;
-        bool keep_group_length;
-        if(pdv.pdvType == DUL_COMMANDPDV)
-        {
-            /* DIMSE commands are always specified in the little endian implicit
-             * transfer syntax. Additionally, we want to remove group length
-             * tags.
-             */
-            transfer_syntax = registry::ImplicitVRLittleEndian;
-            keep_group_length = false;
-        }
-        else if(pdv.pdvType == DUL_DATASETPDV)
-        {
-            /* figure out if is this a valid presentation context */
-            T_ASC_PresentationContext pc;
-            OFCondition const cond =
-                ASC_findAcceptedPresentationContext(
-                    this->_association->get_association()->params, pid, &pc);
-            if (cond.bad())
-            {
-                throw Exception("No valid presentation context");
-            }
-
-            /* determine the transfer syntax which is specified in the presentation context */
-            transfer_syntax = pc.acceptedTransferSyntax;
-            keep_group_length = true;
-        }
-        else
-        {
-            throw Exception("Unkown PDV type");
-        }
-
-        Reader reader(stream, transfer_syntax, keep_group_length);
-        data_set = reader.read_data_set();
-
-        /* update the following variables which will be evaluated at the beginning of each loop iteration. */
-        last = pdv.lastPDV;
-        type = pdv.pdvType;
-
-        /* update the counter that counts how many PDVs were received on the incoming */
-        /* socket stream. This variable will be used for determining the first */
-        /* loop iteration and dumping general information. */
-        pdvCount++;
-    }
-
-    return std::make_pair(data_set, type);
-}
-
-DUL_PDV
-ServiceRole
-::_read_next_pdv() const
-    /*
-     * This function returns the next PDV which was (earlier or just now) received on the incoming
-     * socket stream. If there are no PDVs (which were already received earlier) waiting to be picked
-     * up, this function will go ahead and read a new PDU (containing one or more new PDVs) from the
-     * incoming socket stream.
-     */
-{
-    /* get the next PDV from the association, in case there are still some PDVs waiting to be picked up */
-    DUL_PDV pdv;
-    OFCondition cond = DUL_NextPDV(
-        &this->_association->get_association()->DULassociation, &pdv);
-
-    if (cond.bad())
-    {
-        /* in case DUL_NextPDV(...) did not return DUL_NORMAL, the association */
-        /* did not contain any more PDVs that are waiting to be picked up. Hence, */
-        /* we need to read new PDVs from the incoming socket stream. */
-
-        /* if the blocking mode is DIMSE_NONBLOCKING and there is no data waiting after timeout seconds, report an error */
-        if(!ASC_dataWaiting(
-            this->_association->get_association(), this->_network->get_timeout()))
-        {
-            throw Exception(DIMSE_NODATAAVAILABLE);
-        }
-
-        /* try to receive new PDVs on the incoming socket stream (in detail, try to receive one PDU) */
-        cond = DUL_ReadPDVs(
-            &this->_association->get_association()->DULassociation, NULL,
-            DUL_BLOCK, this->_network->get_timeout());
-
-        /* check return value, if it is different from DUL_PDATAPDUARRIVED, an error occurred */
-        if (cond != DUL_PDATAPDUARRIVED)
-        {
-            if (cond == DUL_NULLKEY || cond == DUL_ILLEGALKEY)
-            {
-                throw Exception(DIMSE_ILLEGALASSOCIATION);
-            }
-            else if (cond == DUL_PEERREQUESTEDRELEASE ||
-                cond == DUL_PEERABORTEDASSOCIATION)
-            {
-                throw Exception(cond);
-            }
-            else
-            {
-                throw Exception(
-                    makeDcmnetSubCondition(
-                        DIMSEC_READPDVFAILED, OF_error,
-                        "DIMSE Read PDV failed", cond));
-            }
-        }
-
-        /* get the next PDV, assign it to pdv */
-        cond = DUL_NextPDV(
-            &this->_association->get_association()->DULassociation, &pdv);
-        if (cond.bad())
-        {
-            throw Exception(
-                makeDcmnetSubCondition(
-                    DIMSEC_READPDVFAILED, OF_error,
-                    "DIMSE Read PDV failed", cond));
-        }
-    }
-
-    return pdv;
-}
-
-void
-ServiceRole
-::_check_dimse_ready() const
-{
-    if(this->_network == NULL)
-    {
-        throw Exception("No network");
-    }
-    else if(!this->_network->is_initialized())
-    {
-        throw Exception("Network is not initialized");
-    }
-    else if(this->_association == NULL)
-    {
-        throw Exception("No association");
-    }
-    else if(!this->_association->is_associated())
-    {
-        throw Exception("Not associated");
-    }
-}
-
-}
diff --git a/src/dcmtkpp/ServiceRole.h b/src/dcmtkpp/ServiceRole.h
deleted file mode 100644
index 0c9b25b..0000000
--- a/src/dcmtkpp/ServiceRole.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#ifndef _fa7d372a_dd27_4a1e_9b29_be9d5fbe602a
-#define _fa7d372a_dd27_4a1e_9b29_be9d5fbe602a
-
-#include <functional>
-#include <utility>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/assoc.h>
-
-#include "dcmtkpp/Association.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/Network.h"
-
-namespace dcmtkpp
-{
-
-/// @brief Base class for all Service Class Users and Providers.
-class ServiceRole
-{
-public:
-    /// @brief Progress callback, following the semantics of DCMTK.
-    typedef std::function<void(void *, unsigned long)> ProgressCallback;
-    
-    /// @brief Create a default Service Role with no network and no association.
-    ServiceRole();
-    
-    /// @brief Copy the other service role.
-    ServiceRole(ServiceRole const & other);
-    
-    /// @brief Copy the other service role.
-    ServiceRole const & operator=(ServiceRole const & other);
-    
-    /// @brief Destructor, network and association are not modified.
-    virtual ~ServiceRole();
-    
-    /// @brief Return the network used by the ServiceRole.
-    Network * get_network() const;
-    /// @brief Set the network used by the ServiceRole.
-    void set_network(Network * network);
-    
-    /// @brief Return the association used by the ServiceRole.
-    Association * get_association() const;
-    /// @brief Set the association used by the ServiceRole.
-    void set_association(Association * association);
-
-protected:
-    /// @brief Wrapper class for DMCTK progress callbacks.
-    struct ProgressCallbackData
-    {
-        /// @brief Callback function.
-        ProgressCallback callback;
-        
-        /// @brief Callback data.
-        void * data;
-    };
-    
-    /// @brief Network used by the ServiceRole.
-    Network * _network;
-    
-    /// @brief Association used by the ServiceRole.
-    Association * _association;
-
-    /// @brief Wrapper from ProgressCallback to DIMSE_ProgressCallback.
-    static void _progress_callback_wrapper(void * data, unsigned long bytes_count);
-    
-    /// @brief Find an accepted presentation context.
-    T_ASC_PresentationContextID _find_presentation_context(
-        std::string const & abstract_syntax) const;
-    
-    /// @brief Send a DIMSE message.
-    void _send(
-        Message const & message, std::string const & abstract_syntax,
-        ProgressCallback callback=NULL, void* callback_data=NULL) const;
-    
-    /// @brief Receive a generic DIMSE message.
-    Message _receive(ProgressCallback callback=NULL, void* callback_data=NULL) const;
-    
-    /**
-     * @brief Receive a DIMSE message of specific type.
-     *
-     * Throw an exception if the received message is not of the requested type.
-     */
-    template<typename TMessage>
-    TMessage _receive(ProgressCallback callback=NULL, void* callback_data=NULL) const;
-
-private:
-    void _send(
-        DataSet const & obj, T_ASC_PresentationContextID presID,
-        std::string const & transfer_syntax, DUL_DATAPDV pdvType,
-        ProgressCallback callback, void *callbackContext) const;
-
-    std::pair<DataSet, DUL_DATAPDV> _receive_dataset(
-        ProgressCallback callback, void *callbackContext) const;
-    
-    DUL_PDV _read_next_pdv() const;
-
-    void _check_dimse_ready() const;
-};
-
-}
-
-#include "ServiceRole.txx"
-
-#endif // _fa7d372a_dd27_4a1e_9b29_be9d5fbe602a
diff --git a/src/dcmtkpp/ServiceRole.txx b/src/dcmtkpp/ServiceRole.txx
deleted file mode 100644
index 32dc73f..0000000
--- a/src/dcmtkpp/ServiceRole.txx
+++ /dev/null
@@ -1,38 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#ifndef _8ac39caa_b7b1_44a8_82fc_e8e3de18b2f8
-#define _8ac39caa_b7b1_44a8_82fc_e8e3de18b2f8
-
-#include "ServiceRole.h"
-
-#include <functional>
-#include <utility>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/assoc.h>
-
-#include "dcmtkpp/Association.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/Network.h"
-
-namespace dcmtkpp
-{
-
-template<typename TMessage>
-TMessage
-ServiceRole
-::_receive(ProgressCallback callback, void* callback_data) const
-{
-    Message const message = this->_receive(callback, callback_data);
-    return TMessage(message);
-}
-
-}
-
-#endif // _8ac39caa_b7b1_44a8_82fc_e8e3de18b2f8
diff --git a/src/dcmtkpp/StoreSCP.cpp b/src/dcmtkpp/StoreSCP.cpp
deleted file mode 100644
index c6d152b..0000000
--- a/src/dcmtkpp/StoreSCP.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "dcmtkpp/StoreSCP.h"
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CStoreRequest.h"
-#include "dcmtkpp/CStoreResponse.h"
-#include "dcmtkpp/DataSet.h"
-
-namespace dcmtkpp
-{
-
-bool 
-StoreSCP
-::store(Callback callback) const
-{
-    Message request;
-    try
-    {
-        request = this->_receive();
-    }
-    catch(Exception const & e)
-    {
-        if(e.get_source() != Exception::Source::Condition ||
-           e.get_condition() != DUL_PEERREQUESTEDRELEASE)
-        {
-            throw;
-        }
-        else
-        {
-            return true;
-        }
-    }
-    
-    if(request.get_command_field() == Message::Command::C_ECHO_RQ)
-    {
-        this->_send_echo_response(CEchoRequest(request));
-    }
-    else if(request.get_command_field() == Message::Command::C_STORE_RQ)
-    {
-        this->store(CStoreRequest(request), callback);
-        request.delete_data_set();
-    }
-    else
-    {
-        std::ostringstream message;
-        message << "DIMSE: Unexpected Response Command Field: 0x" 
-                << std::hex << request.get_command_field();
-        throw Exception(message.str());
-    }
-    
-    return false;
-}
-
-void
-StoreSCP
-::store(CStoreRequest const & request, Callback callback) const
-{
-    // Execute user callback
-    Uint16 status = STATUS_Success;
-    if(!request.has_data_set() || request.get_data_set().empty())
-    {
-        status = STATUS_STORE_Error_CannotUnderstand;
-    }
-    else
-    {
-        try
-        {
-            callback(request.get_data_set());
-        }
-        catch(...)
-        {
-            // FIXME: logging
-            status = STATUS_STORE_Error_CannotUnderstand;
-        }
-    }
-    
-    // Send store response
-    CStoreResponse response(request.get_message_id(), status);
-    response.set_affected_sop_class_uid(request.get_affected_sop_class_uid());
-    response.set_affected_sop_instance_uid(
-        request.get_affected_sop_instance_uid());
-    this->_send(response, request.get_affected_sop_class_uid());
-}
-
-}
diff --git a/src/dcmtkpp/StoreSCP.h b/src/dcmtkpp/StoreSCP.h
deleted file mode 100644
index 9d6c8ed..0000000
--- a/src/dcmtkpp/StoreSCP.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#ifndef _fdbf3f51_91f5_464a_b449_c3f994297210
-#define _fdbf3f51_91f5_464a_b449_c3f994297210
-
-#include <functional>
-
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/CStoreRequest.h"
-#include "dcmtkpp/SCP.h"
-
-namespace dcmtkpp
-{
-
-class StoreSCP: public SCP
-{
-public:
-    /// @brief Callback called when a response is received.
-    typedef std::function<void(DataSet const &)> Callback;
-    
-    /**
-     * @brief Receive a store request and respond to it.
-     * @param callback function called with the dataset to be stored.
-     */
-    bool store(Callback callback) const;
-    
-    /**
-     * @brief Respond to a store request.
-     * @param request
-     * @param callback function called with the dataset to be stored.
-     */
-    void store(CStoreRequest const & request, Callback callback) const;
-};
-
-}
-
-#endif // _fdbf3f51_91f5_464a_b449_c3f994297210
diff --git a/src/dcmtkpp/uid.cpp b/src/dcmtkpp/uid.cpp
deleted file mode 100644
index e9013e5..0000000
--- a/src/dcmtkpp/uid.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
- * Distributed under the terms of the CeCILL-B license, as published by
- * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
- * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
- * for details.
- ************************************************************************/
-
-#include "dcmtkpp/uid.h"
-
-#include <string>
-
-#include <uuid/uuid.h>
-
-#include "dcmtkpp/Exception.h"
-
-namespace dcmtkpp
-{
-
-std::string generate_uid()
-{
-    if(uid_prefix.size() > 26)
-    {
-        throw Exception("UID prefix is too long");
-    }
-
-    // Our prefix is 26 characters long. With the "." for separation, 37
-    // characters remain. Since a random UUID has 122 random bits and since
-    // 2^122-1 is 37 decimal digits long, the UID can be generated from a UUID.
-    uuid_t uuid;
-    uuid_generate(uuid);
-
-    // Convert the UUID to two 61 bits integers
-    uint64_t first=0;
-    // time-high-and-version: 12 bits random, 4 bits version (2 bytes)
-    first = (first + (uuid[7]>>4)) << 8;
-    first = (first + ((uuid[7]&0xf)<<4)+(uuid[6]&0xf)) << 8;
-    // time-mid: random (2 bytes)
-    first = (first + uuid[5]) << 8;
-    first = (first + uuid[4]) << 8;
-    // time-low: random (4 bytes)
-    first = (first + uuid[3]) << 8;
-    first = (first + uuid[2]) << 8;
-    first = (first + uuid[1]) << 8;
-    first = (first + uuid[0]);
-
-    uint64_t second=0;
-    // node: 48 bits random (6 bytes)
-    second = (second + uuid[15]) << 8;
-    second = (second + uuid[14]) << 8;
-    second = (second + uuid[13]) << 8;
-    second = (second + uuid[12]) << 8;
-    second = (second + uuid[11]) << 8;
-    second = (second + uuid[10]) << 8;
-    // clock_seq_low: 8 bits random (1 byte)
-    second = (second + uuid[9]) << 8;
-    // clock_seq_hi_and_reserved: 4 bits random, 4 bits variant (1 byte)
-    second = (second + ((uuid[8]&0xf) << 4)) >> 4;
-
-    std::string suffix;
-    int remainder = 0;
-    while(first != 0)
-    {
-        int digit = first%10;
-        first = (first-digit)/10;
-        if(first != 0)
-        {
-            suffix = std::to_string(digit)+suffix;
-        }
-        else
-        {
-            remainder = digit;
-        }
-    }
-
-    second += remainder;
-    while(second != 0)
-    {
-        int digit = second%10;
-        second = (second-digit)/10;
-
-        suffix = std::to_string(digit)+suffix;
-    }
-
-    return uid_prefix+"."+suffix;
-}
-
-}
diff --git a/src/odil/Association.cpp b/src/odil/Association.cpp
new file mode 100644
index 0000000..2d0fb47
--- /dev/null
+++ b/src/odil/Association.cpp
@@ -0,0 +1,496 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "Association.h"
+
+#include <algorithm>
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "odil/AssociationParameters.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/uid.h"
+#include "odil/dul/StateMachine.h"
+#include "odil/message/Message.h"
+#include "odil/pdu/AAbort.h"
+#include "odil/pdu/AAssociate.h"
+#include "odil/pdu/AAssociateRJ.h"
+#include "odil/pdu/AReleaseRP.h"
+#include "odil/pdu/AReleaseRQ.h"
+#include "odil/pdu/ImplementationClassUID.h"
+#include "odil/pdu/ImplementationVersionName.h"
+#include "odil/pdu/PDataTF.h"
+#include "odil/pdu/PresentationContextAC.h"
+#include "odil/pdu/PresentationContextRQ.h"
+#include "odil/pdu/RoleSelection.h"
+#include "odil/pdu/UserIdentityRQ.h"
+#include "odil/pdu/UserInformation.h"
+#include "odil/Reader.h"
+#include "odil/Writer.h"
+
+namespace odil
+{
+
+Association
+::Association()
+: _state_machine(), _peer_host(""), _peer_port(104), _association_parameters(),
+  _transfer_syntaxes_by_abstract_syntax(), _transfer_syntaxes_by_id(),
+  _next_message_id(1)
+{
+    this->set_tcp_timeout(boost::posix_time::pos_infin);
+    this->set_message_timeout(boost::posix_time::seconds(30));
+}
+
+Association
+::Association(Association const & other)
+: _state_machine(), _peer_host(other._peer_host), _peer_port(other._peer_port),
+  _association_parameters(other._association_parameters),
+  _transfer_syntaxes_by_abstract_syntax(), _transfer_syntaxes_by_id(),
+  _next_message_id(other._next_message_id)
+{
+    this->set_tcp_timeout(other.get_tcp_timeout());
+    this->set_message_timeout(other.get_message_timeout());
+}
+
+Association
+::~Association()
+{
+    // Nothing to do, everything is taken care of by the StateMachine
+}
+
+dul::Transport &
+Association
+::get_transport()
+{
+    return this->_state_machine.get_transport();
+}
+
+Association &
+Association
+::operator=(Association const & other)
+{
+    if(this != &other)
+    {
+        this->set_peer_host(other.get_peer_host());
+        this->set_peer_port(other.get_peer_port());
+        this->set_parameters(other.get_parameters());
+    }
+
+    return *this;
+}
+
+std::string const &
+Association
+::get_peer_host() const
+{
+    return this->_peer_host;
+}
+
+void
+Association
+::set_peer_host(std::string const & host)
+{
+    if(this->is_associated())
+    {
+        throw Exception("Cannot set member while associated");
+    }
+
+    this->_peer_host = host;
+}
+
+uint16_t
+Association
+::get_peer_port() const
+{
+    return this->_peer_port;
+}
+
+void
+Association
+::set_peer_port(uint16_t port)
+{
+    if(this->is_associated())
+    {
+        throw Exception("Cannot set member while associated");
+    }
+
+    this->_peer_port = port;
+}
+
+AssociationParameters const &
+Association
+::get_parameters() const
+{
+    return this->_association_parameters;
+}
+
+AssociationParameters &
+Association
+::update_parameters()
+{
+    if(this->is_associated())
+    {
+        throw Exception("Cannot set member while associated");
+    }
+
+    return this->_association_parameters;
+}
+
+void
+Association
+::set_parameters(AssociationParameters const & value)
+{
+    if(this->is_associated())
+    {
+        throw Exception("Cannot set member while associated");
+    }
+
+    this->_association_parameters = value;
+}
+
+AssociationParameters const &
+Association
+::get_negotiated_parameters() const
+{
+    return this->_negotiated_parameters;
+}
+
+Association::duration_type
+Association
+::get_tcp_timeout() const
+{
+    return this->_state_machine.get_transport().get_timeout();
+}
+
+void
+Association
+::set_tcp_timeout(duration_type const & duration)
+{
+    this->_state_machine.get_transport().set_timeout(duration);
+}
+
+Association::duration_type
+Association
+::get_message_timeout() const
+{
+    return this->_state_machine.get_timeout();
+}
+
+void
+Association
+::set_message_timeout(duration_type const & duration)
+{
+    this->_state_machine.set_timeout(duration);
+}
+
+bool
+Association
+::is_associated() const
+{
+    return this->_state_machine.get_transport().is_open();
+}
+
+void
+Association
+::associate()
+{
+    boost::asio::ip::tcp::resolver resolver(
+        this->_state_machine.get_transport().get_service());
+    boost::asio::ip::tcp::resolver::query const query(this->_peer_host, "");
+    auto const endpoint_it = resolver.resolve(query);
+
+    dul::EventData data;
+    data.peer_endpoint = *endpoint_it;
+    data.peer_endpoint.port(this->_peer_port);
+
+    auto const request =
+        std::make_shared<pdu::AAssociateRQ>(
+            this->_association_parameters.as_a_associate_rq());
+
+    data.pdu = request;
+
+    this->_state_machine.send_pdu(data);
+    this->_state_machine.receive_pdu(data);
+
+    if(data.pdu == nullptr)
+    {
+        throw Exception("No response received");
+    }
+    else
+    {
+        auto const acceptation = std::dynamic_pointer_cast<pdu::AAssociateAC>(data.pdu);
+        auto const rejection = std::dynamic_pointer_cast<pdu::AAssociateRJ>(data.pdu);
+        if(acceptation != nullptr)
+        {
+            this->_negotiated_parameters = AssociationParameters(
+                *acceptation, this->_association_parameters);
+
+            this->_transfer_syntaxes_by_abstract_syntax.clear();
+            this->_transfer_syntaxes_by_id.clear();
+
+            for(auto const & pc: this->_negotiated_parameters.get_presentation_contexts())
+            {
+                if(pc.result != AssociationParameters::PresentationContext::Result::Acceptance)
+                {
+                    continue;
+                }
+
+                this->_transfer_syntaxes_by_id[pc.id] = pc.transfer_syntaxes[0];
+                this->_transfer_syntaxes_by_abstract_syntax[pc.abstract_syntax] =
+                    {pc.id, pc.transfer_syntaxes[0]};
+            }
+        }
+        else if(rejection != nullptr)
+        {
+            throw Exception("Association rejected");
+        }
+        else
+        {
+            throw Exception("Invalid response");
+        }
+    }
+}
+
+void
+Association
+::receive_association(
+    boost::asio::ip::tcp const & protocol, unsigned short port,
+    AssociationAcceptor acceptor)
+{
+    dul::EventData data;
+    data.peer_endpoint = dul::Transport::Socket::endpoint_type(protocol, port);
+
+    this->_state_machine.set_association_acceptor(acceptor);
+
+    this->_state_machine.receive(data);
+    this->_state_machine.receive_pdu(data);
+
+    if(data.pdu == NULL)
+    {
+        // We have rejected the request
+        //return false;
+    }
+    else
+    {
+        auto const & request = std::dynamic_pointer_cast<pdu::AAssociateRQ>(data.pdu);
+        if(request == nullptr)
+        {
+            throw Exception("Invalid response");
+        }
+
+        auto const endpoint =
+            this->_state_machine.get_transport().get_socket()->remote_endpoint();
+        this->_peer_host = endpoint.address().to_string();
+        this->_peer_port = endpoint.port();
+
+        this->_negotiated_parameters = data.association_parameters;
+
+        this->_transfer_syntaxes_by_abstract_syntax.clear();
+        this->_transfer_syntaxes_by_id.clear();
+
+        for(auto const & pc: this->_negotiated_parameters.get_presentation_contexts())
+        {
+            if(pc.result != AssociationParameters::PresentationContext::Result::Acceptance)
+            {
+                continue;
+            }
+
+            this->_transfer_syntaxes_by_id[pc.id] = pc.transfer_syntaxes[0];
+            this->_transfer_syntaxes_by_abstract_syntax[pc.abstract_syntax] =
+                {pc.id, pc.transfer_syntaxes[0]};
+        }
+
+        data.pdu = std::make_shared<pdu::AAssociateAC>(
+            this->_negotiated_parameters.as_a_associate_ac());
+        this->_state_machine.send_pdu(data);
+    }
+}
+
+void
+Association
+::release()
+{
+    if(!this->is_associated())
+    {
+        throw Exception("Not associated");
+    }
+
+    auto pdu = std::make_shared<pdu::AReleaseRQ>();
+    dul::EventData data;
+    data.pdu = pdu;
+    this->_state_machine.send_pdu(data);
+    this->_state_machine.receive_pdu(data);
+
+    auto const reply = std::dynamic_pointer_cast<pdu::AReleaseRP>(data.pdu);
+    if(reply == nullptr)
+    {
+        // Invalid response, accept it nevertheless.
+    }
+}
+
+void
+Association
+::abort(int source, int reason)
+{
+    if(!this->is_associated())
+    {
+        throw Exception("Not associated");
+    }
+
+    auto pdu = std::make_shared<pdu::AAbort>(source, reason);
+    dul::EventData data;
+    data.pdu = pdu;
+    this->_state_machine.send_pdu(data);
+}
+
+message::Message
+Association
+::receive_message()
+{
+    bool done = false;
+    int presentation_context_id;
+    bool command_set_received=false;
+    bool has_data_set=true;
+    bool data_set_received=false;
+
+    DataSet command_set;
+    std::stringstream command_stream, data_stream;
+
+    while(!done)
+    {
+        dul::EventData data;
+        data.pdu = nullptr;
+        this->_state_machine.receive_pdu(data);
+
+        auto const a_release_rq = std::dynamic_pointer_cast<pdu::AReleaseRQ>(data.pdu);
+        if(a_release_rq != nullptr)
+        {
+            data.pdu = std::make_shared<pdu::AReleaseRP>();
+            this->_state_machine.send_pdu(data);
+            throw AssociationReleased();
+        }
+
+        auto const a_abort = std::dynamic_pointer_cast<pdu::AAbort>(data.pdu);
+        if(a_abort != nullptr)
+        {
+            throw AssociationAborted(a_abort->get_source(), a_abort->get_reason());
+        }
+
+        auto const p_data_tf = std::dynamic_pointer_cast<pdu::PDataTF>(data.pdu);
+
+        if(p_data_tf == nullptr)
+        {
+            throw Exception("Invalid PDU received");
+        }
+
+        for(auto const & pdv: p_data_tf->get_pdv_items())
+        {
+            presentation_context_id = pdv.get_presentation_context_id();
+            bool & received =
+                pdv.is_command()?command_set_received:data_set_received;
+            received |= pdv.is_last_fragment();
+
+            auto const & fragment_data = pdv.get_fragment();
+
+            std::stringstream  & stream =
+                pdv.is_command()?command_stream:data_stream;
+            stream.write(&fragment_data[0], fragment_data.size());
+
+            if(command_set_received && command_set.empty())
+            {
+                Reader reader(command_stream, registry::ImplicitVRLittleEndian);
+                command_set = reader.read_data_set();
+                auto const value =
+                    command_set.as_int(registry::CommandDataSetType, 0);
+
+                if(value == message::Message::DataSetType::ABSENT)
+                {
+                    has_data_set = false;
+                }
+            }
+        }
+
+        done = command_set_received && (!has_data_set || data_set_received);
+    }
+
+    if(has_data_set)
+    {
+        auto const transfer_syntax_it =
+            this->_transfer_syntaxes_by_id.find(presentation_context_id);
+        if(transfer_syntax_it == this->_transfer_syntaxes_by_id.end())
+        {
+            throw Exception("No such Presentation Context ID");
+        }
+
+        Reader reader(data_stream, transfer_syntax_it->second);
+        auto const data_set = reader.read_data_set();
+
+        return message::Message(command_set, data_set);
+    }
+    else
+    {
+        return message::Message(command_set);
+    }
+}
+
+void
+Association
+::send_message(
+    message::Message const & message, std::string const & abstract_syntax)
+{
+    auto const transfer_syntax_it =
+        this->_transfer_syntaxes_by_abstract_syntax.find(abstract_syntax);
+    if(transfer_syntax_it == this->_transfer_syntaxes_by_abstract_syntax.end())
+    {
+        throw Exception("No transfer syntax for "+abstract_syntax);
+    }
+
+    auto const & transfer_syntax = transfer_syntax_it->second.second;
+    auto const & id = transfer_syntax_it->second.first;
+
+    std::vector<pdu::PDataTF::PresentationDataValueItem> pdv_items;
+
+    std::ostringstream command_stream;
+    Writer command_writer(
+        command_stream, registry::ImplicitVRLittleEndian, // implicit vr for command
+        Writer::ItemEncoding::ExplicitLength, true); // true for Command
+    command_writer.write_data_set(message.get_command_set());
+    pdv_items.push_back(
+        pdu::PDataTF::PresentationDataValueItem(id, 3, command_stream.str()));
+
+    if (message.has_data_set())
+    {
+        std::ostringstream data_stream;
+        Writer data_writer(
+            data_stream, transfer_syntax,
+            Writer::ItemEncoding::ExplicitLength, false);
+        data_writer.write_data_set(message.get_data_set());
+        pdv_items.push_back(
+            pdu::PDataTF::PresentationDataValueItem(
+                transfer_syntax_it->second.first, 2, data_stream.str()));
+    }
+
+    auto pdu = std::make_shared<pdu::PDataTF>(pdv_items);
+
+    dul::EventData data;
+    data.pdu = pdu;
+
+    this->_state_machine.send_pdu(data);
+}
+
+uint16_t
+Association
+::next_message_id()
+{
+    return ++this->_next_message_id;
+}
+
+}
diff --git a/src/odil/Association.h b/src/odil/Association.h
new file mode 100644
index 0000000..6210f36
--- /dev/null
+++ b/src/odil/Association.h
@@ -0,0 +1,222 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _a52696bc_5c6e_402d_a343_6cb085eb0138
+#define _a52696bc_5c6e_402d_a343_6cb085eb0138
+
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "odil/AssociationAcceptor.h"
+#include "odil/AssociationParameters.h"
+#include "odil/dul/StateMachine.h"
+#include "odil/message/Message.h"
+
+namespace odil
+{
+
+/**
+ * @brief Association.
+ */
+class Association
+{
+public:
+    /// @brief Association result (ITU-T X.227, PS 3.8, 7.1.1.7 and PS 3.8, 9.3.4).
+    enum Result
+    {
+        Accepted=0,
+        RejectedPermanent=1,
+        RejectedTransient=2,
+    };
+
+    /// @brief Source of association result (PS 3.8, 7.1.1.8 and PS 3.8, 9.3.4).
+    enum ResultSource
+    {
+        ULServiceUser=1,
+        ULServiceProviderACSERelatedFunction=2,
+        ULServiceProvderPresentationRelatedFunction=3,
+    };
+
+    // PS 3.8, 7.1.1.9, and PS 3.8, 9.3.4, and ITU-T X.227 (UL service-user,
+    // UL service-provider ACSE-related function), ITU-T X.226 (UL
+    // service-provider presentation-related function)
+    /// @brief Diagnostic of association result
+    enum Diagnostic
+    {
+        // UL service-user
+        NoReasonGiven=1,
+        ApplicationContextNameNotSupported=2,
+        CallingAETitleNotRecognized=3,
+        CallingAPInvocationIdentifierNotRecognized=4,
+        CallingAEQualifierNotRecognized=5,
+        CallingAEInvocationIdentifierNotRecognized=6,
+        CalledAETitleNotRecognized=7,
+        CalledAPInvocationIdentifierNotRecognized=8,
+        CalledAEQualifierNotRecognized=9,
+        CalledAEInvocationIdentifierNotRecognized=10,
+
+        // UL service-provider, ACSE-related function
+        NoCommonULVersion=2,
+
+        // UL service-provider, presentation-related function
+        TemporaryCongestion=1,
+        LocalLimitExceeded=2,
+        CalledPresentationAddressUnknown=3,
+        PresentationProtocolVersionNotSupported=4,
+        NoPresentationServiceAccessPointAvailable=7,
+    };
+
+    typedef dul::StateMachine::duration_type duration_type;
+
+    /// @brief Create a default, un-associated, association.
+    Association();
+
+    /// @brief Create an un-associated association.
+    Association(Association const & other);
+
+    /// @brief Destroy the association, release it if necessary.
+    ~Association();
+
+    dul::Transport & get_transport();
+
+    /// @brief Assing an un-associated association; it remains un-associated.
+    Association & operator=(Association const & other);
+
+    /// @name Peer
+    /// @{
+
+    /// @brief Return the host name of the peer. Defaults to "".
+    std::string const & get_peer_host() const;
+    /// @brief Set the host name of the peer.
+    void set_peer_host(std::string const & host);
+
+    /// @brief Return the port of the peer. Defaults to 104.
+    uint16_t get_peer_port() const;
+    /// @brief Set the port of the peer.
+    void set_peer_port(uint16_t port);
+
+    /// @}
+
+    /// @brief Return the association parameters.
+    AssociationParameters const & get_parameters() const;
+
+    /// @brief Return the association parameters.
+    AssociationParameters & update_parameters();
+
+    /// @brief Set the association parameters, throw an exception when associated.
+    void set_parameters(AssociationParameters const & value);
+
+    /// @brief Return the negotiated association parameters.
+    AssociationParameters const & get_negotiated_parameters() const;
+
+    /// @name Timeouts
+    /// @{
+
+    /// @brief Return the TCP timeout, default to infinity.
+    duration_type get_tcp_timeout() const;
+
+    /// @brief Set the timeout.
+    void set_tcp_timeout(duration_type const & duration);
+
+    /// @brief Return the DIMSE timeout, default to 30s.
+    duration_type get_message_timeout() const;
+
+    /// @brief Set the DIMSE timeout.
+    void set_message_timeout(duration_type const & duration);
+
+    /// @}
+
+    /// @name Association
+    /// @{
+
+    /// @brief Test whether the object is currently associated to its peer.
+    bool is_associated() const;
+
+    /// @brief Request an association with the peer.
+    void associate();
+
+    /// @brief Receive an association from a peer.
+    void receive_association(
+        boost::asio::ip::tcp const & protocol, unsigned short port,
+        AssociationAcceptor acceptor=default_association_acceptor);
+
+    /// @brief Reject the received association request.
+    void reject(Result result, ResultSource result_source, Diagnostic diagnostic);
+
+    /// @brief Gracefully release the association. Throws an exception if not associated.
+    void release();
+    /// @brief Forcefully release the association. Throws an exception if not associated.
+    void abort(int source, int reason);
+
+    /// @}
+
+    /// @name DIMSE messages sending and reception.
+    /// @{
+
+    /**
+     * @brief Receive a generic DIMSE message.
+     *
+     * Throw an AssociationReleased or AssociationAborted if the peer released
+     * or aborted the association.
+     */
+    message::Message receive_message();
+
+    /// @brief Send a DIMSE message.
+    void send_message(
+        message::Message const & message, std::string const & abstract_syntax);
+
+    /// @brief Return the next available message id.
+    uint16_t next_message_id();
+
+    /// @}
+
+private:
+    dul::StateMachine _state_machine;
+
+    std::string _peer_host;
+    uint16_t _peer_port;
+
+    AssociationParameters _association_parameters;
+    AssociationParameters _negotiated_parameters;
+
+    std::map<std::string, std::pair<uint8_t, std::string>>
+        _transfer_syntaxes_by_abstract_syntax;
+    std::map<uint8_t, std::string> _transfer_syntaxes_by_id;
+
+    uint16_t _next_message_id;
+};
+
+class AssociationReleased: public Exception
+{
+public:
+    AssociationReleased()
+    : Exception("Association released")
+    {
+        // Nothing else.
+    }
+};
+
+class AssociationAborted: public Exception
+{
+public:
+    uint8_t source;
+    uint8_t reason;
+
+    AssociationAborted(unsigned char source, unsigned char reason)
+    : Exception("Association aborted"), source(source), reason(reason)
+    {
+        // Nothing else.
+    }
+};
+
+}
+
+#endif // _a52696bc_5c6e_402d_a343_6cb085eb0138
diff --git a/src/odil/AssociationAcceptor.cpp b/src/odil/AssociationAcceptor.cpp
new file mode 100644
index 0000000..bcdd8b2
--- /dev/null
+++ b/src/odil/AssociationAcceptor.cpp
@@ -0,0 +1,98 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/AssociationAcceptor.h"
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "odil/AssociationParameters.h"
+#include "odil/Exception.h"
+
+namespace odil
+{
+
+AssociationParameters
+default_association_acceptor(AssociationParameters const & input)
+{
+    AssociationParameters output;
+
+    output.set_called_ae_title(input.get_calling_ae_title());
+    output.set_calling_ae_title(input.get_called_ae_title());
+
+    std::vector<AssociationParameters::PresentationContext>
+        presentation_contexts = input.get_presentation_contexts();
+    for(auto & presentation_context: presentation_contexts)
+    {
+        presentation_context.transfer_syntaxes =
+            { presentation_context.transfer_syntaxes[0] };
+        presentation_context.result =
+            AssociationParameters::PresentationContext::Result::Acceptance;
+    }
+    output.set_presentation_contexts(presentation_contexts);
+
+    output.set_maximum_length(input.get_maximum_length());
+
+    return output;
+}
+
+AssociationRejected
+::AssociationRejected(
+    unsigned char result, unsigned char source, unsigned char reason,
+    std::string const & message)
+: Exception(message)
+{
+    this->set_result(result);
+    this->set_source(source);
+    this->set_reason(reason);
+}
+
+unsigned char
+AssociationRejected
+::get_result() const
+{
+    return this->_result;
+}
+
+void
+AssociationRejected
+::set_result(unsigned char value)
+{
+    this->_result = value;
+}
+
+unsigned char
+AssociationRejected
+::get_source() const
+{
+    return this->_source;
+}
+
+void
+AssociationRejected
+::set_source(unsigned char value)
+{
+    this->_source = value;
+}
+
+unsigned char
+AssociationRejected
+::get_reason() const
+{
+    return this->_reason;
+}
+
+void
+AssociationRejected
+::set_reason(unsigned char value)
+{
+    this->_reason = value;
+}
+
+}
diff --git a/src/odil/AssociationAcceptor.h b/src/odil/AssociationAcceptor.h
new file mode 100644
index 0000000..0603d92
--- /dev/null
+++ b/src/odil/AssociationAcceptor.h
@@ -0,0 +1,64 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _b21e4f11_9337_4612_8152_b59dedc16f18
+#define _b21e4f11_9337_4612_8152_b59dedc16f18
+
+#include <functional>
+#include <string>
+
+#include "odil/AssociationParameters.h"
+#include "odil/Exception.h"
+
+namespace odil
+{
+
+/**
+ * @brief Callback to check whether the association request is acceptable.
+ *
+ * The callback shall return the parameters for the acceptation or throw an
+ * AssociationRejected exception if the association is not acceptable.
+ */
+typedef
+    std::function<AssociationParameters(AssociationParameters const &)>
+    AssociationAcceptor;
+
+/**
+ * @brief Default association acceptor.
+ *
+ * Reverse the calling and called AE titles, accept the first transfer syntax
+ * and the roles of each presentation context, do not check identity,
+ * keep maximum length.
+ */
+AssociationParameters
+default_association_acceptor(AssociationParameters const & input);
+
+struct AssociationRejected: public Exception
+{
+public:
+    AssociationRejected(
+        unsigned char result, unsigned char source, unsigned char reason,
+        std::string const & message="Association rejected");
+
+    unsigned char get_result() const;
+    void set_result(unsigned char value);
+
+    unsigned char get_source() const;
+    void set_source(unsigned char value);
+
+    unsigned char get_reason() const;
+    void set_reason(unsigned char value);
+private:
+    unsigned char _result;
+    unsigned char _source;
+    unsigned char _reason;
+};
+
+}
+
+#endif // _b21e4f11_9337_4612_8152_b59dedc16f18
diff --git a/src/odil/AssociationParameters.cpp b/src/odil/AssociationParameters.cpp
new file mode 100644
index 0000000..ccae6ed
--- /dev/null
+++ b/src/odil/AssociationParameters.cpp
@@ -0,0 +1,457 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/AssociationParameters.h"
+
+#include <cstdint>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "odil/pdu/AAssociateAC.h"
+#include "odil/pdu/AAssociateRQ.h"
+#include "odil/Exception.h"
+#include "odil/uid.h"
+#include "odil/pdu/ImplementationClassUID.h"
+#include "odil/pdu/ImplementationVersionName.h"
+#include "odil/pdu/PresentationContextAC.h"
+#include "odil/pdu/PresentationContextRQ.h"
+#include "odil/pdu/RoleSelection.h"
+
+namespace odil
+{
+
+AssociationParameters
+::AssociationParameters()
+: _called_ae_title(""), _calling_ae_title(""), _presentation_contexts(),
+  _user_identity({UserIdentity::Type::None, "", ""}), _maximum_length(16384)
+{
+    // Nothing else.
+}
+
+AssociationParameters
+::AssociationParameters(pdu::AAssociateRQ const & pdu)
+{
+    this->set_called_ae_title(pdu.get_called_ae_title());
+    this->set_calling_ae_title(pdu.get_calling_ae_title());
+
+    auto const user_information = pdu.get_user_information();
+
+    // Presentation contexts
+    auto const & pcs_pdu = pdu.get_presentation_contexts();
+
+    std::map<std::string, std::pair<bool, bool>> roles_map;
+    auto const roles = user_information.get_sub_items<pdu::RoleSelection>();
+    for(auto const & role: roles)
+    {
+        roles_map[role.get_sop_class_uid()] =
+            std::make_pair(
+                role.get_scu_role_support(),
+                role.get_scp_role_support());
+    }
+
+    std::vector<AssociationParameters::PresentationContext> pcs_parameters;
+    pcs_parameters.reserve(pcs_pdu.size());
+    for(auto const & pc_pdu: pcs_pdu)
+    {
+        AssociationParameters::PresentationContext pc_parameters;
+
+        pc_parameters.id = pc_pdu.get_id();
+        pc_parameters.abstract_syntax = pc_pdu.get_abstract_syntax();
+        pc_parameters.transfer_syntaxes = pc_pdu.get_transfer_syntaxes();
+
+        auto const it = roles_map.find(pc_pdu.get_abstract_syntax());
+        pc_parameters.scu_role_support =
+            (it!=roles_map.end())?it->second.first:true;
+        pc_parameters.scp_role_support =
+            (it!=roles_map.end())?it->second.second:false;
+
+        pcs_parameters.push_back(pc_parameters);
+    }
+    this->set_presentation_contexts(pcs_parameters);
+
+    // User identity
+    auto const user_identity =
+        user_information.get_sub_items<pdu::UserIdentityRQ>();
+    if(!user_identity.empty())
+    {
+        if(user_identity[0].get_type() == 1)
+        {
+            this->set_user_identity_to_username(
+                user_identity[0].get_primary_field());
+        }
+        else if(user_identity[0].get_type() == 2)
+        {
+            this->set_user_identity_to_username_and_password(
+                user_identity[0].get_primary_field(),
+                user_identity[0].get_secondary_field());
+        }
+        else if(user_identity[0].get_type() == 3)
+        {
+            this->set_user_identity_to_kerberos(
+                user_identity[0].get_primary_field());
+        }
+        else if(user_identity[0].get_type() == 4)
+        {
+            this->set_user_identity_to_saml(
+                user_identity[0].get_primary_field());
+        }
+    }
+
+    // Maximum length
+    auto const maximum_length =
+        user_information.get_sub_items<pdu::MaximumLength>();
+    if(!maximum_length.empty())
+    {
+        this->set_maximum_length(maximum_length[0].get_maximum_length());
+    }
+}
+
+AssociationParameters
+::AssociationParameters(
+    pdu::AAssociateAC const & pdu, AssociationParameters const & request)
+{
+    // Calling and Called AE titles are not meaningful in A-ASSOCIATE-AC
+    this->set_called_ae_title(request.get_called_ae_title());
+    this->set_calling_ae_title(request.get_calling_ae_title());
+
+    auto const user_information = pdu.get_user_information();
+
+    // Presentation contexts
+    auto const & pcs_request = request.get_presentation_contexts();
+    std::map<uint8_t, PresentationContext> pcs_request_map;
+    for(auto const & pc: pcs_request)
+    {
+        pcs_request_map[pc.id] = pc;
+    }
+
+    auto const & pcs_pdu = pdu.get_presentation_contexts();
+
+    std::map<std::string, std::pair<bool, bool>> roles_map;
+    auto const roles = user_information.get_sub_items<pdu::RoleSelection>();
+    for(auto const & role: roles)
+    {
+        roles_map[role.get_sop_class_uid()] =
+            std::make_pair(
+                role.get_scu_role_support(),
+                role.get_scp_role_support());
+    }
+
+    std::vector<AssociationParameters::PresentationContext> pcs_parameters;
+    pcs_parameters.reserve(pcs_pdu.size());
+    for(auto const & pc_pdu: pcs_pdu)
+    {
+        AssociationParameters::PresentationContext pc_parameters;
+        auto const & pc_request = pcs_request_map.at(pc_pdu.get_id());
+
+        pc_parameters.id = pc_pdu.get_id();
+        pc_parameters.abstract_syntax = pc_request.abstract_syntax;
+        pc_parameters.transfer_syntaxes = { pc_pdu.get_transfer_syntax() };
+
+        auto const it = roles_map.find(pc_request.abstract_syntax);
+        pc_parameters.scu_role_support =
+            (it!=roles_map.end())?it->second.first:pc_request.scu_role_support;
+        pc_parameters.scp_role_support =
+            (it!=roles_map.end())?it->second.second:pc_request.scp_role_support;
+
+        pc_parameters.result =
+            static_cast<PresentationContext::Result>(pc_pdu.get_result_reason());
+
+        pcs_parameters.push_back(pc_parameters);
+    }
+    this->set_presentation_contexts(pcs_parameters);
+
+    // User identity
+    auto const user_identity =
+        user_information.get_sub_items<pdu::UserIdentityAC>();
+    if(!user_identity.empty())
+    {
+        auto const type = request.get_user_identity().type;
+        if(type == UserIdentity::Type::Kerberos)
+        {
+            this->set_user_identity_to_kerberos(
+                user_identity[0].get_server_response());
+        }
+        else if(type == UserIdentity::Type::SAML)
+        {
+            this->set_user_identity_to_saml(
+                user_identity[0].get_server_response());
+        }
+    }
+
+    // Maximum length
+    auto const maximum_length =
+        user_information.get_sub_items<pdu::MaximumLength>();
+    if(!maximum_length.empty())
+    {
+        this->set_maximum_length(maximum_length[0].get_maximum_length());
+    }
+}
+
+std::string const &
+AssociationParameters
+::get_called_ae_title() const
+{
+    return this->_called_ae_title;
+}
+
+AssociationParameters &
+AssociationParameters
+::set_called_ae_title(std::string const & value)
+{
+    if(value.size() > 16)
+    {
+        throw Exception("AE Title must be less than 16 characters");
+    }
+
+    this->_called_ae_title = value;
+    return *this;
+}
+
+std::string const &
+AssociationParameters
+::get_calling_ae_title() const
+{
+    return this->_calling_ae_title;
+}
+
+AssociationParameters &
+AssociationParameters
+::set_calling_ae_title(std::string const & value)
+{
+    if(value.size() > 16)
+    {
+        throw Exception("AE Title must be less than 16 characters");
+    }
+
+    this->_calling_ae_title = value;
+    return *this;
+}
+
+std::vector<AssociationParameters::PresentationContext> const &
+AssociationParameters
+::get_presentation_contexts() const
+{
+    return this->_presentation_contexts;
+}
+
+AssociationParameters &
+AssociationParameters
+::set_presentation_contexts(std::vector<PresentationContext> const & value)
+{
+    std::set<uint8_t> ids;
+    for(auto const context: value)
+    {
+        auto const id = context.id;
+        if(id%2 == 0)
+        {
+            throw Exception("Presentation Context ID must be odd");
+        }
+        ids.insert(id);
+    }
+    if(ids.size() != value.size())
+    {
+        throw Exception("All Presentation Context IDs must be unique");
+    }
+
+    this->_presentation_contexts = value;
+    return *this;
+}
+
+AssociationParameters::UserIdentity const &
+AssociationParameters
+::get_user_identity() const
+{
+    return this->_user_identity;
+}
+
+AssociationParameters &
+AssociationParameters
+::set_user_identity_to_none()
+{
+    return this->_set_user_identity({UserIdentity::Type::None, "", ""});
+}
+
+AssociationParameters &
+AssociationParameters
+::set_user_identity_to_username(std::string const & username)
+{
+    return this->_set_user_identity(
+        {UserIdentity::Type::Username, username, ""});
+}
+
+AssociationParameters &
+AssociationParameters
+::set_user_identity_to_username_and_password(
+    std::string const & username, std::string const & password)
+{
+    return this->_set_user_identity(
+        {UserIdentity::Type::UsernameAndPassword, username, password});
+}
+
+AssociationParameters &
+AssociationParameters
+::set_user_identity_to_kerberos(std::string const & ticket)
+{
+    return this->_set_user_identity({UserIdentity::Type::Kerberos, ticket, ""});
+}
+
+AssociationParameters &
+AssociationParameters
+::set_user_identity_to_saml(std::string const & assertion)
+{
+    return this->_set_user_identity({UserIdentity::Type::SAML, assertion, ""});
+}
+
+uint32_t
+AssociationParameters
+::get_maximum_length() const
+{
+    return this->_maximum_length;
+}
+
+AssociationParameters &
+AssociationParameters
+::set_maximum_length(uint32_t value)
+{
+    this->_maximum_length = value;
+    return *this;
+}
+
+pdu::AAssociateRQ
+AssociationParameters
+::as_a_associate_rq() const
+{
+    pdu::AAssociateRQ pdu;
+    pdu.set_protocol_version(1);
+    pdu.set_application_context(std::string("1.2.840.10008.3.1.1.1"));
+    pdu.set_called_ae_title(this->get_called_ae_title());
+    pdu.set_calling_ae_title(this->get_calling_ae_title());
+
+    // Presentation contexts
+    {
+        auto const & source = this->get_presentation_contexts();
+
+        std::vector<pdu::PresentationContextRQ> destination;
+        destination.reserve(source.size());
+
+        for(auto const & source_pc: source)
+        {
+            pdu::PresentationContextRQ const pc(
+                source_pc.id, source_pc.abstract_syntax,
+                source_pc.transfer_syntaxes);
+            destination.push_back(pc);
+        }
+
+        pdu.set_presentation_contexts(destination);
+    }
+
+    pdu::UserInformation user_information;
+
+    user_information.set_sub_items<pdu::MaximumLength>(
+        {this->get_maximum_length()});
+
+    user_information.set_sub_items<pdu::ImplementationClassUID>(
+        {implementation_class_uid});
+    user_information.set_sub_items<pdu::ImplementationVersionName>(
+        {implementation_version_name});
+
+    std::vector<pdu::RoleSelection> roles;
+    for(auto const & presentation_context: this->get_presentation_contexts())
+    {
+        pdu::RoleSelection const role(
+            presentation_context.abstract_syntax,
+            presentation_context.scu_role_support,
+            presentation_context.scp_role_support);
+        roles.push_back(role);
+    }
+    user_information.set_sub_items(roles);
+
+    auto const & user_identity = this->get_user_identity();
+    if(user_identity.type != AssociationParameters::UserIdentity::Type::None)
+    {
+        pdu::UserIdentityRQ sub_item;
+        sub_item.set_type(static_cast<int>(user_identity.type));
+        sub_item.set_primary_field(user_identity.primary_field);
+        sub_item.set_secondary_field(user_identity.secondary_field);
+
+        // TODO
+        sub_item.set_positive_response_requested(true);
+
+        user_information.set_sub_items<pdu::UserIdentityRQ>({sub_item});
+    }
+
+    pdu.set_user_information(user_information);
+
+    return pdu;
+}
+
+pdu::AAssociateAC
+AssociationParameters
+::as_a_associate_ac() const
+{
+    pdu::AAssociateAC pdu;
+    pdu.set_protocol_version(1);
+    pdu.set_application_context(std::string("1.2.840.10008.3.1.1.1"));
+    pdu.set_called_ae_title(this->get_called_ae_title());
+    pdu.set_calling_ae_title(this->get_calling_ae_title());
+
+    // Presentation contexts
+    {
+        auto const & source = this->get_presentation_contexts();
+
+        std::vector<pdu::PresentationContextAC> destination;
+        destination.reserve(source.size());
+
+        for(auto const & source_pc: source)
+        {
+            pdu::PresentationContextAC const pc(
+                source_pc.id, source_pc.transfer_syntaxes[0],
+                static_cast<uint8_t>(source_pc.result));
+            destination.push_back(pc);
+        }
+
+        pdu.set_presentation_contexts(destination);
+    }
+
+    pdu::UserInformation user_information;
+
+    user_information.set_sub_items<pdu::MaximumLength>(
+        {this->get_maximum_length()});
+
+    user_information.set_sub_items<pdu::ImplementationClassUID>(
+        {implementation_class_uid});
+    user_information.set_sub_items<pdu::ImplementationVersionName>(
+        {implementation_version_name});
+
+    std::vector<pdu::RoleSelection> roles;
+    for(auto const & presentation_context: this->get_presentation_contexts())
+    {
+        pdu::RoleSelection const role(
+            presentation_context.abstract_syntax,
+            presentation_context.scu_role_support,
+            presentation_context.scp_role_support);
+        roles.push_back(role);
+    }
+    user_information.set_sub_items(roles);
+
+    pdu.set_user_information(user_information);
+
+    return pdu;
+}
+
+AssociationParameters &
+AssociationParameters
+::_set_user_identity(UserIdentity const & value)
+{
+    this->_user_identity = value;
+    return *this;
+}
+
+}
diff --git a/src/odil/AssociationParameters.h b/src/odil/AssociationParameters.h
new file mode 100644
index 0000000..c251834
--- /dev/null
+++ b/src/odil/AssociationParameters.h
@@ -0,0 +1,156 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _061fafd4_982e_4a7e_9eb0_29e06443ebf3
+#define _061fafd4_982e_4a7e_9eb0_29e06443ebf3
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "odil/pdu/AAssociateAC.h"
+#include "odil/pdu/AAssociateRQ.h"
+
+namespace odil
+{
+
+/// @brief Encapsulate association parameters
+class AssociationParameters
+{
+public:
+    /**
+     * @brief Presentation Context, cf. PS 3.8, 9.3.2.2, PS 3.8, 9.3.3.2,
+     * PS 3.7, D.3.3.4.1 and PS 3.7 D.3.3.4.2.
+     */
+    struct PresentationContext
+    {
+        enum class Result
+        {
+            Acceptance = 0,
+            UserRejection = 1,
+            NoReason = 2,
+            AbstractSyntaxNotSupported = 3,
+            TransferSyntaxesNotSupported = 4,
+        };
+
+        uint8_t id;
+        std::string abstract_syntax;
+        std::vector<std::string> transfer_syntaxes;
+        bool scu_role_support;
+        bool scp_role_support;
+        Result result;
+    };
+
+    /// @brief User Identity, cf. PS3.8 D.3.3.7
+    struct UserIdentity
+    {
+        enum class Type
+        {
+            None = 0,
+            Username = 1,
+            UsernameAndPassword = 2,
+            Kerberos = 3,
+            SAML = 4
+        };
+
+        Type type;
+        std::string primary_field;
+        std::string secondary_field;
+    };
+
+    /// @brief Constructor.
+    AssociationParameters();
+
+    /// @brief Constructor from an A-ASSOCIATE-RQ PDU.
+    AssociationParameters(pdu::AAssociateRQ const & pdu);
+
+    /// @brief Constructor from an A-ASSOCIATE-RQ PDU.
+    AssociationParameters(
+        pdu::AAssociateAC const & pdu, AssociationParameters const & request);
+
+    /// @brief Return the called AE title, default to empty.
+    std::string const & get_called_ae_title() const;
+
+    /**
+     * @brief Set the called AE title.
+     *
+     * An exception is raised if the value is empty or if it is longer than
+     * 16 characters.
+     */
+    AssociationParameters & set_called_ae_title(std::string const & value);
+
+    /// @brief Return the calling AE title, default to empty.
+    std::string const & get_calling_ae_title() const;
+
+    /**
+     * @brief Set the calling AE title.
+     *
+     * An exception is raised if the value is empty or if it is longer than
+     * 16 characters.
+     */
+    AssociationParameters & set_calling_ae_title(std::string const & value);
+
+    /// @brief Return the presentation contexts, default to empty.
+    std::vector<PresentationContext> const & get_presentation_contexts() const;
+
+    /// @brief Set the presentation contexts. All ids must be odd and unique.
+    AssociationParameters &
+    set_presentation_contexts(std::vector<PresentationContext> const & value);
+
+    /// @brief Return the user identity, default to None.
+    UserIdentity const & get_user_identity() const;
+
+    /// @brief Do no authenticate user.
+    AssociationParameters & set_user_identity_to_none();
+
+    /// @brief Authenticate user using only a username.
+    AssociationParameters &
+    set_user_identity_to_username(std::string const & username);
+
+    /// @brief Authenticate user using a username and a password.
+    AssociationParameters &
+    set_user_identity_to_username_and_password(
+        std::string const & username, std::string const & password);
+
+    /// @brief Authenticate user using a Kerberos ticket.
+    AssociationParameters &
+    set_user_identity_to_kerberos(std::string const & ticket);
+
+    /// @brief Authenticate user using a SAML assertion.
+    AssociationParameters &
+    set_user_identity_to_saml(std::string const & assertion);
+
+    /// @brief Return the maximum length of a PDU, default to 16384.
+    uint32_t get_maximum_length() const;
+
+    /**
+     * @brief Set the maximum length of a PDU, the value 0 meaning
+     * no maximum length.
+     */
+    AssociationParameters & set_maximum_length(uint32_t value);
+
+    /// @brief Create an A-ASSOCIATE-RQ PDU.
+    pdu::AAssociateRQ as_a_associate_rq() const;
+
+    /// @brief Create an A-ASSOCIATE-AC PDU.
+    pdu::AAssociateAC as_a_associate_ac() const;
+
+private:
+    std::string _called_ae_title;
+    std::string _calling_ae_title;
+    std::vector<PresentationContext> _presentation_contexts;
+    UserIdentity _user_identity;
+    uint32_t _maximum_length;
+
+    /// @brief Set the user identity.
+    AssociationParameters & _set_user_identity(UserIdentity const & value);
+};
+
+}
+
+#endif // _061fafd4_982e_4a7e_9eb0_29e06443ebf3
diff --git a/src/dcmtkpp/BasicDirectoryCreator.cpp b/src/odil/BasicDirectoryCreator.cpp
similarity index 96%
rename from src/dcmtkpp/BasicDirectoryCreator.cpp
rename to src/odil/BasicDirectoryCreator.cpp
index 3730b81..b3c5aa8 100644
--- a/src/dcmtkpp/BasicDirectoryCreator.cpp
+++ b/src/odil/BasicDirectoryCreator.cpp
@@ -1,18 +1,19 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/BasicDirectoryCreator.h"
+#include "odil/BasicDirectoryCreator.h"
 
 #include <algorithm>
 #include <fstream>
 #include <iterator>
 #include <map>
 #include <memory>
+#include <sstream>
 #include <stack>
 #include <string>
 #include <utility>
@@ -21,14 +22,14 @@
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Reader.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/uid.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/Reader.h"
+#include "odil/registry.h"
+#include "odil/Tag.h"
+#include "odil/uid.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 std::map<std::string, std::vector<BasicDirectoryCreator::RecordKey>> const
@@ -59,8 +60,7 @@ BasicDirectoryCreator
 BasicDirectoryCreator
 ::BasicDirectoryCreator(
     std::string const & root, std::vector<std::string> const & files,
-    std::map<std::string, std::vector<RecordKey>> const & extra_record_keys,
-    Writer::ItemEncoding item_encoding)
+    RecordKeyMap const & extra_record_keys, Writer::ItemEncoding item_encoding)
 : root(root), files(files), extra_record_keys(extra_record_keys),
     item_encoding(item_encoding)
 {
@@ -227,7 +227,7 @@ BasicDirectoryCreator
         }
     }
 
-    registry::RecordInUseFlag;
+    // TODO: registry::RecordInUseFlag;
 }
 
 BasicDirectoryCreator::LinearizedTree
@@ -368,8 +368,7 @@ BasicDirectoryCreator
     meta_information.add(
         registry::ImplementationClassUID, { implementation_class_uid });
     meta_information.add(
-        registry::ImplementationVersionName,
-        { "DCMTK++ " DCMTKPP_STRINGIFY(DCMTKPP_MAJOR_VERSION) });
+        registry::ImplementationVersionName, { implementation_version_name });
 
     DataSet basic_directory;
 
diff --git a/src/dcmtkpp/BasicDirectoryCreator.h b/src/odil/BasicDirectoryCreator.h
similarity index 87%
rename from src/dcmtkpp/BasicDirectoryCreator.h
rename to src/odil/BasicDirectoryCreator.h
index c441665..bd745f0 100644
--- a/src/dcmtkpp/BasicDirectoryCreator.h
+++ b/src/odil/BasicDirectoryCreator.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -15,21 +15,25 @@
 #include <utility>
 #include <vector>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/Writer.h"
+#include "odil/DataSet.h"
+#include "odil/Tag.h"
+#include "odil/Writer.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief Write a Basic Directory (i.e. DICOMDIR) object to the disk.
 class BasicDirectoryCreator
 {
 public:
+    /// @brief The tag and its associated type in the record.
     typedef std::pair<Tag, int> RecordKey;
 
+    /// @brief Map from a record type to the extra keys.
+    typedef std::map<std::string, std::vector<RecordKey>> RecordKeyMap;
+
     /// @brief Default record keys, classified by record type.
-    static std::map<std::string, std::vector<RecordKey>> const default_record_keys;
+    static RecordKeyMap const default_record_keys;
 
     /**
      * @brief Root of the DICOM files and location of the output DICOMDIR file.
@@ -47,7 +51,7 @@ public:
     std::vector<std::string> files;
 
     /// @brief User-defined record keys, classified by record type.
-    std::map<std::string, std::vector<RecordKey>> extra_record_keys;
+    RecordKeyMap extra_record_keys;
 
     /// @brief Encoding of sequence items, defaults to Writer::ItemEncoding::ExplicitLength.
     Writer::ItemEncoding item_encoding;
@@ -55,8 +59,7 @@ public:
     BasicDirectoryCreator(
         std::string const & root="",
         std::vector<std::string> const & files=std::vector<std::string>(),
-        std::map<std::string, std::vector<RecordKey>> const & extra_record_keys=
-            std::map<std::string, std::vector<RecordKey>>(),
+        RecordKeyMap const & extra_record_keys=RecordKeyMap(),
         Writer::ItemEncoding item_encoding=Writer::ItemEncoding::ExplicitLength
         );
 
diff --git a/src/dcmtkpp/DataSet.cpp b/src/odil/DataSet.cpp
similarity index 92%
rename from src/dcmtkpp/DataSet.cpp
rename to src/odil/DataSet.cpp
index 82f70f8..4a3cbd4 100644
--- a/src/dcmtkpp/DataSet.cpp
+++ b/src/odil/DataSet.cpp
@@ -1,12 +1,12 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/DataSet.h"
+#include "odil/DataSet.h"
 
 #include <cstddef>
 #include <cstdint>
@@ -14,11 +14,11 @@
 #include <string>
 #include <vector>
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/VR.h"
+#include "odil/Exception.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 DataSet
@@ -46,19 +46,19 @@ DataSet
         vr = as_vr(tag);
     }
 
-    if(::dcmtkpp::is_int(vr))
+    if(::odil::is_int(vr))
     {
         value = Value::Integers();
     }
-    else if(::dcmtkpp::is_real(vr))
+    else if(::odil::is_real(vr))
     {
         value = Value::Reals();
     }
-    else if(::dcmtkpp::is_string(vr))
+    else if(::odil::is_string(vr))
     {
         value = Value::Strings();
     }
-    else if(::dcmtkpp::is_binary(vr))
+    else if(::odil::is_binary(vr))
     {
         value = Value::Binary();
     }
@@ -68,7 +68,7 @@ DataSet
     }
     else
     {
-        throw Exception("Unknown VR: "+::dcmtkpp::as_string(vr));
+        throw Exception("Unknown VR: "+::odil::as_string(vr));
     }
 
     this->add(tag, Element(value, vr));
diff --git a/src/dcmtkpp/DataSet.h b/src/odil/DataSet.h
similarity index 83%
rename from src/dcmtkpp/DataSet.h
rename to src/odil/DataSet.h
index 2114f86..9924185 100644
--- a/src/dcmtkpp/DataSet.h
+++ b/src/odil/DataSet.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -16,13 +16,13 @@
 #include <string>
 #include <vector>
 
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Value.h"
+#include "odil/Element.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
-#define dcmtkppElementTypeMacro(name, Type) \
+#define odilElementTypeMacro(name, Type) \
 bool is_##name(Tag const & tag) const \
 { \
     auto const it = this->_elements.find(tag); \
@@ -41,7 +41,7 @@ Value::Type const & as_##name(Tag const & tag) const \
     } \
     return it->second.as_##name(); \
 } \
-Value::Type::value_type const & as_##name(Tag const & tag, int position) const \
+Value::Type::value_type const & as_##name(Tag const & tag, unsigned int position) const \
 { \
     auto const & data = this->as_##name(tag); \
     if(data.size() <= position) \
@@ -123,7 +123,7 @@ public:
     /**
      * @brief Remove an element from the data set.
      *
-     * If the element is not in the data set, a dcmtkpp::Exception is raised.
+     * If the element is not in the data set, a odil::Exception is raised.
      */
     void remove(Tag const & tag);
 
@@ -139,50 +139,48 @@ public:
     /**
      * @brief Return the VR of an element in the data set.
      *
-     * If the element is not in the data set, a dcmtkpp::Exception is raised.
+     * If the element is not in the data set, a odil::Exception is raised.
      */
     VR get_vr(Tag const & tag) const;
 
     /**
      * @brief Test whether an element of the data set is empty.
      *
-     * If the element is not in the data set, a dcmtkpp::Exception is raised.
+     * If the element is not in the data set, a odil::Exception is raised.
      */
     bool empty(Tag const & tag) const;
 
     /**
      * @brief Return the number of values in an element of the data set.
      *
-     * If the element is not in the data set, a dcmtkpp::Exception is raised.
+     * If the element is not in the data set, a odil::Exception is raised.
      */
     std::size_t size(Tag const & tag) const;
 
     /**
      * @brief Access the given element.
      *
-     * If the element is not in the data set, a dcmtkpp::Exception is raised.
+     * If the element is not in the data set, a odil::Exception is raised.
      */
     Element const & operator[](Tag const & tag) const;
 
     /**
      * @brief Access the given element.
      *
-     * If the element is not in the data set, a dcmtkpp::Exception is raised.
+     * If the element is not in the data set, a odil::Exception is raised.
      */
     Element & operator[](Tag const & tag);
 
-    dcmtkppElementTypeMacro(int, Integers);
-    dcmtkppElementTypeMacro(real, Reals);
-    dcmtkppElementTypeMacro(string, Strings);
-    dcmtkppElementTypeMacro(data_set, DataSets);
-    dcmtkppElementTypeMacro(binary, Binary);
+    odilElementTypeMacro(int, Integers);
+    odilElementTypeMacro(real, Reals);
+    odilElementTypeMacro(string, Strings);
+    odilElementTypeMacro(data_set, DataSets);
+    odilElementTypeMacro(binary, Binary);
 
     typedef std::map<Tag, Element>::const_iterator const_iterator;
     const_iterator begin() const { return this->_elements.begin(); }
     const_iterator end() const { return this->_elements.end(); }
 
-    // FIXME: AT, binary
-
     /// @brief Equality test
     bool operator==(DataSet const & other) const;
 
diff --git a/src/odil/EchoSCP.cpp b/src/odil/EchoSCP.cpp
new file mode 100644
index 0000000..f663c77
--- /dev/null
+++ b/src/odil/EchoSCP.cpp
@@ -0,0 +1,82 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/EchoSCP.h"
+
+#include <functional>
+
+#include "odil/Association.h"
+#include "odil/SCP.h"
+#include "odil/Value.h"
+#include "odil/message/CEchoRequest.h"
+#include "odil/message/CEchoResponse.h"
+#include "odil/message/Message.h"
+
+namespace odil
+{
+
+EchoSCP
+::EchoSCP(Association & association)
+: SCP(association), _callback()
+{
+    // Nothing else.
+}
+
+EchoSCP
+::EchoSCP(Association & association, Callback const & callback)
+: SCP(association), _callback()
+{
+    this->set_callback(callback);
+}
+
+EchoSCP
+::~EchoSCP()
+{
+    // Nothing to do.
+}
+
+EchoSCP::Callback const &
+EchoSCP::get_callback() const
+{
+    return this->_callback;
+}
+
+void
+EchoSCP
+::set_callback(Callback const & callback)
+{
+    this->_callback = callback;
+}
+
+void
+EchoSCP
+::operator()(message::Message const & message)
+{
+    message::CEchoRequest const request(message);
+
+    Value::Integer status=message::CEchoResponse::Success;
+
+    try
+    {
+        status = this->_callback(request);
+    }
+    catch(Exception const &)
+    {
+        status = message::CEchoResponse::ProcessingFailure;
+        // Error Comment
+        // Error ID
+        // Affected SOP Class UID
+    }
+
+    message::CEchoResponse const response(
+        request.get_message_id(), status, request.get_affected_sop_class_uid());
+    this->_association.send_message(
+        response, request.get_affected_sop_class_uid());
+}
+
+}
diff --git a/src/odil/EchoSCP.h b/src/odil/EchoSCP.h
new file mode 100644
index 0000000..ef9680b
--- /dev/null
+++ b/src/odil/EchoSCP.h
@@ -0,0 +1,54 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _1a61d976_ba12_4dba_af34_67f064d38506
+#define _1a61d976_ba12_4dba_af34_67f064d38506
+
+#include <functional>
+
+#include "odil/Association.h"
+#include "odil/SCP.h"
+#include "odil/Value.h"
+#include "odil/message/CEchoRequest.h"
+#include "odil/message/Message.h"
+
+namespace odil
+{
+
+/// @brief SCP for C-Echo services.
+class EchoSCP: public SCP
+{
+public:
+    /// @brief Callback called when a request is received.
+    typedef std::function<Value::Integer(message::CEchoRequest const &)> Callback;
+
+    /// @brief Constructor.
+    EchoSCP(Association & association);
+
+    /// @brief Constructor.
+    EchoSCP(Association & association, Callback const & callback);
+
+    /// @brief Destructor.
+    virtual ~EchoSCP();
+
+    /// @brief Return the callback.
+    Callback const & get_callback() const;
+
+    /// @brief Set the callback.
+    void set_callback(Callback const & callback);
+
+    /// @brief Process a C-Echo request.
+    virtual void operator()(message::Message const & message);
+
+private:
+    Callback _callback;
+};
+
+}
+
+#endif // _1a61d976_ba12_4dba_af34_67f064d38506
diff --git a/src/dcmtkpp/Element.cpp b/src/odil/Element.cpp
similarity index 95%
rename from src/dcmtkpp/Element.cpp
rename to src/odil/Element.cpp
index ab58fd7..4ac0d7d 100644
--- a/src/dcmtkpp/Element.cpp
+++ b/src/odil/Element.cpp
@@ -1,17 +1,17 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/Element.h"
+#include "odil/Element.h"
 
-#include "dcmtkpp/Value.h"
-#include "dcmtkpp/DataSet.h"
+#include "odil/Value.h"
+#include "odil/DataSet.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 Element
diff --git a/src/dcmtkpp/Element.h b/src/odil/Element.h
similarity index 83%
rename from src/dcmtkpp/Element.h
rename to src/odil/Element.h
index 9959c3b..25774da 100644
--- a/src/dcmtkpp/Element.h
+++ b/src/odil/Element.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -12,11 +12,11 @@
 #include <cstddef>
 #include <initializer_list>
 
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/Value.h"
-#include "dcmtkpp/VR.h"
+#include "odil/Tag.h"
+#include "odil/Value.h"
+#include "odil/VR.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /**
@@ -85,14 +85,14 @@ public:
     /**
      * @brief Return the integers contained in the element.
      *
-     * If the element does not contain integers, a dcmtkpp::Exception is raised.
+     * If the element does not contain integers, a odil::Exception is raised.
      */
     Value::Integers const & as_int() const;
 
     /**
      * @brief Return the integers contained in the element.
      *
-     * If the element does not contain integers, a dcmtkpp::Exception is raised.
+     * If the element does not contain integers, a odil::Exception is raised.
      */
     Value::Integers & as_int();
 
@@ -102,14 +102,14 @@ public:
     /**
      * @brief Return the reals contained in the element.
      *
-     * If the element does not contain reals, a dcmtkpp::Exception is raised.
+     * If the element does not contain reals, a odil::Exception is raised.
      */
     Value::Reals const & as_real() const;
 
     /**
      * @brief Return the reals contained in the element.
      *
-     * If the element does not contain reals, a dcmtkpp::Exception is raised.
+     * If the element does not contain reals, a odil::Exception is raised.
      */
     Value::Reals & as_real();
 
@@ -119,14 +119,14 @@ public:
     /**
      * @brief Return the strings contained in the element.
      *
-     * If the element does not contain strings, a dcmtkpp::Exception is raised.
+     * If the element does not contain strings, a odil::Exception is raised.
      */
     Value::Strings const & as_string() const;
 
     /**
      * @brief Return the strings contained in the element.
      *
-     * If the element does not contain strings, a dcmtkpp::Exception is raised.
+     * If the element does not contain strings, a odil::Exception is raised.
      */
     Value::Strings & as_string();
 
@@ -136,14 +136,14 @@ public:
     /**
      * @brief Return the data sets contained in the element.
      *
-     * If the element does not contain data sets, a dcmtkpp::Exception is raised.
+     * If the element does not contain data sets, a odil::Exception is raised.
      */
     Value::DataSets const & as_data_set() const;
 
     /**
      * @brief Return the data sets contained in the element.
      *
-     * If the element does not contain data sets, a dcmtkpp::Exception is raised.
+     * If the element does not contain data sets, a odil::Exception is raised.
      */
     Value::DataSets & as_data_set();
 
@@ -153,14 +153,14 @@ public:
     /**
      * @brief Return the binary data contained in the element.
      *
-     * If the element does not contain binary data, a dcmtkpp::Exception is raised.
+     * If the element does not contain binary data, a odil::Exception is raised.
      */
     Value::Binary const & as_binary() const;
 
     /**
      * @brief Return the binary data contained in the element.
      *
-     * If the element does not contain binary data, a dcmtkpp::Exception is raised.
+     * If the element does not contain binary data, a odil::Exception is raised.
      */
     Value::Binary & as_binary();
 
@@ -207,6 +207,6 @@ apply_visitor(TVisitor const & visitor, Element const & element);
 
 }
 
-#include "dcmtkpp/Element.txx"
+#include "odil/Element.txx"
 
 #endif // _9c3d8f32_0310_4e3a_b5d2_6d69f229a2cf
diff --git a/src/dcmtkpp/Element.txx b/src/odil/Element.txx
similarity index 91%
rename from src/dcmtkpp/Element.txx
rename to src/odil/Element.txx
index 2ec2f54..9580c00 100644
--- a/src/dcmtkpp/Element.txx
+++ b/src/odil/Element.txx
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,13 +9,13 @@
 #ifndef _feb2071a_de26_4adb_9305_59d0dd64c970
 #define _feb2071a_de26_4adb_9305_59d0dd64c970
 
-#include "dcmtkpp/Element.h"
+#include "odil/Element.h"
 
 #include <vector>
 
-#include "dcmtkpp/Exception.h"
+#include "odil/Exception.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 template<typename TVisitor>
diff --git a/src/dcmtkpp/ElementsDictionary.cpp b/src/odil/ElementsDictionary.cpp
similarity index 84%
rename from src/dcmtkpp/ElementsDictionary.cpp
rename to src/odil/ElementsDictionary.cpp
index 6ee89c5..c570967 100644
--- a/src/dcmtkpp/ElementsDictionary.cpp
+++ b/src/odil/ElementsDictionary.cpp
@@ -1,16 +1,16 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/ElementsDictionary.h"
+#include "odil/ElementsDictionary.h"
 
 #include <string>
 
-namespace dcmtkpp
+namespace odil
 {
 
 ElementsDictionaryEntry
diff --git a/src/dcmtkpp/ElementsDictionary.h b/src/odil/ElementsDictionary.h
similarity index 90%
rename from src/dcmtkpp/ElementsDictionary.h
rename to src/odil/ElementsDictionary.h
index 0589f14..c2336df 100644
--- a/src/dcmtkpp/ElementsDictionary.h
+++ b/src/odil/ElementsDictionary.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -12,9 +12,9 @@
 #include <map>
 #include <string>
 
-#include <dcmtkpp/Tag.h>
+#include <odil/Tag.h>
 
-namespace dcmtkpp
+namespace odil
 {
 
 /**
diff --git a/src/dcmtkpp/UIDsDictionary.cpp b/src/odil/Exception.cpp
similarity index 55%
copy from src/dcmtkpp/UIDsDictionary.cpp
copy to src/odil/Exception.cpp
index 35f4559..ce8ae19 100644
--- a/src/dcmtkpp/UIDsDictionary.cpp
+++ b/src/odil/Exception.cpp
@@ -1,26 +1,37 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/UIDsDictionary.h"
+#include "odil/Exception.h"
 
-#include <map>
+#include <stdexcept>
 #include <string>
 
-namespace dcmtkpp
+namespace odil
 {
 
-UIDsDictionaryEntry
-::UIDsDictionaryEntry(
-        std::string const & name, std::string const & keyword,
-        std::string const & type)
-: name(name), keyword(keyword), type(type)
+Exception
+::Exception(std::string const & message)
+: _message(message)
 {
     // Nothing else.
 }
 
+Exception
+::~Exception() throw()
+{
+    // Nothing to do.
+}
+
+char const *
+Exception
+::what() const throw()
+{
+    return this->_message.c_str();
+}
+
 }
diff --git a/src/odil/Exception.h b/src/odil/Exception.h
new file mode 100644
index 0000000..8c4b14e
--- /dev/null
+++ b/src/odil/Exception.h
@@ -0,0 +1,37 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _b9607695_cb3b_4188_8caa_bc8bb051ef28
+#define _b9607695_cb3b_4188_8caa_bc8bb051ef28
+
+#include <exception>
+#include <string>
+
+namespace odil
+{
+
+/// @brief Base class for odil exceptions.
+class Exception: public std::exception
+{
+public: 
+    /// @brief Message string constructor.
+    Exception(std::string const & message="");
+
+    /// @brief Destructor.
+    virtual ~Exception() throw();
+    
+    /// @brief Return the reason for the exception.
+    virtual const char* what() const throw();
+
+protected:
+    std::string _message;
+};
+
+}
+
+#endif // _b9607695_cb3b_4188_8caa_bc8bb051ef28
diff --git a/src/odil/FindSCP.cpp b/src/odil/FindSCP.cpp
new file mode 100644
index 0000000..bc7badb
--- /dev/null
+++ b/src/odil/FindSCP.cpp
@@ -0,0 +1,94 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/FindSCP.h"
+
+#include <memory>
+
+#include "odil/Association.h"
+#include "odil/Exception.h"
+#include "odil/SCP.h"
+#include "odil/message/CFindRequest.h"
+#include "odil/message/CFindResponse.h"
+#include "odil/message/Response.h"
+
+namespace odil
+{
+
+FindSCP
+::FindSCP(Association & association)
+: SCP(association), _generator(nullptr)
+{
+    // Nothing else.
+}
+
+FindSCP
+::FindSCP(
+    Association & association,
+    std::shared_ptr<DataSetGenerator> const & generator)
+: SCP(association), _generator(nullptr)
+{
+    this->set_generator(generator);
+}
+
+FindSCP
+::~FindSCP()
+{
+    // Nothing to do.
+}
+
+SCP::DataSetGenerator const &
+FindSCP
+::get_generator() const
+{
+    return *this->_generator;
+}
+
+void
+FindSCP
+::set_generator(std::shared_ptr<DataSetGenerator> const & generator)
+{
+    this->_generator = generator;
+}
+
+void
+FindSCP
+::operator()(message::Message const & message)
+{
+    message::CFindRequest const request(message);
+
+    try
+    {
+        this->_generator->initialize(request);
+        while(!this->_generator->done())
+        {
+            auto const data_set = this->_generator->get();
+            message::CFindResponse const response(
+                request.get_message_id(), message::CFindResponse::Pending,
+                data_set);
+            this->_association.send_message(
+                response, request.get_affected_sop_class_uid());
+            this->_generator->next();
+        }
+    }
+    catch(Exception const & e)
+    {
+        message::CFindResponse response(
+            request.get_message_id(), message::CFindResponse::UnableToProcess);
+        this->_association.send_message(
+            response, request.get_affected_sop_class_uid());
+        return;
+    }
+
+    message::CFindResponse response(
+        request.get_message_id(), message::CFindResponse::Success);
+    this->_association.send_message(
+        response, request.get_affected_sop_class_uid());
+}
+
+}
diff --git a/src/odil/FindSCP.h b/src/odil/FindSCP.h
new file mode 100644
index 0000000..eb61994
--- /dev/null
+++ b/src/odil/FindSCP.h
@@ -0,0 +1,52 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _d54d5d3d_791c_43e5_b13a_397954053963
+#define _d54d5d3d_791c_43e5_b13a_397954053963
+
+#include <memory>
+
+#include "odil/Association.h"
+#include "odil/SCP.h"
+#include "odil/message/Message.h"
+
+namespace odil
+{
+
+/// @brief SCP for C-Find services.
+class FindSCP: public SCP
+{
+public:
+
+    /// @brief Constructor.
+    FindSCP(Association & association);
+
+    /// @brief Constructor.
+    FindSCP(
+        Association & association,
+        std::shared_ptr<DataSetGenerator> const & generator);
+
+    /// @brief Destructor.
+    virtual ~FindSCP();
+
+    /// @brief Return the generator.
+    DataSetGenerator const & get_generator() const;
+
+    /// @brief Set the generator.
+    void set_generator(std::shared_ptr<DataSetGenerator> const & generator);
+
+    /// @brief Process a C-Find request.
+    virtual void operator()(message::Message const & message);
+
+private:
+    std::shared_ptr<DataSetGenerator> _generator;
+};
+
+}
+
+#endif // _d54d5d3d_791c_43e5_b13a_397954053963
diff --git a/src/dcmtkpp/FindSCU.cpp b/src/odil/FindSCU.cpp
similarity index 72%
rename from src/dcmtkpp/FindSCU.cpp
rename to src/odil/FindSCU.cpp
index e5650f0..1c928ae 100644
--- a/src/dcmtkpp/FindSCU.cpp
+++ b/src/odil/FindSCU.cpp
@@ -1,27 +1,32 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/FindSCU.h"
+#include "odil/FindSCU.h"
 
 #include <functional>
 #include <sstream>
 #include <vector>
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/message/CFindRequest.h"
+#include "odil/message/CFindResponse.h"
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/CFindRequest.h"
-#include "dcmtkpp/CFindResponse.h"
+namespace odil
+{
 
-namespace dcmtkpp
+FindSCU
+::FindSCU(Association & asociation)
+: SCU(asociation)
 {
+    // Nothing else.
+}
 
 FindSCU
 ::~FindSCU()
@@ -29,21 +34,22 @@ FindSCU
     // Nothing to do
 }
 
-void 
+void
 FindSCU
 ::find(DataSet const & query, Callback callback) const
 {
-    CFindRequest request(
-        this->_association->get_association()->nextMsgID++,
-        this->_affected_sop_class, Message::Priority::MEDIUM, query);
-    this->_send(request, this->_affected_sop_class);
+    message::CFindRequest request(
+        this->_association.next_message_id(),
+        this->_affected_sop_class, message::Message::Priority::MEDIUM, query);
+    this->_association.send_message(request, this->_affected_sop_class);
 
     // Receive the responses
     bool done = false;
     while(!done)
     {
         // FIXME: include progress callback
-        auto response = this->_receive<CFindResponse>();
+        message::CFindResponse const response =
+            this->_association.receive_message();
 
         if(response.get_message_id_being_responded_to() != request.get_message_id())
         {
@@ -63,7 +69,7 @@ FindSCU
             throw Exception(message.str());
         }
 
-        done = !DICOM_PENDING_STATUS(response.get_status());
+        done = !response.is_pending();
         if(!done)
         {
             callback(response.get_data_set());
@@ -80,7 +86,7 @@ FindSCU
         result.push_back(dataset);
     };
     this->find(query, callback);
-    
+
     return result;
 }
 
diff --git a/src/dcmtkpp/FindSCU.h b/src/odil/FindSCU.h
similarity index 85%
rename from src/dcmtkpp/FindSCU.h
rename to src/odil/FindSCU.h
index f1fa84e..4cd30ea 100644
--- a/src/dcmtkpp/FindSCU.h
+++ b/src/odil/FindSCU.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -11,10 +11,11 @@
 
 #include <vector>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/SCU.h"
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/SCU.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief SCU for C-FIND services.
@@ -23,6 +24,8 @@ class FindSCU: public SCU
 public:
     /// @brief Callback called when a response is received.
     typedef std::function<void(DataSet const &)> Callback;
+
+    FindSCU(Association & association);
     
     /// @brief Destructor.
     virtual ~FindSCU();
diff --git a/src/odil/GetSCP.cpp b/src/odil/GetSCP.cpp
new file mode 100644
index 0000000..a72262c
--- /dev/null
+++ b/src/odil/GetSCP.cpp
@@ -0,0 +1,141 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/GetSCP.h"
+
+#include <memory>
+
+#include "odil/Association.h"
+#include "odil/Exception.h"
+#include "odil/SCP.h"
+#include "odil/StoreSCU.h"
+#include "odil/message/CGetRequest.h"
+#include "odil/message/CGetResponse.h"
+#include "odil/message/Response.h"
+
+namespace odil
+{
+
+GetSCP
+::GetSCP(Association & association)
+: SCP(association), _generator(nullptr)
+{
+    // Nothing else.
+}
+
+GetSCP
+::GetSCP(
+    Association & association,
+    std::shared_ptr<DataSetGenerator> const & generator)
+: SCP(association), _generator(nullptr)
+{
+    this->set_generator(generator);
+}
+
+GetSCP
+::~GetSCP()
+{
+    // Nothing to do.
+}
+
+GetSCP::DataSetGenerator const &
+GetSCP
+::get_generator() const
+{
+    return *this->_generator;
+}
+
+void
+GetSCP
+::set_generator(std::shared_ptr<DataSetGenerator> const & generator)
+{
+    this->_generator = generator;
+}
+
+void
+GetSCP
+::operator()(message::Message const & message)
+{
+    message::CGetRequest const request(message);
+
+    StoreSCU store_scu(this->_association);
+
+    unsigned int remaining_sub_operations = 0;
+    unsigned int completed_sub_operations=0;
+    unsigned int failed_sub_operations=0;
+    unsigned int warning_sub_operations=0;
+
+    try
+    {
+        this->_generator->initialize(request);
+        remaining_sub_operations = this->_generator->count();
+
+        while(!this->_generator->done())
+        {
+            message::CGetResponse response(
+                request.get_message_id(), message::CGetResponse::Pending);
+            response.set_number_of_remaining_sub_operations(
+                remaining_sub_operations);
+            response.set_number_of_completed_sub_operations(
+                completed_sub_operations);
+            response.set_number_of_failed_sub_operations(
+                failed_sub_operations);
+            response.set_number_of_warning_sub_operations(
+                warning_sub_operations);
+            this->_association.send_message(
+                response, request.get_affected_sop_class_uid());
+
+            auto const data_set = this->_generator->get();
+            store_scu.set_affected_sop_class(data_set);
+            try
+            {
+                store_scu.store(data_set);
+
+                --remaining_sub_operations;
+                ++completed_sub_operations;
+            }
+            catch(Exception const &)
+            {
+                ++failed_sub_operations;
+            }
+
+            this->_generator->next();
+        }
+    }
+    catch(Exception const &)
+    {
+        message::CGetResponse response(
+            request.get_message_id(), message::CGetResponse::UnableToProcess);
+        response.set_number_of_remaining_sub_operations(
+            remaining_sub_operations);
+        response.set_number_of_completed_sub_operations(
+            completed_sub_operations);
+        response.set_number_of_failed_sub_operations(
+            failed_sub_operations);
+        response.set_number_of_warning_sub_operations(
+            warning_sub_operations);
+        this->_association.send_message(
+            response, request.get_affected_sop_class_uid());
+        return;
+    }
+
+    message::CGetResponse response(
+        request.get_message_id(), message::CGetResponse::Success);
+    response.set_number_of_remaining_sub_operations(
+        remaining_sub_operations);
+    response.set_number_of_completed_sub_operations(
+        completed_sub_operations);
+    response.set_number_of_failed_sub_operations(
+        failed_sub_operations);
+    response.set_number_of_warning_sub_operations(
+        warning_sub_operations);
+    this->_association.send_message(
+        response, request.get_affected_sop_class_uid());
+}
+
+}
diff --git a/src/odil/GetSCP.h b/src/odil/GetSCP.h
new file mode 100644
index 0000000..8702338
--- /dev/null
+++ b/src/odil/GetSCP.h
@@ -0,0 +1,59 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _2f0ad1fd_8779_4ab3_b7e8_6d37fdc0c018
+#define _2f0ad1fd_8779_4ab3_b7e8_6d37fdc0c018
+
+#include <memory>
+
+#include "odil/Association.h"
+#include "odil/SCP.h"
+#include "odil/message/Message.h"
+
+namespace odil
+{
+
+/// @brief SCP for C-Get services.
+class GetSCP: public SCP
+{
+public:
+
+    class DataSetGenerator: public SCP::DataSetGenerator
+    {
+    public:
+        virtual unsigned int count() const =0;
+    };
+
+    /// @brief Constructor.
+    GetSCP(Association & association);
+
+    /// @brief Constructor.
+    GetSCP(
+        Association & association,
+        std::shared_ptr<DataSetGenerator> const & generator);
+
+    /// @brief Destructor.
+    virtual ~GetSCP();
+
+    /// @brief Return the generator.
+    DataSetGenerator const & get_generator() const;
+
+    /// @brief Set the generator.
+    void set_generator(std::shared_ptr<DataSetGenerator> const & generator);
+
+    /// @brief Process a C-Get request.
+    virtual void operator()(message::Message const & message);
+
+private:
+    std::shared_ptr<DataSetGenerator> _generator;
+};
+
+}
+
+#endif // _2f0ad1fd_8779_4ab3_b7e8_6d37fdc0c018
+
diff --git a/src/odil/GetSCU.cpp b/src/odil/GetSCU.cpp
new file mode 100644
index 0000000..ca73ffe
--- /dev/null
+++ b/src/odil/GetSCU.cpp
@@ -0,0 +1,115 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/GetSCU.h"
+
+#include <functional>
+#include <sstream>
+#include <vector>
+
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/StoreSCP.h"
+#include "odil/message/CGetRequest.h"
+#include "odil/message/CGetResponse.h"
+#include "odil/message/CStoreRequest.h"
+
+namespace odil
+{
+
+GetSCU
+::GetSCU(Association & association)
+: SCU(association)
+{
+    // Nothing else
+}
+
+GetSCU
+::~GetSCU()
+{
+    // Nothing to do
+}
+
+void
+GetSCU
+::get(DataSet const & query, Callback callback) const
+{
+    // Send the request
+    message::CGetRequest request(
+        this->_association.next_message_id(),
+        this->_affected_sop_class, message::Message::Priority::MEDIUM, query);
+    this->_association.send_message(request, this->_affected_sop_class);
+
+    // Receive the responses
+    bool done = false;
+    while(!done)
+    {
+        message::Message const message = this->_association.receive_message();
+
+        if(message.get_command_field() == message::Message::Command::C_GET_RSP)
+        {
+            done = this->_handle_get_response(message::CGetResponse(message));
+        }
+        else if(message.get_command_field() == message::Message::Command::C_STORE_RQ)
+        {
+            try
+            {
+                this->_handle_store_request(
+                    message::CStoreRequest(message), callback);
+            }
+            catch(...)
+            {
+                // FIXME: logging
+                done = true;
+            }
+        }
+        else
+        {
+            std::ostringstream exception_message;
+            exception_message << "DIMSE: Unexpected Response Command Field: 0x"
+                              << std::hex << message.get_command_field();
+            throw Exception(exception_message.str());
+        }
+    }
+}
+
+std::vector<DataSet>
+GetSCU
+::get(DataSet const & query) const
+{
+    std::vector<DataSet> result;
+    auto callback = [&result](DataSet const & data_set) {
+        result.push_back(data_set);
+    };
+    this->get(query, callback);
+
+    return result;
+}
+
+bool
+GetSCU
+::_handle_get_response(message::CGetResponse const & response) const
+{
+    return !response.is_pending();
+}
+
+void
+GetSCU
+::_handle_store_request(
+    message::CStoreRequest const & request, Callback callback) const
+{
+    auto const store_callback = [&callback](message::CStoreRequest const & request) {
+        callback(request.get_data_set());
+        return message::Response::Success;
+    };
+    StoreSCP scp(this->_association, store_callback);
+    scp(request);
+}
+
+}
diff --git a/src/dcmtkpp/GetSCU.h b/src/odil/GetSCU.h
similarity index 64%
rename from src/dcmtkpp/GetSCU.h
rename to src/odil/GetSCU.h
index acc72bf..b44b914 100644
--- a/src/dcmtkpp/GetSCU.h
+++ b/src/odil/GetSCU.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,16 +9,16 @@
 #ifndef _f82a15e2_fd13_44b5_af7d_c6983494c9c6
 #define _f82a15e2_fd13_44b5_af7d_c6983494c9c6
 
-#include "SCU.h"
-
+#include <functional>
 #include <vector>
 
-#include "dcmtkpp/CGetResponse.h"
-#include "dcmtkpp/CStoreRequest.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/StoreSCP.h"
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/SCU.h"
+#include "odil/message/CGetResponse.h"
+#include "odil/message/CStoreRequest.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief SCU for C-GET services.
@@ -26,8 +26,11 @@ class GetSCU: public SCU
 {
 public:
     /// @brief Callback called when a response is received.
-    typedef StoreSCP::Callback Callback;
-    
+    typedef std::function<void(DataSet const &)> Callback;
+
+    /// @brief Constructor.
+    GetSCU(Association & association);
+
     /// @brief Destructor.
     virtual ~GetSCU();
     
@@ -40,8 +43,9 @@ public:
     std::vector<DataSet> get(DataSet const & query) const;
 
 private:
-    bool _get_response(CGetResponse const & response) const;
-    void _store_request(CStoreRequest const & request, Callback callback) const;
+    bool _handle_get_response(message::CGetResponse const & response) const;
+    void _handle_store_request(
+        message::CStoreRequest const & request, Callback callback) const;
 };
 
 }
diff --git a/src/odil/MoveSCP.cpp b/src/odil/MoveSCP.cpp
new file mode 100644
index 0000000..9b9b7bf
--- /dev/null
+++ b/src/odil/MoveSCP.cpp
@@ -0,0 +1,147 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/MoveSCP.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "odil/Association.h"
+#include "odil/Exception.h"
+#include "odil/SCP.h"
+#include "odil/StoreSCU.h"
+#include "odil/message/CMoveRequest.h"
+#include "odil/message/CMoveResponse.h"
+#include "odil/message/Response.h"
+
+namespace odil
+{
+
+MoveSCP
+::MoveSCP(Association & association)
+: SCP(association), _generator(nullptr)
+{
+    // Nothing else.
+}
+
+MoveSCP
+::MoveSCP(
+    Association & association,
+    std::shared_ptr<DataSetGenerator> const & generator)
+: SCP(association), _generator(nullptr)
+{
+    this->set_generator(generator);
+}
+
+MoveSCP
+::~MoveSCP()
+{
+    // Nothing to do.
+}
+
+MoveSCP::DataSetGenerator const &
+MoveSCP
+::get_generator() const
+{
+    return *this->_generator;
+}
+
+void
+MoveSCP
+::set_generator(std::shared_ptr<DataSetGenerator> const & generator)
+{
+    this->_generator = generator;
+}
+
+void
+MoveSCP
+::operator()(message::Message const & message)
+{
+    message::CMoveRequest const request(message);
+
+    auto move_association = this->_generator->get_association(request);
+    move_association.associate();
+    StoreSCU store_scu(move_association);
+
+    unsigned int remaining_sub_operations = 0;
+    unsigned int completed_sub_operations=0;
+    unsigned int failed_sub_operations=0;
+    unsigned int warning_sub_operations=0;
+
+    try
+    {
+        this->_generator->initialize(request);
+        remaining_sub_operations = this->_generator->count();
+
+        while(!this->_generator->done())
+        {
+            message::CMoveResponse response(
+                request.get_message_id(), message::CMoveResponse::Pending);
+            response.set_number_of_remaining_sub_operations(
+                remaining_sub_operations);
+            response.set_number_of_completed_sub_operations(
+                completed_sub_operations);
+            response.set_number_of_failed_sub_operations(
+                failed_sub_operations);
+            response.set_number_of_warning_sub_operations(
+                warning_sub_operations);
+            this->_association.send_message(
+                response, request.get_affected_sop_class_uid());
+
+            auto const data_set = this->_generator->get();
+            store_scu.set_affected_sop_class(data_set);
+            try
+            {
+                store_scu.store(data_set);
+
+                --remaining_sub_operations;
+                ++completed_sub_operations;
+            }
+            catch(Exception const &)
+            {
+                --remaining_sub_operations;
+                ++failed_sub_operations;
+            }
+
+            this->_generator->next();
+        }
+    }
+    catch(Exception const &)
+    {
+        message::CMoveResponse response(
+            request.get_message_id(), message::CMoveResponse::UnableToProcess);
+        response.set_number_of_remaining_sub_operations(
+            remaining_sub_operations);
+        response.set_number_of_completed_sub_operations(
+            completed_sub_operations);
+        response.set_number_of_failed_sub_operations(
+            failed_sub_operations);
+        response.set_number_of_warning_sub_operations(
+            warning_sub_operations);
+        this->_association.send_message(
+            response, request.get_affected_sop_class_uid());
+        return;
+    }
+
+    message::CMoveResponse response(
+        request.get_message_id(), message::CMoveResponse::Success);
+    response.set_number_of_remaining_sub_operations(
+        remaining_sub_operations);
+    response.set_number_of_completed_sub_operations(
+        completed_sub_operations);
+    response.set_number_of_failed_sub_operations(
+        failed_sub_operations);
+    response.set_number_of_warning_sub_operations(
+        warning_sub_operations);
+    this->_association.send_message(
+        response, request.get_affected_sop_class_uid());
+}
+
+}
diff --git a/src/odil/MoveSCP.h b/src/odil/MoveSCP.h
new file mode 100644
index 0000000..77a3e6f
--- /dev/null
+++ b/src/odil/MoveSCP.h
@@ -0,0 +1,65 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _7e899e10_2a21_45b8_a2d6_af1d13cbfd29
+#define _7e899e10_2a21_45b8_a2d6_af1d13cbfd29
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "odil/Association.h"
+#include "odil/SCP.h"
+#include "odil/message/CMoveRequest.h"
+#include "odil/message/Message.h"
+
+namespace odil
+{
+
+/// @brief SCP for C-Move services.
+class MoveSCP: public SCP
+{
+public:
+
+    class DataSetGenerator: public SCP::DataSetGenerator
+    {
+    public:
+        virtual unsigned int count() const =0;
+        virtual Association get_association(message::CMoveRequest const &) const =0;
+    };
+
+    /// @brief Constructor.
+    MoveSCP(Association & association);
+
+    /// @brief Constructor.
+    MoveSCP(
+        Association & association,
+        std::shared_ptr<DataSetGenerator> const & generator);
+
+    /// @brief Destructor.
+    virtual ~MoveSCP();
+
+    /// @brief Return the generator.
+    DataSetGenerator const & get_generator() const;
+
+    /// @brief Set the generator.
+    void set_generator(std::shared_ptr<DataSetGenerator> const & generator);
+
+    /// @brief Process a C-Get request.
+    virtual void operator()(message::Message const & message);
+
+private:
+    std::shared_ptr<DataSetGenerator> _generator;
+};
+
+}
+
+
+#endif // _7e899e10_2a21_45b8_a2d6_af1d13cbfd29
+
diff --git a/src/odil/MoveSCU.cpp b/src/odil/MoveSCU.cpp
new file mode 100644
index 0000000..ae24730
--- /dev/null
+++ b/src/odil/MoveSCU.cpp
@@ -0,0 +1,169 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/MoveSCU.h"
+
+#include <chrono>
+#include <functional>
+#include <thread>
+#include <vector>
+
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/StoreSCP.h"
+#include "odil/message/CMoveRequest.h"
+#include "odil/message/CMoveResponse.h"
+#include "odil/message/Message.h"
+
+namespace odil
+{
+
+MoveSCU
+::MoveSCU(Association & association)
+: SCU(association), _move_destination("")
+{
+    // Nothing else.
+}
+
+MoveSCU
+::~MoveSCU()
+{
+    // Nothing to do
+}
+
+std::string const &
+MoveSCU
+::get_move_destination() const
+{
+    return this->_move_destination;
+}
+
+void
+MoveSCU
+::set_move_destination(std::string const & move_destination)
+{
+    this->_move_destination = move_destination;
+}
+
+void
+MoveSCU
+::move(DataSet const & query, Callback callback) const
+{
+    // Send the request
+    message::CMoveRequest const request(
+        this->_association.next_message_id(),
+        this->_affected_sop_class, message::Message::Priority::MEDIUM,
+        this->_move_destination, query);
+    this->_association.send_message(request, this->_affected_sop_class);
+
+    // Receive the responses
+    Association store_association;
+    bool done = false;
+    while(!done)
+    {
+        // Use a small timeout to avoid blocking for a long time.
+        boost::posix_time::milliseconds const timeout(1000);
+        store_association.set_tcp_timeout(timeout);
+        store_association.set_message_timeout(timeout);
+
+        if(!store_association.is_associated())
+        {
+            try
+            {
+                store_association.receive_association(boost::asio::ip::tcp::v4(), 11113);
+                store_association.set_tcp_timeout(
+                    this->_association.get_tcp_timeout());
+                store_association.set_message_timeout(
+                    this->_association.get_message_timeout());
+            }
+            catch(Exception const & e)
+            {
+                // Ignore
+            }
+
+            if(store_association.is_associated())
+            {
+                done = true;
+            }
+        }
+    }
+
+    this->_dispatch(store_association, callback);
+}
+
+std::vector<DataSet>
+MoveSCU
+::move(DataSet const & query) const
+{
+    std::vector<DataSet> result;
+    auto callback = [&result](DataSet const & data_set) {
+        result.push_back(data_set);
+    };
+    this->move(query, callback);
+
+    return result;
+}
+
+void
+MoveSCU
+::_dispatch(Association & store_association, Callback callback) const
+{
+    bool store_done = false;
+    bool main_done = false;
+    while(!(store_done && main_done))
+    {
+        if(store_association.get_transport().get_socket()->available() > 0)
+        {
+            store_done =
+                this->_handle_store_association(store_association, callback);
+        }
+        if(this->_association.get_transport().get_socket()->available() > 0)
+        {
+            main_done = this->_handle_main_association();
+        }
+
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    }
+}
+
+bool
+MoveSCU
+::_handle_main_association() const
+{
+    message::CMoveResponse const response = this->_association.receive_message();
+    return !response.is_pending();
+}
+
+bool
+MoveSCU
+::_handle_store_association(Association & association, Callback callback) const
+{
+    bool result = false;
+    try
+    {
+        auto const store_callback = [&callback](message::CStoreRequest const & request) {
+            callback(request.get_data_set());
+            return message::Response::Success;
+        };
+        StoreSCP scp(association, store_callback);
+        scp.receive_and_process();
+    }
+    catch(odil::AssociationReleased const &)
+    {
+        result = true;
+    }
+    catch(odil::AssociationAborted const & e)
+    {
+        result = true;
+    }
+
+    return result;
+}
+
+}
diff --git a/src/dcmtkpp/MoveSCU.h b/src/odil/MoveSCU.h
similarity index 81%
rename from src/dcmtkpp/MoveSCU.h
rename to src/odil/MoveSCU.h
index 9189309..9d62d58 100644
--- a/src/dcmtkpp/MoveSCU.h
+++ b/src/odil/MoveSCU.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,14 +9,15 @@
 #ifndef _5ff4d940_4db7_4d85_9d3a_230b944b31fe
 #define _5ff4d940_4db7_4d85_9d3a_230b944b31fe
 
+#include <functional>
 #include <string>
 #include <vector>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/SCU.h"
-#include "dcmtkpp/StoreSCP.h"
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/SCU.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief SCU for C-MOVE services.
@@ -24,10 +25,10 @@ class MoveSCU: public SCU
 {
 public:
     /// @brief Callback called when a response is received.
-    typedef StoreSCP::Callback Callback;
+    typedef std::function<void(DataSet const &)> Callback;
     
     /// @brief Constructor.
-    MoveSCU();
+    MoveSCU(Association & association);
     
     /// @brief Destructor.
     virtual ~MoveSCU();
@@ -48,7 +49,7 @@ public:
 private:
     std::string _move_destination;
     
-    bool _dispatch(Association & association, Callback callback) const;
+    void _dispatch(Association & store_association, Callback callback) const;
     
     bool _handle_main_association() const;
     bool _handle_store_association(Association & association, Callback callback) const;
diff --git a/src/dcmtkpp/Reader.cpp b/src/odil/Reader.cpp
similarity index 87%
rename from src/dcmtkpp/Reader.cpp
rename to src/odil/Reader.cpp
index 836c928..9e69061 100644
--- a/src/dcmtkpp/Reader.cpp
+++ b/src/odil/Reader.cpp
@@ -1,32 +1,32 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/Reader.h"
+#include "odil/Reader.h"
 
 #include <algorithm>
 #include <cstdint>
-#include <endian.h>
 #include <functional>
 #include <istream>
 #include <sstream>
 #include <string>
 #include <utility>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/Value.h"
-#include "dcmtkpp/VR.h"
-#include "dcmtkpp/VRFinder.h"
-
-#define dcmtkpp_read_binary(type, value, stream, byte_ordering, size) \
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/endian.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/Tag.h"
+#include "odil/Value.h"
+#include "odil/VR.h"
+#include "odil/VRFinder.h"
+
+#define odil_read_binary(type, value, stream, byte_ordering, size) \
 type value; \
 { \
     uint##size##_t raw; \
@@ -35,13 +35,13 @@ type value; \
     { \
         throw Exception("Could not read from stream"); \
     } \
-    if(byte_ordering == LITTLE_ENDIAN) \
+    if(byte_ordering == ByteOrdering::LittleEndian) \
     { \
-        raw = le##size##toh(raw); \
+        raw = little_endian_to_host(raw); \
     } \
-    else if(byte_ordering == BIG_ENDIAN) \
+    else if(byte_ordering == ByteOrdering::BigEndian) \
     { \
-        raw = be##size##toh(raw); \
+        raw = big_endian_to_host(raw); \
     } \
     else \
     { \
@@ -50,18 +50,25 @@ type value; \
     value = *reinterpret_cast<type*>(&raw);\
 }
 
+#define odil_ignore(stream, size) \
+    stream.ignore(size); \
+    if(!stream) \
+    { \
+        throw Exception("Could not read from stream"); \
+    }
+
 std::string read_string(std::istream & stream, unsigned int size)
 {
     std::string value(size, '\0');
     stream.read(&value[0], value.size());
     if(!stream)
     {
-        throw dcmtkpp::Exception("Cannot read string");
+        throw odil::Exception("Cannot read string");
     }
     return value;
 }
 
-namespace dcmtkpp
+namespace odil
 {
 
 Reader
@@ -71,7 +78,7 @@ Reader
 : stream(stream), transfer_syntax(transfer_syntax),
     byte_ordering(
         (transfer_syntax==registry::ExplicitVRBigEndian_Retired)?
-        BIG_ENDIAN:LITTLE_ENDIAN),
+        ByteOrdering::BigEndian:ByteOrdering::LittleEndian),
     explicit_vr(transfer_syntax!=registry::ImplicitVRLittleEndian),
     keep_group_length(keep_group_length)
 {
@@ -115,8 +122,8 @@ Tag
 Reader
 ::read_tag() const
 {
-    dcmtkpp_read_binary(uint16_t, group, this->stream, this->byte_ordering, 16);
-    dcmtkpp_read_binary(uint16_t, element, this->stream, this->byte_ordering, 16);
+    odil_read_binary(uint16_t, group, this->stream, this->byte_ordering, 16);
+    odil_read_binary(uint16_t, element, this->stream, this->byte_ordering, 16);
     return Tag(group, element);
 }
 
@@ -223,7 +230,7 @@ Reader
 Reader::Visitor
 ::Visitor(
     std::istream & stream, VR vr, std::string const & transfer_syntax,
-    int byte_ordering, bool explicit_vr, bool keep_group_length)
+    ByteOrdering byte_ordering, bool explicit_vr, bool keep_group_length)
 : stream(stream), vr(vr), transfer_syntax(transfer_syntax),
     byte_ordering(byte_ordering), explicit_vr(explicit_vr),
     keep_group_length(keep_group_length)
@@ -271,25 +278,25 @@ Reader::Visitor
         {
             if(this->vr == VR::SL)
             {
-                dcmtkpp_read_binary(
+                odil_read_binary(
                     int32_t, item, this->stream, this->byte_ordering, 32);
                 value[i] = item;
             }
             else if(this->vr == VR::SS)
             {
-                dcmtkpp_read_binary(
+                odil_read_binary(
                     int16_t, item, this->stream, this->byte_ordering, 16);
                 value[i] = item;
             }
             else if(this->vr == VR::UL)
             {
-                dcmtkpp_read_binary(
+                odil_read_binary(
                     uint32_t, item, this->stream, this->byte_ordering, 32);
                 value[i] = item;
             }
             else if(this->vr == VR::AT || this->vr == VR::US)
             {
-                dcmtkpp_read_binary(
+                odil_read_binary(
                     uint16_t, item, this->stream, this->byte_ordering, 16);
                 value[i] = item;
             }
@@ -336,13 +343,13 @@ Reader::Visitor
         {
             if(this->vr == VR::FD)
             {
-                dcmtkpp_read_binary(
+                odil_read_binary(
                     double, item, this->stream, this->byte_ordering, 64);
                 value[i] = item;
             }
             else if(this->vr == VR::FL)
             {
-                dcmtkpp_read_binary(
+                odil_read_binary(
                     float, item, this->stream, this->byte_ordering, 32);
                 value[i] = item;
             }
@@ -381,6 +388,18 @@ Reader::Visitor
         {
             value = this->split_strings(string);
         }
+
+        // Remove padding
+        static std::string const padding={ '\0', ' ' };
+
+        for(auto & item: value)
+        {
+            auto const last_char = item.find_last_not_of(padding);
+            if(last_char != std::string::npos)
+            {
+                item = item.substr(0, last_char+1);
+            }
+        }
     }
 }
 
@@ -431,8 +450,7 @@ Reader::Visitor
             else if(tag == registry::SequenceDelimitationItem)
             {
                 done = true;
-                dcmtkpp_read_binary(
-                    uint32_t, unused, this->stream, this->byte_ordering, 32);
+                odil_ignore(this->stream, 4);
             }
             else
             {
@@ -463,7 +481,7 @@ Reader::Visitor
         value.resize(vl);
         for(unsigned int i=0; i<value.size(); i+=4)
         {
-            dcmtkpp_read_binary(
+            odil_read_binary(
                 float, item, this->stream, this->byte_ordering, 32);
             *reinterpret_cast<float*>(&value[i]) = item;
         }
@@ -478,11 +496,16 @@ Reader::Visitor
         value.resize(vl);
         for(unsigned int i=0; i<value.size(); i+=2)
         {
-            dcmtkpp_read_binary(
+            odil_read_binary(
                 uint16_t, item, this->stream, this->byte_ordering, 16);
             *reinterpret_cast<uint16_t*>(&value[i]) = item;
         }
     }
+    else if(this->vr == VR::UN)
+    {
+        value.resize(vl);
+        this->stream.read(reinterpret_cast<char*>(&value[0]), value.size());
+    }
     else
     {
         throw Exception("Cannot read "+as_string(this->vr)+" as binary");
@@ -499,22 +522,21 @@ Reader::Visitor
         if(vr == VR::OB || vr == VR::OW || vr == VR::OF || vr == VR::SQ ||
            vr == VR::UC || vr == VR::UR || vr == VR::UT || vr == VR::UN)
         {
-            dcmtkpp_read_binary(
-                uint16_t, reserved, this->stream, this->byte_ordering, 16);
-            dcmtkpp_read_binary(
+            odil_ignore(this->stream, 2);
+            odil_read_binary(
                 uint32_t, vl, this->stream, this->byte_ordering, 32);
             length = vl;
         }
         else
         {
-            dcmtkpp_read_binary(
+            odil_read_binary(
                 uint16_t, vl, this->stream, this->byte_ordering, 16);
             length = vl;
         }
     }
     else
     {
-        dcmtkpp_read_binary(
+        odil_read_binary(
             uint32_t, vl, this->stream, this->byte_ordering, 32);
         length = vl;
     }
@@ -544,18 +566,6 @@ Reader::Visitor
         begin = (end==std::string::npos)?string.size():(end+1);
     }
 
-    if(!value.empty())
-    {
-        // Remove padding
-        static std::string const padding={ '\0', ' ' };
-        auto & last_item = value[value.size()-1];
-        auto const last_char = last_item.find_last_not_of(padding);
-        if(last_char != std::string::npos)
-        {
-            last_item = last_item.substr(0, last_char+1);
-        }
-    }
-
     return value;
 }
 
@@ -563,7 +573,7 @@ DataSet
 Reader::Visitor
 ::read_item(std::istream & specific_stream) const
 {
-    dcmtkpp_read_binary(
+    odil_read_binary(
         uint32_t, item_length, specific_stream, this->byte_ordering, 32);
 
     DataSet item;
@@ -589,8 +599,7 @@ Reader::Visitor
         {
             throw Exception("Unexpected tag: "+std::string(tag));
         }
-        dcmtkpp_read_binary(
-            uint32_t, unused, specific_stream, this->byte_ordering, 32);
+        odil_ignore(specific_stream, 4);
     }
 
     return item;
@@ -598,4 +607,5 @@ Reader::Visitor
 
 }
 
-#undef dcmtkpp_read_binary
+#undef odil_ignore
+#undef odil_read_binary
diff --git a/src/dcmtkpp/Reader.h b/src/odil/Reader.h
similarity index 87%
rename from src/dcmtkpp/Reader.h
rename to src/odil/Reader.h
index 7a47275..aa3528b 100644
--- a/src/dcmtkpp/Reader.h
+++ b/src/odil/Reader.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -14,13 +14,14 @@
 #include <string>
 #include <utility>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/Value.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/endian.h"
+#include "odil/Tag.h"
+#include "odil/Value.h"
+#include "odil/VR.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief Read DICOM objects from a stream.
@@ -33,8 +34,8 @@ public:
     /// @brief Transfer syntax used to read the file.
     std::string transfer_syntax;
 
-    /// @brief Endianness (LITTLE_ENDIAN or BIG_ENDIAN).
-    int byte_ordering;
+    /// @brief Endianness.
+    ByteOrdering byte_ordering;
 
     /// @brief Explicit-ness of the Value Representations.
     bool explicit_vr;
@@ -79,13 +80,13 @@ private:
         VR vr;
 
         std::string transfer_syntax;
-        int byte_ordering;
+        ByteOrdering byte_ordering;
         bool explicit_vr;
         bool keep_group_length;
 
         Visitor(
             std::istream & stream, VR vr, std::string const & transfer_syntax,
-            int byte_ordering, bool explicit_vr, bool keep_group_length);
+            ByteOrdering byte_ordering, bool explicit_vr, bool keep_group_length);
 
         result_type operator()(Value::Integers & value) const;
         result_type operator()(Value::Reals & value) const;
diff --git a/src/odil/SCP.cpp b/src/odil/SCP.cpp
new file mode 100644
index 0000000..6132188
--- /dev/null
+++ b/src/odil/SCP.cpp
@@ -0,0 +1,46 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "SCP.h"
+
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/message/Request.h"
+
+namespace odil
+{
+
+SCP::DataSetGenerator
+::~DataSetGenerator()
+{
+    // Nothing to do.
+}
+
+SCP
+::SCP(Association & association)
+: _association(association)
+{
+    // Nothing else.
+}
+
+SCP
+::~SCP()
+{
+    // Nothing to do.
+}
+
+void
+SCP
+::receive_and_process()
+{
+    auto const message = this->_association.receive_message();
+    (*this)(message);
+}
+
+}
diff --git a/src/odil/SCP.h b/src/odil/SCP.h
new file mode 100644
index 0000000..9f1b2f7
--- /dev/null
+++ b/src/odil/SCP.h
@@ -0,0 +1,55 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _f4680d8c_18a8_4317_956d_3ae238cb39cc
+#define _f4680d8c_18a8_4317_956d_3ae238cb39cc
+
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/message/Request.h"
+
+namespace odil
+{
+
+/// @brief Base class for all Service Class Providers.
+class SCP
+{
+public:
+    /// @brief Abstract base class for SCP returning multiple data sets.
+    class DataSetGenerator
+    {
+    public:
+        virtual ~DataSetGenerator() =0;
+
+        virtual void initialize(message::Request const & request) =0;
+
+        virtual bool done() const =0;
+        virtual void next() =0;
+
+        virtual DataSet get() const =0;
+    };
+
+    /// @brief Create a Service Class Provider.
+    SCP(Association & association);
+
+    /// @brief Destructor
+    virtual ~SCP();
+
+    /// @brief Receive and process a message.
+    void receive_and_process();
+
+    /// @brief Process a message.
+    virtual void operator()(message::Message const & message) =0;
+protected:
+    Association & _association;
+};
+
+}
+
+#endif // _f4680d8c_18a8_4317_956d_3ae238cb39cc
diff --git a/src/odil/SCPDispatcher.cpp b/src/odil/SCPDispatcher.cpp
new file mode 100644
index 0000000..6ddcbdc
--- /dev/null
+++ b/src/odil/SCPDispatcher.cpp
@@ -0,0 +1,78 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/SCPDispatcher.h"
+
+#include <map>
+#include <memory>
+
+#include "odil/Association.h"
+#include "odil/Exception.h"
+#include "odil/SCP.h"
+#include "odil/Value.h"
+
+namespace odil
+{
+
+SCPDispatcher
+::SCPDispatcher(Association & association)
+: _association(association)
+{
+    // Nothing else.
+}
+
+SCPDispatcher
+::~SCPDispatcher()
+{
+    // Nothing to do.
+}
+
+bool
+SCPDispatcher
+::has_scp(Value::Integer command) const
+{
+    auto const it = this->_providers.find(command);
+    return (it != this->_providers.end());
+}
+
+std::shared_ptr<SCP> const &
+SCPDispatcher
+::get_scp(Value::Integer command) const
+{
+    auto const it = this->_providers.find(command);
+    if(it == this->_providers.end())
+    {
+        throw Exception("No such provider");
+    }
+    return it->second;
+}
+
+void
+SCPDispatcher
+::set_scp(Value::Integer command, std::shared_ptr<SCP> const & scp)
+{
+    this->_providers[command] = scp;
+}
+
+void
+SCPDispatcher
+::dispatch()
+{
+    auto const message = this->_association.receive_message();
+
+    auto const it = this->_providers.find(message.get_command_field());
+    if(it == this->_providers.end())
+    {
+        throw Exception("No such provider");
+    }
+
+    auto & scp = *(it->second);
+    scp(message);
+}
+
+}
diff --git a/src/odil/SCPDispatcher.h b/src/odil/SCPDispatcher.h
new file mode 100644
index 0000000..bc7e002
--- /dev/null
+++ b/src/odil/SCPDispatcher.h
@@ -0,0 +1,47 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _f0b69ac6_7d52_401d_a2f3_d5d3f7d69376
+#define _f0b69ac6_7d52_401d_a2f3_d5d3f7d69376
+
+#include <map>
+#include <memory>
+
+#include "odil/Association.h"
+#include "odil/SCP.h"
+#include "odil/Value.h"
+
+namespace odil
+{
+
+class SCPDispatcher
+{
+public:
+    /// @brief Create a dispatcher with network and association.
+    SCPDispatcher(Association & association);
+
+    /// @brief Destructor.
+    ~SCPDispatcher();
+
+    bool has_scp(Value::Integer command) const;
+
+    std::shared_ptr<SCP> const & get_scp(Value::Integer command) const;
+
+    void set_scp(Value::Integer command, std::shared_ptr<SCP> const & scp);
+
+    void dispatch();
+private:
+    typedef std::shared_ptr<SCP> SCPPointer;
+
+    Association & _association;
+    std::map<Value::Integer, std::shared_ptr<SCP>> _providers;
+};
+
+}
+
+#endif // _f0b69ac6_7d52_401d_a2f3_d5d3f7d69376
diff --git a/src/dcmtkpp/SCU.cpp b/src/odil/SCU.cpp
similarity index 58%
rename from src/dcmtkpp/SCU.cpp
rename to src/odil/SCU.cpp
index 9110c2e..0952b9d 100644
--- a/src/dcmtkpp/SCU.cpp
+++ b/src/odil/SCU.cpp
@@ -1,31 +1,30 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/SCU.h"
+#include "odil/SCU.h"
 
 #include <string>
 
-#include <unistd.h>
+#include "odil/Association.h"
+#include "odil/Exception.h"
+#include "odil/message/CEchoRequest.h"
+#include "odil/message/CEchoResponse.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/CEchoRequest.h"
-#include "dcmtkpp/CEchoResponse.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-
-namespace dcmtkpp
+namespace odil
 {
 
 SCU
-::SCU()
-: ServiceRole(), _affected_sop_class("")
+::SCU(Association & association)
+: _association(association), _affected_sop_class("")
 {
-    // Nothing else
+    // Nothing else.
 }
 
 SCU
@@ -52,12 +51,14 @@ void
 SCU
 ::echo() const
 {
-    Uint16 const message_id = this->_association->get_association()->nextMsgID++;
+    auto const message_id = this->_association.next_message_id();
     
-    CEchoRequest const request(message_id, registry::VerificationSOPClass);
-    this->_send(request, request.get_affected_sop_class_uid());
+    message::CEchoRequest const request(
+        message_id, registry::VerificationSOPClass);
+    this->_association.send_message(
+        request, request.get_affected_sop_class_uid());
     
-    CEchoResponse const response = this->_receive<CEchoResponse>();
+    message::CEchoResponse const response = this->_association.receive_message();
     if(response.get_message_id_being_responded_to() != message_id)
     {
         std::ostringstream message;
diff --git a/src/dcmtkpp/SCU.h b/src/odil/SCU.h
similarity index 77%
rename from src/dcmtkpp/SCU.h
rename to src/odil/SCU.h
index efdd52c..1c51249 100644
--- a/src/dcmtkpp/SCU.h
+++ b/src/odil/SCU.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -11,17 +11,20 @@
 
 #include <string>
 
-#include "dcmtkpp/ServiceRole.h"
+#include "odil/Association.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief Base class for all Service Class Users.
-class SCU: public ServiceRole
+class SCU
 {
 public:
-    SCU();
-    ~SCU();
+    /// @brief Create a default Service Class User.
+    SCU(Association & association);
+
+    /// @brief Destructor.
+    virtual ~SCU();
     
     /// @brief Return the affected SOP class. Defaults to "".
     std::string const & get_affected_sop_class() const;
@@ -32,6 +35,7 @@ public:
     void echo() const;
 
 protected:
+    Association & _association;
     /// @brief Affected SOP class.
     std::string _affected_sop_class;
 };
diff --git a/src/odil/StoreSCP.cpp b/src/odil/StoreSCP.cpp
new file mode 100644
index 0000000..2dd4b79
--- /dev/null
+++ b/src/odil/StoreSCP.cpp
@@ -0,0 +1,84 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/StoreSCP.h"
+
+#include <functional>
+
+#include "odil/Association.h"
+#include "odil/Exception.h"
+#include "odil/SCP.h"
+#include "odil/Value.h"
+#include "odil/message/CStoreRequest.h"
+#include "odil/message/CStoreResponse.h"
+#include "odil/message/CStoreRequest.h"
+#include "odil/message/CStoreResponse.h"
+
+namespace odil
+{
+
+StoreSCP
+::StoreSCP(Association & association)
+: SCP(association), _callback()
+{
+    // Nothing else.
+}
+
+StoreSCP
+::StoreSCP(Association & association, Callback const & callback)
+: SCP(association), _callback()
+{
+    this->set_callback(callback);
+}
+
+StoreSCP
+::~StoreSCP()
+{
+    // Nothing to do.
+}
+
+StoreSCP::Callback const &
+StoreSCP
+::get_callback() const
+{
+    return this->_callback;
+}
+
+void
+StoreSCP
+::set_callback(Callback const & callback)
+{
+    this->_callback = callback;
+}
+
+void
+StoreSCP
+::operator()(message::Message const & message)
+{
+    message::CStoreRequest const request(message);
+
+    Value::Integer status=message::CStoreResponse::Success;
+
+    try
+    {
+        status = this->_callback(request);
+    }
+    catch(Exception const & exception)
+    {
+        status = message::CStoreResponse::ProcessingFailure;
+        // Error Comment
+        // Error ID
+        // Affected SOP Class UID
+    }
+
+    message::CStoreResponse const response(request.get_message_id(), status);
+    this->_association.send_message(
+        response, request.get_affected_sop_class_uid());
+}
+
+}
diff --git a/src/odil/StoreSCP.h b/src/odil/StoreSCP.h
new file mode 100644
index 0000000..f92b71f
--- /dev/null
+++ b/src/odil/StoreSCP.h
@@ -0,0 +1,54 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _fdbf3f51_91f5_464a_b449_c3f994297210
+#define _fdbf3f51_91f5_464a_b449_c3f994297210
+
+#include <functional>
+
+#include "odil/Association.h"
+#include "odil/SCP.h"
+#include "odil/Value.h"
+#include "odil/message/CStoreRequest.h"
+#include "odil/message/Message.h"
+
+namespace odil
+{
+
+/// @brief SCP for C-Store services.
+class StoreSCP: public SCP
+{
+public:
+    /// @brief Callback called when a request is received.
+    typedef std::function<Value::Integer(message::CStoreRequest const &)> Callback;
+
+    /// @brief Constructor.
+    StoreSCP(Association & association);
+
+    /// @brief Constructor.
+    StoreSCP(Association & association, Callback const & callback);
+
+    /// @brief Destructor.
+    virtual ~StoreSCP();
+
+    /// @brief Return the callback.
+    Callback const & get_callback() const;
+
+    /// @brief Set the callback.
+    void set_callback(Callback const & callback);
+
+    /// @brief Process a C-Store request.
+    virtual void operator()(message::Message const & message);
+
+private:
+    Callback _callback;
+};
+
+}
+
+#endif // _fdbf3f51_91f5_464a_b449_c3f994297210
diff --git a/src/dcmtkpp/StoreSCU.cpp b/src/odil/StoreSCU.cpp
similarity index 79%
rename from src/dcmtkpp/StoreSCU.cpp
rename to src/odil/StoreSCU.cpp
index b086831..33fa4bd 100644
--- a/src/dcmtkpp/StoreSCU.cpp
+++ b/src/odil/StoreSCU.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -13,15 +13,24 @@
 #include <string>
 #include <vector>
 
-#include "dcmtkpp/CStoreRequest.h"
-#include "dcmtkpp/CStoreResponse.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
+#include "odil/message/CStoreRequest.h"
+#include "odil/message/CStoreResponse.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/SCU.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 StoreSCU
+::StoreSCU(Association & association)
+: SCU(association)
+{
+    // Nothing else.
+}
+
+StoreSCU
 ::~StoreSCU()
 {
     // Nothing to do.
@@ -55,17 +64,17 @@ StoreSCU
 
 void 
 StoreSCU
-::store(DataSet const & dataset, ProgressCallback callback, void * data) const
+::store(DataSet const & dataset) const
 {
-    CStoreRequest const request(
-        this->_association->get_association()->nextMsgID++,
+    message::CStoreRequest const request(
+        this->_association.next_message_id(),
         this->_affected_sop_class,
         dataset.as_string(registry::SOPInstanceUID, 0),
-        Message::Priority::MEDIUM,
+        message::Message::Priority::MEDIUM,
         dataset);
-    this->_send(request, this->_affected_sop_class, callback, data);
+    this->_association.send_message(request, this->_affected_sop_class);
     
-    CStoreResponse const response = this->_receive<CStoreResponse>();
+    message::CStoreResponse const response = this->_association.receive_message();
 
     if(response.get_message_id_being_responded_to() != request.get_message_id())
     {
diff --git a/src/dcmtkpp/StoreSCU.h b/src/odil/StoreSCU.h
similarity index 72%
rename from src/dcmtkpp/StoreSCU.h
rename to src/odil/StoreSCU.h
index d805a73..d1f71bf 100644
--- a/src/dcmtkpp/StoreSCU.h
+++ b/src/odil/StoreSCU.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,26 +9,28 @@
 #ifndef _1b2f876e_1ad2_464d_9423_28181320aed0
 #define _1b2f876e_1ad2_464d_9423_28181320aed0
 
-#include "SCU.h"
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/SCU.h"
 
-#include "dcmtkpp/DataSet.h"
-
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief SCU for C-Store services.
 class StoreSCU: public SCU
 {
 public:
+    /// @brief Constructor.
+    StoreSCU(Association & association);
+
     /// @brief Destructor.
     virtual ~StoreSCU();
     
     /// @brief Set the affected SOP class based on the dataset.
     void set_affected_sop_class(DataSet const & dataset);
     
-    /// @brief Perform the C-STORE using an optional callback.
-    void store(DataSet const & dataset,
-        ProgressCallback callback=NULL, void * data=NULL) const;
+    /// @brief Perform the C-STORE.
+    void store(DataSet const & dataset) const;
 };
 
 }
diff --git a/src/dcmtkpp/Tag.cpp b/src/odil/Tag.cpp
similarity index 96%
rename from src/dcmtkpp/Tag.cpp
rename to src/odil/Tag.cpp
index 8211fb5..e6db3da 100644
--- a/src/dcmtkpp/Tag.cpp
+++ b/src/odil/Tag.cpp
@@ -1,22 +1,22 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/Tag.h"
+#include "odil/Tag.h"
 
 #include <iomanip>
 #include <ostream>
 #include <sstream>
 #include <string>
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 Tag
diff --git a/src/dcmtkpp/Tag.h b/src/odil/Tag.h
similarity index 89%
rename from src/dcmtkpp/Tag.h
rename to src/odil/Tag.h
index 12ec1d6..43bc170 100644
--- a/src/dcmtkpp/Tag.h
+++ b/src/odil/Tag.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -13,7 +13,7 @@
 #include <ostream>
 #include <string>
 
-namespace dcmtkpp
+namespace odil
 {
 
 /**
@@ -33,7 +33,7 @@ public:
      * numeric value.
      *
      * If the name cannot be found in the public data dictionary, or if the
-     * string is not the representation of a numeric value, a dcmtkpp::Exception
+     * string is not the representation of a numeric value, a odil::Exception
      * is raised.
      */
     Tag(std::string const & name);
@@ -43,7 +43,7 @@ public:
      * numeric value.
      *
      * If the name cannot be found in the public data dictionary, or if the
-     * string is not the representation of a numeric value, a dcmtkpp::Exception
+     * string is not the representation of a numeric value, a odil::Exception
      * is raised.
      */
     Tag(char const * name);
@@ -60,7 +60,7 @@ public:
      * @brief Return the name of the tag.
      *
      * If the tag cannot be found in the public data dictionary,
-     * a dcmtkpp::Exception is raised.
+     * a odil::Exception is raised.
      */
     std::string get_name() const;
 
diff --git a/src/dcmtkpp/UIDsDictionary.cpp b/src/odil/UIDsDictionary.cpp
similarity index 85%
copy from src/dcmtkpp/UIDsDictionary.cpp
copy to src/odil/UIDsDictionary.cpp
index 35f4559..0ea0fbd 100644
--- a/src/dcmtkpp/UIDsDictionary.cpp
+++ b/src/odil/UIDsDictionary.cpp
@@ -1,17 +1,17 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/UIDsDictionary.h"
+#include "odil/UIDsDictionary.h"
 
 #include <map>
 #include <string>
 
-namespace dcmtkpp
+namespace odil
 {
 
 UIDsDictionaryEntry
diff --git a/src/dcmtkpp/UIDsDictionary.h b/src/odil/UIDsDictionary.h
similarity index 92%
rename from src/dcmtkpp/UIDsDictionary.h
rename to src/odil/UIDsDictionary.h
index 2ffe188..a82d131 100644
--- a/src/dcmtkpp/UIDsDictionary.h
+++ b/src/odil/UIDsDictionary.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -12,7 +12,7 @@
 #include <map>
 #include <string>
 
-namespace dcmtkpp
+namespace odil
 {
 
 /**
diff --git a/src/dcmtkpp/VR.cpp b/src/odil/VR.cpp
similarity index 79%
rename from src/dcmtkpp/VR.cpp
rename to src/odil/VR.cpp
index 69bd5dd..73c5fef 100644
--- a/src/dcmtkpp/VR.cpp
+++ b/src/odil/VR.cpp
@@ -1,23 +1,23 @@
-#include <dcmtkpp/VR.h>
+#include <odil/VR.h>
 
 #include <map>
 #include <stdexcept>
 #include <string>
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Tag.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/Tag.h"
 
 // Anonymous namespace, should not be publicly accessed
 namespace
 {
 
-#define ADD_TO_MAP(map, vr) map[dcmtkpp::VR::vr] = #vr
+#define ADD_TO_MAP(map, vr) map[odil::VR::vr] = #vr
 
-std::map<dcmtkpp::VR, std::string>
+std::map<odil::VR, std::string>
 _build_enum_to_name()
 {
-    std::map<dcmtkpp::VR, std::string> result;
+    std::map<odil::VR, std::string> result;
     ADD_TO_MAP(result, AE);
     ADD_TO_MAP(result, AS);
     ADD_TO_MAP(result, AT);
@@ -53,13 +53,13 @@ _build_enum_to_name()
 
 #undef ADD_TO_MAP
 
-std::map<std::string, dcmtkpp::VR>
+std::map<std::string, odil::VR>
 _build_name_to_enum()
 {
-    std::map<dcmtkpp::VR, std::string> const enum_to_name = _build_enum_to_name();
+    std::map<odil::VR, std::string> const enum_to_name = _build_enum_to_name();
 
-    std::map<std::string, dcmtkpp::VR> result;
-    for(std::map<dcmtkpp::VR, std::string>::const_iterator it = enum_to_name.begin();
+    std::map<std::string, odil::VR> result;
+    for(std::map<odil::VR, std::string>::const_iterator it = enum_to_name.begin();
         it != enum_to_name.end(); ++it)
     {
         result[it->second] = it->first;
@@ -68,15 +68,15 @@ _build_name_to_enum()
     return result;
 }
 
-std::map<dcmtkpp::VR, std::string> const
+std::map<odil::VR, std::string> const
 _enum_to_name = _build_enum_to_name();
 
-std::map<std::string, dcmtkpp::VR> const
+std::map<std::string, odil::VR> const
 _name_to_enum = _build_name_to_enum();
 
 }
 
-namespace dcmtkpp
+namespace odil
 {
 
 std::string as_string(VR vr)
@@ -91,7 +91,7 @@ std::string as_string(VR vr)
     }
 }
 
-VR as_vr(std::string const vr)
+VR as_vr(std::string const & vr)
 {
     try
     {
diff --git a/src/dcmtkpp/VR.h b/src/odil/VR.h
similarity index 83%
rename from src/dcmtkpp/VR.h
rename to src/odil/VR.h
index f60b4b0..1f945e9 100644
--- a/src/dcmtkpp/VR.h
+++ b/src/odil/VR.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -11,7 +11,7 @@
 
 #include <string>
 
-namespace dcmtkpp
+namespace odil
 {
 
 class Tag;
@@ -31,14 +31,14 @@ std::string as_string(VR vr);
 /**
  * @brief Convert a string to its VR.
  *
- * If the string does not represent a VR, a dcmtkpp::Exception is raised.
+ * If the string does not represent a VR, a odil::Exception is raised.
  */
-VR as_vr(std::string const vr);
+VR as_vr(std::string const & vr);
 
 /**
  * @brief Guess a VR from a tag.
  *
- * If the VR cannot be guessed, a dcmtkpp::Exception is raised.
+ * If the VR cannot be guessed, a odil::Exception is raised.
  */
 VR as_vr(Tag const & tag);
 
diff --git a/src/dcmtkpp/VRFinder.cpp b/src/odil/VRFinder.cpp
similarity index 93%
rename from src/dcmtkpp/VRFinder.cpp
rename to src/odil/VRFinder.cpp
index 060a7fa..6401932 100644
--- a/src/dcmtkpp/VRFinder.cpp
+++ b/src/odil/VRFinder.cpp
@@ -1,24 +1,24 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/VRFinder.h"
+#include "odil/VRFinder.h"
 
 #include <functional>
 #include <string>
 #include <vector>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 std::vector<VRFinder::Finder> const
@@ -132,7 +132,7 @@ VRFinder
         {
             return VR::OW;
         }
-        else if((tag.group<<8) == 0x60 and tag.element == 0x3000)
+        else if((tag.group<<8) == 0x60 && tag.element == 0x3000)
         {
             return VR::OW;
         }
diff --git a/src/dcmtkpp/VRFinder.h b/src/odil/VRFinder.h
similarity index 93%
rename from src/dcmtkpp/VRFinder.h
rename to src/odil/VRFinder.h
index ebaa3d7..4ed1ffa 100644
--- a/src/dcmtkpp/VRFinder.h
+++ b/src/odil/VRFinder.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -13,11 +13,11 @@
 #include <string>
 #include <vector>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief Find the VR of elements in an implicit VR data set.
diff --git a/src/dcmtkpp/Value.cpp b/src/odil/Value.cpp
similarity index 95%
rename from src/dcmtkpp/Value.cpp
rename to src/odil/Value.cpp
index 725b219..af2f77b 100644
--- a/src/dcmtkpp/Value.cpp
+++ b/src/odil/Value.cpp
@@ -1,22 +1,22 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/Value.h"
+#include "odil/Value.h"
 
 #include <cstdint>
 #include <initializer_list>
 #include <string>
 #include <vector>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 Value
diff --git a/src/dcmtkpp/Value.h b/src/odil/Value.h
similarity index 83%
rename from src/dcmtkpp/Value.h
rename to src/odil/Value.h
index 40964cc..231ad74 100644
--- a/src/dcmtkpp/Value.h
+++ b/src/odil/Value.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -14,7 +14,7 @@
 #include <string>
 #include <vector>
 
-namespace dcmtkpp
+namespace odil
 {
 
 class DataSet;
@@ -99,70 +99,70 @@ public:
     /**
      * @brief Return the integers contained in the value.
      *
-     * If the value does not contain integers, a dcmtkpp::Exception is raised.
+     * If the value does not contain integers, a odil::Exception is raised.
      */
     Integers const & as_integers() const;
 
     /**
      * @brief Return the integers contained in the value.
      *
-     * If the value does not contain integers, a dcmtkpp::Exception is raised.
+     * If the value does not contain integers, a odil::Exception is raised.
      */
     Integers & as_integers();
 
     /**
      * @brief Return the reals contained in the value.
      *
-     * If the value does not contain reals, a dcmtkpp::Exception is raised.
+     * If the value does not contain reals, a odil::Exception is raised.
      */
     Reals const & as_reals() const;
 
     /**
      * @brief Return the reals contained in the value.
      *
-     * If the value does not contain reals, a dcmtkpp::Exception is raised.
+     * If the value does not contain reals, a odil::Exception is raised.
      */
     Reals & as_reals();
 
     /**
      * @brief Return the strings contained in the value.
      *
-     * If the value does not contain strings, a dcmtkpp::Exception is raised.
+     * If the value does not contain strings, a odil::Exception is raised.
      */
     Strings const & as_strings() const;
 
     /**
      * @brief Return the strings contained in the value.
      *
-     * If the value does not contain strings, a dcmtkpp::Exception is raised.
+     * If the value does not contain strings, a odil::Exception is raised.
      */
     Strings & as_strings();
 
     /**
      * @brief Return the data sets contained in the value.
      *
-     * If the value does not contain data sets, a dcmtkpp::Exception is raised.
+     * If the value does not contain data sets, a odil::Exception is raised.
      */
     DataSets const & as_data_sets() const;
 
     /**
      * @brief Return the data sets contained in the value.
      *
-     * If the value does not contain data sets, a dcmtkpp::Exception is raised.
+     * If the value does not contain data sets, a odil::Exception is raised.
      */
     DataSets & as_data_sets();
 
     /**
      * @brief Return the binary data contained in the value.
      *
-     * If the value does not contain binary data, a dcmtkpp::Exception is raised.
+     * If the value does not contain binary data, a odil::Exception is raised.
      */
     Binary const & as_binary() const;
 
     /**
      * @brief Return the binary data contained in the value.
      *
-     * If the value does not contain binary data, a dcmtkpp::Exception is raised.
+     * If the value does not contain binary data, a odil::Exception is raised.
      */
     Binary & as_binary();
 
@@ -198,6 +198,6 @@ apply_visitor(TVisitor const & visitor, Value & value);
 
 }
 
-#include "dcmtkpp/Value.txx"
+#include "odil/Value.txx"
 
 #endif // _dca5b15b_b8df_4925_a446_d42efe06c923
diff --git a/src/dcmtkpp/Value.txx b/src/odil/Value.txx
similarity index 94%
rename from src/dcmtkpp/Value.txx
rename to src/odil/Value.txx
index c1a7bcd..765676f 100644
--- a/src/dcmtkpp/Value.txx
+++ b/src/odil/Value.txx
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,11 @@
 #ifndef _de64b8e1_4116_41f8_a085_dabfdc6c63c3
 #define _de64b8e1_4116_41f8_a085_dabfdc6c63c3
 
-#include "dcmtkpp/Value.h"
+#include "odil/Value.h"
 
-#include "dcmtkpp/Exception.h"
+#include "odil/Exception.h"
 
-namespace dcmtkpp
+namespace odil
 {
 template<typename TVisitor>
 typename TVisitor::result_type
diff --git a/src/dcmtkpp/Writer.cpp b/src/odil/Writer.cpp
similarity index 82%
rename from src/dcmtkpp/Writer.cpp
rename to src/odil/Writer.cpp
index 9e70ad6..4998a20 100644
--- a/src/dcmtkpp/Writer.cpp
+++ b/src/odil/Writer.cpp
@@ -1,39 +1,38 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/Writer.h"
+#include "odil/Writer.h"
 
 #include <cstdint>
-#include <endian.h>
 #include <map>
 #include <ostream>
 #include <sstream>
 #include <string>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/uid.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/endian.h"
+#include "odil/Element.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/Tag.h"
+#include "odil/uid.h"
+#include "odil/VR.h"
 
-#define dcmtkpp_write_binary(value, stream, byte_ordering, size) \
+#define odil_write_binary(value, stream, byte_ordering, size) \
 { \
-    auto const copy = value; \
-    uint##size##_t raw = *reinterpret_cast<uint##size##_t const *>(&copy); \
-    if(byte_ordering == LITTLE_ENDIAN) \
+    auto raw = value; \
+    if(byte_ordering == ByteOrdering::LittleEndian) \
     { \
-        raw = htole##size(raw); \
+        raw = host_to_little_endian(raw); \
     } \
-    else if(byte_ordering == BIG_ENDIAN) \
+    else if(byte_ordering == ByteOrdering::BigEndian) \
     { \
-        raw = htobe##size(raw); \
+        raw = host_to_big_endian(raw); \
     } \
     else \
     { \
@@ -46,13 +45,13 @@
     } \
 }
 
-namespace dcmtkpp
+namespace odil
 {
 
 Writer
 ::Writer(
     std::ostream & stream,
-    int byte_ordering, bool explicit_vr, ItemEncoding item_encoding,
+    ByteOrdering byte_ordering, bool explicit_vr, ItemEncoding item_encoding,
     bool use_group_length)
 : stream(stream), byte_ordering(byte_ordering), explicit_vr(explicit_vr),
     item_encoding(item_encoding), use_group_length(use_group_length)
@@ -68,7 +67,7 @@ Writer
 : stream(stream),
     byte_ordering(
         (transfer_syntax==registry::ExplicitVRBigEndian_Retired)?
-        BIG_ENDIAN:LITTLE_ENDIAN),
+        ByteOrdering::BigEndian:ByteOrdering::LittleEndian),
     explicit_vr(transfer_syntax!=registry::ImplicitVRLittleEndian),
     item_encoding(item_encoding), use_group_length(use_group_length)
 {
@@ -130,8 +129,8 @@ void
 Writer
 ::write_tag(Tag const & tag) const
 {
-    dcmtkpp_write_binary(tag.group, this->stream, this->byte_ordering, 16);
-    dcmtkpp_write_binary(tag.element, this->stream, this->byte_ordering, 16);
+    odil_write_binary(tag.group, this->stream, this->byte_ordering, 16);
+    odil_write_binary(tag.element, this->stream, this->byte_ordering, 16);
 }
 
 void
@@ -166,7 +165,7 @@ Writer
         if(vr == VR::OB || vr == VR::OW || vr == VR::OF || vr == VR::SQ ||
            vr == VR::UC || vr == VR::UR || vr == VR::UT || vr == VR::UN)
         {
-            dcmtkpp_write_binary(uint16_t(0), this->stream, this->byte_ordering, 16);
+            odil_write_binary(uint16_t(0), this->stream, this->byte_ordering, 16);
 
             uint32_t vl;
             if(vr == VR::SQ &&
@@ -178,16 +177,16 @@ Writer
             {
                vl = value_stream.tellp();
             }
-            dcmtkpp_write_binary(vl, this->stream, this->byte_ordering, 32);
+            odil_write_binary(vl, this->stream, this->byte_ordering, 32);
         }
         else
         {
-            dcmtkpp_write_binary(uint16_t(value_stream.tellp()), this->stream, this->byte_ordering, 16);
+            odil_write_binary(uint16_t(value_stream.tellp()), this->stream, this->byte_ordering, 16);
         }
     }
     else
     {
-        dcmtkpp_write_binary(uint32_t(value_stream.tellp()), this->stream, this->byte_ordering, 32);
+        odil_write_binary(uint32_t(value_stream.tellp()), this->stream, this->byte_ordering, 32);
     }
 
     this->stream.write(value_stream.str().c_str(), value_stream.tellp());
@@ -199,14 +198,14 @@ Writer
 
 void
 Writer
-::write_file(
-    DataSet const &data_set , std::ostream & stream,
-    std::string const & transfer_syntax, ItemEncoding item_encoding,
-    bool use_group_length)
+::write_file(DataSet const &data_set , std::ostream & stream,
+             odil::DataSet const & meta_information,
+             std::string const & transfer_syntax, ItemEncoding item_encoding,
+             bool use_group_length)
 {
     // Build File Meta Information, PS3.10, 7.1
-    DataSet meta_information;
-    meta_information.add(
+    DataSet meta_info = meta_information;
+    meta_info.add(
         registry::FileMetaInformationVersion, Value::Binary({0x00, 0x01}));
 
     if(!data_set.has(registry::SOPClassUID))
@@ -222,7 +221,7 @@ Writer
         throw Exception("Empty SOP Class UID");
     }
 
-    meta_information.add(
+    meta_info.add(
         registry::MediaStorageSOPClassUID,
         data_set.as_string(registry::SOPClassUID));
 
@@ -239,19 +238,19 @@ Writer
         throw Exception("Empty SOP Instance UID");
     }
 
-    meta_information.add(registry::MediaStorageSOPInstanceUID,
+    meta_info.add(registry::MediaStorageSOPInstanceUID,
         data_set.as_string(registry::SOPInstanceUID));
 
-    meta_information.add(registry::TransferSyntaxUID, {transfer_syntax});
-    meta_information.add(
+    meta_info.add(registry::TransferSyntaxUID, {transfer_syntax});
+    meta_info.add(
         registry::ImplementationClassUID, {implementation_class_uid});
-    meta_information.add(
-        registry::ImplementationVersionName,
-        { "DCMTK++ " DCMTKPP_STRINGIFY(DCMTKPP_MAJOR_VERSION) });
+    meta_info.add(
+        registry::ImplementationVersionName, { implementation_version_name });
 
-    // SourceApplicationEntityTitle
-    // SendingApplicationEntityTitle
-    // ReceivingApplicationEntityTitle
+    // Information set by input attribut 'meta_information':
+    // - SourceApplicationEntityTitle
+    // - SendingApplicationEntityTitle
+    // - ReceivingApplicationEntityTitle
 
     // File preamble
     for(unsigned int i=0; i<128; ++i)
@@ -277,7 +276,7 @@ Writer
     // Little Endian Transfer Syntax
     Writer meta_information_writer(
         stream, registry::ExplicitVRLittleEndian, item_encoding, use_group_length);
-    meta_information_writer.write_data_set(meta_information);
+    meta_information_writer.write_data_set(meta_info);
 
     // Data Set
     Writer data_set_writer(
@@ -288,7 +287,7 @@ Writer
 Writer::Visitor
 ::Visitor(
     std::ostream & stream, VR vr,
-    int byte_ordering, bool explicit_vr, Writer::ItemEncoding item_encoding,
+    ByteOrdering byte_ordering, bool explicit_vr, Writer::ItemEncoding item_encoding,
     bool use_group_length)
 : stream(stream), vr(vr), byte_ordering(byte_ordering), explicit_vr(explicit_vr),
     item_encoding(item_encoding), use_group_length(use_group_length)
@@ -308,7 +307,7 @@ Writer::Visitor
     {
         for(auto item: value)
         {
-            dcmtkpp_write_binary(
+            odil_write_binary(
                 int32_t(item), this->stream, this->byte_ordering, 32);
         }
     }
@@ -316,7 +315,7 @@ Writer::Visitor
     {
         for(auto item: value)
         {
-            dcmtkpp_write_binary(
+            odil_write_binary(
                 int16_t(item), this->stream, this->byte_ordering, 16);
         }
     }
@@ -324,7 +323,7 @@ Writer::Visitor
     {
         for(auto item: value)
         {
-            dcmtkpp_write_binary(
+            odil_write_binary(
                 uint32_t(item), this->stream, this->byte_ordering, 32);
         }
     }
@@ -332,7 +331,7 @@ Writer::Visitor
     {
         for(auto item: value)
         {
-            dcmtkpp_write_binary(
+            odil_write_binary(
                 uint16_t(item), this->stream, this->byte_ordering, 16);
         }
     }
@@ -354,7 +353,7 @@ Writer::Visitor
     {
         for(auto const & item: value)
         {
-            dcmtkpp_write_binary(
+            odil_write_binary(
                 double(item), this->stream, this->byte_ordering, 64);
         }
     }
@@ -362,7 +361,7 @@ Writer::Visitor
     {
         for(auto const & item: value)
         {
-            dcmtkpp_write_binary(
+            odil_write_binary(
                 float(item), this->stream, this->byte_ordering, 32);
         }
     }
@@ -425,7 +424,7 @@ Writer::Visitor
         {
             item_length = 0xffffffff;
         }
-        dcmtkpp_write_binary(item_length, sequence_stream, this->byte_ordering, 32);
+        odil_write_binary(item_length, sequence_stream, this->byte_ordering, 32);
 
         // Data set
         sequence_stream.write(item_stream.str().c_str(), item_stream.tellp());
@@ -438,7 +437,7 @@ Writer::Visitor
         if(this->item_encoding == ItemEncoding::UndefinedLength)
         {
             sequence_writer.write_tag(registry::ItemDelimitationItem);
-            dcmtkpp_write_binary(uint32_t(0), sequence_stream, this->byte_ordering, 32);
+            odil_write_binary(uint32_t(0), sequence_stream, this->byte_ordering, 32);
         }
     }
 
@@ -446,7 +445,7 @@ Writer::Visitor
     if(this->item_encoding == ItemEncoding::UndefinedLength)
     {
         sequence_writer.write_tag(registry::SequenceDelimitationItem);
-        dcmtkpp_write_binary(uint32_t(0), sequence_stream, this->byte_ordering, 32);
+        odil_write_binary(uint32_t(0), sequence_stream, this->byte_ordering, 32);
     }
 
     this->stream.write(sequence_stream.str().c_str(), sequence_stream.tellp());
@@ -473,7 +472,7 @@ Writer::Visitor
         for(int i=0; i<value.size(); i+=2)
         {
             uint16_t item = *reinterpret_cast<uint16_t const *>(&value[i]);
-            dcmtkpp_write_binary(item, this->stream, this->byte_ordering, 16);
+            odil_write_binary(item, this->stream, this->byte_ordering, 16);
         }
     }
     else if(this->vr == VR::OF)
@@ -485,7 +484,7 @@ Writer::Visitor
         for(int i=0; i<value.size(); i+=4)
         {
             uint32_t item = *reinterpret_cast<uint32_t const *>(&value[i]);
-            dcmtkpp_write_binary(item, this->stream, this->byte_ordering, 32);
+            odil_write_binary(item, this->stream, this->byte_ordering, 32);
         }
     }
     else
@@ -544,4 +543,4 @@ Writer::Visitor
 
 }
 
-#undef dcmtkpp_write_binary
+#undef odil_write_binary
diff --git a/src/dcmtkpp/Writer.h b/src/odil/Writer.h
similarity index 82%
rename from src/dcmtkpp/Writer.h
rename to src/odil/Writer.h
index 09fee4f..81a3c1e 100644
--- a/src/dcmtkpp/Writer.h
+++ b/src/odil/Writer.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -12,13 +12,15 @@
 #include <ostream>
 #include <string>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/Value.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/endian.h"
+#include "odil/registry.h"
+#include "odil/Tag.h"
+#include "odil/Value.h"
+#include "odil/VR.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief Write DICOM objects to a stream.
@@ -35,8 +37,8 @@ public:
     /// @brief Output stream.
     std::ostream & stream;
 
-    /// @brief Endianness (LITTLE_ENDIAN or BIG_ENDIAN).
-    int byte_ordering;
+    /// @brief Endianness.
+    ByteOrdering byte_ordering;
     /// @brief Explicit-ness of the Value Representations.
     bool explicit_vr;
     /// @brief Encoding of sequence items.
@@ -47,7 +49,7 @@ public:
     /// @brief Build a writer.
     Writer(
         std::ostream & stream,
-        int byte_ordering, bool explicit_vr,
+        ByteOrdering byte_ordering, bool explicit_vr,
         ItemEncoding item_encoding=ItemEncoding::ExplicitLength,
         bool use_group_length=false);
 
@@ -73,7 +75,8 @@ public:
     /// @brief Write a file (meta-information and data set).
     static void write_file(
         DataSet const &data_set, std::ostream & stream,
-        std::string const & transfer_syntax,
+        DataSet const & meta_information = DataSet(),
+        std::string const & transfer_syntax = registry::ExplicitVRLittleEndian,
         ItemEncoding item_encoding=ItemEncoding::ExplicitLength,
         bool use_group_length=false);
 
@@ -86,14 +89,14 @@ private:
         std::ostream & stream;
         VR vr;
 
-        int byte_ordering;
+        ByteOrdering byte_ordering;
         bool explicit_vr;
         ItemEncoding item_encoding;
         bool use_group_length;
 
         Visitor(
             std::ostream & stream, VR vr,
-            int byte_ordering, bool explicit_vr, ItemEncoding item_encoding,
+            ByteOrdering byte_ordering, bool explicit_vr, ItemEncoding item_encoding,
             bool use_group_length);
 
         result_type operator()(Value::Integers const & value) const;
diff --git a/src/dcmtkpp/UIDsDictionary.cpp b/src/odil/asio.cpp
similarity index 50%
rename from src/dcmtkpp/UIDsDictionary.cpp
rename to src/odil/asio.cpp
index 35f4559..33401af 100644
--- a/src/dcmtkpp/UIDsDictionary.cpp
+++ b/src/odil/asio.cpp
@@ -1,26 +1,10 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/UIDsDictionary.h"
-
-#include <map>
-#include <string>
-
-namespace dcmtkpp
-{
-
-UIDsDictionaryEntry
-::UIDsDictionaryEntry(
-        std::string const & name, std::string const & keyword,
-        std::string const & type)
-: name(name), keyword(keyword), type(type)
-{
-    // Nothing else.
-}
-
-}
+// Build Boost.asio in Odil
+#include <boost/asio/impl/src.hpp>
diff --git a/src/odil/base64.cpp b/src/odil/base64.cpp
new file mode 100644
index 0000000..f431e45
--- /dev/null
+++ b/src/odil/base64.cpp
@@ -0,0 +1,36 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/base64.h"
+
+#include <string>
+
+namespace odil
+{
+
+namespace base64
+{
+
+std::string const symbols =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+// Assume ASCII encoding
+std::string const reversed_symbols(
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // Not used: 00-0F
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // Not used: 10-1F
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // Not used: 20-2F
+    "\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x00\x00\x00\x00\x00\x00" // 0-9
+    "\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" // A-O
+    "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x00\x00\x00\x00\x00" // P-Z
+    "\x00\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28" // a-o
+    "\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x00\x00\x00\x00\x00" // p-z
+    , 0x80);
+
+}
+
+}
diff --git a/src/odil/base64.h b/src/odil/base64.h
new file mode 100644
index 0000000..3619886
--- /dev/null
+++ b/src/odil/base64.h
@@ -0,0 +1,42 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _203e7be8_beaa_4d97_94b2_6a0070f158a1
+#define _203e7be8_beaa_4d97_94b2_6a0070f158a1
+
+#include <string>
+
+namespace odil
+{
+
+namespace base64
+{
+
+/// @brief Dictionary of symbols for Base64.
+extern std::string const symbols;
+
+/// @brief Mapping of ASCII characters to values of Base64 symbols.
+extern std::string const reversed_symbols;
+
+/// @brief Encode a sequence of 8 bits data to Base64.
+template<typename TInputIterator, typename TOutputIterator>
+void encode(
+    TInputIterator begin, TInputIterator end, TOutputIterator destination);
+
+/// @brief Decode a sequence of 8 bits data from Base64.
+template<typename TInputIterator, typename TOutputIterator>
+void decode(
+    TInputIterator begin, TInputIterator end, TOutputIterator destination);
+
+}
+
+}
+
+#include "odil/base64.txx"
+
+#endif // _203e7be8_beaa_4d97_94b2_6a0070f158a1
diff --git a/src/odil/base64.txx b/src/odil/base64.txx
new file mode 100644
index 0000000..652dc1b
--- /dev/null
+++ b/src/odil/base64.txx
@@ -0,0 +1,131 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/base64.h"
+
+#include <iostream>
+#include <string>
+
+#ifndef _90eec83b_0ab6_4669_8454_e5a7e0a11a26
+#define _90eec83b_0ab6_4669_8454_e5a7e0a11a26
+
+namespace odil
+{
+
+namespace base64
+{
+
+template<typename TInputIterator, typename TOutputIterator>
+void encode(
+    TInputIterator begin, TInputIterator end, TOutputIterator destination)
+{
+    auto iterator = begin;
+    unsigned char carry=0;
+    unsigned int position=0;
+    while(iterator != end)
+    {
+        unsigned char const input = static_cast<unsigned char>(*iterator);
+
+        unsigned char symbol_index;
+        if(position%3 == 0)
+        {
+            symbol_index = input >> 2;
+            carry = input & 3;
+
+            *destination = symbols[symbol_index];
+        }
+        else if(position%3 == 1)
+        {
+            symbol_index = (carry << 4) + (input >> 4);
+            carry = input & 15;
+
+            *destination = symbols[symbol_index];
+        }
+        else // position%3 == 2
+        {
+            symbol_index = (carry << 2) + (input >> 6);
+            carry = input & 63;
+
+            *destination = symbols[symbol_index];
+            ++destination;
+            *destination = symbols[carry];
+        }
+
+        ++iterator;
+        ++destination;
+        ++position;
+    }
+
+    if(position%3 == 1)
+    {
+        *destination = symbols[carry<<4];
+        ++destination;
+        *destination = '=';
+        ++destination;
+        *destination = '=';
+    }
+    else if(position%3 == 2)
+    {
+        *destination = symbols[carry<<2];
+        ++destination;
+        *destination = '=';
+    }
+    // Otherwise (position%3 == 0), nothing to do.
+}
+
+template<typename TInputIterator, typename TOutputIterator>
+void decode(
+    TInputIterator begin, TInputIterator end, TOutputIterator destination)
+{
+    auto iterator = begin;
+    unsigned char current=0;
+    unsigned int position=0;
+
+    while(iterator != end)
+    {
+        unsigned char input = static_cast<unsigned char>(*iterator);
+        if(input != '=')
+        {
+            input = reversed_symbols[input];
+
+            if(position%4 == 0)
+            {
+                current = input;
+            }
+            else if(position%4 == 1)
+            {
+                current = (current << 2) + (input >> 4);
+                *destination = current;
+                ++destination;
+                current = input & 15;
+            }
+            else if(position%4 == 2)
+            {
+                current = (current << 4) + (input >> 2);
+                *destination = current;
+                ++destination;
+                current = input & 3;
+            }
+            else // position%4 == 3
+            {
+                current = (current << 6) + input;
+                *destination = current;
+                ++destination;
+            }
+        }
+
+        ++iterator;
+        ++position;
+    }
+}
+
+}
+
+}
+
+#endif // _90eec83b_0ab6_4669_8454_e5a7e0a11a26
diff --git a/src/dcmtkpp/ElementAccessor.cpp b/src/odil/dcmtk/ElementAccessor.cpp
similarity index 90%
rename from src/dcmtkpp/ElementAccessor.cpp
rename to src/odil/dcmtk/ElementAccessor.cpp
index fa9e2c5..ed345e6 100644
--- a/src/dcmtkpp/ElementAccessor.cpp
+++ b/src/odil/dcmtk/ElementAccessor.cpp
@@ -1,17 +1,20 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/ElementAccessor.h"
+#include "odil/dcmtk/ElementAccessor.h"
 
 #include <string>
 #include <vector>
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace dcmtk
 {
 
 #define DEFINE_ELEMENT_ACCESSOR(TValueType, getter, setter) \
@@ -37,3 +40,5 @@ DEFINE_ELEMENT_ACCESSOR(Uint32, get_default, set_default)
 #undef DEFINE_ELEMENT_ACCESSOR
 
 }
+
+}
diff --git a/src/dcmtkpp/ElementAccessor.h b/src/odil/dcmtk/ElementAccessor.h
similarity index 89%
rename from src/dcmtkpp/ElementAccessor.h
rename to src/odil/dcmtk/ElementAccessor.h
index 784614c..cbaaf7f 100644
--- a/src/dcmtkpp/ElementAccessor.h
+++ b/src/odil/dcmtk/ElementAccessor.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -15,10 +15,13 @@
 #include <dcmtk/dcmdata/dcelem.h>
 #include <dcmtk/dcmdata/dcdatset.h>
 
-#include "dcmtkpp/ElementTraits.h"
-#include "dcmtkpp/Exception.h"
+#include "odil/dcmtk/ElementTraits.h"
+#include "odil/dcmtk/Exception.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace dcmtk
 {
 
 /// @brief Generic access to values in DcmElement.
@@ -58,6 +61,8 @@ struct ElementAccessor
 
 }
 
-#include "ElementAccessor.txx"
+}
+
+#include "odil/dcmtk/ElementAccessor.txx"
 
 #endif // _dfa4858b_1c9d_4ce9_b220_a1c15d873602
diff --git a/src/dcmtkpp/ElementAccessor.txx b/src/odil/dcmtk/ElementAccessor.txx
similarity index 97%
rename from src/dcmtkpp/ElementAccessor.txx
rename to src/odil/dcmtk/ElementAccessor.txx
index 586280a..94ba22a 100644
--- a/src/dcmtkpp/ElementAccessor.txx
+++ b/src/odil/dcmtk/ElementAccessor.txx
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,7 +9,7 @@
 #ifndef _f9af3e63_3597_4513_8c10_b55058f5370b
 #define _f9af3e63_3597_4513_8c10_b55058f5370b
 
-#include "ElementAccessor.h"
+#include "odil/dcmtk/ElementAccessor.h"
 
 #include <dcmtk/config/osconfig.h>
 #include <dcmtk/dcmdata/dcdatset.h>
@@ -17,7 +17,10 @@
 #include <dcmtk/dcmdata/dcerror.h>
 #include <dcmtk/ofstd/ofstring.h>
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace dcmtk
 {
 
 template<typename TValueType>
@@ -204,4 +207,6 @@ set_binary(
 
 }
 
+}
+
 #endif // _f9af3e63_3597_4513_8c10_b55058f5370b
diff --git a/src/dcmtkpp/ElementTraits.cpp b/src/odil/dcmtk/ElementTraits.cpp
similarity index 93%
rename from src/dcmtkpp/ElementTraits.cpp
rename to src/odil/dcmtk/ElementTraits.cpp
index 37492fd..d81ef7b 100644
--- a/src/dcmtkpp/ElementTraits.cpp
+++ b/src/odil/dcmtk/ElementTraits.cpp
@@ -1,12 +1,12 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/ElementTraits.h"
+#include "odil/dcmtk/ElementTraits.h"
 
 #include <functional>
 #include <string>
@@ -15,9 +15,12 @@
 #include <dcmtk/dcmdata/dcelem.h>
 #include <dcmtk/dcmdata/dcvr.h>
 
-#include "dcmtkpp/Exception.h"
+#include "odil/Exception.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace dcmtk
 {
 
 OFCondition
@@ -75,3 +78,5 @@ DEFINE_ELEMENT_TRAITS(Uint32, &DcmElement::getUint32, &DcmElement::putUint32)
 #undef DEFINE_ELEMENT_TRAITS
 
 }
+
+}
diff --git a/src/dcmtkpp/ElementTraits.h b/src/odil/dcmtk/ElementTraits.h
similarity index 93%
rename from src/dcmtkpp/ElementTraits.h
rename to src/odil/dcmtk/ElementTraits.h
index 60c4904..03ebb77 100644
--- a/src/dcmtkpp/ElementTraits.h
+++ b/src/odil/dcmtk/ElementTraits.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -16,11 +16,14 @@
 #include <dcmtk/dcmdata/dcelem.h>
 #include <dcmtk/dcmdata/dcvr.h>
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace dcmtk
 {
 
 /**
- * @class dcmtkpp::ElementTraits
+ * @class odil::ElementTraits
  * @brief Traits for generic data access to values of DcmElement.
  *
  * The members are:
@@ -44,4 +47,6 @@ struct ElementTraits
 
 }
 
+}
+
 #endif // _3ae28d18_6f01_4e10_98e2_1c0d21fdcab7
diff --git a/src/dcmtkpp/Exception.cpp b/src/odil/dcmtk/Exception.cpp
similarity index 72%
rename from src/dcmtkpp/Exception.cpp
rename to src/odil/dcmtk/Exception.cpp
index cfe9123..d918f27 100644
--- a/src/dcmtkpp/Exception.cpp
+++ b/src/odil/dcmtk/Exception.cpp
@@ -1,42 +1,47 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/Exception.h"
+#include "odil/dcmtk/Exception.h"
 
-#include <string>
+#include <stdexcept>
 
 #include <dcmtk/config/osconfig.h>
 #include <dcmtk/ofstd/ofcond.h>
 
-namespace dcmtkpp
+#include "odil/Exception.h"
+
+namespace odil
+{
+
+namespace dcmtk
 {
 
 Exception
 ::Exception(std::string const & message)
-: _source(Source::Message), _message(message), _condition()
+: ::odil::Exception(message), _source(Source::Message), _condition()
 {
     // Nothing else.
 }
 
 Exception
 ::Exception(OFCondition const & condition)
-: _source(Source::Condition), _message(), _condition(condition)
+: ::odil::Exception(), _source(Source::Condition), _condition(condition)
 {
     // Nothing else.
 }
 
 Exception
-::~Exception() throw()
+::~Exception() noexcept
 {
     // Nothing to do.
 }
 
-char const * 
+char const *
 Exception
 ::what() const throw()
 {
@@ -48,6 +53,10 @@ Exception
     {
         return this->_condition.text();
     }
+    else
+    {
+        throw std::runtime_error("Invalid source");
+    }
 }
 
 Exception::Source
@@ -69,3 +78,5 @@ Exception
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/Exception.h b/src/odil/dcmtk/Exception.h
similarity index 73%
rename from src/dcmtkpp/Exception.h
rename to src/odil/dcmtk/Exception.h
index a4c9bc0..38a6b30 100644
--- a/src/dcmtkpp/Exception.h
+++ b/src/odil/dcmtk/Exception.h
@@ -1,66 +1,68 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#ifndef _b9607695_cb3b_4188_8caa_bc8bb051ef28
-#define _b9607695_cb3b_4188_8caa_bc8bb051ef28
-
-#include <exception>
-#include <string>
+#ifndef _9e106372_aca6_4551_a591_95695eb00e4c
+#define _9e106372_aca6_4551_a591_95695eb00e4c
 
 #include <dcmtk/config/osconfig.h>
 #include <dcmtk/ofstd/ofcond.h>
 
-namespace dcmtkpp
+#include "odil/Exception.h"
+
+namespace odil
+{
+
+namespace dcmtk
 {
 
-/// @brief Base class for dcmtkpp exceptions.
-class Exception: public std::exception
+class Exception: public ::odil::Exception
 {
-public: 
-    /** 
-     * @brief Source of the Exception: either a message string or an 
+public:
+    /**
+     * @brief Source of the Exception: either a message string or an
      * OFCondition.
      */
     enum class Source { Message, Condition };
-    
+
     /// @brief Message string constructor, set the source to Source::Message.
     Exception(std::string const & message);
-    
+
     /// @brief Condition constructor, set the source to Source::Condition.
     Exception(OFCondition const & condition);
-    
+
     /// @brief Destructor.
-    virtual ~Exception() throw();
-    
-    /**
-     * @brief Return the reason for the exception.
-     * 
-     * The reason for the exception is set to the message (for Source::Message) or
-     * to the text of the condition (for Source::Condition).
-     */
-    virtual const char* what() const throw();
-    
+    virtual ~Exception() noexcept;
+
     /// @brief Return the exception source.
     Source get_source() const;
-    
+
     /**
      * @brief Return the condition that was used to create this exception.
-     * 
+     *
      * If the source is not Source::Condition, throw an exception.
      */
     OFCondition const & get_condition() const;
 
+    /**
+     * @brief Return the reason for the exception.
+     *
+     * The reason for the exception is set to the message (for Source::Message) or
+     * to the text of the condition (for Source::Condition).
+     */
+    virtual const char* what() const noexcept;
 private:
     Source _source;
-    std::string _message;
     OFCondition _condition;
 };
 
 }
 
-#endif // _b9607695_cb3b_4188_8caa_bc8bb051ef28
+}
+
+#endif // _9e106372_aca6_4551_a591_95695eb00e4c
+
diff --git a/src/dcmtkpp/VRTraits.h b/src/odil/dcmtk/VRTraits.h
similarity index 90%
rename from src/dcmtkpp/VRTraits.h
rename to src/odil/dcmtk/VRTraits.h
index feb3491..eb7b335 100644
--- a/src/dcmtkpp/VRTraits.h
+++ b/src/odil/dcmtk/VRTraits.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,19 @@
 #ifndef _4f556093_02e3_4659_9026_2b16bc2c8a51
 #define _4f556093_02e3_4659_9026_2b16bc2c8a51
 
-namespace dcmtkpp
+#include <vector>
+
+#include <dcmtk/config/osconfig.h>
+#include <dcmtk/dcmdata/dcvr.h>
+
+namespace odil
+{
+
+namespace dcmtk
 {
 
 /**
- * @class dcmtkpp::VRTraits
+ * @class odil::VRTraits
  * @brief Information related to VR.
  *
  * The members are:
@@ -63,4 +71,6 @@ DECLARE_VR_TRAITS(EVR_UT, std::string)
 
 }
 
+}
+
 #endif // _4f556093_02e3_4659_9026_2b16bc2c8a51
diff --git a/src/dcmtkpp/conversion.cpp b/src/odil/dcmtk/conversion.cpp
similarity index 98%
rename from src/dcmtkpp/conversion.cpp
rename to src/odil/dcmtk/conversion.cpp
index bd57e4e..ebb57a5 100644
--- a/src/dcmtkpp/conversion.cpp
+++ b/src/odil/dcmtk/conversion.cpp
@@ -1,23 +1,26 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/conversion.h"
+#include "odil/dcmtk/conversion.h"
 
 #include <dcmtk/config/osconfig.h>
 #include <dcmtk/dcmdata/dctk.h>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
+#include "odil/dcmtk/Exception.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace dcmtk
 {
 
 DcmEVR convert(VR vr)
@@ -331,7 +334,7 @@ void convert<std::vector<uint8_t>, Value::Binary>(
 {
     auto & destination_values = (destination.*getter)();
     destination_values =
-        dcmtkpp::ElementAccessor<std::vector<uint8_t>>::element_get(*source, 0);
+        ElementAccessor<std::vector<uint8_t>>::element_get(*source, 0);
 }
 
 Element convert(DcmElement * source)
@@ -519,3 +522,5 @@ DataSet convert(DcmItem * source)
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/conversion.h b/src/odil/dcmtk/conversion.h
similarity index 73%
rename from src/dcmtkpp/conversion.h
rename to src/odil/dcmtk/conversion.h
index e10e485..fddc6fc 100644
--- a/src/dcmtkpp/conversion.h
+++ b/src/odil/dcmtk/conversion.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -12,27 +12,30 @@
 #include <dcmtk/config/osconfig.h>
 #include <dcmtk/dcmdata/dctk.h>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
-/// @brief Convert a dcmtkpp::VR to a DcmVR.
+namespace dcmtk
+{
+
+/// @brief Convert a odil::VR to a DcmVR.
 DcmEVR convert(VR vr);
 
-/// @brief Convert a DcmVR to a dcmtkpp::VR.
+/// @brief Convert a DcmVR to a odil::VR.
 VR convert(DcmEVR evr);
 
-/// @brief Convert a dcmtkpp::Tag to a DcmTagKey.
+/// @brief Convert a odil::Tag to a DcmTagKey.
 DcmTagKey convert(Tag const & tag);
 
-/// @brief Convert a DcmTagKey to a dcmtkpp::Tag.
+/// @brief Convert a DcmTagKey to a odil::Tag.
 Tag convert(DcmTagKey const & tag);
 
-/// @brief Convert a dcmtkpp::Element to a DcmElement.
+/// @brief Convert a odil::Element to a DcmElement.
 DcmElement * convert(Tag const & tag, Element const & source);
 
 /// @brief Low-level element converter.
@@ -41,7 +44,7 @@ void convert(
     Element const & source, DcmElement * destination,
     TSourceType const & (Element::*getter)() const);
 
-/// @brief Convert a DcmElement to a dcmtkpp::Element.
+/// @brief Convert a DcmElement to a odil::Element.
 Element convert(DcmElement * source);
 
 /// @brief Low-level element converter.
@@ -62,14 +65,16 @@ void convert(
     DcmElement * source, Element & destination,
     TDestinationType & (Element::*getter)());
 
-/// @brief Convert a dcmtkpp::DataSet to a DcmDataset or a DcmItem.
+/// @brief Convert a odil::DataSet to a DcmDataset or a DcmItem.
 DcmItem * convert(DataSet const & source, bool as_data_set=true);
 
-/// @brief Convert a DcmDataset to a dcmtkpp::DataSet.
+/// @brief Convert a DcmDataset to a odil::DataSet.
 DataSet convert(DcmItem * source);
 
 }
 
-#include <dcmtkpp/conversion.txx>
+}
+
+#include "odil/dcmtk/conversion.txx"
 
 #endif // _d5ecacb8_04ff_48b0_8026_570c9b2ae360
diff --git a/src/dcmtkpp/conversion.txx b/src/odil/dcmtk/conversion.txx
similarity index 89%
rename from src/dcmtkpp/conversion.txx
rename to src/odil/dcmtk/conversion.txx
index 0c296eb..07ddb47 100644
--- a/src/dcmtkpp/conversion.txx
+++ b/src/odil/dcmtk/conversion.txx
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,14 +9,17 @@
 #ifndef _665b5269_140c_4320_94f2_ad8a7c052e9b
 #define _665b5269_140c_4320_94f2_ad8a7c052e9b
 
-#include "dcmtkpp/conversion.h"
+#include "odil/dcmtk/conversion.h"
 
 #include <sstream>
 
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/ElementAccessor.h"
+#include "odil/Element.h"
+#include "odil/dcmtk/ElementAccessor.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace dcmtk
 {
 
 template<typename TSourceType, typename TDestinationType>
@@ -73,10 +76,12 @@ void convert(
     for(auto i = 0; i<source->getVM(); ++i)
     {
         destination_values.push_back(
-            dcmtkpp::ElementAccessor<TSourceType>::element_get(*source, i));
+            ElementAccessor<TSourceType>::element_get(*source, i));
     }
 }
 
 }
 
+}
+
 #endif // _665b5269_140c_4320_94f2_ad8a7c052e9b
diff --git a/src/odil/dul/EventData.h b/src/odil/dul/EventData.h
new file mode 100644
index 0000000..9402309
--- /dev/null
+++ b/src/odil/dul/EventData.h
@@ -0,0 +1,39 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _350775b8_701f_4069_ab1e_c974a209389c
+#define _350775b8_701f_4069_ab1e_c974a209389c
+
+#include <memory>
+
+#include "odil/AssociationAcceptor.h"
+#include "odil/AssociationParameters.h"
+#include "odil/dul/Transport.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace dul
+{
+
+/// @brief Data related to events of the DUL state machine.
+class EventData
+{
+public:
+    Transport::Socket::endpoint_type peer_endpoint;
+    std::shared_ptr<pdu::Object> pdu;
+    AssociationParameters association_parameters;
+    std::shared_ptr<AssociationRejected> reject;
+};
+
+}
+
+}
+
+#endif // _350775b8_701f_4069_ab1e_c974a209389c
diff --git a/src/odil/dul/StateMachine.cpp b/src/odil/dul/StateMachine.cpp
new file mode 100644
index 0000000..6f6dc5a
--- /dev/null
+++ b/src/odil/dul/StateMachine.cpp
@@ -0,0 +1,764 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/dul/StateMachine.h"
+
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <sstream>
+#include <tuple>
+#include <utility>
+
+#include <boost/asio.hpp>
+#include <boost/system/system_error.hpp>
+
+#include "odil/AssociationParameters.h"
+#include "odil/endian.h"
+#include "odil/Exception.h"
+#include "odil/dul/EventData.h"
+#include "odil/dul/Transport.h"
+#include "odil/pdu/AAbort.h"
+#include "odil/pdu/AAssociate.h"
+#include "odil/pdu/AAssociateRJ.h"
+#include "odil/pdu/AReleaseRP.h"
+#include "odil/pdu/AReleaseRQ.h"
+#include "odil/pdu/PDataTF.h"
+
+namespace odil
+{
+
+namespace dul
+{
+
+StateMachine
+::StateMachine()
+: _state(State::Sta1), _timeout(boost::posix_time::pos_infin),
+  _artim_timer(_transport.get_service()),
+  _association_acceptor(default_association_acceptor)
+{
+    // Nothing else.
+}
+
+StateMachine
+::~StateMachine()
+{
+    // Nothing to to, transport is closed by ~Transport.
+}
+
+void
+StateMachine
+::transition(Event const & event, EventData & data)
+{
+    auto const guard_iterator = StateMachine::_guards.find(
+        {this->_state, event});
+    auto const guard_value =
+        (guard_iterator != StateMachine::_guards.end())?
+        guard_iterator->second(*this, data):true;
+
+    auto const transition_iterator = StateMachine::_transitions.find(
+        std::make_tuple(this->_state, event, guard_value));
+    if(transition_iterator == StateMachine::_transitions.end())
+    {
+        throw Exception("No such transition");
+    }
+
+    auto const & action = transition_iterator->second.first;
+    auto const & next_state = transition_iterator->second.second;
+
+    // Do action
+    if(action == Action::AE_1) { this->AE_1(data); }
+    else if(action == Action::AE_2) { this->AE_2(data); }
+    else if(action == Action::AE_3) { this->AE_3(data); }
+    else if(action == Action::AE_4) { this->AE_4(data); }
+    else if(action == Action::AE_5) { this->AE_5(data); }
+    else if(action == Action::AE_6) { this->AE_6(data); }
+    else if(action == Action::AE_7) { this->AE_7(data); }
+    else if(action == Action::AE_8) { this->AE_8(data); }
+    else if(action == Action::DT_1) { this->DT_1(data); }
+    else if(action == Action::DT_2) { this->DT_2(data); }
+    else if(action == Action::AR_1) { this->AR_1(data); }
+    else if(action == Action::AR_2) { this->AR_2(data); }
+    else if(action == Action::AR_3) { this->AR_3(data); }
+    else if(action == Action::AR_4) { this->AR_4(data); }
+    else if(action == Action::AR_5) { this->AR_5(data); }
+    else if(action == Action::AR_6) { this->AR_6(data); }
+    else if(action == Action::AR_7) { this->AR_7(data); }
+    else if(action == Action::AR_8) { this->AR_8(data); }
+    else if(action == Action::AR_9) { this->AR_9(data); }
+    else if(action == Action::AR_10) { this->AR_10(data); }
+    else if(action == Action::AA_1) { this->AA_1(data); }
+    else if(action == Action::AA_2) { this->AA_2(data); }
+    else if(action == Action::AA_3) { this->AA_3(data); }
+    else if(action == Action::AA_4) { this->AA_4(data); }
+    else if(action == Action::AA_5) { this->AA_5(data); }
+    else if(action == Action::AA_6) { this->AA_6(data); }
+    else if(action == Action::AA_7) { this->AA_7(data); }
+    else if(action == Action::AA_8) { this->AA_8(data); }
+    else
+    {
+        throw Exception("Unknown action");
+    }
+
+    this->_state = next_state;
+}
+
+StateMachine::State
+StateMachine
+::get_state() const
+{
+    return this->_state;
+}
+
+Transport const &
+StateMachine
+::get_transport() const
+{
+    return this->_transport;
+}
+
+Transport &
+StateMachine
+::get_transport()
+{
+    return this->_transport;
+}
+
+StateMachine::duration_type
+StateMachine
+::get_timeout() const
+{
+    return this->_timeout;
+}
+
+void
+StateMachine
+::set_timeout(duration_type timeout)
+{
+    this->_timeout = timeout;
+}
+
+void
+StateMachine
+::receive(EventData & data)
+{
+    this->_transport.receive(data.peer_endpoint);
+    this->transition(Event::TransportConnectionIndication, data);
+}
+
+void
+StateMachine
+::send_pdu(EventData & data)
+{
+    if(data.pdu == nullptr)
+    {
+        throw Exception("No PDU");
+    }
+    auto const & item = data.pdu->get_item();
+    auto const type = item.as_unsigned_int_8("PDU-type");
+
+    if(type == 0x01)
+    {
+        this->transition(Event::AAssociateRQLocal, data);
+        this->transition(Event::TransportConnectionConfirmation, data);
+    }
+    else if(type == 0x02)
+    {
+        this->transition(Event::AAssociateACLocal, data);
+    }
+    else if(type == 0x03)
+    {
+        this->transition(Event::AAssociateRJLocal, data);
+    }
+    else if(type == 0x04)
+    {
+        this->transition(Event::PDataTFLocal, data);
+    }
+    else if(type == 0x05)
+    {
+        this->transition(Event::AReleaseRQLocal, data);
+    }
+    else if(type == 0x06)
+    {
+        this->transition(Event::AReleaseRPLocal, data);
+    }
+    else if(type == 0x07)
+    {
+        this->transition(Event::AAbortLocal, data);
+    }
+    else
+    {
+        this->transition(Event::InvalidPDU, data);
+    }
+}
+
+void
+StateMachine
+::receive_pdu(EventData & data)
+{
+    auto const header = this->_transport.read(6);
+
+    uint8_t const type = header[0];
+    uint32_t const length = big_endian_to_host(
+        *reinterpret_cast<uint32_t const *>(&header[0]+2));
+    auto const pdu_data = this->_transport.read(length);
+
+    std::stringstream stream;
+    stream.write(&header[0], header.size());
+    stream.write(&pdu_data[0], pdu_data.size());
+
+    data.pdu=nullptr;
+    Event event = Event::None;
+    if(type == 0x01)
+    {
+        data.pdu = std::make_shared<pdu::AAssociateRQ>(stream);
+        event = Event::AAssociateRQRemote;
+    }
+    else if(type == 0x02)
+    {
+        data.pdu = std::make_shared<pdu::AAssociateAC>(stream);
+        event = Event::AAssociateACRemote;
+    }
+    else if(type == 0x03)
+    {
+        data.pdu = std::make_shared<pdu::AAssociateRJ>(stream);
+        event = Event::AAssociateRJRemote;
+    }
+    else if(type == 0x04)
+    {
+        data.pdu = std::make_shared<pdu::PDataTF>(stream);
+        event = Event::PDataTFRemote;
+    }
+    else if(type == 0x05)
+    {
+        data.pdu = std::make_shared<pdu::AReleaseRQ>(stream);
+        event = Event::AReleaseRQRemote;
+    }
+    else if(type == 0x06)
+    {
+        data.pdu = std::make_shared<pdu::AReleaseRP>(stream);
+        event = Event::AReleaseRPRemote;
+    }
+    else if(type == 0x07)
+    {
+        data.pdu = std::make_shared<pdu::AAbort>(stream);
+        event = Event::AAbortRemote;
+    }
+    else
+    {
+        event = Event::InvalidPDU;
+    }
+
+    this->transition(event, data);
+
+}
+
+void
+StateMachine
+::start_timer(EventData & data)
+{
+    return;
+
+
+    auto const canceled = this->_artim_timer.expires_from_now(this->_timeout);
+    if(canceled != 0)
+    {
+        throw Exception("ARTIM timer started with pending operations");
+    }
+
+    this->_artim_timer.async_wait(
+        [this,&data](boost::system::error_code const & e)
+        {
+            //source = Source::TIMER;
+            //error = e;
+
+            if(!e)
+            {
+                this->transition(Event::ARTIMTimerExpired, data);
+            }
+            else if(e == boost::asio::error::operation_aborted)
+            {
+                // Do nothing
+            }
+            else
+            {
+                throw boost::system::system_error(e);
+            }
+        }
+    );
+}
+
+void
+StateMachine
+::stop_timer()
+{
+    return;
+
+
+    this->_artim_timer.expires_at(boost::posix_time::pos_infin);
+    this->_transport.get_service().poll();
+    // FIXME: check that the timer was aborted
+    /*
+        if(source != Source::TIMER)
+        {
+            throw Exception("Unknown event");
+        }
+        else if(error != boost::asio::error::operation_aborted)
+        {
+            throw Exception("TCP timer error: "+error.message());
+        }
+    */
+    this->_transport.get_service().reset();
+}
+
+AssociationAcceptor const &
+StateMachine
+::get_association_acceptor() const
+{
+    return this->_association_acceptor;
+}
+
+void
+StateMachine
+::set_association_acceptor(AssociationAcceptor const & acceptor)
+{
+    this->_association_acceptor = acceptor;
+}
+
+#define transition_full(start, event, guard, action, end) { \
+    std::make_tuple(StateMachine::State::start, StateMachine::Event::event, guard), \
+    { StateMachine::Action::action, StateMachine::State::end } }
+
+#define transition(start, event, action, end) \
+    transition_full(start, event, true, action, end)
+
+StateMachine::TransitionMap const
+StateMachine
+::_transitions = {
+    transition(Sta1, AAssociateRQLocal, AE_1, Sta4),
+    transition(Sta1, TransportConnectionIndication, AE_5, Sta2),
+
+    transition(Sta2, AAssociateACRemote, AA_1, Sta13),
+    transition(Sta2, AAssociateRJRemote, AA_1, Sta13),
+    transition_full(Sta2, AAssociateRQRemote, true, AE_6, Sta3),
+    transition_full(Sta2, AAssociateRQRemote, false, AE_6, Sta13),
+    transition(Sta2, PDataTFRemote, AA_1, Sta13),
+    transition(Sta2, AReleaseRQRemote, AA_1, Sta13),
+    transition(Sta2, AReleaseRPRemote, AA_1, Sta13),
+    transition(Sta2, AAbortRemote, AA_2, Sta1),
+    transition(Sta2, TransportConnectionClosedIndication, AA_5, Sta1),
+    transition(Sta2, ARTIMTimerExpired, AA_2, Sta1),
+    transition(Sta2, InvalidPDU, AA_1, Sta13),
+
+    transition(Sta3, AAssociateACRemote, AA_8, Sta13),
+    transition(Sta3, AAssociateRJRemote, AA_8, Sta13),
+    transition(Sta3, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta3, AAssociateACLocal, AE_7, Sta6),
+    transition(Sta3, AAssociateRJLocal, AE_8, Sta13),
+    transition(Sta3, PDataTFRemote, AA_8, Sta13),
+    transition(Sta3, AReleaseRQRemote, AA_8, Sta13),
+    transition(Sta3, AReleaseRPRemote, AA_8, Sta13),
+    transition(Sta3, AAbortLocal, AA_1, Sta13),
+    transition(Sta3, AAbortRemote, AA_3, Sta1),
+    transition(Sta3, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta3, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta4, TransportConnectionConfirmation, AE_2, Sta5),
+    transition(Sta4, AAbortLocal, AA_2, Sta1),
+    transition(Sta4, TransportConnectionClosedIndication, AA_4, Sta1),
+
+    transition(Sta5, AAssociateACRemote, AE_3, Sta6),
+    transition(Sta5, AAssociateRJRemote, AE_4, Sta1),
+    transition(Sta5, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta5, PDataTFRemote, AA_8, Sta13),
+    transition(Sta5, AReleaseRQRemote, AA_8, Sta13),
+    transition(Sta5, AReleaseRPRemote, AA_8, Sta13),
+    transition(Sta5, AAbortLocal, AA_1, Sta13),
+    transition(Sta5, AAbortRemote, AA_3, Sta1),
+    transition(Sta5, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta5, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta6, AAssociateACRemote, AA_8, Sta13),
+    transition(Sta6, AAssociateRJRemote, AA_8, Sta13),
+    transition(Sta6, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta6, PDataTFLocal, DT_1, Sta6),
+    transition(Sta6, PDataTFRemote, DT_2, Sta6),
+    transition(Sta6, AReleaseRQLocal, AR_1, Sta7),
+    transition(Sta6, AReleaseRQRemote, AR_2, Sta8),
+    transition(Sta6, AReleaseRPRemote, AA_8, Sta13),
+    transition(Sta6, AAbortLocal, AA_1, Sta13),
+    transition(Sta6, AAbortRemote, AA_3, Sta1),
+    transition(Sta6, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta6, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta7, AAssociateACRemote, AA_8, Sta13),
+    transition(Sta7, AAssociateRJRemote, AA_8, Sta13),
+    transition(Sta7, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta7, PDataTFRemote, AR_6, Sta7),
+    //transition(Sta7, AReleaseRQRemote, AR_8, Sta9Or10),
+    transition(Sta7, AReleaseRPRemote, AR_3, Sta1),
+    transition(Sta7, AAbortLocal, AA_1, Sta13),
+    transition(Sta7, AAbortRemote, AA_3, Sta1),
+    transition(Sta7, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta7, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta8, AAssociateACRemote, AA_8, Sta13),
+    transition(Sta8, AAssociateRJRemote, AA_8, Sta13),
+    transition(Sta8, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta8, PDataTFLocal, AR_7, Sta8),
+    transition(Sta8, PDataTFRemote, AA_8, Sta13),
+    transition(Sta8, AReleaseRQRemote, AA_8, Sta13),
+    transition(Sta8, AReleaseRPRemote, AA_8, Sta13),
+    transition(Sta8, AReleaseRPLocal, AR_4, Sta13),
+    transition(Sta8, AAbortLocal, AA_1, Sta13),
+    transition(Sta8, AAbortRemote, AA_3, Sta1),
+    transition(Sta8, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta8, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta9, AAssociateACRemote, AA_8, Sta13),
+    transition(Sta9, AAssociateRJRemote, AA_8, Sta13),
+    transition(Sta9, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta9, PDataTFRemote, AA_8, Sta13),
+    transition(Sta9, AReleaseRQRemote, AA_8, Sta13),
+    transition(Sta9, AReleaseRPRemote, AA_8, Sta13),
+    transition(Sta9, AReleaseRPLocal, AR_9, Sta11),
+    transition(Sta9, AAbortLocal, AA_1, Sta13),
+    transition(Sta9, AAbortRemote, AA_3, Sta1),
+    transition(Sta9, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta9, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta10, AAssociateACRemote, AA_8, Sta13),
+    transition(Sta10, AAssociateRJRemote, AA_8, Sta13),
+    transition(Sta10, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta10, PDataTFRemote, AA_8, Sta13),
+    transition(Sta10, AReleaseRQRemote, AA_8, Sta13),
+    transition(Sta10, AReleaseRPRemote, AR_10, Sta12),
+    transition(Sta10, AAbortLocal, AA_1, Sta13),
+    transition(Sta10, AAbortRemote, AA_3, Sta1),
+    transition(Sta10, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta10, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta11, AAssociateACRemote, AA_8, Sta13),
+    transition(Sta11, AAssociateRJRemote, AA_8, Sta13),
+    transition(Sta11, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta11, PDataTFRemote, AA_8, Sta13),
+    transition(Sta11, AReleaseRQRemote, AA_8, Sta13),
+    transition(Sta11, AReleaseRPRemote, AR_3, Sta1),
+    transition(Sta11, AAbortLocal, AA_1, Sta13),
+    transition(Sta11, AAbortRemote, AA_3, Sta1),
+    transition(Sta11, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta11, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta12, AAssociateACRemote, AA_8, Sta13),
+    transition(Sta12, AAssociateRJRemote, AA_8, Sta13),
+    transition(Sta12, AAssociateRQRemote, AA_8, Sta13),
+    transition(Sta12, PDataTFRemote, AA_8, Sta13),
+    transition(Sta12, AReleaseRQRemote, AA_8, Sta13),
+    transition(Sta12, AReleaseRPRemote, AA_8, Sta13),
+    transition(Sta12, AReleaseRPLocal, AR_4, Sta13),
+    transition(Sta12, AAbortLocal, AA_1, Sta13),
+    transition(Sta12, AAbortRemote, AA_3, Sta1),
+    transition(Sta12, TransportConnectionClosedIndication, AA_4, Sta1),
+    transition(Sta12, InvalidPDU, AA_8, Sta13),
+
+    transition(Sta13, AAssociateACRemote, AA_6, Sta13),
+    transition(Sta13, AAssociateRJRemote, AA_6, Sta13),
+    transition(Sta13, AAssociateRQRemote, AA_7, Sta13),
+    transition(Sta13, PDataTFRemote, AA_6, Sta13),
+    transition(Sta13, AReleaseRQRemote, AA_6, Sta13),
+    transition(Sta13, AReleaseRPRemote, AA_6, Sta13),
+    transition(Sta13, AAbortRemote, AA_2, Sta1),
+    transition(Sta13, TransportConnectionClosedIndication, AR_5, Sta1),
+    transition(Sta13, ARTIMTimerExpired, AA_2, Sta1),
+    transition(Sta13, InvalidPDU, AA_7, Sta13),
+};
+
+StateMachine::GuardMap const
+StateMachine
+::_guards = {
+    {
+        {StateMachine::State::Sta2, StateMachine::Event::AAssociateRQRemote},
+        [](StateMachine const & state_machine, EventData & data)
+        {
+            try
+            {
+                AssociationParameters const input_parameters(
+                    *std::dynamic_pointer_cast<pdu::AAssociateRQ>(data.pdu));
+                data.association_parameters =
+                    state_machine.get_association_acceptor()(input_parameters);
+            }
+            catch(AssociationRejected const & reject)
+            {
+                data.reject = std::make_shared<AssociationRejected>(reject);
+                return false;
+            }
+            return true;
+        }
+    },
+};
+
+#undef transition
+#undef transition_full
+
+void
+StateMachine
+::_send_pdu(EventData & data, uint8_t pdu_type)
+{
+    if(data.pdu == nullptr)
+    {
+        throw Exception("No PDU");
+    }
+    auto const & item = data.pdu->get_item();
+
+    if(item.as_unsigned_int_8("PDU-type") != pdu_type)
+    {
+        throw Exception("Invalid PDU");
+    }
+
+    std::ostringstream stream;
+    stream << item;
+    this->_transport.write(stream.str());
+}
+
+void
+StateMachine
+::AE_1(EventData & data)
+{
+    this->_transport.connect(data.peer_endpoint);
+}
+
+void
+StateMachine
+::AE_2(EventData & data)
+{
+    this->_send_pdu(data, 0x01);
+}
+
+void
+StateMachine
+::AE_3(EventData & )
+{
+    // Do nothing: notification is implicit since this function is only called
+    // by receive_pdu
+}
+
+void
+StateMachine
+::AE_4(EventData & )
+{
+    // Notification is implicit since this function is only called by
+    // receive_pdu
+    this->_transport.close();
+}
+
+void
+StateMachine
+::AE_5(EventData & data)
+{
+    // Connection response has already been sent.
+    this->start_timer(data);
+}
+
+void
+StateMachine
+::AE_6(EventData & data)
+{
+    this->stop_timer();
+
+    if(data.reject)
+    {
+        data.pdu = std::make_shared<pdu::AAssociateRJ>(
+            data.reject->get_result(), data.reject->get_source(),
+            data.reject->get_reason());
+        this->_send_pdu(data, 0x03);
+        data.pdu = NULL;
+    }
+    else
+    {
+        // Issue A-ASSOCIATE indication
+        // Do nothing: notification is implicit since this function is only
+        // called by receive_pdu
+    }
+}
+
+void
+StateMachine
+::AE_7(EventData & data)
+{
+    this->_send_pdu(data, 0x02);
+}
+
+void
+StateMachine
+::AE_8(EventData & data)
+{
+    this->_send_pdu(data, 0x03);
+    this->start_timer(data);
+}
+
+void
+StateMachine
+::DT_1(EventData & data)
+{
+    this->_send_pdu(data, 0x04);
+}
+
+void
+StateMachine
+::DT_2(EventData & )
+{
+    // Do nothing: notification is implicit since this function is only called
+    // by receive_pdu
+}
+
+void
+StateMachine
+::AR_1(EventData & data)
+{
+    this->_send_pdu(data, 0x05);
+}
+
+void
+StateMachine
+::AR_2(EventData & )
+{
+    // Do nothing: notification is implicit since this function is only called
+    // by receive_pdu
+}
+
+void
+StateMachine
+::AR_3(EventData & )
+{
+    // Notification is implicit since this function is only called by
+    // receive_pdu
+    this->_transport.close();
+}
+
+void
+StateMachine
+::AR_4(EventData & data)
+{
+    this->_send_pdu(data, 0x06);
+    this->start_timer(data);
+}
+
+void
+StateMachine
+::AR_5(EventData & )
+{
+    this->stop_timer();
+}
+
+void
+StateMachine
+::AR_6(EventData & )
+{
+    // Do nothing: notification is implicit since this function is only called
+    // by receive_pdu
+}
+
+void
+StateMachine
+::AR_7(EventData & data)
+{
+    this->_send_pdu(data, 0x04);
+}
+
+void
+StateMachine
+::AR_8(EventData & )
+{
+    // Do nothing: notification is implicit since this function is only called
+    // by receive_pdu
+}
+
+void
+StateMachine
+::AR_9(EventData & data)
+{
+    this->_send_pdu(data, 0x06);
+}
+
+void
+StateMachine
+::AR_10(EventData & )
+{
+    // Do nothing: notification is implicit since this function is only called
+    // by receive_pdu
+}
+
+void
+StateMachine
+::AA_1(EventData & data)
+{
+    data.pdu = std::make_shared<pdu::AAbort>(1, 2);
+    this->send_pdu(data);
+
+    this->start_timer(data);
+}
+
+void
+StateMachine
+::AA_2(EventData & )
+{
+    this->stop_timer();
+    this->_transport.close();
+}
+
+void
+StateMachine
+::AA_3(EventData & )
+{
+    // Notification is implicit since this function is only called
+    // by receive_pdu
+    this->_transport.close();
+}
+
+void
+StateMachine
+::AA_4(EventData & )
+{
+    // Do nothing: notification is implicit since this function is only called
+    // by receive_pdu
+}
+
+void
+StateMachine
+::AA_5(EventData & )
+{
+    this->stop_timer();
+}
+
+void
+StateMachine
+::AA_6(EventData & )
+{
+    // Nothing to do.
+}
+
+void
+StateMachine
+::AA_7(EventData & data)
+{
+    this->_send_pdu(data, 0x07);
+}
+
+void
+StateMachine
+::AA_8(EventData & data)
+{
+    data.pdu = std::make_shared<pdu::AAbort>(2, 2);
+    this->_send_pdu(data, 0x07);
+    // Notification is implicit
+    this->start_timer(data);
+}
+
+}
+
+}
diff --git a/src/odil/dul/StateMachine.h b/src/odil/dul/StateMachine.h
new file mode 100644
index 0000000..a0c9703
--- /dev/null
+++ b/src/odil/dul/StateMachine.h
@@ -0,0 +1,274 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _981c80db_b2ac_4f25_af6c_febf5563d178
+#define _981c80db_b2ac_4f25_af6c_febf5563d178
+
+#include <functional>
+#include <map>
+#include <tuple>
+#include <utility>
+
+#include <boost/asio.hpp>
+
+#include "odil/AssociationAcceptor.h"
+#include "odil/dul/EventData.h"
+#include "odil/dul/Transport.h"
+
+namespace odil
+{
+
+namespace dul
+{
+
+/// @brief State machine for the DICOM upper layer.
+class StateMachine
+{
+public:
+
+    /// @brief States of the state machine.
+    enum class State
+    {
+        Sta1,
+        Sta2, Sta3, Sta4, Sta5,
+        Sta6,
+        Sta7, Sta8, Sta9, Sta10, Sta11, Sta12,
+        Sta13
+    };
+
+    /// @brief Event causing the transitions.
+    enum class Event
+    {
+        None, // dummy event to allow easier initialization
+        TransportConnectionIndication, TransportConnectionConfirmation,
+            TransportConnectionClosedIndication,
+
+        AAssociateRQLocal, AAssociateRQRemote,
+        AAssociateACLocal, AAssociateACRemote,
+        AAssociateRJLocal, AAssociateRJRemote,
+
+        AReleaseRQLocal, AReleaseRQRemote,
+        AReleaseRPLocal, AReleaseRPRemote,
+
+        PDataTFLocal, PDataTFRemote,
+
+        AAbortLocal, AAbortRemote,
+
+        ARTIMTimerExpired, InvalidPDU,
+    };
+
+    /// @brief Duration of the timeout.
+    typedef boost::asio::deadline_timer::duration_type duration_type;
+
+    /// @brief Constructor, initializing to Sta1.
+    StateMachine();
+
+    /// @brief Destructor, closing the transport.
+    ~StateMachine();
+
+    /**
+     * @brief Perform the transition related to the event and current state.
+     * Raise an exception if no such transition exists.
+     */
+    void transition(Event const & event, EventData & data);
+
+    /// @brief Return the current state.
+    State get_state() const;
+
+    /// @brief Return the TCP transport.
+    Transport const & get_transport() const;
+
+    /// @brief Return the TCP transport.
+    Transport & get_transport();
+
+    /// @brief Return the timeout, default to infinity.
+    duration_type get_timeout() const;
+
+    /// @brief Set the timeout.
+    void set_timeout(duration_type timeout);
+
+    /**
+     * @brief Receive a connection on the TCP transport, perform the
+     * corresponding transition.
+     */
+    void receive(EventData & data);
+
+    /// @brief Send a PDU to the transport, perform the corresponding transition.
+    void send_pdu(EventData & data);
+
+    /// @brief Receive a PDU on the transport, perform the corresponding transition.
+    void receive_pdu(EventData & data);
+
+    /// @brief Start (or re-start if already started) the ARTIM timer.
+    void start_timer(EventData & data);
+
+    /// @brief Stop the ARTIM timer.
+    void stop_timer();
+
+    /**
+     * @brief Return the callback checking whether the association request is
+     * acceptable.
+     *
+     * By default, all association requests are accepted.
+     */
+    AssociationAcceptor const & get_association_acceptor() const;
+
+    /**
+     * @brief Set the callback checking whether the association request is
+     * acceptable.
+     */
+    void set_association_acceptor(AssociationAcceptor const & acceptor);
+
+private:
+
+    enum class Action
+    {
+        AE_1, AE_2, AE_3, AE_4, AE_5, AE_6, AE_7, AE_8,
+        DT_1, DT_2,
+        AR_1, AR_2, AR_3, AR_4, AR_5, AR_6, AR_7, AR_8, AR_9, AR_10,
+        AA_1, AA_2, AA_3, AA_4, AA_5, AA_6, AA_7, AA_8
+    };
+
+    typedef std::map<
+        std::tuple<State, Event, bool>,
+        std::pair<Action, State>> TransitionMap;
+
+    typedef std::map<
+        std::pair<State, Event>,
+        std::function<bool(StateMachine const &, EventData &)>> GuardMap;
+
+    static TransitionMap const _transitions;
+    static GuardMap const _guards;
+
+    /// @brief Current state.
+    State _state;
+
+    /// @brief TCP transport.
+    Transport _transport;
+
+    /// @brief Timeout of the ARTIM timer.
+    duration_type _timeout;
+
+    /// @brief Association Request/Reject/Release Timer.
+    boost::asio::deadline_timer _artim_timer;
+
+    /// @brief Callback checking whether an association request is acceptable.
+    AssociationAcceptor _association_acceptor;
+
+    /// @brief Check the PDU type in data and send it.
+    void _send_pdu(EventData & data, uint8_t pdu_type);
+
+    /**
+     * @brief Issue TRANSPORT CONNECT request primitive to local transport
+     * service.
+     */
+    void AE_1(EventData & data);
+
+    /// @brief Send A-ASSOCIATE-RQ-PDU.
+    void AE_2(EventData & data);
+
+    /// @brief Issue A-ASSOCIATE confirmation (accept) primitive.
+    void AE_3(EventData & data);
+
+    /**
+     * @brief Issue A-ASSOCIATE confirmation (reject) primitive and close
+     * transport connection.
+     */
+    void AE_4(EventData & data);
+
+    /// @brief Issue Transport connection response primitive; start ARTIM timer.
+    void AE_5(EventData & data);
+
+    /// @brief Stop ARTIM timer and accept or reject connection.
+    void AE_6(EventData & data);
+
+    /// @brief Send A-ASSOCIATE-AC PDU.
+    void AE_7(EventData & data);
+
+    /// @brief Send A-ASSOCIATE-RJ PDU and start ARTIM timer.
+    void AE_8(EventData & data);
+
+    /// @brief Send P-DATA-TF PDU.
+    void DT_1(EventData & data);
+
+    /// @brief Send P-DATA indication primitive.
+    void DT_2(EventData & data);
+
+    /// @brief Send A-RELEASE-RQ PDU.
+    void AR_1(EventData & data);
+
+    /// @brief Issue A-RELEASE indication primitive.
+    void AR_2(EventData & data);
+
+    /**
+     * @brief Issue A-RELEASE confirmation primitive, and close transport.
+     * connection.
+     */
+    void AR_3(EventData & data);
+
+    /// @brief Issue A-RELEASE-RP PDU and start ARTIM timer.
+    void AR_4(EventData & data);
+
+    /// @brief Stop ARTIM timer.
+    void AR_5(EventData & data);
+
+    /// @brief Issue P-DATA indication.
+    void AR_6(EventData & data);
+
+    /// @brief Issue P-DATA-TF PDU.
+    void AR_7(EventData & data);
+
+    /// @brief Issue A-RELEASE indication (release collision).
+    void AR_8(EventData & data);
+
+    /// @brief Send A-RELEASE-RP PDU.
+    void AR_9(EventData & data);
+
+    /// @brief Issue A-RELEASE confirmation primitive.
+    void AR_10(EventData & data);
+
+    /**
+     * @brief Send A-ABORT PDU (service-user source) and start (or restart if
+     * already started) ARTIM timer.
+     */
+    void AA_1(EventData & data);
+
+    /// @brief Stop ARTIM timer if running. Close transport connection.
+    void AA_2(EventData & data);
+
+    /**
+     * @brief If (service-user inititated abort): issue A-ABORT indication and
+     * close transport connection ; otherwise (service-provider inititated
+     * abort): issue A-P-ABORT indication and close transport connection.
+     */
+    void AA_3(EventData & data);
+
+    /// @brief Issue A-P-ABORT indication primitive.
+    void AA_4(EventData & data);
+
+    /// @brief Stop ARTIM timer.
+    void AA_5(EventData & data);
+
+    /// @brief Ignore PDU.
+    void AA_6(EventData & data);
+
+    /// @brief Send A-ABORT PDU.
+    void AA_7(EventData & data);
+
+    /**
+     * @brief Send A-ABORT PDU (service-provider source-), issue an A-P-ABORT
+     * indication, and start ARTIM timer.
+     */
+    void AA_8(EventData & data);
+};
+
+}
+
+}
+
+#endif // _981c80db_b2ac_4f25_af6c_febf5563d178
diff --git a/src/odil/dul/Transport.cpp b/src/odil/dul/Transport.cpp
new file mode 100644
index 0000000..bd813c7
--- /dev/null
+++ b/src/odil/dul/Transport.cpp
@@ -0,0 +1,295 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/dul/Transport.h"
+
+#include <memory>
+#include <string>
+
+#include <boost/asio.hpp>
+#include <boost/date_time.hpp>
+
+#include "odil/Exception.h"
+
+namespace odil
+{
+
+namespace dul
+{
+
+Transport
+::Transport()
+: _service(), _socket(nullptr), _timeout(boost::posix_time::pos_infin),
+  _deadline(_service)
+{
+    // Nothing else
+}
+
+Transport
+::~Transport()
+{
+    if(this->is_open())
+    {
+        this->close();
+    }
+}
+
+boost::asio::io_service const &
+Transport
+::get_service() const
+{
+    return this->_service;
+}
+
+boost::asio::io_service &
+Transport
+::get_service()
+{
+    return this->_service;
+}
+
+std::shared_ptr<Transport::Socket const>
+Transport
+::get_socket() const
+{
+    return this->_socket;
+}
+
+std::shared_ptr<Transport::Socket>
+Transport
+::get_socket()
+{
+    return this->_socket;
+}
+
+Transport::duration_type
+Transport
+::get_timeout() const
+{
+    return this->_timeout;
+}
+
+void
+Transport
+::set_timeout(duration_type timeout)
+{
+    this->_timeout = timeout;
+}
+
+bool
+Transport
+::is_open() const
+{
+    return (this->_socket != nullptr && this->_socket->is_open());
+}
+
+void
+Transport
+::connect(Socket::endpoint_type const & peer_endpoint)
+{
+    if(this->is_open())
+    {
+        throw Exception("Already connected");
+    }
+
+    auto source = Source::NONE;
+    boost::system::error_code error;
+    this->_start_deadline(source, error);
+
+    this->_socket = std::make_shared<Socket>(this->_service);
+    this->_socket->async_connect(
+        peer_endpoint,
+        [&source,&error](boost::system::error_code const & e)
+        {
+            source = Source::OPERATION;
+            error = e;
+        }
+    );
+
+    this->_run(source, error);
+}
+
+void
+Transport
+::receive(Socket::endpoint_type const & endpoint)
+{
+    if(this->is_open())
+    {
+        throw Exception("Already connected");
+    }
+
+    auto source = Source::NONE;
+    boost::system::error_code error;
+    this->_start_deadline(source, error);
+
+    this->_socket = std::make_shared<Socket>(this->_service);
+    boost::asio::ip::tcp::acceptor acceptor(this->_service, endpoint);
+    acceptor.async_accept(
+        *this->_socket,
+        [&source,&error](boost::system::error_code const & e)
+        {
+            source = Source::OPERATION;
+            error = e;
+        }
+    );
+
+    this->_run(source, error);
+}
+
+void
+Transport
+::close()
+{
+    if(!this->is_open())
+    {
+        throw Exception("Not connected");
+    }
+
+    this->_socket->close();
+    this->_socket = nullptr;
+}
+
+std::string
+Transport
+::read(std::size_t length)
+{
+    if(!this->is_open())
+    {
+        throw Exception("Not connected");
+    }
+
+    std::string data(length, 'a');
+
+    auto source = Source::NONE;
+    boost::system::error_code error;
+    this->_start_deadline(source, error);
+
+    boost::asio::async_read(
+        *this->_socket,
+        boost::asio::buffer(&data[0], data.size()),
+        [&source,&error](boost::system::error_code const & e, std::size_t)
+        {
+            source = Source::OPERATION;
+            error = e;
+        }
+    );
+
+    this->_run(source, error);
+
+    return data;
+}
+
+void
+Transport
+::write(std::string const & data)
+{
+    if(!this->is_open())
+    {
+        throw Exception("Not connected");
+    }
+
+    auto source = Source::NONE;
+    boost::system::error_code error;
+    this->_start_deadline(source, error);
+
+    boost::asio::async_write(
+        *this->_socket, boost::asio::buffer(data),
+        [&source,&error](boost::system::error_code const & e, std::size_t)
+        {
+            source = Source::OPERATION;
+            error = e;
+        }
+    );
+
+    this->_run(source, error);
+}
+
+void
+Transport
+::_start_deadline(Source & source, boost::system::error_code & error)
+{
+    auto const canceled = this->_deadline.expires_from_now(this->_timeout);
+    if(canceled != 0)
+    {
+        throw Exception("TCP timer started with pending operations");
+    }
+
+    this->_deadline.async_wait(
+        [&source,&error](boost::system::error_code const & e)
+        {
+            source = Source::TIMER;
+            error = e;
+        }
+    );
+}
+
+void
+Transport
+::_stop_deadline()
+{
+    this->_deadline.expires_at(boost::posix_time::pos_infin);
+}
+
+void
+Transport
+::_run(Source & source, boost::system::error_code & error)
+{
+    // WARNING: it seems that run_one runs a *simple* operation, not a
+    // *composed* operation, as is done by async_read/async_write
+    while(source == Source::NONE)
+    {
+        auto const ran = this->_service.run_one();
+        if(ran == 0)
+        {
+            throw Exception("No operations ran");
+        }
+        this->_service.reset();
+    }
+
+    if(source == Source::OPERATION)
+    {
+        if(error)
+        {
+            throw Exception("Operation error: "+error.message());
+        }
+
+        source = Source::NONE;
+        this->_stop_deadline();
+
+        while(source == Source::NONE)
+        {
+            auto const polled = this->_service.poll_one();
+            if(polled == 0)
+            {
+                throw Exception("No operations polled");
+            }
+            this->_service.reset();
+        }
+
+        if(source != Source::TIMER)
+        {
+            throw Exception("Unknown event");
+        }
+        else if(error != boost::asio::error::operation_aborted)
+        {
+            throw Exception("TCP timer error: "+error.message());
+        }
+    }
+    else if(source == Source::TIMER)
+    {
+        throw Exception("TCP time out");
+    }
+    else
+    {
+        throw Exception("Unknown source");
+    }
+}
+
+}
+
+}
diff --git a/src/odil/dul/Transport.h b/src/odil/dul/Transport.h
new file mode 100644
index 0000000..726ef87
--- /dev/null
+++ b/src/odil/dul/Transport.h
@@ -0,0 +1,107 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _1619bae8_acba_4bf8_8205_aa8dd0085c66
+#define _1619bae8_acba_4bf8_8205_aa8dd0085c66
+
+#include <memory>
+#include <string>
+
+#include <boost/asio.hpp>
+#include <boost/date_time.hpp>
+
+namespace odil
+{
+
+namespace dul
+{
+
+/**
+ * @brief TCP transport for the DICOM Upper Layer.
+ *
+ * The behavior of connect, receive, read and write is governed by the timeout
+ * value: if the timeout expires before the operation is completed, an exception
+ * will be raised.
+ */
+struct Transport
+{
+    /// @brief Socket type.
+    typedef boost::asio::ip::tcp::socket Socket;
+
+    /// @brief Duration of the timeout.
+    typedef boost::asio::deadline_timer::duration_type duration_type;
+
+    /// @brief Constructor.
+    Transport();
+
+    /// @brief Destructor.
+    ~Transport();
+
+    /// @brief Return the io_service.
+    boost::asio::io_service const & get_service() const;
+
+    /// @brief Return the io_service.
+    boost::asio::io_service & get_service();
+
+    /// @brief Return the socket.
+    std::shared_ptr<Socket const> get_socket() const;
+
+    /// @brief Return the socket.
+    std::shared_ptr<Socket> get_socket();
+
+    /// @brief Return the timeout, default to infinity.
+    duration_type get_timeout() const;
+
+    /// @brief Set the timeout.
+    void set_timeout(duration_type timeout);
+
+    /// @brief Test whether the transport is open.
+    bool is_open() const;
+
+    /// @brief Connect to the specified endpoint, raise an exception upon error.
+    void connect(Socket::endpoint_type const & peer_endpoint);
+
+    /**
+     * @brief Receive a connection on the specified endpoint, raise an
+     * exception upon error.
+     */
+    void receive(Socket::endpoint_type const & endpoint);
+
+    /// @brief Close the connection.
+    void close();
+
+    /// @brief Read data, raise an exception on error.
+    std::string read(std::size_t length);
+
+    /// @brief Write data, raise an exception on error.
+    void write(std::string const & data);
+
+private:
+    boost::asio::io_service _service;
+    std::shared_ptr<Socket> _socket;
+    duration_type _timeout;
+    boost::asio::deadline_timer _deadline;
+
+    enum class Source
+    {
+        NONE,
+        TIMER,
+        OPERATION,
+    };
+
+    void _start_deadline(Source & source, boost::system::error_code & error);
+    void _stop_deadline();
+
+    void _run(Source & source, boost::system::error_code & error);
+};
+
+}
+
+}
+
+#endif // _1619bae8_acba_4bf8_8205_aa8dd0085c66
diff --git a/src/odil/endian.h b/src/odil/endian.h
new file mode 100644
index 0000000..141fdaa
--- /dev/null
+++ b/src/odil/endian.h
@@ -0,0 +1,87 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _05d00816_25d0_41d1_9768_afd39f0503da
+#define _05d00816_25d0_41d1_9768_afd39f0503da
+
+#include <boost/detail/endian.hpp>
+
+#define ODIL_SWAP \
+    auto source = reinterpret_cast<char const *>(&value); \
+    auto const end = source + sizeof(value); \
+    T result; \
+    auto destination = reinterpret_cast<char *>(&result) + sizeof(result) - 1; \
+    while(source != end) \
+    { \
+        *destination = *source; \
+        ++source; \
+        --destination; \
+    }
+
+namespace odil
+{
+
+enum class ByteOrdering
+{
+    LittleEndian,
+    BigEndian
+};
+
+template<typename T>
+T host_to_big_endian(T const & value)
+{
+#ifdef BOOST_LITTLE_ENDIAN
+    ODIL_SWAP
+
+    return result;
+#else
+    return value;
+#endif
+}
+
+template<typename T>
+T host_to_little_endian(T const & value)
+{
+#ifdef BOOST_BIG_ENDIAN
+    ODIL_SWAP
+
+    return result;
+#else
+    return value;
+#endif
+}
+
+template<typename T>
+T big_endian_to_host(T const & value)
+{
+#ifdef BOOST_LITTLE_ENDIAN
+    ODIL_SWAP
+
+    return result;
+#else
+    return value;
+#endif
+}
+
+template<typename T>
+T little_endian_to_host(T const & value)
+{
+#ifdef BOOST_BIG_ENDIAN
+    ODIL_SWAP
+
+    return result;
+#else
+    return value;
+#endif
+}
+
+}
+
+#undef ODIL_SWAP
+
+#endif // _05d00816_25d0_41d1_9768_afd39f0503da
diff --git a/src/dcmtkpp/json_converter.cpp b/src/odil/json_converter.cpp
similarity index 87%
rename from src/dcmtkpp/json_converter.cpp
rename to src/odil/json_converter.cpp
index ac53f04..3b5e5ef 100644
--- a/src/dcmtkpp/json_converter.cpp
+++ b/src/odil/json_converter.cpp
@@ -1,24 +1,24 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/json_converter.h"
+#include "odil/json_converter.h"
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/ofstd/ofstd.h>
-#include <dcmtk/ofstd/ofstream.h>
-#include <jsoncpp/json/json.h>
+#include <iterator>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Value.h"
-#include "dcmtkpp/VR.h"
+#include <json/json.h>
 
-namespace dcmtkpp
+#include "odil/base64.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/Value.h"
+#include "odil/VR.h"
+
+namespace odil
 {
 
 struct ToJSONVisitor
@@ -141,11 +141,10 @@ struct ToJSONVisitor
 
         result["vr"] = as_string(vr);
 
-        unsigned char const * data =
-            reinterpret_cast<unsigned char const *>(&value[0]);
-        OFStringStream stream;
-        OFStandard::encodeBase64(stream, data, value.size());
-        result["InlineBinary"] = stream.str().c_str();
+        std::string encoded;
+        encoded.reserve(value.size()*4/3);
+        base64::encode(value.begin(), value.end(), std::back_inserter(encoded));
+        result["InlineBinary"] = encoded;
 
         return result;
     }
@@ -256,15 +255,10 @@ DataSet as_dataset(Json::Value const & json)
             element = Element(Value::Binary(), vr);
 
             auto const & encoded = json_element["InlineBinary"].asString();
-            OFString const encoded_dcmtk(encoded.c_str());
-            unsigned char * decoded;
-            size_t const decoded_size =
-                OFStandard::decodeBase64(encoded_dcmtk, decoded);
-
-            element.as_binary().resize(decoded_size);
-            std::copy(decoded, decoded+decoded_size, element.as_binary().begin());
-
-            delete[] decoded;
+            auto & decoded = element.as_binary();
+            decoded.reserve(encoded.size()*3/4);
+            base64::decode(
+                encoded.begin(), encoded.end(), std::back_inserter(decoded));
         }
         else
         {
diff --git a/src/dcmtkpp/json_converter.h b/src/odil/json_converter.h
similarity index 84%
rename from src/dcmtkpp/json_converter.h
rename to src/odil/json_converter.h
index b80c478..f6df7f2 100644
--- a/src/dcmtkpp/json_converter.h
+++ b/src/odil/json_converter.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,10 +9,10 @@
 #ifndef _6f5dc463_a89a_4f77_a0ed_36dca74b9e59
 #define _6f5dc463_a89a_4f77_a0ed_36dca74b9e59
 
-#include <jsoncpp/json/json.h>
-#include "dcmtkpp/DataSet.h"
+#include <json/json.h>
+#include "odil/DataSet.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief Convert a data set to its JSON representation.
diff --git a/src/dcmtkpp/CEchoRequest.cpp b/src/odil/message/CEchoRequest.cpp
similarity index 86%
rename from src/dcmtkpp/CEchoRequest.cpp
rename to src/odil/message/CEchoRequest.cpp
index 858abbb..8f5458f 100644
--- a/src/dcmtkpp/CEchoRequest.cpp
+++ b/src/odil/message/CEchoRequest.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,11 +8,14 @@
 
 #include "CEchoRequest.h"
 
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
+#include "odil/message/Request.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CEchoRequest
@@ -45,3 +48,5 @@ CEchoRequest
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CEchoRequest.h b/src/odil/message/CEchoRequest.h
similarity index 83%
rename from src/dcmtkpp/CEchoRequest.h
rename to src/odil/message/CEchoRequest.h
index deb60a5..d8a2f3c 100644
--- a/src/dcmtkpp/CEchoRequest.h
+++ b/src/odil/message/CEchoRequest.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,14 @@
 #ifndef _aec786b8_0074_4cb2_b9a1_4bf26bbd20fc
 #define _aec786b8_0074_4cb2_b9a1_4bf26bbd20fc
 
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
+#include "odil/message/Request.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief C-ECHO-RQ message.
@@ -38,10 +41,12 @@ public:
     /// @brief Destructor.
     virtual ~CEchoRequest();
     
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
 };
 
 }
 
+}
+
 #endif // _aec786b8_0074_4cb2_b9a1_4bf26bbd20fc
diff --git a/src/dcmtkpp/CEchoResponse.cpp b/src/odil/message/CEchoResponse.cpp
similarity index 85%
rename from src/dcmtkpp/CEchoResponse.cpp
rename to src/odil/message/CEchoResponse.cpp
index ec176b3..655a2f0 100644
--- a/src/dcmtkpp/CEchoResponse.cpp
+++ b/src/odil/message/CEchoResponse.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,12 +8,15 @@
 
 #include "CEchoResponse.h"
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CEchoResponse
@@ -47,3 +50,5 @@ CEchoResponse
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CEchoResponse.h b/src/odil/message/CEchoResponse.h
similarity index 84%
rename from src/dcmtkpp/CEchoResponse.h
rename to src/odil/message/CEchoResponse.h
index 51c19d9..45b13d6 100644
--- a/src/dcmtkpp/CEchoResponse.h
+++ b/src/odil/message/CEchoResponse.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,14 @@
 #ifndef _266252d9_e801_479e_a805_004b101c5250
 #define _266252d9_e801_479e_a805_004b101c5250
 
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief C-ECHO-RSP message.
@@ -38,10 +41,12 @@ public:
     /// @brief Destructor.
     virtual ~CEchoResponse();
     
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
 };
 
 }
 
+}
+
 #endif // _266252d9_e801_479e_a805_004b101c5250
diff --git a/src/dcmtkpp/CFindRequest.cpp b/src/odil/message/CFindRequest.cpp
similarity index 88%
rename from src/dcmtkpp/CFindRequest.cpp
rename to src/odil/message/CFindRequest.cpp
index 1c096c3..032a74d 100644
--- a/src/dcmtkpp/CFindRequest.cpp
+++ b/src/odil/message/CFindRequest.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -10,12 +10,15 @@
 
 #include <string>
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/Value.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/message/Request.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CFindRequest
@@ -63,3 +66,5 @@ CFindRequest
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CFindRequest.h b/src/odil/message/CFindRequest.h
similarity index 80%
rename from src/dcmtkpp/CFindRequest.h
rename to src/odil/message/CFindRequest.h
index 6b9e685..f3a23cc 100644
--- a/src/dcmtkpp/CFindRequest.h
+++ b/src/odil/message/CFindRequest.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,14 @@
 #ifndef _74cfa9e7_da35_4130_a941_e17cb6932f60
 #define _74cfa9e7_da35_4130_a941_e17cb6932f60
 
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/Value.h"
+#include "odil/registry.h"
+#include "odil/message/Request.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief C-FIND-RQ message.
@@ -38,11 +41,13 @@ public:
     /// @brief Destructor.
     virtual ~CFindRequest();
     
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(priority, registry::Priority)
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(priority, registry::Priority)
 };
 
 }
 
+}
+
 #endif // _74cfa9e7_da35_4130_a941_e17cb6932f60
diff --git a/src/dcmtkpp/CFindResponse.cpp b/src/odil/message/CFindResponse.cpp
similarity index 82%
rename from src/dcmtkpp/CFindResponse.cpp
rename to src/odil/message/CFindResponse.cpp
index a70d6d8..c118dbe 100644
--- a/src/dcmtkpp/CFindResponse.cpp
+++ b/src/odil/message/CFindResponse.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,13 +8,16 @@
 
 #include "CFindResponse.h"
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CFindResponse
@@ -45,9 +48,9 @@ CFindResponse
     }
     this->set_command_field(message.get_command_field());
     
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), message_id, registry::MessageID, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), affected_sop_class_uid,
         registry::AffectedSOPClassUID, as_string)
 
@@ -64,3 +67,5 @@ CFindResponse
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CFindResponse.h b/src/odil/message/CFindResponse.h
similarity index 68%
rename from src/dcmtkpp/CFindResponse.h
rename to src/odil/message/CFindResponse.h
index af44833..a628fb0 100644
--- a/src/dcmtkpp/CFindResponse.h
+++ b/src/odil/message/CFindResponse.h
@@ -1,26 +1,40 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
- 
+
 #ifndef _5fd36547_9498_4cf3_87cc_737af51e93a9
 #define _5fd36547_9498_4cf3_87cc_737af51e93a9
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
+
+namespace odil
+{
 
-namespace dcmtkpp
+namespace message
 {
 
 /// @brief C-FIND-RSP message.
 class CFindResponse: public Response
 {
 public:
+    /// @brief C-FIND status codes, PS 3.4, C.4.1.1.4
+    enum Status
+    {
+        // Failure
+        RefusedOutOfResources=0xA700,
+        IdentifierDoesNotMatchSOPClass=0xA900,
+        UnableToProcess=0xC000,
+        // Pending
+        PendingWarningOptionalKeysNotSupported=0xFF01,
+    };
+
     /**
      * @brief Create an find response with given Message ID, and status.
      */
@@ -44,12 +58,14 @@ public:
 
     /// @brief Destructor.
     virtual ~CFindResponse();
-    
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(message_id, registry::MessageID)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
+
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(message_id, registry::MessageID)
+    ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
 };
 
 }
 
+}
+
 #endif // _5fd36547_9498_4cf3_87cc_737af51e93a9
diff --git a/src/dcmtkpp/CGetRequest.cpp b/src/odil/message/CGetRequest.cpp
similarity index 87%
rename from src/dcmtkpp/CGetRequest.cpp
rename to src/odil/message/CGetRequest.cpp
index 3a0d5a6..6283fa3 100644
--- a/src/dcmtkpp/CGetRequest.cpp
+++ b/src/odil/message/CGetRequest.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -10,13 +10,16 @@
 
 #include <string>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/message/Request.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CGetRequest
@@ -64,3 +67,5 @@ CGetRequest
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CGetRequest.h b/src/odil/message/CGetRequest.h
similarity index 78%
rename from src/dcmtkpp/CGetRequest.h
rename to src/odil/message/CGetRequest.h
index a9061b8..8152f5e 100644
--- a/src/dcmtkpp/CGetRequest.h
+++ b/src/odil/message/CGetRequest.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,12 +9,15 @@
 #ifndef _6a22f126_7cc6_47ab_81c2_5f66b2714345
 #define _6a22f126_7cc6_47ab_81c2_5f66b2714345
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/registry.h"
+#include "odil/message/Request.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief C-GET-RQ message.
@@ -39,11 +42,13 @@ public:
     /// @brief Destructor.
     virtual ~CGetRequest();
 
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(priority, registry::Priority)
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(priority, registry::Priority)
 };
 
 }
 
+}
+
 #endif // _6a22f126_7cc6_47ab_81c2_5f66b2714345
diff --git a/src/dcmtkpp/CGetResponse.cpp b/src/odil/message/CGetResponse.cpp
similarity index 80%
rename from src/dcmtkpp/CGetResponse.cpp
rename to src/odil/message/CGetResponse.cpp
index 3deda8f..c624da0 100644
--- a/src/dcmtkpp/CGetResponse.cpp
+++ b/src/odil/message/CGetResponse.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,13 +8,16 @@
 
 #include "CGetResponse.h"
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CGetResponse
@@ -45,22 +48,22 @@ CGetResponse
     }
     this->set_command_field(message.get_command_field());
     
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), message_id, registry::MessageID, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), affected_sop_class_uid,
         registry::AffectedSOPClassUID, as_string)
 
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), number_of_remaining_sub_operations,
         registry::NumberOfRemainingSuboperations, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), number_of_completed_sub_operations,
         registry::NumberOfCompletedSuboperations, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), number_of_failed_sub_operations,
         registry::NumberOfFailedSuboperations, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), number_of_warning_sub_operations,
         registry::NumberOfWarningSuboperations, as_int)
 
@@ -77,3 +80,5 @@ CGetResponse
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CGetResponse.h b/src/odil/message/CGetResponse.h
similarity index 64%
rename from src/dcmtkpp/CGetResponse.h
rename to src/odil/message/CGetResponse.h
index d51cba1..cc01120 100644
--- a/src/dcmtkpp/CGetResponse.h
+++ b/src/odil/message/CGetResponse.h
@@ -1,26 +1,41 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
- 
+
 #ifndef _7b9819f1_d7a2_4898_a850_3ed6a61f08c6
 #define _7b9819f1_d7a2_4898_a850_3ed6a61f08c6
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
+
+namespace odil
+{
 
-namespace dcmtkpp
+namespace message
 {
 
 /// @brief C-GET-RSP message.
 class CGetResponse: public Response
 {
 public:
+    /// @brief C-GET status codes, PS 3.4, C.4.3.1.4
+    enum Status
+    {
+        // Failure
+        RefusedOutOfResourcesUnableToCalculateNumberOfMatches=0xA701,
+        RefusedOutOfResourcesUnableToPerformSubOperations=0xA702,
+        IdentifierDoesNotMatchSOPClass=0xA900,
+        UnableToProcess=0xC000,
+        // Warning
+        SubOperationsCompleteOneOrMoreFailuresOrWarnings=0xB000
+    };
+
     /**
      * @brief Create an get response with given Message ID, and status.
      */
@@ -44,21 +59,23 @@ public:
 
     /// @brief Destructor.
     virtual ~CGetResponse();
-    
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(message_id, registry::MessageID)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
+
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(message_id, registry::MessageID)
+    ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
 
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         number_of_remaining_sub_operations, registry::NumberOfRemainingSuboperations)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         number_of_completed_sub_operations, registry::NumberOfCompletedSuboperations)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         number_of_failed_sub_operations, registry::NumberOfFailedSuboperations)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         number_of_warning_sub_operations, registry::NumberOfWarningSuboperations)
 };
 
 }
 
+}
+
 #endif // _7b9819f1_d7a2_4898_a850_3ed6a61f08c6
diff --git a/src/dcmtkpp/CMoveRequest.cpp b/src/odil/message/CMoveRequest.cpp
similarity index 88%
rename from src/dcmtkpp/CMoveRequest.cpp
rename to src/odil/message/CMoveRequest.cpp
index df21f83..15e81ab 100644
--- a/src/dcmtkpp/CMoveRequest.cpp
+++ b/src/odil/message/CMoveRequest.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,13 +8,16 @@
 
 #include "CMoveRequest.h"
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/message/Request.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CMoveRequest
@@ -67,3 +70,5 @@ CMoveRequest
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CMoveRequest.h b/src/odil/message/CMoveRequest.h
similarity index 77%
rename from src/dcmtkpp/CMoveRequest.h
rename to src/odil/message/CMoveRequest.h
index 099e1f1..4977e34 100644
--- a/src/dcmtkpp/CMoveRequest.h
+++ b/src/odil/message/CMoveRequest.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,12 +9,15 @@
 #ifndef _f6e243d2_6113_4fe3_8d04_3f034fc796bf
 #define _f6e243d2_6113_4fe3_8d04_3f034fc796bf
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/registry.h"
+#include "odil/message/Request.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief C-MOVE-RQ message.
@@ -40,13 +43,15 @@ public:
     /// @brief Destructor.
     virtual ~CMoveRequest();
 
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(priority, registry::Priority)
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(priority, registry::Priority)
+    ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
         move_destination, registry::MoveDestination)
 };
 
 }
 
+}
+
 #endif // _f6e243d2_6113_4fe3_8d04_3f034fc796bf
diff --git a/src/dcmtkpp/CMoveResponse.cpp b/src/odil/message/CMoveResponse.cpp
similarity index 80%
rename from src/dcmtkpp/CMoveResponse.cpp
rename to src/odil/message/CMoveResponse.cpp
index b1d70b6..126fae8 100644
--- a/src/dcmtkpp/CMoveResponse.cpp
+++ b/src/odil/message/CMoveResponse.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,13 +8,16 @@
 
 #include "CMoveResponse.h"
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CMoveResponse
@@ -45,22 +48,22 @@ CMoveResponse
     }
     this->set_command_field(message.get_command_field());
     
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), message_id, registry::MessageID, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), affected_sop_class_uid,
         registry::AffectedSOPClassUID, as_string)
 
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), number_of_remaining_sub_operations,
         registry::NumberOfRemainingSuboperations, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), number_of_completed_sub_operations,
         registry::NumberOfCompletedSuboperations, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), number_of_failed_sub_operations,
         registry::NumberOfFailedSuboperations, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), number_of_warning_sub_operations,
         registry::NumberOfWarningSuboperations, as_int)
 
@@ -77,3 +80,5 @@ CMoveResponse
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CMoveResponse.h b/src/odil/message/CMoveResponse.h
similarity index 63%
rename from src/dcmtkpp/CMoveResponse.h
rename to src/odil/message/CMoveResponse.h
index 21a5f48..abded08 100644
--- a/src/dcmtkpp/CMoveResponse.h
+++ b/src/odil/message/CMoveResponse.h
@@ -1,26 +1,42 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
- 
+
 #ifndef _b245f6f2_50c3_4c7c_80e1_f03d9c831301
 #define _b245f6f2_50c3_4c7c_80e1_f03d9c831301
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
+
+namespace odil
+{
 
-namespace dcmtkpp
+namespace message
 {
 
 /// @brief C-MOVE-RSP message.
 class CMoveResponse: public Response
 {
 public:
+    /// @brief C-MOVE status codes, PS 3.4, C.4.2.1.5
+    enum Status
+    {
+        // Failure
+        RefusedOutOfResourcesUnableToCalculateNumberOfMatches=0xA701,
+        RefusedOutOfResourcesUnableToPerformSubOperations=0xA702,
+        RefusedMoveDestinationUnknown=0xA801,
+        IdentifierDoesNotMatchSOPClass=0xA900,
+        UnableToProcess=0xC000,
+        // Warning
+        SubOperationsCompleteOneOrMoreFailuresOrWarnings=0xB000
+    };
+
     /**
      * @brief Create an move response with given Message ID, and status.
      */
@@ -44,21 +60,23 @@ public:
 
     /// @brief Destructor.
     virtual ~CMoveResponse();
-    
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(message_id, registry::MessageID)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
+
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(message_id, registry::MessageID)
+    ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
 
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         number_of_remaining_sub_operations, registry::NumberOfRemainingSuboperations)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         number_of_completed_sub_operations, registry::NumberOfCompletedSuboperations)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         number_of_failed_sub_operations, registry::NumberOfFailedSuboperations)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         number_of_warning_sub_operations, registry::NumberOfWarningSuboperations)
 };
 
 }
 
+}
+
 #endif // _b245f6f2_50c3_4c7c_80e1_f03d9c831301
diff --git a/src/dcmtkpp/CStoreRequest.cpp b/src/odil/message/CStoreRequest.cpp
similarity index 88%
rename from src/dcmtkpp/CStoreRequest.cpp
rename to src/odil/message/CStoreRequest.cpp
index 1627fc7..e6b1b56 100644
--- a/src/dcmtkpp/CStoreRequest.cpp
+++ b/src/odil/message/CStoreRequest.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -10,11 +10,13 @@
 
 #include <string>
 
-#include "dcmtkpp/ElementAccessor.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Request.h"
+#include "odil/Exception.h"
+#include "odil/message/Request.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CStoreRequest
@@ -52,10 +54,10 @@ CStoreRequest
         message.get_command_set().as_string(registry::AffectedSOPInstanceUID, 0));
     this->set_priority(message.get_command_set().as_int(registry::Priority, 0));
 
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), move_originator_ae_title,
         registry::MoveOriginatorApplicationEntityTitle, as_string)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), move_originator_message_id,
         registry::MoveOriginatorMessageID, as_int)
 
@@ -73,3 +75,5 @@ CStoreRequest
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CStoreRequest.h b/src/odil/message/CStoreRequest.h
similarity index 76%
rename from src/dcmtkpp/CStoreRequest.h
rename to src/odil/message/CStoreRequest.h
index e0dacc3..2f20ac8 100644
--- a/src/dcmtkpp/CStoreRequest.h
+++ b/src/odil/message/CStoreRequest.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -11,12 +11,15 @@
 
 #include <string>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Request.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/registry.h"
+#include "odil/message/Request.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief C-STORE-RQ message.
@@ -42,17 +45,19 @@ public:
     /// @brief Destructor.
     virtual ~CStoreRequest();
     
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(
         affected_sop_instance_uid, registry::AffectedSOPInstanceUID)
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(priority, registry::Priority)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(priority, registry::Priority)
+    ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
         move_originator_ae_title, registry::MoveOriginatorApplicationEntityTitle)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(
         move_originator_message_id, registry::MoveOriginatorMessageID)
 };
 
 }
 
+}
+
 #endif // _b85a19af_b74d_4c96_89a0_f30a41b790b3
diff --git a/src/dcmtkpp/CStoreResponse.cpp b/src/odil/message/CStoreResponse.cpp
similarity index 80%
rename from src/dcmtkpp/CStoreResponse.cpp
rename to src/odil/message/CStoreResponse.cpp
index 1c3a4b7..12ba044 100644
--- a/src/dcmtkpp/CStoreResponse.cpp
+++ b/src/odil/message/CStoreResponse.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,12 +8,15 @@
 
 #include "CStoreResponse.h"
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 CStoreResponse
@@ -34,12 +37,12 @@ CStoreResponse
     }
     this->set_command_field(message.get_command_field());
 
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), message_id, registry::MessageID, as_int)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(), affected_sop_class_uid,
         registry::AffectedSOPClassUID, as_string)
-    DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
+    ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(
         message.get_command_set(),
         affected_sop_instance_uid, registry::AffectedSOPInstanceUID, as_string)
 
@@ -57,3 +60,5 @@ CStoreResponse
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/CStoreResponse.h b/src/odil/message/CStoreResponse.h
similarity index 62%
rename from src/dcmtkpp/CStoreResponse.h
rename to src/odil/message/CStoreResponse.h
index 0565073..7ed82d1 100644
--- a/src/dcmtkpp/CStoreResponse.h
+++ b/src/odil/message/CStoreResponse.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,17 +9,33 @@
 #ifndef _7e193624_081c_47dd_a011_986e96916ea9
 #define _7e193624_081c_47dd_a011_986e96916ea9
 
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Response.h"
-#include "dcmtkpp/Value.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief C-STORE-RSP message.
 class CStoreResponse: public Response
 {
 public:
+    /// @brief C-STORE status codes, PS 3.4, B.2.3
+    enum Status
+    {
+        // Failure
+        RefusedOutOfResources=0xA700,
+        ErrorDataSetDoesNotMatchSOPClass=0xA900,
+        ErrorCannotUnderstand=0xC000,
+        // Warning
+        CoercionOfDataElements=0xB000,
+        DataSetDoesNotMatchSOPClass=0xB007,
+        ElementsDiscarded=0xB006,
+    };
+
     /**
      * @brief Create an store response with given Message ID, and status.
      */
@@ -36,13 +52,15 @@ public:
     /// @brief Destructor.
     virtual ~CStoreResponse();
 
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(message_id, registry::MessageID)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(message_id, registry::MessageID)
+    ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
         affected_sop_class_uid, registry::AffectedSOPClassUID)
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
+    ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(
         affected_sop_instance_uid, registry::AffectedSOPInstanceUID)
 };
 
 }
 
+}
+
 #endif // _7e193624_081c_47dd_a011_986e96916ea9
diff --git a/src/dcmtkpp/Cancellation.cpp b/src/odil/message/Cancellation.cpp
similarity index 76%
rename from src/dcmtkpp/Cancellation.cpp
rename to src/odil/message/Cancellation.cpp
index af23345..0a77a0f 100644
--- a/src/dcmtkpp/Cancellation.cpp
+++ b/src/odil/message/Cancellation.cpp
@@ -1,23 +1,26 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/Cancellation.h"
+#include "odil/message/Cancellation.h"
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
+#include "odil/Exception.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 Cancellation
-::Cancellation(Uint16 message_id_being_responded_to)
+::Cancellation(uint16_t message_id_being_responded_to)
 : Message()
 {
     this->set_command_field(Command::C_CANCEL_RQ);
@@ -44,3 +47,5 @@ Cancellation
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/Cancellation.h b/src/odil/message/Cancellation.h
similarity index 78%
rename from src/dcmtkpp/Cancellation.h
rename to src/odil/message/Cancellation.h
index d34d4f9..8290486 100644
--- a/src/dcmtkpp/Cancellation.h
+++ b/src/odil/message/Cancellation.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,14 @@
 #ifndef _97fc1bfc_4cff_40f2_a1ed_4550c71a0bda
 #define _97fc1bfc_4cff_40f2_a1ed_4550c71a0bda
 
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief Base class for cancellation messages.
@@ -21,7 +24,7 @@ class Cancellation: public Message
 {
 public:
     /// @brief Create a response with given message id being responded to.
-    Cancellation(Uint16 message_id_being_responded_to);
+    Cancellation(uint16_t message_id_being_responded_to);
 
     /**
      * @brief Create a response from the Message ID Being Responded To.
@@ -33,10 +36,12 @@ public:
     /// @brief Destructor.
     ~Cancellation();
 
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(
         message_id_being_responded_to, registry::MessageIDBeingRespondedTo);
 };
 
 }
 
+}
+
 #endif // _97fc1bfc_4cff_40f2_a1ed_4550c71a0bda
diff --git a/src/dcmtkpp/Message.cpp b/src/odil/message/Message.cpp
similarity index 90%
rename from src/dcmtkpp/Message.cpp
rename to src/odil/message/Message.cpp
index fed075c..ff0836a 100644
--- a/src/dcmtkpp/Message.cpp
+++ b/src/odil/message/Message.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,12 +8,15 @@
 
 #include "Message.h"
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 Message
@@ -92,3 +95,5 @@ Message
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/Message.h b/src/odil/message/Message.h
similarity index 77%
rename from src/dcmtkpp/Message.h
rename to src/odil/message/Message.h
index d540fd6..5a74391 100644
--- a/src/dcmtkpp/Message.h
+++ b/src/odil/message/Message.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,14 +9,17 @@
 #ifndef _dcfa5213_ad7e_4194_8b4b_e630aa0df2e8
 #define _dcfa5213_ad7e_4194_8b4b_e630aa0df2e8
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
+#include "odil/DataSet.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
-#define DCMTKPP_MESSAGE_MANDATORY_FIELD_MACRO(name, tag, TValueType, function) \
+namespace message
+{
+
+#define ODIL_MESSAGE_MANDATORY_FIELD_MACRO(name, tag, TValueType, function) \
     /** @brief Return the tag element of the command set. */ \
     TValueType const & get_##name() const \
     { \
@@ -37,8 +40,8 @@ namespace dcmtkpp
         this->_command_set.function(tag) = { value }; \
     }
 
-#define DCMTKPP_MESSAGE_OPTIONAL_FIELD_MACRO(name, tag, TValueType, function) \
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_MACRO(name, tag, TValueType, function) \
+#define ODIL_MESSAGE_OPTIONAL_FIELD_MACRO(name, tag, TValueType, function) \
+    ODIL_MESSAGE_MANDATORY_FIELD_MACRO(name, tag, TValueType, function) \
     bool has_##name() const \
     { \
         return this->_command_set.has(tag);; \
@@ -48,23 +51,23 @@ namespace dcmtkpp
         this->_command_set.remove(tag); \
     }
 
-#define DCMTKPP_MESSAGE_SET_OPTIONAL_FIELD_MACRO(dataset, name, tag, function) \
+#define ODIL_MESSAGE_SET_OPTIONAL_FIELD_MACRO(dataset, name, tag, function) \
     if(dataset.has(tag)) \
     { \
         this->set_##name(dataset.function(tag, 0)); \
     }
 
-#define DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(name, tag) \
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_MACRO(name, tag, Value::Integer, as_int)
+#define ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(name, tag) \
+    ODIL_MESSAGE_MANDATORY_FIELD_MACRO(name, tag, Value::Integer, as_int)
 
-#define DCMTKPP_MESSAGE_MANDATORY_FIELD_STRING_MACRO(name, tag) \
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_MACRO(name, tag, Value::String, as_string)
+#define ODIL_MESSAGE_MANDATORY_FIELD_STRING_MACRO(name, tag) \
+    ODIL_MESSAGE_MANDATORY_FIELD_MACRO(name, tag, Value::String, as_string)
 
-#define DCMTKPP_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(name, tag) \
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_MACRO(name, tag, Value::Integer, as_int)
+#define ODIL_MESSAGE_OPTIONAL_FIELD_INTEGER_MACRO(name, tag) \
+    ODIL_MESSAGE_OPTIONAL_FIELD_MACRO(name, tag, Value::Integer, as_int)
 
-#define DCMTKPP_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(name, tag) \
-    DCMTKPP_MESSAGE_OPTIONAL_FIELD_MACRO(name, tag, Value::String, as_string)
+#define ODIL_MESSAGE_OPTIONAL_FIELD_STRING_MACRO(name, tag) \
+    ODIL_MESSAGE_OPTIONAL_FIELD_MACRO(name, tag, Value::String, as_string)
 
 /**
  * @brief Base class for all DIMSE messages.
@@ -162,7 +165,7 @@ public:
     /// @brief Delete the data set in this message.
     void delete_data_set();
     
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(
         command_field, registry::CommandField)
 
 protected:
@@ -175,4 +178,6 @@ protected:
 
 }
 
+}
+
 #endif // _dcfa5213_ad7e_4194_8b4b_e630aa0df2e8
diff --git a/src/dcmtkpp/Request.cpp b/src/odil/message/Request.cpp
similarity index 79%
rename from src/dcmtkpp/Request.cpp
rename to src/odil/message/Request.cpp
index 403fbd0..d4ca5e4 100644
--- a/src/dcmtkpp/Request.cpp
+++ b/src/odil/message/Request.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,11 +8,14 @@
 
 #include "Request.h"
 
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 Request
@@ -36,3 +39,5 @@ Request
 }
 
 }
+
+}
diff --git a/src/dcmtkpp/Request.h b/src/odil/message/Request.h
similarity index 82%
rename from src/dcmtkpp/Request.h
rename to src/odil/message/Request.h
index 6b74ad2..63aba04 100644
--- a/src/dcmtkpp/Request.h
+++ b/src/odil/message/Request.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,14 @@
 #ifndef _8d06a300_6aee_4d1f_bf10_ecdf4916ae9f
 #define _8d06a300_6aee_4d1f_bf10_ecdf4916ae9f
 
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Value.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
+{
+
+namespace message
 {
 
 /// @brief Base class for all DIMSE request messages.
@@ -34,10 +37,12 @@ public:
     /// @brief Destructor.
     virtual ~Request();
     
-    DCMTKPP_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(
         message_id, registry::MessageID)
 };
 
 }
 
+}
+
 #endif // _8d06a300_6aee_4d1f_bf10_ecdf4916ae9f
diff --git a/src/odil/message/Response.cpp b/src/odil/message/Response.cpp
new file mode 100644
index 0000000..fdeb491
--- /dev/null
+++ b/src/odil/message/Response.cpp
@@ -0,0 +1,97 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "Response.h"
+
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
+
+namespace odil
+{
+
+namespace message
+{
+
+bool
+Response
+::is_pending(Value::Integer status)
+{
+    return (status == Response::Pending || status == 0xFF01);
+}
+
+bool
+Response
+::is_warning(Value::Integer status)
+{
+    return (
+        status == 0x0001 || (status>>12) == 0xB ||
+        status == Response::AttributeListError ||
+        status == Response::AttributeValueOutOfRange);
+}
+
+bool
+Response
+::is_failure(Value::Integer status)
+{
+    return (
+        (status>>12) == 0xA || (status>>12) == 0xC ||
+        ((status>>8) == 0x01 && !is_warning(status)) ||
+        (status>>8) == 0x02
+    );
+}
+
+Response
+::Response(Value::Integer message_id_being_responded_to, Value::Integer status)
+: Message()
+{
+    this->set_message_id_being_responded_to(message_id_being_responded_to);
+    this->set_status(status);
+}
+
+Response
+::Response(Message const & message)
+: Message()
+{
+    this->set_message_id_being_responded_to(
+        message.get_command_set().as_int(
+            registry::MessageIDBeingRespondedTo, 0));
+
+    this->set_status(message.get_command_set().as_int(registry::Status, 0));
+}
+
+Response
+::~Response()
+{
+    // Nothing to do.
+}
+
+bool
+Response
+::is_pending() const
+{
+    return Response::is_pending(this->get_status());
+}
+
+bool
+Response
+::is_warning() const
+{
+    return Response::is_warning(this->get_status());
+}
+
+bool
+Response
+::is_failure() const
+{
+    return Response::is_failure(this->get_status());
+}
+
+}
+
+}
diff --git a/src/odil/message/Response.h b/src/odil/message/Response.h
new file mode 100644
index 0000000..a1f44b6
--- /dev/null
+++ b/src/odil/message/Response.h
@@ -0,0 +1,103 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _0dd2e31e_212a_494a_a8d3_93b235336658
+#define _0dd2e31e_212a_494a_a8d3_93b235336658
+
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+#include "odil/Value.h"
+
+namespace odil
+{
+
+namespace message
+{
+
+/// @brief Base class for all DIMSE response messages.
+class Response: public Message
+{
+public:
+    /// @brief General status codes, from PS3.7, C
+    enum Status
+    {
+        Success=0x0000,
+        // Warning: 0001 or Bxxx
+        // Failure: Axxx or Cxxx
+        Cancel=0xFE00,
+        Pending=0xFF00,
+
+        // Warning Status Classes, PS3.7, C.3
+        AttributeListError=0x0107,
+        AttributeValueOutOfRange=0x0116,
+
+        // Failure Status Classes, PS3.7, C.4
+        SOPClassNotSupported=0x0122,
+        ClassInstanceConflict=0x0119,
+        DuplicateSOPInstance=0x0111,
+        DuplicateInvocation=0x0210,
+        InvalidArgumentValue=0x0115,
+        InvalidAttributeValue=0x0106,
+        InvalidObjectInstance=0x0117,
+        MissingAttribute=0x0120,
+        MissingAttributeValue=0x0121,
+        MistypedArgument=0x0212,
+        NoSuchArgument=0x0114,
+        NoSuchAttribute=0x0105,
+        NoSuchEventType=0x0113,
+        NoSuchSOPInstance=0x0112,
+        NoSuchSOPClass=0x0118,
+        ProcessingFailure=0x0110,
+        ResourceLimitation=0x0213,
+        UnrecognizedOperation=0x0211,
+        NoSuchActionType=0x0123,
+        RefusedNotAuthorized=0x0124,
+    };
+
+    /// @brief Test whether the status class is pending.
+    static bool is_pending(Value::Integer status);
+
+    /// @brief Test whether the status class is warning.
+    static bool is_warning(Value::Integer status);
+
+    /// @brief Test whether the status class is failure.
+    static bool is_failure(Value::Integer status);
+
+    /// @brief Create a response with given message id and status;
+    Response(Value::Integer message_id_being_responded_to, Value::Integer status);
+
+    /**
+     * @brief Create a response from the Message ID Being Responded To and the
+     * Status stored in the message command set.
+     *
+     * Raise an exception is either of those elements is missing.
+     */
+    Response(Message const & message);
+
+    /// @brief Destructor.
+    virtual ~Response();
+
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(
+        message_id_being_responded_to, registry::MessageIDBeingRespondedTo)
+    ODIL_MESSAGE_MANDATORY_FIELD_INTEGER_MACRO(status, registry::Status)
+
+    /// @brief Test whether the status class is pending.
+    bool is_pending() const;
+
+    /// @brief Test whether the status class is warning.
+    bool is_warning() const;
+
+    /// @brief Test whether the status class is failure.
+    bool is_failure() const;
+};
+
+}
+
+}
+
+#endif // _0dd2e31e_212a_494a_a8d3_93b235336658
diff --git a/src/odil/pdu/AAbort.cpp b/src/odil/pdu/AAbort.cpp
new file mode 100644
index 0000000..323c476
--- /dev/null
+++ b/src/odil/pdu/AAbort.cpp
@@ -0,0 +1,93 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/AAbort.h"
+
+#include <cstdint>
+#include <istream>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+AAbort
+::AAbort(unsigned char source, unsigned char reason)
+{
+    this->_item.add("PDU-type", uint8_t(0x07));
+    this->_item.add("Reserved-1", uint8_t(0));
+    this->_item.add("PDU-length", uint32_t(4));
+    this->_item.add("Reserved-2", uint8_t(0));
+    this->_item.add("Reserved-3", uint8_t(0));
+    this->_item.add("Source", uint8_t(0));
+    this->_item.add("Reason", uint8_t(0));
+
+    this->set_source(source);
+    this->set_reason(reason);
+}
+
+AAbort
+::AAbort(std::istream & stream)
+{
+    this->_item.read(stream, "PDU-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("PDU-type") != 0x07)
+    {
+        throw Exception("Invalid PDU type");
+    }
+
+    this->_item.read(stream, "Reserved-1", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "PDU-length", Item::Field::Type::unsigned_int_32);
+    this->_item.read(stream, "Reserved-2", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Reserved-3", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Source", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Reason", Item::Field::Type::unsigned_int_8);
+}
+
+unsigned char
+AAbort
+::get_source() const
+{
+    return this->_item.as_unsigned_int_8("Source");
+}
+
+void
+AAbort
+::set_source(unsigned char source)
+{
+    if(source > 2)
+    {
+        throw Exception("Unknown source");
+    }
+    this->_item.as_unsigned_int_8("Source") = source;
+}
+
+unsigned char
+AAbort
+::get_reason() const
+{
+    return this->_item.as_unsigned_int_8("Reason");
+}
+
+void
+AAbort
+::set_reason(unsigned char reason)
+{
+    if(reason > 6)
+    {
+        throw Exception("Unknown reason");
+    }
+    this->_item.as_unsigned_int_8("Reason") = reason;
+}
+
+}
+
+}
diff --git a/src/odil/pdu/AAbort.h b/src/odil/pdu/AAbort.h
new file mode 100644
index 0000000..7da2f07
--- /dev/null
+++ b/src/odil/pdu/AAbort.h
@@ -0,0 +1,49 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _536f93f0_914e_40ff_8917_36644acbc4b1
+#define _536f93f0_914e_40ff_8917_36644acbc4b1
+
+#include <istream>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief A-ABORT PDU, cf. PS 3.8, 9.3.8.
+class AAbort: public Object
+{
+public:
+    /// @brief Constructor.
+    AAbort(unsigned char source, unsigned char reason);
+
+    /// @brief Constructor for binary data.
+    AAbort(std::istream & stream);
+
+    /// @brief Return the source.
+    unsigned char get_source() const;
+
+    /// @brief Set the source, must be 0, 1 or 2.
+    void set_source(unsigned char source);
+
+    /// @brief Return the reason.
+    unsigned char get_reason() const;
+
+    /// @brief Set the reason, must be between 0 and 6.
+    void set_reason(unsigned char reason);
+};
+
+}
+
+}
+
+#endif // _536f93f0_914e_40ff_8917_36644acbc4b1
diff --git a/src/odil/pdu/AAssociate.cpp b/src/odil/pdu/AAssociate.cpp
new file mode 100644
index 0000000..9d820fd
--- /dev/null
+++ b/src/odil/pdu/AAssociate.cpp
@@ -0,0 +1,272 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/AAssociate.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <istream>
+#include <iterator>
+#include <string>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/ApplicationContext.h"
+#include "odil/pdu/Item.h"
+#include "odil/pdu/Object.h"
+#include "odil/pdu/PresentationContextAC.h"
+#include "odil/pdu/PresentationContextRQ.h"
+#include "odil/pdu/UserInformation.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+AAssociate
+::AAssociate()
+{
+    this->_item.add("PDU-type", uint8_t(0));
+    this->_item.add("Reserved-1", uint8_t(0));
+    this->_item.add("PDU-length", uint32_t(0));
+    this->_item.add("Protocol-version", uint16_t(0));
+    this->_item.add("Reserved-2", uint16_t(0));
+    this->_item.add("Called-AE-title", std::string());
+    this->_item.add("Calling-AE-title", std::string());
+    this->_item.add("Reserved-3", std::string(32, '\0'));
+    this->_item.add("Variable-items", std::vector<Item>());
+
+    this->_item.as_unsigned_int_32("PDU-length") = this->_compute_length();
+}
+
+AAssociate
+::AAssociate(std::istream & stream)
+{
+    this->_item.read(stream, "PDU-type", Item::Field::Type::unsigned_int_8);
+    auto const type = this->_item.as_unsigned_int_8("PDU-type");
+    if(type != 0x01 && type != 0x02)
+    {
+        throw Exception("Invalid PDU type");
+    }
+
+    this->_item.read(stream, "Reserved-1", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "PDU-length", Item::Field::Type::unsigned_int_32);
+
+    auto const begin = stream.tellg();
+
+    this->_item.read(
+        stream, "Protocol-version", Item::Field::Type::unsigned_int_16);
+    this->_item.read(stream, "Reserved-2", Item::Field::Type::unsigned_int_16);
+    this->_item.read(stream, "Called-AE-title", Item::Field::Type::string, 16);
+    this->_item.read(stream, "Calling-AE-title", Item::Field::Type::string, 16);
+    this->_item.read(stream, "Reserved-3", Item::Field::Type::string, 32);
+
+    auto const length = this->_item.as_unsigned_int_32("PDU-length");
+
+    this->_item.add("Variable-items", std::vector<Item>());
+    auto & variable_items = this->_item.as_items("Variable-items");
+    while(stream.tellg()-begin < length)
+    {
+        auto const type = stream.peek();
+        Item sub_item;
+        if(type == 0x10)
+        {
+            sub_item = ApplicationContext(stream).get_item();
+        }
+        else if(type == 0x20)
+        {
+            sub_item = PresentationContextRQ(stream).get_item();
+        }
+        else if(type == 0x21)
+        {
+            sub_item = PresentationContextAC(stream).get_item();
+        }
+        else if(type == 0x50)
+        {
+            sub_item = UserInformation(stream).get_item();
+        }
+        else
+        {
+            throw Exception("Invalid sub-item");
+        }
+
+        variable_items.push_back(sub_item);
+    }
+
+    this->_item.as_unsigned_int_32("PDU-length") = this->_compute_length();
+}
+
+AAssociate
+::~AAssociate()
+{
+    // Nothing to do.
+}
+
+uint16_t
+AAssociate
+::get_protocol_version() const
+{
+    return this->_item.as_unsigned_int_16("Protocol-version");
+}
+
+void
+AAssociate
+::set_protocol_version(uint16_t value)
+{
+    this->_item.as_unsigned_int_16("Protocol-version") = value;
+}
+
+std::string
+AAssociate
+::get_called_ae_title() const
+{
+    return this->_decode_ae_title(this->_item.as_string("Called-AE-title"));
+}
+
+void
+AAssociate
+::set_called_ae_title(std::string const & value)
+{
+    this->_item.as_string("Called-AE-title") = this->_encode_ae_title(value);
+}
+
+std::string
+AAssociate
+::get_calling_ae_title() const
+{
+    return this->_decode_ae_title(this->_item.as_string("Calling-AE-title"));
+}
+
+void
+AAssociate
+::set_calling_ae_title(std::string const & value)
+{
+    this->_item.as_string("Calling-AE-title") = this->_encode_ae_title(value);
+}
+
+ApplicationContext
+AAssociate
+::get_application_context() const
+{
+    auto const & sub_items = this->_item.as_items("Variable-items");
+    auto const it = std::find_if(
+        sub_items.begin(), sub_items.end(),
+        [](Item const & x) {
+            return x.as_unsigned_int_8("Item-type") == 0x10; });
+    if(it == sub_items.end())
+    {
+        throw Exception("No Application Context");
+    }
+
+    std::stringstream stream;
+    stream << *it;
+    return ApplicationContext(stream);
+}
+
+void
+AAssociate
+::set_application_context(ApplicationContext const & value)
+{
+    auto const & old_items = this->_item.as_items("Variable-items");
+    std::vector<Item> new_items;
+
+    new_items.push_back(value.get_item());
+
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item) {
+            return item.as_unsigned_int_8("Item-type") == 0x21; });
+
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item) {
+            return item.as_unsigned_int_8("Item-type") == 0x50; });
+
+    this->_item.as_items("Variable-items") = new_items;
+
+    this->_item.as_unsigned_int_32("PDU-length") = this->_compute_length();
+}
+
+UserInformation
+AAssociate
+::get_user_information() const
+{
+    auto const & sub_items = this->_item.as_items("Variable-items");
+    auto const it = std::find_if(
+        sub_items.begin(), sub_items.end(),
+        [](Item const & x) {
+            return x.as_unsigned_int_8("Item-type") == 0x50; });
+    if(it == sub_items.end())
+    {
+        throw Exception("No User Information");
+    }
+
+    std::stringstream stream;
+    stream << *it;
+    return UserInformation(stream);
+}
+
+void
+AAssociate
+::set_user_information(UserInformation const & value)
+{
+    auto const & old_items = this->_item.as_items("Variable-items");
+    std::vector<Item> new_items;
+
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item) {
+            return item.as_unsigned_int_8("Item-type") == 0x10; });
+
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item) {
+            return (
+                item.as_unsigned_int_8("Item-type") == 0x20 ||
+                item.as_unsigned_int_8("Item-type") == 0x21); });
+
+    new_items.push_back(value.get_item());
+
+    this->_item.as_items("Variable-items") = new_items;
+
+    this->_item.as_unsigned_int_32("PDU-length") = this->_compute_length();
+}
+
+std::string
+AAssociate
+::_encode_ae_title(std::string const & value)
+{
+    if(value.empty() || value.size() > 16)
+    {
+        throw Exception("Invalid AE title");
+    }
+
+    auto const padded = value+std::string(16-value.size(), ' ');
+    return padded;
+}
+
+std::string
+AAssociate
+::_decode_ae_title(std::string const & value)
+{
+    auto const begin = value.find_first_not_of(' ');
+    if(begin == std::string::npos)
+    {
+        return "";
+    }
+    else
+    {
+        auto const end = value.find_last_not_of(' ');
+        return value.substr(begin, end-begin+1);
+    }
+}
+
+}
+
+}
diff --git a/src/odil/pdu/AAssociate.h b/src/odil/pdu/AAssociate.h
new file mode 100644
index 0000000..ed18e27
--- /dev/null
+++ b/src/odil/pdu/AAssociate.h
@@ -0,0 +1,91 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _00e9792a_98a8_4ca7_9272_43dcd7e331ea
+#define _00e9792a_98a8_4ca7_9272_43dcd7e331ea
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/pdu/ApplicationContext.h"
+#include "odil/pdu/Object.h"
+#include "odil/pdu/UserInformation.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief A-ASSOCIATE-RQ and A-ASSOCIATE-AC PDU, cf. PS 3.8, 9.3.2 and 9.3.3.
+class AAssociate: public Object
+{
+public:
+    /// @brief Constructor.
+    AAssociate();
+
+    /// @brief Constructor for binary data.
+    AAssociate(std::istream & stream);
+
+    /// @brief Destructor.
+    virtual ~AAssociate() =0;
+
+    /// @brief Return the protocol version.
+    uint16_t get_protocol_version() const;
+
+    /// @brief Set the protocol version.
+    void set_protocol_version(uint16_t value);
+
+    /// @brief Return the called AE title.
+    std::string get_called_ae_title() const;
+
+    /**
+     * @brief Set the called AE title.
+     *
+     * An exception is raised if the value is empty or if it is longer than
+     * 16 characters.
+     */
+    void set_called_ae_title(std::string const & value);
+
+    /// @brief Return the calling AE title.
+    std::string get_calling_ae_title() const;
+
+    /**
+     * @brief Set the calling AE title.
+     *
+     * An exception is raised if the value is empty or if it is longer than
+     * 16 characters.
+     */
+    void set_calling_ae_title(std::string const & value);
+
+    /// @brief Return the Application Context sub-item.
+    ApplicationContext get_application_context() const;
+
+    /// @brief Set the Application Context sub-item.
+    void set_application_context(ApplicationContext const & value);
+
+    /// @brief Return the User Information sub-item.
+    UserInformation get_user_information() const;
+
+    /// @brief Set the User Information sub-item.
+    void set_user_information(UserInformation const & value);
+
+private:
+    /// @brief Pad the value with spaces so that it is 16 characters long.
+    static std::string _encode_ae_title(std::string const & value);
+
+    /// @brief Remove the leading and trailing spaces.
+    static std::string _decode_ae_title(std::string const & value);
+};
+
+}
+
+}
+
+#endif // _00e9792a_98a8_4ca7_9272_43dcd7e331ea
diff --git a/src/odil/pdu/AAssociateAC.cpp b/src/odil/pdu/AAssociateAC.cpp
new file mode 100644
index 0000000..c57c209
--- /dev/null
+++ b/src/odil/pdu/AAssociateAC.cpp
@@ -0,0 +1,93 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/AAssociateAC.h"
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/AAssociate.h"
+#include "odil/pdu/PresentationContextAC.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+AAssociateAC
+::AAssociateAC()
+: AAssociate()
+{
+    this->_item.as_unsigned_int_8("PDU-type") = 0x02;
+}
+
+AAssociateAC
+::AAssociateAC(std::istream & stream)
+: AAssociate(stream)
+{
+    if(this->_item.as_unsigned_int_8("PDU-type") != 0x02)
+    {
+        throw Exception("Invalid PDU type");
+    }
+}
+
+AAssociateAC
+::~AAssociateAC()
+{
+    // Nothing to do.
+}
+
+std::vector<PresentationContextAC>
+AAssociateAC
+::get_presentation_contexts() const
+{
+    std::vector<PresentationContextAC> result;
+    for(auto const & item: this->_item.as_items("Variable-items"))
+    {
+        if(item.as_unsigned_int_8("Item-type") == 0x21)
+        {
+            std::stringstream stream;
+            stream << item;
+            result.push_back(PresentationContextAC(stream));
+        }
+    }
+
+    return result;
+}
+
+void
+AAssociateAC
+::set_presentation_contexts(std::vector<PresentationContextAC> const & value)
+{
+    auto const & old_items = this->_item.as_items("Variable-items");
+    std::vector<Item> new_items;
+
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item)  {
+            return item.as_unsigned_int_8("Item-type") == 0x10; });
+
+    std::transform(
+        value.begin(), value.end(), std::back_inserter(new_items),
+        [](PresentationContext const & x) { return x.get_item(); });
+
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item) {
+            return item.as_unsigned_int_8("Item-type") == 0x50; });
+
+    this->_item.as_items("Variable-items") = new_items;
+    this->_item.as_unsigned_int_32("PDU-length") = this->_compute_length();
+}
+
+}
+
+}
diff --git a/src/odil/pdu/AAssociateAC.h b/src/odil/pdu/AAssociateAC.h
new file mode 100644
index 0000000..3982bdd
--- /dev/null
+++ b/src/odil/pdu/AAssociateAC.h
@@ -0,0 +1,47 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _973ae1d9_c41c_4518_9c35_df13b2253369
+#define _973ae1d9_c41c_4518_9c35_df13b2253369
+
+#include <vector>
+
+#include "odil/pdu/AAssociate.h"
+#include "odil/pdu/PresentationContextAC.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief A-ASSOCIATE-AC PDU, cf. PS 3.8, 9.3.3.
+class AAssociateAC: public AAssociate
+{
+public:
+    /// @brief Constructor.
+    AAssociateAC();
+
+    /// @brief Constructor for binary data.
+    AAssociateAC(std::istream & stream);
+
+    /// @brief Destructor.
+    ~AAssociateAC();
+
+    /// @brief Return the Presentation Context sub-items.
+    std::vector<PresentationContextAC> get_presentation_contexts() const;
+
+    /// @brief Set the Presentation Context sub-items.
+    void set_presentation_contexts(std::vector<PresentationContextAC> const & value);
+};
+
+}
+
+}
+
+#endif // _973ae1d9_c41c_4518_9c35_df13b2253369
diff --git a/src/odil/pdu/AAssociateRJ.cpp b/src/odil/pdu/AAssociateRJ.cpp
new file mode 100644
index 0000000..51d643b
--- /dev/null
+++ b/src/odil/pdu/AAssociateRJ.cpp
@@ -0,0 +1,112 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/AAssociateRJ.h"
+
+#include <cstdint>
+#include <istream>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+AAssociateRJ
+::AAssociateRJ(unsigned char result, unsigned char source, unsigned char reason)
+{
+    this->_item.add("PDU-type", uint8_t(0x03));
+    this->_item.add("Reserved-1", uint8_t(0));
+    this->_item.add("PDU-length", uint32_t(4));
+    this->_item.add("Reserved-2", uint8_t(0));
+    this->_item.add("Result", uint8_t(0));
+    this->_item.add("Source", uint8_t(0));
+    this->_item.add("Reason", uint8_t(0));
+
+    this->set_result(result);
+    this->set_source(source);
+    this->set_reason(reason);
+}
+
+AAssociateRJ
+::AAssociateRJ(std::istream & stream)
+{
+    this->_item.read(stream, "PDU-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("PDU-type") != 0x03)
+    {
+        throw Exception("Invalid PDU type");
+    }
+
+    this->_item.read(stream, "Reserved-1", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "PDU-length", Item::Field::Type::unsigned_int_32);
+    this->_item.read(stream, "Reserved-2", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Result", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Source", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Reason", Item::Field::Type::unsigned_int_8);
+}
+
+unsigned char
+AAssociateRJ
+::get_result() const
+{
+    return this->_item.as_unsigned_int_8("Result");
+}
+
+void
+AAssociateRJ
+::set_result(unsigned char result)
+{
+    if(result > 2)
+    {
+        throw Exception("Unknown result");
+    }
+    this->_item.as_unsigned_int_8("Result") = result;
+}
+
+unsigned char
+AAssociateRJ
+::get_source() const
+{
+    return this->_item.as_unsigned_int_8("Source");
+}
+
+void
+AAssociateRJ
+::set_source(unsigned char source)
+{
+    if(source > 3)
+    {
+        throw Exception("Unknown source");
+    }
+    this->_item.as_unsigned_int_8("Source") = source;
+}
+
+unsigned char
+AAssociateRJ
+::get_reason() const
+{
+    return this->_item.as_unsigned_int_8("Reason");
+}
+
+void
+AAssociateRJ
+::set_reason(unsigned char reason)
+{
+    if(reason > 10)
+    {
+        throw Exception("Unknown reason");
+    }
+    this->_item.as_unsigned_int_8("Reason") = reason;
+}
+
+}
+
+}
diff --git a/src/odil/pdu/AAssociateRJ.h b/src/odil/pdu/AAssociateRJ.h
new file mode 100644
index 0000000..2886113
--- /dev/null
+++ b/src/odil/pdu/AAssociateRJ.h
@@ -0,0 +1,54 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _3980566c_9185_40a2_8e7d_6286c2cd1959
+#define _3980566c_9185_40a2_8e7d_6286c2cd1959
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief A-ASSOCIATE-RJ PDU, cf. PS 3.8, 9.3.4.
+class AAssociateRJ: public Object
+{
+public:
+    /// @brief Constructor.
+    AAssociateRJ(
+        unsigned char result, unsigned char source, unsigned char reason);
+
+    /// @brief Constructor from stream.
+    AAssociateRJ(std::istream & stream);
+
+    /// @brief Return the result.
+    unsigned char get_result() const;
+
+    /// @brief Set the result, must be 1 or 2.
+    void set_result(unsigned char result);
+
+    /// @brief Return the source.
+    unsigned char get_source() const;
+
+    /// @brief Set the source, must be 1, 2 or 3.
+    void set_source(unsigned char source);
+
+    /// @brief Return the reason.
+    unsigned char get_reason() const;
+
+    /// @brief Set the reason.
+    void set_reason(unsigned char reason);
+};
+
+}
+
+}
+
+#endif // _3980566c_9185_40a2_8e7d_6286c2cd1959
diff --git a/src/odil/pdu/AAssociateRQ.cpp b/src/odil/pdu/AAssociateRQ.cpp
new file mode 100644
index 0000000..72b0f93
--- /dev/null
+++ b/src/odil/pdu/AAssociateRQ.cpp
@@ -0,0 +1,93 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/AAssociateRQ.h"
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/AAssociate.h"
+#include "odil/pdu/PresentationContextRQ.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+AAssociateRQ
+::AAssociateRQ()
+: AAssociate()
+{
+    this->_item.as_unsigned_int_8("PDU-type") = 0x01;
+}
+
+AAssociateRQ
+::AAssociateRQ(std::istream & stream)
+: AAssociate(stream)
+{
+    if(this->_item.as_unsigned_int_8("PDU-type") != 0x01)
+    {
+        throw Exception("Invalid PDU type");
+    }
+}
+
+AAssociateRQ
+::~AAssociateRQ()
+{
+    // Nothing to do.
+}
+
+std::vector<PresentationContextRQ>
+AAssociateRQ
+::get_presentation_contexts() const
+{
+    std::vector<PresentationContextRQ> result;
+    for(auto const & item: this->_item.as_items("Variable-items"))
+    {
+        if(item.as_unsigned_int_8("Item-type") == 0x20)
+        {
+            std::stringstream stream;
+            stream << item;
+            result.push_back(PresentationContextRQ(stream));
+        }
+    }
+
+    return result;
+}
+
+void
+AAssociateRQ
+::set_presentation_contexts(std::vector<PresentationContextRQ> const & value)
+{
+    auto const & old_items = this->_item.as_items("Variable-items");
+    std::vector<Item> new_items;
+
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item)  {
+            return item.as_unsigned_int_8("Item-type") == 0x10; });
+
+    std::transform(
+        value.begin(), value.end(), std::back_inserter(new_items),
+        [](PresentationContext const & x) { return x.get_item(); });
+
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item) {
+            return item.as_unsigned_int_8("Item-type") == 0x50; });
+
+    this->_item.as_items("Variable-items") = new_items;
+    this->_item.as_unsigned_int_32("PDU-length") = this->_compute_length();
+}
+
+}
+
+}
diff --git a/src/odil/pdu/AAssociateRQ.h b/src/odil/pdu/AAssociateRQ.h
new file mode 100644
index 0000000..98b8809
--- /dev/null
+++ b/src/odil/pdu/AAssociateRQ.h
@@ -0,0 +1,47 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _22fc6fe5_f7d1_461b_932f_cedfcae13897
+#define _22fc6fe5_f7d1_461b_932f_cedfcae13897
+
+#include <vector>
+
+#include "odil/pdu/AAssociate.h"
+#include "odil/pdu/PresentationContextRQ.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief A-ASSOCIATE-RQ, cf. PS 3.8, 9.3.2
+class AAssociateRQ: public AAssociate
+{
+public:
+    /// @brief Constructor.
+    AAssociateRQ();
+
+    /// @brief Constructor for binary data.
+    AAssociateRQ(std::istream & stream);
+
+    /// @brief Destructor.
+    ~AAssociateRQ();
+
+    /// @brief Return the Presentation Context sub-items.
+    std::vector<PresentationContextRQ> get_presentation_contexts() const;
+
+    /// @brief Set the Presentation Context sub-items.
+    void set_presentation_contexts(std::vector<PresentationContextRQ> const & value);
+};
+
+}
+
+}
+
+#endif // _22fc6fe5_f7d1_461b_932f_cedfcae13897
diff --git a/src/odil/pdu/AReleaseRP.cpp b/src/odil/pdu/AReleaseRP.cpp
new file mode 100644
index 0000000..98e76a3
--- /dev/null
+++ b/src/odil/pdu/AReleaseRP.cpp
@@ -0,0 +1,48 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/AReleaseRP.h"
+
+#include <cstdint>
+#include <istream>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+AReleaseRP
+::AReleaseRP()
+{
+    this->_item.add("PDU-type", uint8_t(0x06));
+    this->_item.add("Reserved-1", uint8_t(0));
+    this->_item.add("PDU-length", uint32_t(4));
+    this->_item.add("Reserved-2", uint32_t(0));
+}
+
+AReleaseRP
+::AReleaseRP(std::istream & stream)
+{
+    this->_item.read(stream, "PDU-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("PDU-type") != 0x06)
+    {
+        throw Exception("Invalid PDU type");
+    }
+
+    this->_item.read(stream, "Reserved-1", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "PDU-length", Item::Field::Type::unsigned_int_32);
+    this->_item.read(stream, "Reserved-2", Item::Field::Type::unsigned_int_32);
+}
+
+}
+
+}
diff --git a/src/odil/pdu/AReleaseRP.h b/src/odil/pdu/AReleaseRP.h
new file mode 100644
index 0000000..d9f0cc2
--- /dev/null
+++ b/src/odil/pdu/AReleaseRP.h
@@ -0,0 +1,37 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _a70fb011_8d69_4768_a84d_7f535621c335
+#define _a70fb011_8d69_4768_a84d_7f535621c335
+
+#include <istream>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief A-RELEASE-RP PDU, cf. PS 3.8, 9.3.7.
+class AReleaseRP: public Object
+{
+public:
+    /// @brief Constructor.
+    AReleaseRP();
+
+    /// @brief Constructor from stream.
+    AReleaseRP(std::istream & stream);
+};
+
+}
+
+}
+
+#endif // _a70fb011_8d69_4768_a84d_7f535621c335
diff --git a/src/odil/pdu/AReleaseRQ.cpp b/src/odil/pdu/AReleaseRQ.cpp
new file mode 100644
index 0000000..a866720
--- /dev/null
+++ b/src/odil/pdu/AReleaseRQ.cpp
@@ -0,0 +1,48 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/AReleaseRQ.h"
+
+#include <cstdint>
+#include <istream>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+AReleaseRQ
+::AReleaseRQ()
+{
+    this->_item.add("PDU-type", uint8_t(0x05));
+    this->_item.add("Reserved-1", uint8_t(0));
+    this->_item.add("PDU-length", uint32_t(4));
+    this->_item.add("Reserved-2", uint32_t(0));
+}
+
+AReleaseRQ
+::AReleaseRQ(std::istream & stream)
+{
+    this->_item.read(stream, "PDU-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("PDU-type") != 0x05)
+    {
+        throw Exception("Invalid PDU type");
+    }
+
+    this->_item.read(stream, "Reserved-1", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "PDU-length", Item::Field::Type::unsigned_int_32);
+    this->_item.read(stream, "Reserved-2", Item::Field::Type::unsigned_int_32);
+}
+
+}
+
+}
diff --git a/src/odil/pdu/AReleaseRQ.h b/src/odil/pdu/AReleaseRQ.h
new file mode 100644
index 0000000..87c2fcf
--- /dev/null
+++ b/src/odil/pdu/AReleaseRQ.h
@@ -0,0 +1,37 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _87f54a57_5741_4fde_871b_ea9c774795ad
+#define _87f54a57_5741_4fde_871b_ea9c774795ad
+
+#include <istream>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief A-RELEASE-RQ PDU, cf. PS 3.8, 9.3.6.
+class AReleaseRQ: public Object
+{
+public:
+    /// @brief Constructor.
+    AReleaseRQ();
+
+    /// @brief Constructor from stream.
+    AReleaseRQ(std::istream & stream);
+};
+
+}
+
+}
+
+#endif // _87f54a57_5741_4fde_871b_ea9c774795ad
diff --git a/src/odil/pdu/ApplicationContext.cpp b/src/odil/pdu/ApplicationContext.cpp
new file mode 100644
index 0000000..ea2f4d0
--- /dev/null
+++ b/src/odil/pdu/ApplicationContext.cpp
@@ -0,0 +1,68 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/ApplicationContext.h"
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+ApplicationContext
+::ApplicationContext(std::string const & name)
+{
+    this->_item.add("Item-type", uint8_t(0x10));
+    this->_item.add("Reserved", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(0));
+    this->_item.add("Application-context-name", std::string());
+
+    this->set_name(name);
+}
+
+ApplicationContext
+::ApplicationContext(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("Item-type") != 0x10)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "Application-context-name", Item::Field::Type::string,
+        this->_item.as_unsigned_int_16("Item-length"));
+}
+
+std::string
+ApplicationContext
+::get_name() const
+{
+    return this->_item.as_string("Application-context-name");
+}
+
+void
+ApplicationContext
+::set_name(std::string const & name)
+{
+    this->_item.as_string("Application-context-name") = name;
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+}
+
+}
diff --git a/src/odil/pdu/ApplicationContext.h b/src/odil/pdu/ApplicationContext.h
new file mode 100644
index 0000000..7d7260a
--- /dev/null
+++ b/src/odil/pdu/ApplicationContext.h
@@ -0,0 +1,46 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _12bdb8f1_48c2_44d0_957a_bc1fb6f6733a
+#define _12bdb8f1_48c2_44d0_957a_bc1fb6f6733a
+
+#include <istream>
+#include <string>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/**
+ * @brief Application Context item, (PS 3.8, 9.3.2.1).
+ */
+class ApplicationContext: public Object
+{
+public:
+    /// @brief Create an Application Context.
+    ApplicationContext(std::string const & name);
+
+    /// @brief Read an Application Context from a stream.
+    ApplicationContext(std::istream & stream);
+
+    /// @brief Return the name of the Application Context.
+    std::string get_name() const;
+
+    /// @brief Set the name of the Application Context.
+    void set_name(std::string const & name);
+};
+
+}
+
+}
+
+#endif // _12bdb8f1_48c2_44d0_957a_bc1fb6f6733a
diff --git a/src/odil/pdu/ImplementationClassUID.cpp b/src/odil/pdu/ImplementationClassUID.cpp
new file mode 100644
index 0000000..018f90b
--- /dev/null
+++ b/src/odil/pdu/ImplementationClassUID.cpp
@@ -0,0 +1,68 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/ImplementationClassUID.h"
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+ImplementationClassUID
+::ImplementationClassUID(std::string const & implementation_class_uid)
+{
+    this->_item.add("Item-type", this->type);
+    this->_item.add("Reserved", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(0));
+    this->_item.add("Implementation-class-uid", std::string());
+
+    this->set_implementation_class_uid(implementation_class_uid);
+}
+
+ImplementationClassUID
+::ImplementationClassUID(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("Item-type") != this->type)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "Implementation-class-uid", Item::Field::Type::string,
+        this->_item.as_unsigned_int_16("Item-length"));
+}
+
+std::string
+ImplementationClassUID
+::get_implementation_class_uid() const
+{
+    return this->_item.as_string("Implementation-class-uid");
+}
+
+void
+ImplementationClassUID
+::set_implementation_class_uid(std::string const & value)
+{
+    this->_item.as_string("Implementation-class-uid") = value;
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+}
+
+}
diff --git a/src/odil/pdu/ImplementationClassUID.h b/src/odil/pdu/ImplementationClassUID.h
new file mode 100644
index 0000000..3a3a41f
--- /dev/null
+++ b/src/odil/pdu/ImplementationClassUID.h
@@ -0,0 +1,48 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _6b085e0c_afd5_4ed7_ab4d_6bdb9b28ca67
+#define _6b085e0c_afd5_4ed7_ab4d_6bdb9b28ca67
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief Implementation Class UID Sub-Item (PS 3.7, D.3.3.2.1 and D.3.3.2.2).
+class ImplementationClassUID: public Object
+{
+public:
+    /// @brief Item type.
+    static uint8_t const type=0x52;
+
+    /// @brief Create a Implementation Class UID item.
+    ImplementationClassUID(std::string const & implementation_class_uid);
+
+    /// @brief Read a Implementation Class UID item from a stream.
+    ImplementationClassUID(std::istream & stream);
+
+    /// @brief Return the Implementation Class UID.
+    std::string get_implementation_class_uid() const;
+
+    /// @brief Set the Implementation Class UID.
+    void set_implementation_class_uid(std::string const & value);
+};
+
+}
+
+}
+
+#endif // _6b085e0c_afd5_4ed7_ab4d_6bdb9b28ca67
diff --git a/src/odil/pdu/ImplementationVersionName.cpp b/src/odil/pdu/ImplementationVersionName.cpp
new file mode 100644
index 0000000..c5f2424
--- /dev/null
+++ b/src/odil/pdu/ImplementationVersionName.cpp
@@ -0,0 +1,72 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/ImplementationVersionName.h"
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+ImplementationVersionName
+::ImplementationVersionName(std::string const & implementation_version_name)
+{
+    this->_item.add("Item-type", this->type);
+    this->_item.add("Reserved", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(0));
+    this->_item.add("Implementation-version-name", std::string());
+
+    this->set_implementation_version_name(implementation_version_name);
+}
+
+ImplementationVersionName
+::ImplementationVersionName(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("Item-type") != this->type)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "Implementation-version-name", Item::Field::Type::string,
+        this->_item.as_unsigned_int_16("Item-length"));
+}
+
+std::string
+ImplementationVersionName
+::get_implementation_version_name() const
+{
+    return this->_item.as_string("Implementation-version-name");
+}
+
+void
+ImplementationVersionName
+::set_implementation_version_name(std::string const & value)
+{
+    if(value.empty() || value.size()>16)
+    {
+        throw Exception("Invalid implementation version name");
+    }
+    this->_item.as_string("Implementation-version-name") = value;
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+}
+
+}
diff --git a/src/odil/pdu/ImplementationVersionName.h b/src/odil/pdu/ImplementationVersionName.h
new file mode 100644
index 0000000..1218709
--- /dev/null
+++ b/src/odil/pdu/ImplementationVersionName.h
@@ -0,0 +1,48 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _43545543_c8f8_44c4_b3ce_93cf2adfd365
+#define _43545543_c8f8_44c4_b3ce_93cf2adfd365
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief Implementation Version Name Sub-Item (PS 3.7, D.3.3.2.3 and D.3.3.2.4).
+class ImplementationVersionName: public Object
+{
+public:
+    /// @brief Item type.
+    static uint8_t const type=0x55;
+
+    /// @brief Create a Implementation Version Name item.
+    ImplementationVersionName(std::string const & implementation_version_name);
+
+    /// @brief Read a Implementation Version Name item from a stream.
+    ImplementationVersionName(std::istream & stream);
+
+    /// @brief Return the Implementation Version Name.
+    std::string get_implementation_version_name() const;
+
+    /// @brief Set the Implementation Version Name.
+    void set_implementation_version_name(std::string const & value);
+};
+
+}
+
+}
+
+#endif // _43545543_c8f8_44c4_b3ce_93cf2adfd365
diff --git a/src/odil/pdu/Item.cpp b/src/odil/pdu/Item.cpp
new file mode 100644
index 0000000..368a50a
--- /dev/null
+++ b/src/odil/pdu/Item.cpp
@@ -0,0 +1,454 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/Item.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <istream>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "odil/endian.h"
+#include "odil/Exception.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+Item::Field
+::Field(uint8_t value)
+: _type(Type::unsigned_int_8), _uint8(value)
+{
+    // Nothing else.
+}
+
+Item::Field
+::Field(uint16_t value)
+: _type(Type::unsigned_int_16), _uint16(value)
+{
+    // Nothing else.
+}
+
+Item::Field
+::Field(uint32_t value)
+: _type(Type::unsigned_int_32), _uint32(value)
+{
+    // Nothing else.
+}
+
+Item::Field
+::Field(std::string const & value)
+: _type(Type::string), _string(value)
+{
+    // Nothing else.
+}
+
+Item::Field
+::Field(std::vector<Item> const & value)
+: _type(Type::items), _items(value)
+{
+    // Nothing else.
+}
+
+Item::Field::Type
+Item::Field
+::get_type() const
+{
+    return this->_type;
+}
+
+uint8_t
+Item::Field
+::as_unsigned_int_8() const
+{
+    if(this->_type != Type::unsigned_int_8)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_uint8;
+}
+
+uint8_t &
+Item::Field
+::as_unsigned_int_8()
+{
+    if(this->_type != Type::unsigned_int_8)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_uint8;
+}
+
+uint16_t
+Item::Field
+::as_unsigned_int_16() const
+{
+    if(this->_type != Type::unsigned_int_16)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_uint16;
+}
+
+uint16_t &
+Item::Field
+::as_unsigned_int_16()
+{
+    if(this->_type != Type::unsigned_int_16)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_uint16;
+}
+
+uint32_t
+Item::Field
+::as_unsigned_int_32() const
+{
+    if(this->_type != Type::unsigned_int_32)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_uint32;
+}
+
+uint32_t &
+Item::Field
+::as_unsigned_int_32()
+{
+    if(this->_type != Type::unsigned_int_32)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_uint32;
+}
+
+std::string const &
+Item::Field
+::as_string() const
+{
+    if(this->_type != Type::string)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_string;
+}
+
+std::string &
+Item::Field
+::as_string()
+{
+    if(this->_type != Type::string)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_string;
+}
+
+std::vector<Item> const &
+Item::Field
+::as_items() const
+{
+    if(this->_type != Type::items)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_items;
+}
+
+std::vector<Item> &
+Item::Field
+::as_items()
+{
+    if(this->_type != Type::items)
+    {
+        throw odil::Exception("Incorrect type");
+    }
+    return this->_items;
+}
+
+Item
+::Item()
+{
+    // Nothing to do.
+}
+
+Item
+::Item(std::vector<std::pair<std::string, Field>> const & fields)
+: _fields(fields)
+{
+    // Nothing else.
+}
+
+Item &
+Item
+::add(std::string const & name, Field const & field)
+{
+    this->_fields.push_back(std::make_pair(name, field));
+    return *this;
+}
+
+Item::Container::size_type
+Item
+::size() const
+{
+    return this->_fields.size();
+}
+
+bool
+Item
+::empty() const
+{
+    return this->_fields.empty();
+}
+
+bool
+Item
+::has_field(std::string const & name) const
+{
+    auto const iterator = std::find_if(
+        this->_fields.begin(), this->_fields.end(),
+        [&name](std::pair<std::string, Field> const & x)
+        { return (x.first == name); });
+    return (iterator != this->_fields.end());
+}
+
+Item::Field const &
+Item
+::operator[](std::string const & name) const
+{
+    auto const iterator = std::find_if(
+        this->_fields.begin(), this->_fields.end(),
+        [&name](std::pair<std::string, Field> const & x)
+        { return (x.first == name); });
+    if(iterator == this->_fields.end())
+    {
+        throw Exception("No such field");
+    }
+    return iterator->second;
+}
+
+Item::Field &
+Item
+::operator[](std::string const & name)
+{
+    auto const iterator = std::find_if(
+        this->_fields.begin(), this->_fields.end(),
+        [&name](std::pair<std::string, Field> const & x)
+        { return (x.first == name); });
+    if(iterator == this->_fields.end())
+    {
+        throw Exception("No such field");
+    }
+    return iterator->second;
+}
+
+uint8_t
+Item
+::as_unsigned_int_8(std::string const & name) const
+{
+    auto const & field = (*this)[name];
+    return field.as_unsigned_int_8();
+}
+
+uint8_t &
+Item
+::as_unsigned_int_8(std::string const & name)
+{
+    auto & field = (*this)[name];
+    return field.as_unsigned_int_8();
+}
+
+uint16_t
+Item
+::as_unsigned_int_16(std::string const & name) const
+{
+    auto const & field = (*this)[name];
+    return field.as_unsigned_int_16();
+}
+
+uint16_t &
+Item
+::as_unsigned_int_16(std::string const & name)
+{
+    auto & field = (*this)[name];
+    return field.as_unsigned_int_16();
+}
+
+uint32_t
+Item
+::as_unsigned_int_32(std::string const & name) const
+{
+    auto const & field = (*this)[name];
+    return field.as_unsigned_int_32();
+}
+
+uint32_t &
+Item
+::as_unsigned_int_32(std::string const & name)
+{
+    auto & field = (*this)[name];
+    return field.as_unsigned_int_32();
+}
+
+std::string const &
+Item
+::as_string(std::string const & name) const
+{
+    auto & field = (*this)[name];
+    return field.as_string();
+}
+
+std::string &
+Item
+::as_string(std::string const & name)
+{
+    auto & field = (*this)[name];
+    return field.as_string();
+}
+
+std::vector<Item> const &
+Item
+::as_items(std::string const & name) const
+{
+    auto & field = (*this)[name];
+    return field.as_items();
+}
+
+std::vector<Item> &
+Item
+::as_items(std::string const & name)
+{
+    auto & field = (*this)[name];
+    return field.as_items();
+}
+
+Item::const_iterator
+Item
+::begin() const
+{
+    return this->_fields.begin();
+}
+
+Item::const_iterator
+Item
+::end() const
+{
+    return this->_fields.end();
+}
+
+void
+Item
+::read(
+    std::istream & stream, std::string const & name, Field::Type type,
+    std::streamsize size)
+{
+    if(type == Field::Type::unsigned_int_8)
+    {
+        uint8_t value;
+        stream.read(reinterpret_cast<char*>(&value), sizeof(value));
+        if(!stream.good())
+        {
+            throw Exception("Could not read unsigned_int_8 field");
+        }
+
+        this->add(name, Field(value));
+    }
+    else if(type == Field::Type::unsigned_int_16)
+    {
+        uint16_t value;
+        stream.read(reinterpret_cast<char*>(&value), sizeof(value));
+        if(!stream.good())
+        {
+            throw Exception("Could not read unsigned_int_16 field");
+        }
+
+        value = big_endian_to_host(value);
+        this->add(name, Field(value));
+    }
+    else if(type == Field::Type::unsigned_int_32)
+    {
+        uint32_t value;
+        stream.read(reinterpret_cast<char*>(&value), sizeof(value));
+        if(!stream.good())
+        {
+            throw Exception("Could not read unsigned_int_32 field");
+        }
+
+        value = big_endian_to_host(value);
+        this->add(name, Field(value));
+    }
+    else if(type == Field::Type::string)
+    {
+        std::string value(size, '\0');
+        stream.read(reinterpret_cast<char*>(&value[0]), value.size());
+        if(!stream.good())
+        {
+            throw Exception("Could not read string field");
+        }
+
+        this->add(name, Field(value));
+    }
+    else
+    {
+        throw Exception("Unknown field type");
+    }
+}
+
+std::ostream &
+operator<<(std::ostream & stream, Item const & item)
+{
+    for(auto const & named_field: item)
+    {
+        auto const & field = named_field.second;
+        auto const type = field.get_type();
+        if(type == Item::Field::Type::unsigned_int_8)
+        {
+            auto const value = field.as_unsigned_int_8();
+            stream.write(reinterpret_cast<char const*>(&value), sizeof(value));
+        }
+        else if(type == Item::Field::Type::unsigned_int_16)
+        {
+            auto const value = host_to_big_endian(field.as_unsigned_int_16());
+            stream.write(reinterpret_cast<char const*>(&value), sizeof(value));
+        }
+        else if(type == Item::Field::Type::unsigned_int_32)
+        {
+            auto const value = host_to_big_endian(field.as_unsigned_int_32());
+            stream.write(reinterpret_cast<char const*>(&value), sizeof(value));
+        }
+        else if(type == Item::Field::Type::string)
+        {
+            auto const value = field.as_string();
+            stream.write(reinterpret_cast<char const*>(&value[0]), value.size());
+        }
+        else if(type == Item::Field::Type::items)
+        {
+            auto const value = field.as_items();
+            for(auto const & sub_item: value)
+            {
+                stream << sub_item;
+            }
+        }
+        else
+        {
+            throw Exception("Unknown type");
+        }
+    }
+
+    return stream;
+}
+
+}
+
+}
diff --git a/src/odil/pdu/Item.h b/src/odil/pdu/Item.h
new file mode 100644
index 0000000..37cb44f
--- /dev/null
+++ b/src/odil/pdu/Item.h
@@ -0,0 +1,266 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _1c7b57cc_79f5_497c_815c_920e0711a864
+#define _1c7b57cc_79f5_497c_815c_920e0711a864
+
+#include <cstdint>
+#include <istream>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief A sequence of fields forming a full PDU or a part of it.
+class Item
+{
+public:
+    /// @brief Generic field.
+    class Field
+    {
+    public:
+        /// @brief Possible types stored in the field.
+        enum class Type
+        {
+            unsigned_int_8,
+            unsigned_int_16,
+            unsigned_int_32,
+            string,
+            items
+        };
+
+        /// @brief Constructor.
+        Field(uint8_t value);
+
+        /// @brief Constructor.
+        Field(uint16_t value);
+
+        /// @brief Constructor.
+        Field(uint32_t value);
+
+        /// @brief Constructor.
+        Field(std::string const & value);
+
+        /// @brief Constructor.
+        Field(std::vector<Item> const & value);
+
+        /// @brief Return the concrete type stored in the field.
+        Type get_type() const;
+
+        /**
+         * @brief Return the uint8_t stored in the field.
+         *
+         * If the field does not contain an uint8_t, a odil::Exception is raised.
+         */
+        uint8_t as_unsigned_int_8() const;
+
+        /**
+         * @brief Return the uint8_t stored in the field.
+         *
+         * If the field does not contain an uint8_t, a odil::Exception is raised.
+         */
+        uint8_t & as_unsigned_int_8();
+
+        /**
+         * @brief Return the uint16_t stored in the field.
+         *
+         * If the field does not contain an uint16_t, a odil::Exception is raised.
+         */
+        uint16_t as_unsigned_int_16() const;
+
+        /**
+         * @brief Return the uint16_t stored in the field.
+         *
+         * If the field does not contain an uint16_t, a odil::Exception is raised.
+         */
+        uint16_t & as_unsigned_int_16();
+
+        /**
+         * @brief Return the uint32_t stored in the field.
+         *
+         * If the field does not contain an uint32_t, a odil::Exception is raised.
+         */
+        uint32_t as_unsigned_int_32() const;
+
+        /**
+         * @brief Return the uint32_t stored in the field.
+         *
+         * If the field does not contain an uint32_t, a odil::Exception is raised.
+         */
+        uint32_t & as_unsigned_int_32();
+
+        /**
+         * @brief Return the string stored in the field.
+         *
+         * If the field does not contain an string, a odil::Exception is raised.
+         */
+        std::string const & as_string() const;
+
+        /**
+         * @brief Return the string stored in the field.
+         *
+         * If the field does not contain an string, a odil::Exception is raised.
+         */
+        std::string & as_string();
+
+        /**
+         * @brief Return the items stored in the field.
+         *
+         * If the field does not contain items, a odil::Exception is raised.
+         */
+        std::vector<Item> const & as_items() const;
+
+        /**
+         * @brief Return the items stored in the field.
+         *
+         * If the field does not contain items, a odil::Exception is raised.
+         */
+        std::vector<Item> & as_items();
+
+    private:
+        Type _type;
+        uint8_t _uint8;
+        uint16_t _uint16;
+        uint32_t _uint32;
+        std::string _string;
+        std::vector<Item> _items;
+    };
+
+    typedef std::vector<std::pair<std::string, Field>> Container;
+
+    /// @brief Create an empty PDU item.
+    Item();
+
+    /// @brief Create an initialized PDU item.
+    Item(std::vector<std::pair<std::string, Field>> const & fields);
+
+    /// @brief Add a new field to the PDU item.
+    Item & add(std::string const & name, Field const & field);
+
+    /// @brief Return the number of fields.
+    Container::size_type size() const;
+
+    /// @brief Test whether the container is empty.
+    bool empty() const;
+
+    /// @brief Test whether the PDU item contains a field with a given name.
+    bool has_field(std::string const & name) const;
+
+    /// @brief Return the named field. Raise an exception if no such field exists.
+    Field const & operator[](std::string const & name) const;
+
+    /// @brief Return the named field. Raise an exception if no such field exists.
+    Field & operator[](std::string const & name);
+
+    /**
+     * @brief Return the named field as an uint8_t.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain an uint8_t.
+     */
+    uint8_t as_unsigned_int_8(std::string const & name) const;
+
+    /**
+     * @brief Return the named field as an uint8_t.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain an uint8_t.
+     */
+    uint8_t & as_unsigned_int_8(std::string const & name);
+
+    /**
+     * @brief Return the named field as an uint16_t.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain an uint16_t.
+     */
+    uint16_t as_unsigned_int_16(std::string const & name) const;
+
+    /**
+     * @brief Return the named field as an uint16_t.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain an uint16_t.
+     */
+    uint16_t & as_unsigned_int_16(std::string const & name);
+
+    /**
+     * @brief Return the named field as an uint32_t.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain an uint32_t.
+     */
+    uint32_t as_unsigned_int_32(std::string const & name) const;
+
+    /**
+     * @brief Return the named field as an uint32_t.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain an uint32_t.
+     */
+    uint32_t & as_unsigned_int_32(std::string const & name);
+
+    /**
+     * @brief Return the named field as a string.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain a string.
+     */
+    std::string const & as_string(std::string const & name) const;
+
+    /**
+     * @brief Return the named field as a sequence of items.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain a sequence of items.
+     */
+    std::string & as_string(std::string const & name);
+
+    std::vector<Item> const & as_items(std::string const & name) const;
+
+    /**
+     * @brief Return the named field as a sequence of items.
+     *
+     * Raise an exception if no such field exists or if the field does not
+     * contain a sequence of items.
+     */
+    std::vector<Item> & as_items(std::string const & name);
+
+    typedef Container::const_iterator const_iterator;
+    const_iterator begin() const;
+    const_iterator end() const;
+
+    /**
+     * @brief Read a field from a stream.
+     *
+     * The size parameter is ignored for non-string types.
+     */
+    void read(
+        std::istream & stream,std::string const & name, Field::Type type,
+        std::streamsize size=0);
+
+private:
+    std::vector<std::pair<std::string, Field>> _fields;
+};
+
+// No operator>> since we need explicit names and types.
+
+std::ostream &
+operator<<(std::ostream & stream, Item const & item);
+
+}
+
+}
+
+#endif // _1c7b57cc_79f5_497c_815c_920e0711a864
diff --git a/src/odil/pdu/MaximumLength.cpp b/src/odil/pdu/MaximumLength.cpp
new file mode 100644
index 0000000..0a7c0c7
--- /dev/null
+++ b/src/odil/pdu/MaximumLength.cpp
@@ -0,0 +1,65 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/MaximumLength.h"
+
+#include <cstdint>
+#include <istream>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+MaximumLength
+::MaximumLength(uint32_t maximum_length)
+{
+    this->_item.add("Item-type", uint8_t(0x51));
+    this->_item.add("Reserved", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(4));
+    this->_item.add("Maximum-length-received", uint32_t(0));
+
+    this->set_maximum_length(maximum_length);
+}
+
+MaximumLength
+::MaximumLength(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("Item-type") != 0x51)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "Maximum-length-received", Item::Field::Type::unsigned_int_32);
+}
+
+uint32_t
+MaximumLength
+::get_maximum_length() const
+{
+    return this->_item.as_unsigned_int_32("Maximum-length-received");
+}
+
+void
+MaximumLength
+::set_maximum_length(uint32_t value)
+{
+    this->_item.as_unsigned_int_32("Maximum-length-received") = value;
+}
+
+}
+
+}
diff --git a/src/odil/pdu/MaximumLength.h b/src/odil/pdu/MaximumLength.h
new file mode 100644
index 0000000..cc01877
--- /dev/null
+++ b/src/odil/pdu/MaximumLength.h
@@ -0,0 +1,47 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _b2091b0b_1a6e_435c_b927_e5c54aff89c5
+#define _b2091b0b_1a6e_435c_b927_e5c54aff89c5
+
+#include <cstdint>
+#include <istream>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief Maximum Length Sub-Item Structure (PS 3.8, D.1).
+class MaximumLength: public Object
+{
+public:
+    /// @brief Item type.
+    static uint8_t const type=0x51;
+
+    /// @brief Create a Maximum Length item.
+    MaximumLength(uint32_t maximum_length=0);
+
+    /// @brief Read a Maximum Length item from a stream.
+    MaximumLength(std::istream & stream);
+
+    /// @brief Return the maximum length.
+    uint32_t get_maximum_length() const;
+
+    /// @brief Set the maximum length.
+    void set_maximum_length(uint32_t value);
+};
+
+}
+
+}
+
+#endif // _b2091b0b_1a6e_435c_b927_e5c54aff89c5
diff --git a/src/odil/pdu/Object.cpp b/src/odil/pdu/Object.cpp
new file mode 100644
index 0000000..c31d14d
--- /dev/null
+++ b/src/odil/pdu/Object.cpp
@@ -0,0 +1,116 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/Object.h"
+
+#include <iterator>
+#include <ostream>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Item.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+Object
+::~Object()
+{
+    // Nothing to do.
+}
+
+Item const &
+Object
+::get_item() const
+{
+    return this->_item;
+}
+
+uint32_t
+Object
+::_compute_length() const
+{
+    uint32_t length=0;
+
+    // First 3 fields are always the same: type, reserved and length.
+    auto it = this->_item.begin();
+    std::advance(it, 3);
+    for(; it!=this->_item.end(); ++it)
+    {
+        Item::Field const & field = it->second;
+        length += this->_compute_length(field);
+    }
+
+    return length;
+}
+
+uint32_t
+Object
+::_compute_length(Item const & item) const
+{
+    uint32_t length=0;
+
+    for(auto const & named_field: item)
+    {
+        Item::Field const & field = named_field.second;
+        length += this->_compute_length(field);
+    }
+
+    return length;
+}
+
+uint32_t
+Object
+::_compute_length(Item::Field const & field) const
+{
+    uint32_t length=0;
+
+    if(field.get_type() == Item::Field::Type::unsigned_int_8)
+    {
+        length += 1;
+    }
+    else if(field.get_type() == Item::Field::Type::unsigned_int_16)
+    {
+        length += 2;
+    }
+    else if(field.get_type() == Item::Field::Type::unsigned_int_32)
+    {
+        length += 4;
+    }
+    else if(field.get_type() == Item::Field::Type::string)
+    {
+        length += field.as_string().size();
+    }
+    else if(field.get_type() == Item::Field::Type::items)
+    {
+        auto const & items = field.as_items();
+        for(auto const & item: items)
+        {
+            length += this->_compute_length(item);
+        }
+    }
+    else
+    {
+        throw Exception("Unknown field type");
+    }
+
+    return length;
+}
+
+std::ostream &
+operator<<(std::ostream & stream, Object const & object)
+{
+    stream << object.get_item();
+    return stream;
+}
+
+}
+
+}
diff --git a/src/odil/pdu/Object.h b/src/odil/pdu/Object.h
new file mode 100644
index 0000000..db1bfcd
--- /dev/null
+++ b/src/odil/pdu/Object.h
@@ -0,0 +1,55 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _da2270e3_d393_415a_9c5c_6253152ed9da
+#define _da2270e3_d393_415a_9c5c_6253152ed9da
+
+#include <ostream>
+#include "odil/pdu/Item.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/**
+ * @brief Base class for all PDU-related high-level objects (PDU, items and
+ * sub-items).
+ */
+class Object
+{
+public:
+    /// @brief Destructor, makes the type polymorphic.
+    virtual ~Object();
+
+    /// @brief Get the underlying item.
+    Item const & get_item() const;
+
+protected:
+    Item _item;
+
+    /// @brief Compute the value of the length field of the object.
+    uint32_t _compute_length() const;
+
+    /// @brief Compute the full size of an item.
+    uint32_t _compute_length(Item const & item) const;
+
+    /// @brief Compute the full size of a field.
+    uint32_t _compute_length(Item::Field const & field) const;
+};
+
+/// @brief Dump the PDU-object in its binary form.
+std::ostream &
+operator<<(std::ostream & stream, Object const & object);
+
+}
+
+}
+
+#endif // _da2270e3_d393_415a_9c5c_6253152ed9da
diff --git a/src/odil/pdu/PDataTF.cpp b/src/odil/pdu/PDataTF.cpp
new file mode 100644
index 0000000..6863669
--- /dev/null
+++ b/src/odil/pdu/PDataTF.cpp
@@ -0,0 +1,186 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/PDataTF.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <istream>
+#include <string>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Item.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+PDataTF::PresentationDataValueItem
+::PresentationDataValueItem(
+    uint8_t presentation_context_id, uint8_t control_header,
+    std::string const & fragment)
+{
+    this->_item.add("Item-length", uint32_t(4));
+    this->_item.add("Presentation-Context-ID", uint8_t(0));
+    this->_item.add("Control-header", uint8_t(0));
+    this->_item.add("Fragment", std::string());
+
+    this->set_presentation_context_id(presentation_context_id);
+    this->set_control_header(control_header);
+    this->set_fragment(fragment);
+}
+
+PDataTF::PresentationDataValueItem
+::PresentationDataValueItem(std::istream & stream)
+{
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_32);
+    this->_item.read(
+        stream, "Presentation-Context-ID", Item::Field::Type::unsigned_int_8);
+    this->_item.read(
+        stream, "Control-header", Item::Field::Type::unsigned_int_8);
+    this->_item.read(
+        stream, "Fragment", Item::Field::Type::string,
+        this->_item.as_unsigned_int_32("Item-length")-2);
+}
+
+uint8_t
+PDataTF::PresentationDataValueItem
+::get_presentation_context_id() const
+{
+    return this->_item.as_unsigned_int_8("Presentation-Context-ID");
+}
+
+void
+PDataTF::PresentationDataValueItem
+::set_presentation_context_id(uint8_t value)
+{
+    if(value%2 == 0)
+    {
+        throw Exception("Invalid Presentation Context ID");
+    }
+    this->_item.as_unsigned_int_8("Presentation-Context-ID") = value;
+}
+
+uint8_t
+PDataTF::PresentationDataValueItem
+::get_control_header() const
+{
+    return this->_item.as_unsigned_int_8("Control-header");
+}
+
+void
+PDataTF::PresentationDataValueItem
+::set_control_header(uint8_t value)
+{
+    this->_item.as_unsigned_int_8("Control-header") = value;
+}
+
+bool
+PDataTF::PresentationDataValueItem
+::is_command() const
+{
+    return (this->get_control_header()%2==1);
+}
+
+bool
+PDataTF::PresentationDataValueItem
+::is_last_fragment() const
+{
+    return ((this->get_control_header()>>1)%2==1);
+}
+
+std::string const &
+PDataTF::PresentationDataValueItem
+::get_fragment() const
+{
+    return this->_item.as_string("Fragment");
+}
+
+void
+PDataTF::PresentationDataValueItem
+::set_fragment(std::string const & fragment)
+{
+    this->_item.as_string("Fragment") = fragment;
+    this->_item.as_unsigned_int_32("Item-length") = 2+fragment.size();
+}
+
+PDataTF
+::PDataTF(std::vector<PresentationDataValueItem> const & pdv_items)
+{
+    this->_item.add("PDU-type", uint8_t(0x04));
+    this->_item.add("Reserved-1", uint8_t(0));
+    this->_item.add("PDU-length", uint32_t(4));
+    this->_item.add("Presentation-data-value-Items", std::vector<Item>());
+
+    this->set_pdv_items(pdv_items);
+}
+
+PDataTF
+::PDataTF(std::istream & stream)
+{
+    this->_item.read(stream, "PDU-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("PDU-type") != 0x04)
+    {
+        throw Exception("Invalid PDU type");
+    }
+
+    this->_item.read(stream, "Reserved-1", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "PDU-length", Item::Field::Type::unsigned_int_32);
+
+    auto const pdu_length = this->_item.as_unsigned_int_32("PDU-length");
+    auto const begin = stream.tellg();
+
+    std::vector<PresentationDataValueItem> pdv_items;
+    while(stream.tellg()-begin < pdu_length)
+    {
+        PresentationDataValueItem pdv_item(stream);
+        pdv_items.push_back(pdv_item);
+    }
+
+    this->_item.add("Presentation-data-value-Items", std::vector<Item>());
+    this->set_pdv_items(pdv_items);
+}
+
+std::vector<PDataTF::PresentationDataValueItem>
+PDataTF
+::get_pdv_items() const
+{
+    std::vector<PresentationDataValueItem> result;
+    for(auto const & item: this->_item.as_items("Presentation-data-value-Items"))
+    {
+        PresentationDataValueItem const pdv_item(
+            item.as_unsigned_int_8("Presentation-Context-ID"),
+            item.as_unsigned_int_8("Control-header"),
+            item.as_string("Fragment"));
+
+        result.push_back(pdv_item);
+    }
+    return result;
+}
+
+void
+PDataTF
+::set_pdv_items(std::vector<PresentationDataValueItem> const &pdv_items)
+{
+    auto & items = this->_item.as_items("Presentation-data-value-Items");
+
+    items.resize(pdv_items.size());
+    std::transform(
+        pdv_items.begin(), pdv_items.end(), items.begin(),
+        [](PresentationDataValueItem const& x){ return x.get_item(); });
+
+    this->_item.as_unsigned_int_32("PDU-length") = this->_compute_length();
+}
+
+}
+
+}
diff --git a/src/odil/pdu/PDataTF.h b/src/odil/pdu/PDataTF.h
new file mode 100644
index 0000000..cc3abbf
--- /dev/null
+++ b/src/odil/pdu/PDataTF.h
@@ -0,0 +1,67 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _b3062f12_8a06_46a8_9dda_8a7edf96e4a6
+#define _b3062f12_8a06_46a8_9dda_8a7edf96e4a6
+
+#include <cstdint>
+#include <istream>
+#include <vector>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief P-DATA-TF PDU, cf. PS 3.8, 9.3.5.
+class PDataTF: public Object
+{
+public:
+    class PresentationDataValueItem: public Object
+    {
+    public:
+        PresentationDataValueItem(
+            uint8_t presentation_context_id, uint8_t control_header,
+            std::string const & fragment);
+
+        PresentationDataValueItem(std::istream & stream);
+
+        uint8_t get_presentation_context_id() const;
+        void set_presentation_context_id(uint8_t value);
+
+        uint8_t get_control_header() const;
+        void set_control_header(uint8_t value);
+
+        bool is_command() const;
+        bool is_last_fragment() const;
+
+        std::string const & get_fragment() const;
+        void set_fragment(std::string const & fragment);
+    };
+
+    /// @brief Constructor.
+    PDataTF(std::vector<PresentationDataValueItem> const & pdv_items);
+
+    /// @brief Constructor from stream.
+    PDataTF(std::istream & stream);
+
+    /// @brief Return the Presentation Data Value Items.
+    std::vector<PresentationDataValueItem> get_pdv_items() const;
+
+    /// @brief Set the Presentation Data Value Items.
+    void set_pdv_items(std::vector<PresentationDataValueItem> const &pdv_items);
+};
+
+}
+
+}
+
+#endif // _b3062f12_8a06_46a8_9dda_8a7edf96e4a6
diff --git a/src/odil/pdu/PresentationContext.cpp b/src/odil/pdu/PresentationContext.cpp
new file mode 100644
index 0000000..d1f84f4
--- /dev/null
+++ b/src/odil/pdu/PresentationContext.cpp
@@ -0,0 +1,218 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/PresentationContext.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+#include "odil/pdu/Item.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+PresentationContext
+::PresentationContext()
+{
+    // Nothing to do.
+}
+
+PresentationContext
+::PresentationContext(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->get_item_type() != 0x20 && this->get_item_type() != 0x21)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved-1", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+    auto const item_length = this->_item.as_unsigned_int_16("Item-length");
+
+    auto const begin = stream.tellg();
+
+    this->_item.read(
+        stream, "Presentation-context-id", Item::Field::Type::unsigned_int_8);
+
+    this->_item.read(stream, "Reserved-2", Item::Field::Type::unsigned_int_8);
+    this->_item.read(
+        stream, "Result/Reason", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Reserved-3", Item::Field::Type::unsigned_int_8);
+
+    std::vector<Item> sub_items;
+    while(stream.tellg()-begin < item_length)
+    {
+        Item item;
+        item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+
+        std::string type;
+
+        auto const & item_type = item.as_unsigned_int_8("Item-type");
+        if(item_type == 0x30)
+        {
+            type = "Abstract";
+        }
+        else if(item_type == 0x40)
+        {
+            type = "Transfer";
+        }
+        else
+        {
+            throw Exception("Invalid item");
+        }
+
+        item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+        item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+        item.read(
+            stream, type+"-syntax-name", Item::Field::Type::string,
+            item.as_unsigned_int_16("Item-length"));
+
+        sub_items.push_back(item);
+    }
+
+    this->_item.add("Abstract/Transfer Syntax Sub-Items", sub_items);
+}
+
+PresentationContext
+::~PresentationContext()
+{
+    // Nothing to do.
+}
+
+
+uint8_t
+PresentationContext
+::get_item_type() const
+{
+    return this->_item.as_unsigned_int_8("Item-type");
+}
+
+uint8_t
+PresentationContext
+::get_id() const
+{
+    return this->_item.as_unsigned_int_8("Presentation-context-id");
+}
+
+void
+PresentationContext
+::set_id(uint8_t id)
+{
+    this->_item.as_unsigned_int_8("Presentation-context-id") = id;
+}
+
+Item
+PresentationContext
+::_make_string_item(
+    std::string const & type, std::string const & value)
+{
+    Item item;
+
+    uint8_t item_type;
+    if(type == "Abstract")
+    {
+        item_type = 0x30;
+    }
+    else if(type == "Transfer")
+    {
+        item_type = 0x40;
+    }
+    else
+    {
+        throw Exception("Unknown sub-item type");
+    }
+    item.add("Item-type", item_type);
+
+    item.add("Reserved", uint8_t(0));
+
+    item.add("Item-length", uint16_t(value.size()));
+    item.add(type+"-syntax-name", value);
+
+    return item;
+}
+
+void
+PresentationContext
+::_add_fields()
+{
+    this->_item.add("Item-type", uint8_t(0x0));
+    this->_item.add("Reserved-1", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(0));
+    this->_item.add("Presentation-context-id", uint8_t(0));
+    this->_item.add("Reserved-2", uint8_t(0));
+    this->_item.add("Result/Reason", uint8_t(0));
+    this->_item.add("Reserved-3", uint8_t(0));
+
+    this->_item.add("Abstract/Transfer Syntax Sub-Items", std::vector<Item>());
+}
+
+std::vector<std::string>
+PresentationContext
+::_get_syntaxes(std::string const & type) const
+{
+    auto const & sub_items = this->_item.as_items(
+        "Abstract/Transfer Syntax Sub-Items");
+
+    std::vector<std::string> result;
+    std::for_each(
+        sub_items.begin(), sub_items.end(),
+        [&type, &result](Item const & item)
+        {
+            uint8_t const item_type = (type=="Abstract")?0x30:0x40;
+            if(item.as_unsigned_int_8("Item-type") == item_type)
+            {
+                auto const value = item.as_string(type+"-syntax-name");
+                result.push_back(value);
+            }
+        });
+
+    return result;
+}
+
+void
+PresentationContext
+::_set_syntaxes(
+    std::string const & type, std::vector<std::string> const & syntaxes)
+{
+    std::string const other_type = (type=="Abstract")?"Transfer":"Abstract";
+    auto const other_syntaxes = this->_get_syntaxes(other_type);
+
+    auto & sub_items = this->_item.as_items(
+        "Abstract/Transfer Syntax Sub-Items");
+
+    sub_items.resize(other_syntaxes.size()+syntaxes.size());
+
+    auto const & abstract_syntaxes = (type=="Abstract")?syntaxes:other_syntaxes;
+    auto const & transfer_syntaxes = (type=="Abstract")?other_syntaxes:syntaxes;
+
+    std::transform(
+        abstract_syntaxes.begin(), abstract_syntaxes.end(),
+        sub_items.begin(),
+        [this](std::string const & name)
+        { return PresentationContext::_make_string_item("Abstract", name); });
+
+    std::transform(
+        transfer_syntaxes.begin(), transfer_syntaxes.end(),
+        sub_items.begin()+abstract_syntaxes.size(),
+        [this](std::string const & name)
+        { return PresentationContext::_make_string_item("Transfer", name); });
+
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+}
+
+}
diff --git a/src/odil/pdu/PresentationContext.h b/src/odil/pdu/PresentationContext.h
new file mode 100644
index 0000000..2cf07c8
--- /dev/null
+++ b/src/odil/pdu/PresentationContext.h
@@ -0,0 +1,70 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _30f1a581_9ae6_4a02_a455_bf2b6ea58bc6
+#define _30f1a581_9ae6_4a02_a455_bf2b6ea58bc6
+
+#include <cstdint>
+#include <istream>
+#include <string>
+#include <vector>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/**
+ * @brief Presentation Context item, either for a A-ASSOCIATE-RQ PDU (PS 3.8,
+ * 9.3.2.2) or for a A-ASSOCIATE-AC PDU (PS 3.8, 9.3.3.2).
+ */
+class PresentationContext: public Object
+{
+public:
+    /// @brief Constructor
+    PresentationContext();
+
+    /// @brief Read from a stream.
+    PresentationContext(std::istream & stream);
+
+    /// @brief Destructor, make the class pure virtual.
+    virtual ~PresentationContext() =0;
+
+    /// @brief Return the Item-type.
+    uint8_t get_item_type() const;
+
+    /// @brief Return the Presentation context id.
+    uint8_t get_id() const;
+
+    /// @brief Set the Presentation context id.
+    void set_id(uint8_t id);
+
+protected:
+    /// @brief Create an Abstract Syntax or Transfer Syntax sub-item.
+    static Item _make_string_item(
+        std::string const & type, std::string const & value);
+
+    /// @brief Add common fields.
+    void _add_fields();
+
+    /// @brief Return the Abstract or Transfer syntaxes.
+    std::vector<std::string> _get_syntaxes(std::string const & type) const;
+
+    /// @brief Set the Abstract or Transfer syntaxes.
+    void _set_syntaxes(
+        std::string const & type, std::vector<std::string> const & syntaxes);
+};
+
+}
+
+}
+
+#endif // _30f1a581_9ae6_4a02_a455_bf2b6ea58bc6
diff --git a/src/odil/pdu/PresentationContextAC.cpp b/src/odil/pdu/PresentationContextAC.cpp
new file mode 100644
index 0000000..bf4aa78
--- /dev/null
+++ b/src/odil/pdu/PresentationContextAC.cpp
@@ -0,0 +1,91 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/PresentationContextAC.h"
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/PresentationContext.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+PresentationContextAC
+::PresentationContextAC(
+    uint8_t id, std::string const & transfer_syntax, uint8_t result_reason)
+{
+    this->_add_fields();
+    this->_item.as_unsigned_int_8("Item-type") = 0x21;
+    this->set_id(id);
+    this->set_transfer_syntax(transfer_syntax);
+    this->set_result_reason(result_reason);
+}
+
+PresentationContextAC
+::PresentationContextAC(std::istream & stream)
+: PresentationContext(stream)
+{
+    if(this->get_item_type() != 0x21)
+    {
+        throw Exception("Invalid item type");
+    }
+}
+
+PresentationContextAC
+::~PresentationContextAC()
+{
+    // Nothing to do.
+}
+
+uint8_t
+PresentationContextAC
+::get_result_reason() const
+{
+    return this->_item.as_unsigned_int_8("Result/Reason");
+}
+
+void
+PresentationContextAC
+::set_result_reason(uint8_t result_reason)
+{
+    this->_item.as_unsigned_int_8("Result/Reason") = result_reason;
+}
+
+std::string
+PresentationContextAC
+::get_transfer_syntax() const
+{
+    auto const & syntaxes = this->_get_syntaxes("Transfer");
+    if(syntaxes.empty())
+    {
+        throw Exception("No Transfer Syntax sub-item");
+    }
+    else if(syntaxes.size() > 1)
+    {
+        throw Exception("Several Transfer Syntax sub-items");
+    }
+
+    return syntaxes[0];
+}
+
+void
+PresentationContextAC
+::set_transfer_syntax(std::string const & transfer_syntax)
+{
+    this->_set_syntaxes("Transfer", {transfer_syntax});
+}
+
+}
+
+}
diff --git a/src/odil/pdu/PresentationContextAC.h b/src/odil/pdu/PresentationContextAC.h
new file mode 100644
index 0000000..e04108e
--- /dev/null
+++ b/src/odil/pdu/PresentationContextAC.h
@@ -0,0 +1,55 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _69f1f7a9_d252_4ccd_a2d5_5df0ac4fb054
+#define _69f1f7a9_d252_4ccd_a2d5_5df0ac4fb054
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/pdu/PresentationContext.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief Presentation Context item for a A-ASSOCIATE-AC PDU (PS 3.8, 9.3.3.2).
+class PresentationContextAC: public PresentationContext
+{
+public:
+    /// @brief Constructor.
+    PresentationContextAC(
+        uint8_t id, std::string const & transfer_syntax, uint8_t result_reason);
+
+    /// @brief Read a Presentation Context from a stream.
+    PresentationContextAC(std::istream & stream);
+
+    /// @brief Destructor.
+    ~PresentationContextAC();
+
+    /// @brief Return the Result/Reason.
+    uint8_t get_result_reason() const;
+
+    /// @brief Set the Result/Reason.
+    void set_result_reason(uint8_t result_reason);
+
+    /// @brief Return the Transfer Syntax.
+    std::string get_transfer_syntax() const;
+
+    /// @brief Set the Transfer Syntax.
+    void set_transfer_syntax(std::string const & transfer_syntax);
+};
+
+}
+
+}
+
+#endif // _69f1f7a9_d252_4ccd_a2d5_5df0ac4fb054
diff --git a/src/odil/pdu/PresentationContextRQ.cpp b/src/odil/pdu/PresentationContextRQ.cpp
new file mode 100644
index 0000000..1b4d6ec
--- /dev/null
+++ b/src/odil/pdu/PresentationContextRQ.cpp
@@ -0,0 +1,101 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _952c6d4c_b331_4033_8476_5b0e75022b68
+#define _952c6d4c_b331_4033_8476_5b0e75022b68
+
+#include "odil/pdu/PresentationContextRQ.h"
+
+#include <cstdint>
+#include <istream>
+#include <string>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/PresentationContext.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+PresentationContextRQ
+::PresentationContextRQ(
+    uint8_t id, std::string const & abstract_syntax,
+    std::vector<std::string> const & transfer_syntaxes)
+{
+    this->_add_fields();
+
+    this->_item.as_unsigned_int_8("Item-type") = 0x20;
+    this->set_id(id);
+    this->set_abstract_syntax(abstract_syntax);
+    this->set_transfer_syntaxes(transfer_syntaxes);
+}
+
+PresentationContextRQ
+::PresentationContextRQ(std::istream & stream)
+: PresentationContext(stream)
+{
+    if(this->get_item_type() != 0x20)
+    {
+        throw Exception("Invalid item type");
+    }
+}
+
+PresentationContextRQ
+::~PresentationContextRQ()
+{
+    // Nothing to do.
+}
+
+std::string
+PresentationContextRQ
+::get_abstract_syntax() const
+{
+    auto const & syntaxes = this->_get_syntaxes("Abstract");
+    if(syntaxes.empty())
+    {
+        throw Exception("No Abstract Syntax sub-item");
+    }
+    else if(syntaxes.size() > 1)
+    {
+        throw Exception("Several Abstract Syntax sub-items");
+    }
+
+    return syntaxes[0];
+}
+
+void
+PresentationContextRQ
+::set_abstract_syntax(std::string const & abstract_syntax)
+{
+    this->_set_syntaxes("Abstract", {abstract_syntax});
+}
+
+std::vector<std::string>
+PresentationContextRQ
+::get_transfer_syntaxes() const
+{
+    return this->_get_syntaxes("Transfer");
+}
+
+void
+PresentationContextRQ
+::set_transfer_syntaxes(std::vector<std::string> const & transfer_syntaxes)
+{
+    this->_set_syntaxes("Transfer", transfer_syntaxes);
+}
+
+}
+
+}
+
+#endif // _952c6d4c_b331_4033_8476_5b0e75022b68
+
+
diff --git a/src/odil/pdu/PresentationContextRQ.h b/src/odil/pdu/PresentationContextRQ.h
new file mode 100644
index 0000000..4582601
--- /dev/null
+++ b/src/odil/pdu/PresentationContextRQ.h
@@ -0,0 +1,58 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _e6e42ffd_7318_48f5_b35a_d44093564044
+#define _e6e42ffd_7318_48f5_b35a_d44093564044
+
+#include <cstdint>
+#include <istream>
+#include <string>
+#include <vector>
+
+#include "odil/pdu/PresentationContext.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief Presentation Context item for a A-ASSOCIATE-RQ PDU (PS 3.8, 9.3.2.2).
+class PresentationContextRQ: public PresentationContext
+{
+public:
+    /// @brief Constructor.
+    PresentationContextRQ(
+        uint8_t id, std::string const & abstract_syntax,
+        std::vector<std::string> const & transfer_syntaxes);
+
+    /// @brief Read a Presentation Context from a stream.
+    PresentationContextRQ(std::istream & stream);
+
+    /// @brief Destructor.
+    ~PresentationContextRQ();
+
+    /// @brief Return the Abstract Syntax.
+    std::string get_abstract_syntax() const;
+
+    /// @brief Set the Abstract Syntax.
+    void set_abstract_syntax(std::string const & abstract_syntax);
+
+    /// @brief Return the Transfer Syntaxes.
+    std::vector<std::string> get_transfer_syntaxes() const;
+
+    /// @brief Set the Transfer Syntax sub-items.
+    void set_transfer_syntaxes(
+        std::vector<std::string> const & transfer_syntaxes);
+};
+
+}
+
+}
+
+#endif // _e6e42ffd_7318_48f5_b35a_d44093564044
diff --git a/src/odil/pdu/RoleSelection.cpp b/src/odil/pdu/RoleSelection.cpp
new file mode 100644
index 0000000..5ac0677
--- /dev/null
+++ b/src/odil/pdu/RoleSelection.cpp
@@ -0,0 +1,108 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/RoleSelection.h"
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Item.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+RoleSelection
+::RoleSelection(
+    std::string const & sop_class_uid,
+    bool scu_role_support, bool scp_role_support)
+{
+    this->_item.add("Item-type", this->type);
+    this->_item.add("Reserved", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(0));
+    this->_item.add("UID-length", uint16_t(0));
+    this->_item.add("SOP-class-uid", std::string());
+    this->_item.add("SCU-role", uint8_t(0));
+    this->_item.add("SCP-role", uint8_t(0));
+
+    this->set_sop_class_uid(sop_class_uid);
+    this->set_scu_role_support(scu_role_support);
+    this->set_scp_role_support(scp_role_support);
+}
+
+RoleSelection
+::RoleSelection(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("Item-type") != this->type)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(stream, "UID-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "SOP-class-uid", Item::Field::Type::string,
+        this->_item.as_unsigned_int_16("UID-length"));
+    this->_item.read(stream, "SCU-role", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "SCP-role", Item::Field::Type::unsigned_int_8);
+}
+
+std::string const &
+RoleSelection
+::get_sop_class_uid() const
+{
+    return this->_item.as_string("SOP-class-uid");
+}
+
+void
+RoleSelection
+::set_sop_class_uid(std::string const & value)
+{
+    this->_item.as_string("SOP-class-uid") = value;
+    this->_item.as_unsigned_int_16("UID-length") = value.size();
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+bool
+RoleSelection
+::get_scu_role_support() const
+{
+    return (this->_item.as_unsigned_int_8("SCU-role")!=0);
+}
+
+void
+RoleSelection
+::set_scu_role_support(bool value)
+{
+    this->_item.as_unsigned_int_8("SCU-role") = value?1:0;
+}
+
+bool
+RoleSelection
+::get_scp_role_support() const
+{
+    return (this->_item.as_unsigned_int_8("SCP-role")!=0);
+}
+
+void
+RoleSelection
+::set_scp_role_support(bool value)
+{
+    this->_item.as_unsigned_int_8("SCP-role") = value?1:0;
+}
+
+}
+
+}
diff --git a/src/odil/pdu/RoleSelection.h b/src/odil/pdu/RoleSelection.h
new file mode 100644
index 0000000..edcaaee
--- /dev/null
+++ b/src/odil/pdu/RoleSelection.h
@@ -0,0 +1,65 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _8a990556_f9c3_49f3_bb84_6e604ec9b8f4
+#define _8a990556_f9c3_49f3_bb84_6e604ec9b8f4
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief SCU/SCP Role Selection Sub-Item (PS 3.7, D.3.3.4.1 and D.3.3.4.2).
+class RoleSelection: public Object
+{
+public:
+    /// @brief Item type.
+    static uint8_t const type=0x54;
+
+    /// @brief Create a Role Selection Sub-Item.
+    RoleSelection(
+        std::string const & sop_class_uid="",
+        bool scu_role_support=false, bool scp_role_support=false);
+
+    /// @brief Read a Role Selection Sub-Item from a stream.
+    RoleSelection(std::istream & stream);
+
+    /**
+     * @brief Return the SOP class uid identifying the abstract syntax,
+     * defaults to "".
+     */
+    std::string const & get_sop_class_uid() const;
+
+    /// @brief Set the SOP class uid identifying the abstract syntax.
+    void set_sop_class_uid(std::string const & value);
+
+    /// @brief Return whether the SCU role is supported, defaults to false.
+    bool get_scu_role_support() const;
+
+    /// @brief Set whether the SCP role is supported.
+    void set_scu_role_support(bool value);
+
+    /// @brief Return whether the SCP role is supported, defaults to false.
+    bool get_scp_role_support() const;
+
+    /// @brief Set whether the SCP role is supported.
+    void set_scp_role_support(bool value);
+};
+
+}
+
+}
+
+#endif // _8a990556_f9c3_49f3_bb84_6e604ec9b8f4
diff --git a/src/odil/pdu/UserIdentityAC.cpp b/src/odil/pdu/UserIdentityAC.cpp
new file mode 100644
index 0000000..842dd7d
--- /dev/null
+++ b/src/odil/pdu/UserIdentityAC.cpp
@@ -0,0 +1,75 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/UserIdentityAC.h"
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+UserIdentityAC
+::UserIdentityAC(std::string const & server_response)
+{
+    this->_item.add("Item-type", uint8_t(0x59));
+    this->_item.add("Reserved", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(0));
+
+    this->_item.add("Server-response-length", uint16_t(0));
+    this->_item.add("Server-response", std::string(""));
+
+    this->set_server_response(server_response);
+}
+
+UserIdentityAC
+::UserIdentityAC(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("Item-type") != 0x59)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "Server-response-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "Server-response", Item::Field::Type::string,
+        this->_item.as_unsigned_int_16("Server-response-length"));
+}
+
+std::string const &
+UserIdentityAC
+::get_server_response() const
+{
+    return this->_item.as_string("Server-response");
+}
+
+void
+UserIdentityAC
+::set_server_response(std::string const & value)
+{
+    this->_item.as_unsigned_int_16("Server-response-length") = value.size();
+    this->_item.as_string("Server-response") = value;
+
+    this->_item.as_unsigned_int_16("Item-length") =
+        2+this->get_server_response().size();
+}
+
+}
+
+}
diff --git a/src/odil/pdu/UserIdentityAC.h b/src/odil/pdu/UserIdentityAC.h
new file mode 100644
index 0000000..c646ed6
--- /dev/null
+++ b/src/odil/pdu/UserIdentityAC.h
@@ -0,0 +1,47 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _1c3d6b3c_09a0_4452_9263_aa010ee1d973
+#define _1c3d6b3c_09a0_4452_9263_aa010ee1d973
+
+#include <istream>
+#include <string>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief User Identity Sub-Item Structure (A-ASSOCIATE-AC) (PS 3.7, D.3.3.7.2).
+class UserIdentityAC: public Object
+{
+public:
+    /// @brief Item type.
+    static uint8_t const type=0x59;
+
+    /// @brief Create an User Identity.
+    UserIdentityAC(std::string const & server_response="");
+
+    /// @brief Read an User Identity from a stream.
+    UserIdentityAC(std::istream & stream);
+
+    /// @brief Return the server response.
+    std::string const & get_server_response() const;
+
+    /// @brief Set the server response.
+    void set_server_response(std::string const & value);
+};
+
+}
+
+}
+
+#endif // _1c3d6b3c_09a0_4452_9263_aa010ee1d973
diff --git a/src/odil/pdu/UserIdentityRQ.cpp b/src/odil/pdu/UserIdentityRQ.cpp
new file mode 100644
index 0000000..d41fbcf
--- /dev/null
+++ b/src/odil/pdu/UserIdentityRQ.cpp
@@ -0,0 +1,169 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/UserIdentityRQ.h"
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+UserIdentityRQ
+::UserIdentityRQ()
+{
+    this->_item.add("Item-type", uint8_t(0x58));
+    this->_item.add("Reserved", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(0));
+    this->_item.add("User-Identity-Type", uint8_t(1));
+    this->_item.add("Positive-response-requested", uint8_t(0));
+    this->_item.add("Primary-field-length", uint16_t(0));
+    this->_item.add("Primary-field", std::string(""));
+    this->_item.add("Secondary-field-length", uint16_t(0));
+    this->_item.add("Secondary-field", std::string(""));
+
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+UserIdentityRQ
+::UserIdentityRQ(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("Item-type") != 0x58)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "User-Identity-Type", Item::Field::Type::unsigned_int_8);
+    this->_item.read(
+        stream, "Positive-response-requested",
+        Item::Field::Type::unsigned_int_8);
+    this->_item.read(
+        stream, "Primary-field-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "Primary-field", Item::Field::Type::string,
+        this->_item.as_unsigned_int_16("Primary-field-length"));
+    this->_item.read(
+        stream, "Secondary-field-length", Item::Field::Type::unsigned_int_16);
+    this->_item.read(
+        stream, "Secondary-field", Item::Field::Type::string,
+        this->_item.as_unsigned_int_16("Secondary-field-length"));
+}
+
+uint8_t
+UserIdentityRQ
+::get_type() const
+{
+    return this->_item.as_unsigned_int_8("User-Identity-Type");
+}
+
+void
+UserIdentityRQ
+::set_type(uint8_t type)
+{
+    this->_item.as_unsigned_int_8("User-Identity-Type") = type;
+}
+
+bool
+UserIdentityRQ
+::get_positive_response_requested() const
+{
+    return
+        (this->_item.as_unsigned_int_8("Positive-response-requested") != 0);
+}
+
+void
+UserIdentityRQ
+::set_positive_response_requested(bool value)
+{
+    this->_item.as_unsigned_int_8("Positive-response-requested") =
+        value?1:0;
+}
+
+std::string const &
+UserIdentityRQ
+::get_primary_field() const
+{
+    return this->_item.as_string("Primary-field");
+}
+
+void
+UserIdentityRQ
+::set_primary_field(std::string const & value)
+{
+    this->_item.as_unsigned_int_16("Primary-field-length") = value.size();
+    this->_item.as_string("Primary-field") = value;
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+std::string const &
+UserIdentityRQ
+::get_secondary_field() const
+{
+    return this->_item.as_string("Secondary-field");
+}
+
+void
+UserIdentityRQ
+::set_secondary_field(std::string const & value)
+{
+    this->_item.as_unsigned_int_16("Secondary-field-length") = value.size();
+    this->_item.as_string("Secondary-field") = value;
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+void
+UserIdentityRQ
+::set_username(std::string const & username)
+{
+    this->set_type(1);
+    this->set_primary_field(username);
+    this->set_secondary_field("");
+}
+
+void
+UserIdentityRQ
+::set_username_and_passcode(
+    std::string const & username, std::string const & passcode)
+{
+    this->set_type(2);
+    this->set_primary_field(username);
+    this->set_secondary_field(passcode);
+}
+
+void
+UserIdentityRQ
+::set_kerberos_service_ticket(std::string const & ticket)
+{
+    this->set_type(3);
+    this->set_primary_field(ticket);
+    this->set_secondary_field("");
+}
+
+void
+UserIdentityRQ
+::set_saml_assertion(std::string const & assertion)
+{
+    this->set_type(4);
+    this->set_primary_field(assertion);
+    this->set_secondary_field("");
+}
+
+}
+
+}
diff --git a/src/odil/pdu/UserIdentityRQ.h b/src/odil/pdu/UserIdentityRQ.h
new file mode 100644
index 0000000..3289cfe
--- /dev/null
+++ b/src/odil/pdu/UserIdentityRQ.h
@@ -0,0 +1,75 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _b935542d_12c9_4c81_963c_32b7996af777
+#define _b935542d_12c9_4c81_963c_32b7996af777
+
+#include <cstdint>
+#include <istream>
+#include <string>
+
+#include "odil/pdu/Object.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief User Identity Sub-Item Structure (A-ASSOCIATE-RQ) (PS 3.7, D.3.3.7.1).
+class UserIdentityRQ: public Object
+{
+public:
+    /// @brief Item type.
+    static uint8_t const type=0x58;
+
+    /// @brief Create an User Identity.
+    UserIdentityRQ();
+
+    /// @brief Read an User Identity from a stream.
+    UserIdentityRQ(std::istream & stream);
+
+    /// @brief Return the type of the User Identity.
+    uint8_t get_type() const;
+
+    /// @brief Set the type of the User Identity.
+    void set_type(uint8_t type);
+
+    /// @brief Return whether a positive response is requested.
+    bool get_positive_response_requested() const;
+
+    /// @brief Set whether a positive response is requested.
+    void set_positive_response_requested(bool value);
+
+    /// @brief Return the primary identity field.
+    std::string const & get_primary_field() const;
+
+    /// @brief Set the primary identity field.
+    void set_primary_field(std::string const & value);
+
+    /// @brief Return the secondary identity field.
+    std::string const & get_secondary_field() const;
+
+    /// @brief Set the secondary identity field.
+    void set_secondary_field(std::string const & value);
+
+    void set_username(std::string const & username);
+
+    void set_username_and_passcode(
+        std::string const & username, std::string const & passcode);
+
+    void set_kerberos_service_ticket(std::string const & ticket);
+
+    void set_saml_assertion(std::string const & assertion);
+};
+
+}
+
+}
+
+#endif // _b935542d_12c9_4c81_963c_32b7996af777
diff --git a/src/odil/pdu/UserInformation.cpp b/src/odil/pdu/UserInformation.cpp
new file mode 100644
index 0000000..32b4c52
--- /dev/null
+++ b/src/odil/pdu/UserInformation.cpp
@@ -0,0 +1,113 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/pdu/UserInformation.h"
+
+#include <algorithm>
+#include <istream>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/ImplementationClassUID.h"
+#include "odil/pdu/ImplementationVersionName.h"
+#include "odil/pdu/MaximumLength.h"
+#include "odil/pdu/Object.h"
+#include "odil/pdu/RoleSelection.h"
+#include "odil/pdu/UserIdentityAC.h"
+#include "odil/pdu/UserIdentityRQ.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+UserInformation
+::UserInformation()
+{
+    this->_item.add("Item-type", uint8_t(0x50));
+    this->_item.add("Reserved", uint8_t(0));
+    this->_item.add("Item-length", uint16_t(4));
+    this->_item.add("User-data", std::vector<Item>());
+}
+
+UserInformation
+::UserInformation(std::istream & stream)
+{
+    this->_item.read(stream, "Item-type", Item::Field::Type::unsigned_int_8);
+    if(this->_item.as_unsigned_int_8("Item-type") != 0x50)
+    {
+        throw Exception("Invalid item type");
+    }
+
+    this->_item.read(stream, "Reserved", Item::Field::Type::unsigned_int_8);
+    this->_item.read(stream, "Item-length", Item::Field::Type::unsigned_int_16);
+
+    this->_item.add("User-data", std::vector<Item>());
+
+    auto const begin = stream.tellg();
+    auto const item_length = this->_item.as_unsigned_int_16("Item-length");
+
+    // Store sub-items so that all sub-items of a given type are adjacent, and
+    // that their type is in growing order.
+
+    std::vector<MaximumLength> maximum_length;
+    std::vector<ImplementationClassUID> implementation_class_uid;
+    std::vector<RoleSelection> role_selection;
+    std::vector<ImplementationVersionName> implementation_version_name;
+    std::vector<UserIdentityRQ> user_identity_rq;
+    std::vector<UserIdentityAC> user_identity_ac;
+
+    while(stream.tellg()-begin < item_length)
+    {
+        uint8_t const type = stream.peek();
+        if(type == 0x51)
+        {
+            maximum_length.push_back(MaximumLength(stream));
+        }
+        else if(type == 0x52)
+        {
+            implementation_class_uid.push_back(ImplementationClassUID(stream));
+        }
+        // 0x53: Asynchronous Operations Window, PS 3.7, D.3.3.3.1
+        else if(type == 0x54)
+        {
+            role_selection.push_back(RoleSelection(stream));
+        }
+        else if(type == 0x55)
+        {
+            implementation_version_name.push_back(
+                ImplementationVersionName(stream));
+        }
+        // 0x56: SOP Class Extended Negotiation, PS 3.7, D.3.3.5.1
+        // 0x57: SOP Class Common Extended Negotiation, PS 3.7, D.3.3.6.1
+        else if(type == 0x58)
+        {
+            user_identity_rq.push_back(UserIdentityRQ(stream));
+        }
+        else if(type == 0x59)
+        {
+            user_identity_ac.push_back(UserIdentityAC(stream));
+        }
+        else
+        {
+            throw Exception("Invalid sub-item type");
+        }
+    }
+
+    this->set_sub_items(maximum_length);
+    this->set_sub_items(implementation_class_uid);
+    this->set_sub_items(role_selection);
+    this->set_sub_items(implementation_version_name);
+    this->set_sub_items(user_identity_rq);
+    this->set_sub_items(user_identity_ac);
+}
+
+}
+
+}
diff --git a/src/odil/pdu/UserInformation.h b/src/odil/pdu/UserInformation.h
new file mode 100644
index 0000000..3f7c6fb
--- /dev/null
+++ b/src/odil/pdu/UserInformation.h
@@ -0,0 +1,66 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _7449339a_913f_4545_9846_311f055632c1
+#define _7449339a_913f_4545_9846_311f055632c1
+
+#include <initializer_list>
+#include <istream>
+#include <vector>
+
+#include "odil/pdu/MaximumLength.h"
+#include "odil/pdu/Object.h"
+#include "odil/pdu/UserIdentityAC.h"
+#include "odil/pdu/UserIdentityRQ.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+/// @brief User Information Item Structure (PS 3.8, 9.3.2.3 and 9.3.3.3).
+class UserInformation: public Object
+{
+public:
+    /// @brief Create a User Information item with no sub-items.
+    UserInformation();
+
+    /// @brief Read a User Information item from a stream.
+    UserInformation(std::istream & stream);
+
+    /// @brief Return sub-items of given type.
+    template<typename TObject>
+    std::vector<TObject> get_sub_items() const;
+
+    /// @brief Set a sequence of sub-items.
+    template<typename TObject>
+    void set_sub_items(std::vector<TObject> const & sub_item);
+
+    /// @brief Delete sub-items of given type.
+    template<typename TObject>
+    void delete_sub_items();
+private:
+    typedef std::vector<Item> Items;
+
+    /// @brief Return the iterators to the sub items of given type.
+    template<typename TObject>
+    std::vector<Items::const_iterator> _find_sub_items() const;
+
+    /// @brief Return the iterators to the sub items of given type.
+    template<typename TObject>
+    std::vector<Items::iterator> _find_sub_items();
+};
+
+}
+
+}
+
+#include "odil/pdu/UserInformation.txx"
+
+#endif // _7449339a_913f_4545_9846_311f055632c1
diff --git a/src/odil/pdu/UserInformation.txx b/src/odil/pdu/UserInformation.txx
new file mode 100644
index 0000000..ab1732c
--- /dev/null
+++ b/src/odil/pdu/UserInformation.txx
@@ -0,0 +1,157 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#ifndef _bb325c55_d983_41e3_b6c4_e3b957baedba
+#define _bb325c55_d983_41e3_b6c4_e3b957baedba
+
+#include "odil/pdu/UserInformation.h"
+
+#include <algorithm>
+#include <initializer_list>
+#include <istream>
+#include <iterator>
+#include <sstream>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/Item.h"
+#include "odil/pdu/MaximumLength.h"
+#include "odil/pdu/Object.h"
+#include "odil/pdu/UserIdentityAC.h"
+#include "odil/pdu/UserIdentityRQ.h"
+
+namespace odil
+{
+
+namespace pdu
+{
+
+template<typename TObject>
+std::vector<TObject>
+UserInformation
+::get_sub_items() const
+{
+    auto const iterators = this->_find_sub_items<TObject>();
+
+    std::vector<TObject> result;
+    result.reserve(iterators.size());
+    std::transform(
+        iterators.begin(), iterators.end(), std::back_inserter(result),
+        [](Items::const_iterator const & it)
+        {
+            std::stringstream stream;
+            stream << *it;
+            return TObject(stream);
+        }
+    );
+
+    return result;
+}
+
+template<typename TObject>
+void
+UserInformation
+::set_sub_items(std::vector<TObject> const & sub_items)
+{
+    auto const & old_items = this->_item.as_items("User-data");
+    std::vector<Item> new_items;
+
+    auto old_items_iterator = old_items.begin();
+    while(old_items_iterator != old_items.end() &&
+        old_items_iterator->as_unsigned_int_8("Item-type") < TObject::type)
+    {
+        new_items.push_back(*old_items_iterator);
+        ++old_items_iterator;
+    }
+
+    std::transform(
+        sub_items.begin(), sub_items.end(), std::back_inserter(new_items),
+        [](Object const & object) { return object.get_item(); });
+
+    while(old_items_iterator != old_items.end() &&
+        old_items_iterator->as_unsigned_int_8("Item-type") == TObject::type)
+    {
+        ++old_items_iterator;
+    }
+
+    while(old_items_iterator != old_items.end())
+    {
+        new_items.push_back(*old_items_iterator);
+        ++old_items_iterator;
+    }
+
+    this->_item.as_items("User-data") = new_items;
+
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+template<typename TObject>
+void
+UserInformation
+::delete_sub_items()
+{
+    auto const & old_items = this->_item.as_items("User-data");
+    std::vector<Item> new_items;
+    std::copy_if(
+        old_items.begin(), old_items.end(), std::back_inserter(new_items),
+        [](Item const & item)
+        {
+            return item.as_unsigned_int_8("Item-type") != TObject::type;
+        }
+    );
+
+    this->_item.as_items("User-data") = new_items;
+
+    this->_item.as_unsigned_int_16("Item-length") = this->_compute_length();
+}
+
+template<typename TObject>
+std::vector<UserInformation::Items::const_iterator>
+UserInformation
+::_find_sub_items() const
+{
+    std::vector<Items::const_iterator> result;
+
+    auto const & sub_items = this->_item.as_items("User-data");
+    auto iterator = sub_items.begin();
+    for(; iterator != sub_items.end(); ++iterator)
+    {
+        if(iterator->as_unsigned_int_8("Item-type") == TObject::type)
+        {
+            result.push_back(iterator);
+        }
+    }
+
+    return result;
+}
+
+template<typename TObject>
+std::vector<UserInformation::Items::iterator>
+UserInformation
+::_find_sub_items()
+{
+    std::vector<Items::iterator> result;
+
+    auto & sub_items = this->_item.as_items("User-data");
+    auto iterator = sub_items.begin();
+    for(; iterator != sub_items.end(); ++iterator)
+    {
+        if(iterator->as_unsigned_int_8("Item-type") == TObject::type)
+        {
+            result.push_back(iterator);
+        }
+    }
+
+    return result;
+}
+
+}
+
+}
+
+#endif // _bb325c55_d983_41e3_b6c4_e3b957baedba
diff --git a/src/dcmtkpp/registry.cpp b/src/odil/registry.cpp
similarity index 99%
rename from src/dcmtkpp/registry.cpp
rename to src/odil/registry.cpp
index ee40214..69347b8 100644
--- a/src/dcmtkpp/registry.cpp
+++ b/src/odil/registry.cpp
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -8,14 +8,14 @@
 
 #include <stdint.h>
 
-#include "dcmtkpp/registry.h"
+#include "odil/registry.h"
 
-#include "dcmtkpp/ElementsDictionary.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/VR.h"
-#include "dcmtkpp/UIDsDictionary.h"
+#include "odil/ElementsDictionary.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
+#include "odil/UIDsDictionary.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 namespace registry
@@ -8170,5 +8170,5 @@ UIDsDictionary create_uids_dictionary()
 
 }
 
-dcmtkpp::ElementsDictionary dcmtkpp::registry::public_dictionary=dcmtkpp::registry::create_public_dictionary();
-dcmtkpp::UIDsDictionary dcmtkpp::registry::uids_dictionary=dcmtkpp::registry::create_uids_dictionary();
\ No newline at end of file
+odil::ElementsDictionary odil::registry::public_dictionary=odil::registry::create_public_dictionary();
+odil::UIDsDictionary odil::registry::uids_dictionary=odil::registry::create_uids_dictionary();
\ No newline at end of file
diff --git a/src/dcmtkpp/registry.h b/src/odil/registry.h
similarity index 99%
rename from src/dcmtkpp/registry.h
rename to src/odil/registry.h
index 122216a..449c3e0 100644
--- a/src/dcmtkpp/registry.h
+++ b/src/odil/registry.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -9,11 +9,11 @@
 #ifndef _afc7b2d7_0869_4fea_9a9b_7fe6228baca9
 #define _afc7b2d7_0869_4fea_9a9b_7fe6228baca9
 
-#include "dcmtkpp/ElementsDictionary.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/UIDsDictionary.h"
+#include "odil/ElementsDictionary.h"
+#include "odil/Tag.h"
+#include "odil/UIDsDictionary.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 namespace registry
diff --git a/src/odil/uid.cpp b/src/odil/uid.cpp
new file mode 100644
index 0000000..8a8be2b
--- /dev/null
+++ b/src/odil/uid.cpp
@@ -0,0 +1,43 @@
+/*************************************************************************
+ * odil - Copyright (C) Universite de Strasbourg
+ * Distributed under the terms of the CeCILL-B license, as published by
+ * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
+ * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
+ * for details.
+ ************************************************************************/
+
+#include "odil/uid.h"
+
+#include <random>
+#include <string>
+
+#include "odil/Exception.h"
+
+#define ODIL_STRINGIFY_HELPER(s) #s
+#define ODIL_STRINGIFY(s) ODIL_STRINGIFY_HELPER(s)
+
+namespace odil
+{
+
+#ifdef ODIL_MAJOR_VERSION
+std::string const implementation_class_uid=uid_prefix+"0." ODIL_STRINGIFY(ODIL_MAJOR_VERSION);
+std::string const implementation_version_name="Odil " ODIL_STRINGIFY(ODIL_MAJOR_VERSION);
+#else
+#error ODIL_MAJOR_VERSION must be defined
+#endif
+
+std::string generate_uid()
+{
+    static std::random_device generator;
+    std::uniform_int_distribution<> non_zero(1, 9);
+    std::uniform_int_distribution<> digits(0, 9);
+
+    std::string result = uid_prefix + "." + std::to_string(non_zero(generator));
+    while(result.size()<64)
+    {
+        result += std::to_string(digits(generator));
+    }
+    return result;
+}
+
+}
diff --git a/src/dcmtkpp/uid.h b/src/odil/uid.h
similarity index 60%
rename from src/dcmtkpp/uid.h
rename to src/odil/uid.h
index 5248483..b1fd3c2 100644
--- a/src/dcmtkpp/uid.h
+++ b/src/odil/uid.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -11,20 +11,17 @@
 
 #include <string>
 
-namespace dcmtkpp
+namespace odil
 {
 
-/// @brief UID prefix of DCMTK++.
+/// @brief UID prefix of Odil.
 std::string const uid_prefix="1.2.826.0.1.3680043.9.5560";
 
-#ifdef DCMTKPP_MAJOR_VERSION
-#define DCMTKPP_STRINGIFY_HELPER(s) #s
-#define DCMTKPP_STRINGIFY(s) DCMTKPP_STRINGIFY_HELPER(s)
-/// @brief Implementation class UID of DCMTK++.
-std::string const implementation_class_uid=uid_prefix+"0." DCMTKPP_STRINGIFY(DCMTKPP_MAJOR_VERSION);
-#else
-#error DCMTKPP_MAJOR_VERSION must be defined
-#endif
+/// @brief Implementation class UID of Odil.
+extern std::string const implementation_class_uid;
+
+/// @brief Implementation version name of Odil.
+extern std::string const implementation_version_name;
 
 /// @brief Generate a UID under the UID prefix.
 std::string generate_uid();
diff --git a/src/dcmtkpp/unicode.cpp b/src/odil/unicode.cpp
similarity index 98%
rename from src/dcmtkpp/unicode.cpp
rename to src/odil/unicode.cpp
index 76246c7..b971423 100644
--- a/src/dcmtkpp/unicode.cpp
+++ b/src/odil/unicode.cpp
@@ -1,14 +1,15 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
-#include "dcmtkpp/unicode.h"
+#include "odil/unicode.h"
 
 #include <algorithm>
+#include <iterator>
 #include <map>
 #include <set>
 #include <sstream>
@@ -17,10 +18,10 @@
 #include <unicode/errorcode.h>
 #include <unicode/unistr.h>
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Value.h"
+#include "odil/Exception.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 std::map<std::string, std::string> const icu_encodings = {
diff --git a/src/dcmtkpp/unicode.h b/src/odil/unicode.h
similarity index 87%
rename from src/dcmtkpp/unicode.h
rename to src/odil/unicode.h
index 4815d80..2628fc3 100644
--- a/src/dcmtkpp/unicode.h
+++ b/src/odil/unicode.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -10,9 +10,9 @@
 #define _4a178325_e3d6_4f6f_9a18_ba6a983ee396
 
 #include <string>
-#include "dcmtkpp/Value.h"
+#include "odil/Value.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief Convert a string to its UTF-8 representation
diff --git a/src/dcmtkpp/xml_converter.cpp b/src/odil/xml_converter.cpp
similarity index 95%
rename from src/dcmtkpp/xml_converter.cpp
rename to src/odil/xml_converter.cpp
index 7a80781..227f2f5 100644
--- a/src/dcmtkpp/xml_converter.cpp
+++ b/src/odil/xml_converter.cpp
@@ -1,22 +1,20 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
  * for details.
  ************************************************************************/
 
+#include <iterator>
 #include <map>
 #include <vector>
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/ofstd/ofstd.h>
-#include <dcmtk/ofstd/ofstream.h>
+#include "odil/base64.h"
+#include "odil/registry.h"
+#include "odil/xml_converter.h"
 
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/xml_converter.h"
-
-namespace dcmtkpp
+namespace odil
 {
 
 struct ToXMLVisitor
@@ -216,11 +214,10 @@ struct ToXMLVisitor
 
         boost::property_tree::ptree tag_value;
 
-        unsigned char const * data =
-            reinterpret_cast<unsigned char const *>(&value[0]);
-        OFStringStream stream;
-        OFStandard::encodeBase64(stream, data, value.size());
-        tag_value.put_value(stream.str().c_str());
+        std::string encoded;
+        encoded.reserve(value.size()*4/3);
+        base64::encode(value.begin(), value.end(), std::back_inserter(encoded));
+        tag_value.put_value(encoded);
 
         result.add_child("InlineBinary", tag_value);
 
@@ -569,16 +566,11 @@ DataSet as_dataset(boost::property_tree::ptree const & xml)
                 }
 
                 auto const & encoded = it_value->second.get_value<std::string>();
-                OFString const encoded_dcmtk(encoded.c_str());
-                unsigned char * decoded;
-                size_t const decoded_size =
-                    OFStandard::decodeBase64(encoded_dcmtk, decoded);
-
-                element.as_binary().resize(decoded_size);
-                std::copy(decoded, decoded+decoded_size,
-                          element.as_binary().begin());
-
-                delete[] decoded;
+                auto & decoded = element.as_binary();
+                decoded.reserve(encoded.size()*3/4);
+                base64::decode(
+                    encoded.begin(), encoded.end(),
+                    std::back_inserter(decoded));
 
                 find_inline_binary = true;
             }
@@ -594,4 +586,4 @@ DataSet as_dataset(boost::property_tree::ptree const & xml)
     return data_set;
 }
 
-} // namespace dcmtkpp
+} // namespace odil
diff --git a/src/dcmtkpp/xml_converter.h b/src/odil/xml_converter.h
similarity index 86%
rename from src/dcmtkpp/xml_converter.h
rename to src/odil/xml_converter.h
index fa990af..5b01ef2 100644
--- a/src/dcmtkpp/xml_converter.h
+++ b/src/odil/xml_converter.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * dcmtkpp - Copyright (C) Universite de Strasbourg
+ * odil - Copyright (C) Universite de Strasbourg
  * Distributed under the terms of the CeCILL-B license, as published by
  * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
  * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
@@ -11,9 +11,9 @@
 
 #include <boost/property_tree/ptree.hpp>
 
-#include "dcmtkpp/DataSet.h"
+#include "odil/DataSet.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
 /// @brief Convert a data set to its XML representation.
@@ -22,6 +22,6 @@ boost::property_tree::ptree as_xml(DataSet const & data_set);
 /// @brief Create a data set from its XML representation.
 DataSet as_dataset(boost::property_tree::ptree const & xml);
 
-} // namespace dcmtkpp
+} // namespace odil
 
 #endif // _61eb6ed2_447a_43b5_a6ba_ca7c2a5fb492
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 80b3ced..d112b72 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,17 +1,28 @@
 find_package(Boost COMPONENTS unit_test_framework REQUIRED)
-link_directories(${Boost_LIBRARY_DIRS})
-add_definitions(-DBOOST_TEST_DYN_LINK)
+find_package(DCMTK REQUIRED)
+find_package(JsonCpp REQUIRED)
+
+add_subdirectory(tools)
 
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src)
+file(GLOB headers *.h)
+file(GLOB_RECURSE tests code/*.cpp)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src ${JsonCpp_INCLUDE_DIRS})
+add_definitions(
+    ${DCMTK_DEFINITIONS}
+    -D BOOST_ASIO_DYN_LINK
+    -D ODIL_MAJOR_VERSION=${odil_MAJOR_VERSION}
+    -DBOOST_TEST_DYN_LINK
+)
+link_directories(${Boost_LIBRARY_DIRS})
 
-add_definitions("-D HAVE_CONFIG_H")
-add_definitions("-D DCMTKPP_MAJOR_VERSION=${dcmtkpp_MAJOR_VERSION}")
 
-file(GLOB_RECURSE tests *.cpp)
 foreach(test_file ${tests})
     get_filename_component(test ${test_file} NAME_WE)
-    add_executable(${test} ${test_file})
-    target_link_libraries(${test} dcmtkpp ${Boost_LIBRARIES})
+
+    add_executable(${test} ${test_file} ${headers})
+    target_link_libraries(${test} odil ${Boost_LIBRARIES})
+
     file(READ ${test_file} content)
 
     set(pattern "BOOST_(AUTO|FIXTURE)_TEST_CASE\\(([^),]+)")
@@ -32,11 +43,13 @@ foreach(test_file ${tests})
     foreach(match ${matches})
         string(REGEX REPLACE ${pattern} "\\1" vr ${match})
 
-        set(case "${vr}FromDcmtkpp")
+        set(case "${vr}FromOdil")
         add_test("${test}_${case}" "${test}" "--run_test=${case}")
 
-        set(case "${vr}ToDcmtkpp")
+        set(case "${vr}ToOdil")
         add_test("${test}_${case}" "${test}" "--run_test=${case}")
     endforeach()
 
 endforeach()
+
+add_custom_target(Runner ${CMAKE_COMMAND} -E echo "Test runner" SOURCES run.sh)
diff --git a/tests/MessageFixtureBase.h b/tests/MessageFixtureBase.h
index 55abb73..43d5a23 100644
--- a/tests/MessageFixtureBase.h
+++ b/tests/MessageFixtureBase.h
@@ -1,9 +1,9 @@
 #ifndef _779c46d6_b6ea_443a_9642_f1ec5b87d4b3
 #define _779c46d6_b6ea_443a_9642_f1ec5b87d4b3
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Message.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/message/Message.h"
 
 /// @brief Base class for fixtures of Message-derived classes.
 template<typename TMessage>
@@ -11,40 +11,40 @@ struct MessageFixtureBase
 {
     /// @brief Check that a specific message can be constructed from a generic Message.
     void check_message_constructor(
-        dcmtkpp::DataSet const & command_set)
+        odil::DataSet const & command_set)
     {
-        dcmtkpp::Message const generic_message(command_set);
+        odil::message::Message const generic_message(command_set);
         TMessage const message(generic_message);
         this->check(message);
     }
 
     /// @brief Check that a specific message can be constructed from a generic Message.
     void check_message_constructor(
-        dcmtkpp::DataSet const & command_set, dcmtkpp::DataSet const & data_set)
+        odil::DataSet const & command_set, odil::DataSet const & data_set)
     {
-        dcmtkpp::Message const generic_message(command_set, data_set);
+        odil::message::Message const generic_message(command_set, data_set);
         TMessage const message(generic_message);
         this->check(message);
     }
 
     /// @brief Check that a specific message cannot be constructed from a generic Message.
     void check_message_constructor_throw(
-        dcmtkpp::DataSet const & command_set)
+        odil::DataSet const & command_set)
     {
-        dcmtkpp::Message const generic_message(command_set);
+        odil::message::Message const generic_message(command_set);
         BOOST_CHECK_THROW(
             TMessage const message(generic_message),
-            dcmtkpp::Exception);
+            odil::Exception);
     }
 
     /// @brief Check that a specific message cannot be constructed from a generic Message.
     void check_message_constructor_throw(
-        dcmtkpp::DataSet const & command_set, dcmtkpp::DataSet const & data_set)
+        odil::DataSet const & command_set, odil::DataSet const & data_set)
     {
-        dcmtkpp::Message const generic_message(command_set, data_set);
+        odil::message::Message const generic_message(command_set, data_set);
         BOOST_CHECK_THROW(
             TMessage const message(generic_message),
-            dcmtkpp::Exception);
+            odil::Exception);
     }
 
     /// @brief Check that the specific message attributes are set correctly
diff --git a/tests/PeerFixtureBase.h b/tests/PeerFixtureBase.h
index 532501c..edf62ad 100644
--- a/tests/PeerFixtureBase.h
+++ b/tests/PeerFixtureBase.h
@@ -1,72 +1,46 @@
 #ifndef _b21e8d37_0125_4d64_84aa_f91d9d96612b
 #define _b21e8d37_0125_4d64_84aa_f91d9d96612b
 
-#include <stdint.h>
-#include <stdlib.h>
+#include <cstdint>
+#include <cstdlib>
 #include <string>
-#include <utility>
 #include <vector>
 
 #include <boost/lexical_cast.hpp>
 
-#include "dcmtkpp/Association.h"
-#include "dcmtkpp/Network.h"
+#include "odil/Association.h"
+#include "odil/AssociationParameters.h"
 
 /// @brief Base class for fixtures of requiring a working association.
 class PeerFixtureBase
 {
 public:
-    dcmtkpp::Network network;
-    dcmtkpp::Association association;
+    typedef
+        odil::AssociationParameters::PresentationContext PresentationContext;
+    odil::Association association;
 
-    struct PresentationContext
+    PeerFixtureBase(std::vector<PresentationContext> const & contexts)
     {
-        std::string abstract_syntax;
-        std::vector<std::string> transfer_syntaxes;
-        T_ASC_SC_ROLE role;
-
-        PresentationContext(std::string const & abstract_syntax, std::vector<std::string> const & transfer_syntaxes, T_ASC_SC_ROLE role=ASC_SC_ROLE_DEFAULT)
-        : abstract_syntax(abstract_syntax),
-          transfer_syntaxes(transfer_syntaxes), role(role)
-        {
-            // Nothing else
-        }
-    };
-
-    PeerFixtureBase(T_ASC_NetworkRole role, int port, int timeout,
-        std::vector<PresentationContext> const & presentation_contexts)
-    : network(role, port, timeout), association()
-    {
-        this->network.initialize();
-
-        this->association.set_own_ae_title(
-            this->_get_environment_variable("DCMTKPP_OWN_AET"));
-        this->association.set_peer_host_name(
-            this->_get_environment_variable("DCMTKPP_PEER_HOST_NAME"));
+        this->association.set_peer_host(
+            this->get_environment_variable("ODIL_PEER_HOST_NAME"));
         this->association.set_peer_port(
-            this->_get_environment_variable<uint16_t>("DCMTKPP_PEER_PORT"));
-        this->association.set_peer_ae_title(
-            this->_get_environment_variable("DCMTKPP_PEER_AET"));
-        for(auto const & presentation_context: presentation_contexts)
-        {
-            this->association.add_presentation_context(
-                presentation_context.abstract_syntax,
-                presentation_context.transfer_syntaxes,
-                presentation_context.role);
-        }
-
-        this->association.associate(this->network);
+            this->get_environment_variable<uint16_t>("ODIL_PEER_PORT"));
+        this->association.update_parameters()
+            .set_calling_ae_title(
+                this->get_environment_variable("ODIL_OWN_AET"))
+            .set_called_ae_title(
+                this->get_environment_variable("ODIL_PEER_AET"))
+            .set_presentation_contexts(contexts);
+
+        this->association.associate();
     }
 
     ~PeerFixtureBase()
     {
         this->association.release();
-        this->network.drop();
     }
 
-private:
-
-    std::string _get_environment_variable(std::string const & name) const
+    static std::string get_environment_variable(std::string const & name)
     {
         char* value = getenv(name.c_str());
         if(value == NULL)
@@ -77,9 +51,10 @@ private:
     }
 
     template<typename T>
-    T _get_environment_variable(std::string const & name) const
+    static T get_environment_variable(std::string const & name)
     {
-        return boost::lexical_cast<T>(this->_get_environment_variable(name));
+        return boost::lexical_cast<T>(
+            PeerFixtureBase::get_environment_variable(name));
     }
 };
 
diff --git a/tests/code/Association.cpp b/tests/code/Association.cpp
index b80f222..e452cd1 100644
--- a/tests/code/Association.cpp
+++ b/tests/code/Association.cpp
@@ -3,250 +3,152 @@
 
 #include <ostream>
 
-#include "dcmtkpp/Association.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
+#include "odil/Association.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
 
 #include "../PeerFixtureBase.h"
 
-namespace dcmtkpp
+namespace odil
 {
 
-std::ostream & operator<<(std::ostream & stream, UserIdentityType const & type)
+bool
+operator==(
+    AssociationParameters::PresentationContext const & left,
+    AssociationParameters::PresentationContext const & right)
 {
-    stream << int(type);
-    return stream;
+    return (
+        left.abstract_syntax == right.abstract_syntax &&
+        left.transfer_syntaxes == right.transfer_syntaxes &&
+        left.scu_role_support == right.scu_role_support &&
+        left.scp_role_support == right.scp_role_support
+    );
+}
+
+bool
+operator==(
+    AssociationParameters const & left, AssociationParameters const & right)
+{
+    return (
+        left.get_calling_ae_title() == right.get_calling_ae_title() &&
+        left.get_called_ae_title() == right.get_called_ae_title() &&
+
+        left.get_presentation_contexts() == right.get_presentation_contexts() &&
+
+
+        left.get_user_identity().type == right.get_user_identity().type &&
+        left.get_user_identity().primary_field == right.get_user_identity().primary_field &&
+        left.get_user_identity().secondary_field == right.get_user_identity().secondary_field &&
+
+        left.get_maximum_length() == right.get_maximum_length()
+    );
 }
 
 }
 
+
 BOOST_AUTO_TEST_CASE(DefaultConstructor)
 {
-    dcmtkpp::Association association;
-    BOOST_CHECK_EQUAL(association.get_own_ae_title(), "");
-
-    BOOST_CHECK_EQUAL(association.get_peer_host_name(), "");
+    odil::Association association;
+    BOOST_CHECK_EQUAL(association.get_peer_host(), "");
     BOOST_CHECK_EQUAL(association.get_peer_port(), 104);
-    BOOST_CHECK_EQUAL(association.get_peer_ae_title(), "");
-
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_type(), dcmtkpp::UserIdentityType::None);
-    BOOST_CHECK_EQUAL(association.get_user_identity_primary_field(), "");
-    BOOST_CHECK_EQUAL(association.get_user_identity_secondary_field(), "");
+    BOOST_CHECK(
+        association.get_parameters() ==
+            odil::AssociationParameters()
+    );
 
     BOOST_CHECK(!association.is_associated());
-    BOOST_CHECK_EQUAL(
-        association.get_association(), static_cast<T_ASC_Association*>(NULL));
 }
 
 BOOST_AUTO_TEST_CASE(CopyConstructor)
 {
-    dcmtkpp::Association association;
-    association.set_own_ae_title("local");
-
-    association.set_peer_host_name("pacs.example.com");
+    odil::Association association;
+    association.set_peer_host("pacs.example.com");
     association.set_peer_port(11112);
-    association.set_peer_ae_title("remote");
-
-    association.set_user_identity_to_username_and_password("foo", "bar");
+    association.update_parameters()
+        .set_called_ae_title("remote")
+        .set_user_identity_to_username_and_password("foo", "bar");
 
-    dcmtkpp::Association const other(association);
+    odil::Association const other(association);
 
-    BOOST_CHECK_EQUAL(other.get_own_ae_title(), association.get_own_ae_title());
-
-    BOOST_CHECK_EQUAL(
-        other.get_peer_host_name(), association.get_peer_host_name());
+    BOOST_CHECK_EQUAL(other.get_peer_host(), association.get_peer_host());
     BOOST_CHECK_EQUAL(other.get_peer_port(), association.get_peer_port());
-    BOOST_CHECK_EQUAL(
-        other.get_peer_ae_title(), association.get_peer_ae_title());
-
-    BOOST_CHECK_EQUAL(
-        other.get_user_identity_type(), association.get_user_identity_type());
-    BOOST_CHECK_EQUAL(
-        other.get_user_identity_primary_field(),
-        association.get_user_identity_primary_field());
-    BOOST_CHECK_EQUAL(
-        other.get_user_identity_secondary_field(),
-        association.get_user_identity_secondary_field());
+    BOOST_CHECK(
+        association.get_parameters() ==
+            other.get_parameters()
+    );
 }
 
 BOOST_AUTO_TEST_CASE(Assignment)
 {
-    dcmtkpp::Association association;
-    association.set_own_ae_title("local");
-
-    association.set_peer_host_name("pacs.example.com");
+    odil::Association association;
+    association.set_peer_host("pacs.example.com");
     association.set_peer_port(11112);
-    association.set_peer_ae_title("remote");
-
-    association.set_user_identity_to_username_and_password("foo", "bar");
+    association.update_parameters()
+        .set_called_ae_title("remote")
+        .set_user_identity_to_username_and_password("foo", "bar");
 
-    dcmtkpp::Association other;
+    odil::Association other;
     other = association;
 
-    BOOST_CHECK_EQUAL(other.get_own_ae_title(), association.get_own_ae_title());
-
-    BOOST_CHECK_EQUAL(
-        other.get_peer_host_name(), association.get_peer_host_name());
+    BOOST_CHECK_EQUAL(other.get_peer_host(), association.get_peer_host());
     BOOST_CHECK_EQUAL(other.get_peer_port(), association.get_peer_port());
-    BOOST_CHECK_EQUAL(
-        other.get_peer_ae_title(), association.get_peer_ae_title());
-
-    BOOST_CHECK_EQUAL(
-        other.get_user_identity_type(), association.get_user_identity_type());
-    BOOST_CHECK_EQUAL(
-        other.get_user_identity_primary_field(),
-        association.get_user_identity_primary_field());
-    BOOST_CHECK_EQUAL(
-        other.get_user_identity_secondary_field(),
-        association.get_user_identity_secondary_field());
-}
-
-BOOST_AUTO_TEST_CASE(OwnAETitle)
-{
-    dcmtkpp::Association association;
-    association.set_own_ae_title("myself");
-    BOOST_CHECK_EQUAL(association.get_own_ae_title(), "myself");
+    BOOST_CHECK(
+        association.get_parameters() ==
+            other.get_parameters()
+    );
 }
 
 BOOST_AUTO_TEST_CASE(PeerHostName)
 {
-    dcmtkpp::Association association;
-    association.set_peer_host_name("pacs.example.com");
-    BOOST_CHECK_EQUAL(association.get_peer_host_name(), "pacs.example.com");
+    odil::Association association;
+    association.set_peer_host("pacs.example.com");
+    BOOST_CHECK_EQUAL(association.get_peer_host(), "pacs.example.com");
 }
 
 BOOST_AUTO_TEST_CASE(PeerPort)
 {
-    dcmtkpp::Association association;
+    odil::Association association;
     association.set_peer_port(11112);
     BOOST_CHECK_EQUAL(association.get_peer_port(), 11112);
 }
 
-BOOST_AUTO_TEST_CASE(PeerAETitle)
-{
-    dcmtkpp::Association association;
-    association.set_peer_ae_title("remote");
-    BOOST_CHECK_EQUAL(association.get_peer_ae_title(), "remote");
-}
-
-BOOST_AUTO_TEST_CASE(UserIdentityType)
-{
-    dcmtkpp::Association association;
-    association.set_user_identity_type(dcmtkpp::UserIdentityType::Username);
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_type(),
-        dcmtkpp::UserIdentityType::Username);
-}
-
-BOOST_AUTO_TEST_CASE(UserIdentityPrimaryField)
-{
-    dcmtkpp::Association association;
-    association.set_user_identity_primary_field("user");
-    BOOST_CHECK_EQUAL(association.get_user_identity_primary_field(), "user");
-}
-
-BOOST_AUTO_TEST_CASE(UserIdentitySecondaryField)
-{
-    dcmtkpp::Association association;
-    association.set_user_identity_secondary_field("password");
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_secondary_field(), "password");
-}
-
-BOOST_AUTO_TEST_CASE(UserIdentityNone)
+BOOST_AUTO_TEST_CASE(AssociationParameters)
 {
-    dcmtkpp::Association association;
-    association.set_user_identity_to_none();
-    BOOST_CHECK_EQUAL(association.get_user_identity_primary_field(), "");
-    BOOST_CHECK_EQUAL(association.get_user_identity_secondary_field(), "");
-}
+    odil::Association association;
 
-BOOST_AUTO_TEST_CASE(UserIdentityUsername)
-{
-    dcmtkpp::Association association;
-    association.set_user_identity_to_username("user");
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_type(),
-        dcmtkpp::UserIdentityType::Username);
-    BOOST_CHECK_EQUAL(association.get_user_identity_primary_field(), "user");
-    BOOST_CHECK_EQUAL(association.get_user_identity_secondary_field(), "");
-}
+    odil::AssociationParameters parameters;
+    parameters.set_calling_ae_title("foo");
+    association.set_parameters(parameters);
 
-BOOST_AUTO_TEST_CASE(UserIdentityUsernameAndPassword)
-{
-    dcmtkpp::Association association;
-    association.set_user_identity_to_username_and_password("user", "password");
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_type(),
-        dcmtkpp::UserIdentityType::UsernameAndPassword);
-    BOOST_CHECK_EQUAL(association.get_user_identity_primary_field(), "user");
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_secondary_field(), "password");
-}
-
-BOOST_AUTO_TEST_CASE(UserIdentityKerberos)
-{
-    dcmtkpp::Association association;
-    association.set_user_identity_to_kerberos("ticket");
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_type(),
-        dcmtkpp::UserIdentityType::Kerberos);
-    BOOST_CHECK_EQUAL(association.get_user_identity_primary_field(), "ticket");
-    BOOST_CHECK_EQUAL(association.get_user_identity_secondary_field(), "");
-}
-
-BOOST_AUTO_TEST_CASE(UserIdentitySAML)
-{
-    dcmtkpp::Association association;
-    association.set_user_identity_to_saml("assertion");
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_type(), dcmtkpp::UserIdentityType::SAML);
-    BOOST_CHECK_EQUAL(
-        association.get_user_identity_primary_field(), "assertion");
-    BOOST_CHECK_EQUAL(association.get_user_identity_secondary_field(), "");
+    BOOST_REQUIRE(association.get_parameters() == parameters);
 }
 
 BOOST_AUTO_TEST_CASE(Associate)
 {
-    PeerFixtureBase fixture(NET_REQUESTOR, 0, 10, 
+    PeerFixtureBase fixture({
         {
-            { dcmtkpp::registry::VerificationSOPClass,
-                {dcmtkpp::registry::ImplicitVRLittleEndian}
-            }
-        });
-    BOOST_CHECK_THROW(
-        fixture.association.set_own_ae_title("foo"), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(
-        fixture.association.set_peer_host_name("foo"), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(
-        fixture.association.set_peer_port(1234), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(
-        fixture.association.set_peer_ae_title("foo"), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(
-        fixture.association.add_presentation_context(
-                dcmtkpp::registry::PatientRootQueryRetrieveInformationModelGET,
-                { dcmtkpp::registry::ImplicitVRLittleEndian }),
-        dcmtkpp::Exception);
+            1, odil::registry::VerificationSOPClass,
+            { odil::registry::ImplicitVRLittleEndian }, true, false
+        }
+    });
     BOOST_CHECK_THROW(
-        fixture.association.set_user_identity_type(dcmtkpp::UserIdentityType::SAML), 
-        dcmtkpp::Exception);
+        fixture.association.set_peer_host("foo"), odil::Exception);
     BOOST_CHECK_THROW(
-        fixture.association.set_user_identity_primary_field("foo"), 
-        dcmtkpp::Exception);
+        fixture.association.set_peer_port(1234), odil::Exception);
     BOOST_CHECK_THROW(
-        fixture.association.set_user_identity_secondary_field("foo"), 
-        dcmtkpp::Exception);
+        fixture.association.update_parameters().set_maximum_length(123),
+        odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(Release)
 {
-    dcmtkpp::Association association;
-    BOOST_CHECK_THROW(association.release(), dcmtkpp::Exception);
+    odil::Association association;
+    BOOST_CHECK_THROW(association.release(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(Abort)
 {
-    dcmtkpp::Association association;
-    BOOST_CHECK_THROW(association.abort(), dcmtkpp::Exception);
+    odil::Association association;
+    BOOST_CHECK_THROW(association.abort(2, 4), odil::Exception);
 }
diff --git a/tests/code/AssociationAcceptor.cpp b/tests/code/AssociationAcceptor.cpp
new file mode 100644
index 0000000..15c8cef
--- /dev/null
+++ b/tests/code/AssociationAcceptor.cpp
@@ -0,0 +1,13 @@
+#define BOOST_TEST_MODULE AssociationAcceptor
+#include <boost/test/unit_test.hpp>
+
+#include "odil/AssociationAcceptor.h"
+
+BOOST_AUTO_TEST_CASE(AssociationRejected)
+{
+    odil::AssociationRejected const exception(1, 2, 3, "foo");
+    BOOST_REQUIRE_EQUAL(exception.get_result(), 1);
+    BOOST_REQUIRE_EQUAL(exception.get_source(), 2);
+    BOOST_REQUIRE_EQUAL(exception.get_reason(), 3);
+    BOOST_REQUIRE_EQUAL(exception.what(), "foo");
+}
diff --git a/tests/code/AssociationParameters.cpp b/tests/code/AssociationParameters.cpp
new file mode 100644
index 0000000..e76540e
--- /dev/null
+++ b/tests/code/AssociationParameters.cpp
@@ -0,0 +1,178 @@
+#define BOOST_TEST_MODULE AssociationParameters
+#include <boost/test/unit_test.hpp>
+
+#include "odil/AssociationParameters.h"
+#include "odil/Exception.h"
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::AssociationParameters const parameters;
+    BOOST_REQUIRE_EQUAL(parameters.get_called_ae_title(), "");
+    BOOST_REQUIRE_EQUAL(parameters.get_calling_ae_title(), "");
+    BOOST_REQUIRE(parameters.get_presentation_contexts().empty());
+
+    BOOST_REQUIRE(
+        parameters.get_user_identity().type ==
+            odil::AssociationParameters::UserIdentity::Type::None);
+    BOOST_REQUIRE(parameters.get_user_identity().primary_field.empty());
+    BOOST_REQUIRE(parameters.get_user_identity().secondary_field.empty());
+
+    BOOST_REQUIRE_EQUAL(parameters.get_maximum_length(), 16384);
+}
+
+BOOST_AUTO_TEST_CASE(CalledAETITLE)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_called_ae_title("foo");
+    BOOST_REQUIRE_EQUAL(parameters.get_called_ae_title(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(CalledAETITLETooLong)
+{
+    odil::AssociationParameters parameters;
+    BOOST_REQUIRE_THROW(
+        parameters.set_called_ae_title("0123456789abcdefTOO_LONG"),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(CallingAETITLE)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_calling_ae_title("foo");
+    BOOST_REQUIRE_EQUAL(parameters.get_calling_ae_title(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(CallingAETITLETooLong)
+{
+    odil::AssociationParameters parameters;
+    BOOST_REQUIRE_THROW(
+        parameters.set_calling_ae_title("0123456789abcdefTOO_LONG"),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(PresentationContexts)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_presentation_contexts({
+        { 1, "abstract1", { "transfer1", "transfer2" }, true, false },
+        { 3, "abstract2", { "transfer3" }, false, true }
+    });
+
+    BOOST_REQUIRE_EQUAL(parameters.get_presentation_contexts().size(), 2);
+
+    BOOST_REQUIRE_EQUAL(parameters.get_presentation_contexts()[0].id, 1);
+    BOOST_REQUIRE_EQUAL(
+        parameters.get_presentation_contexts()[0].abstract_syntax, "abstract1");
+    BOOST_REQUIRE_EQUAL(
+        parameters.get_presentation_contexts()[0].transfer_syntaxes.size(), 2);
+    BOOST_REQUIRE_EQUAL(
+        parameters.get_presentation_contexts()[0].transfer_syntaxes[0],
+        "transfer1");
+    BOOST_REQUIRE_EQUAL(
+        parameters.get_presentation_contexts()[0].transfer_syntaxes[1],
+        "transfer2");
+    BOOST_REQUIRE(parameters.get_presentation_contexts()[0].scu_role_support);
+    BOOST_REQUIRE(!parameters.get_presentation_contexts()[0].scp_role_support);
+
+    BOOST_REQUIRE_EQUAL(parameters.get_presentation_contexts()[1].id, 3);
+    BOOST_REQUIRE_EQUAL(
+        parameters.get_presentation_contexts()[1].abstract_syntax, "abstract2");
+    BOOST_REQUIRE_EQUAL(
+        parameters.get_presentation_contexts()[1].transfer_syntaxes.size(), 1);
+    BOOST_REQUIRE_EQUAL(
+        parameters.get_presentation_contexts()[1].transfer_syntaxes[0],
+        "transfer3");
+    BOOST_REQUIRE(!parameters.get_presentation_contexts()[1].scu_role_support);
+    BOOST_REQUIRE(parameters.get_presentation_contexts()[1].scp_role_support);
+}
+
+BOOST_AUTO_TEST_CASE(UserIdentityNone)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_user_identity_to_username_and_password("foo", "bar");
+    parameters.set_user_identity_to_none();
+
+    BOOST_REQUIRE(
+        parameters.get_user_identity().type ==
+            odil::AssociationParameters::UserIdentity::Type::None);
+    BOOST_REQUIRE(parameters.get_user_identity().primary_field.empty());
+    BOOST_REQUIRE(parameters.get_user_identity().secondary_field.empty());
+}
+
+BOOST_AUTO_TEST_CASE(UserIdentityUsername)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_user_identity_to_username("foo");
+
+    BOOST_REQUIRE(
+        parameters.get_user_identity().type ==
+            odil::AssociationParameters::UserIdentity::Type::Username);
+    BOOST_REQUIRE_EQUAL(parameters.get_user_identity().primary_field, "foo");
+    BOOST_REQUIRE(parameters.get_user_identity().secondary_field.empty());
+}
+
+BOOST_AUTO_TEST_CASE(UserIdentityUsernameAndPassword)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_user_identity_to_username_and_password("foo", "bar");
+
+    BOOST_REQUIRE(
+        parameters.get_user_identity().type ==
+            odil::AssociationParameters::UserIdentity::Type::UsernameAndPassword);
+    BOOST_REQUIRE_EQUAL(parameters.get_user_identity().primary_field, "foo");
+    BOOST_REQUIRE_EQUAL(parameters.get_user_identity().secondary_field, "bar");
+}
+
+BOOST_AUTO_TEST_CASE(UserIdentityKerberos)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_user_identity_to_kerberos("foo");
+
+    BOOST_REQUIRE(
+        parameters.get_user_identity().type ==
+            odil::AssociationParameters::UserIdentity::Type::Kerberos);
+    BOOST_REQUIRE_EQUAL(parameters.get_user_identity().primary_field, "foo");
+    BOOST_REQUIRE(parameters.get_user_identity().secondary_field.empty());
+}
+
+BOOST_AUTO_TEST_CASE(UserIdentitySAML)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_user_identity_to_saml("foo");
+
+    BOOST_REQUIRE(
+        parameters.get_user_identity().type ==
+            odil::AssociationParameters::UserIdentity::Type::SAML);
+    BOOST_REQUIRE_EQUAL(parameters.get_user_identity().primary_field, "foo");
+    BOOST_REQUIRE(parameters.get_user_identity().secondary_field.empty());
+}
+
+BOOST_AUTO_TEST_CASE(MaximumLength)
+{
+    odil::AssociationParameters parameters;
+    parameters.set_maximum_length(0x12345678);
+    BOOST_REQUIRE_EQUAL(parameters.get_maximum_length(), 0x12345678);
+}
+
+BOOST_AUTO_TEST_CASE(ChainedSetters)
+{
+    odil::AssociationParameters parameters;
+    parameters
+        .set_called_ae_title("called")
+        .set_calling_ae_title("calling")
+        .set_presentation_contexts({ { 1, "abstract", { "transfer" }, true, true } })
+        .set_user_identity_to_username_and_password("foo", "bar")
+        .set_maximum_length(0x12345678);
+
+    BOOST_REQUIRE_EQUAL(parameters.get_called_ae_title(), "called");
+    BOOST_REQUIRE_EQUAL(parameters.get_calling_ae_title(), "calling");
+    BOOST_REQUIRE_EQUAL(parameters.get_presentation_contexts().size(), 1);
+
+    BOOST_REQUIRE(
+        parameters.get_user_identity().type ==
+            odil::AssociationParameters::UserIdentity::Type::UsernameAndPassword);
+    BOOST_REQUIRE_EQUAL(parameters.get_user_identity().primary_field, "foo");
+    BOOST_REQUIRE_EQUAL(parameters.get_user_identity().secondary_field, "bar");
+
+    BOOST_REQUIRE_EQUAL(parameters.get_maximum_length(), 0x12345678);
+}
diff --git a/tests/code/BasicDirectoryCreator.cpp b/tests/code/BasicDirectoryCreator.cpp
new file mode 100644
index 0000000..ec75cf0
--- /dev/null
+++ b/tests/code/BasicDirectoryCreator.cpp
@@ -0,0 +1,159 @@
+#define BOOST_TEST_MODULE BasicDirectoryCreator
+#include <boost/test/unit_test.hpp>
+
+#include <fstream>
+
+#include "odil/BasicDirectoryCreator.h"
+#include "odil/json_converter.h"
+#include "odil/Reader.h"
+#include "odil/registry.h"
+#include "odil/Writer.h"
+
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+BOOST_AUTO_TEST_CASE(DefaultConstructor)
+{
+    odil::BasicDirectoryCreator const creator;
+    BOOST_REQUIRE_EQUAL(creator.root.empty(), true);
+    BOOST_REQUIRE_EQUAL(creator.files.empty(), true);
+    BOOST_REQUIRE_EQUAL(creator.extra_record_keys.empty(), true);
+    BOOST_REQUIRE(
+        creator.item_encoding == odil::Writer::ItemEncoding::ExplicitLength);
+}
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::BasicDirectoryCreator::RecordKeyMap const extra_records(
+        {
+            {
+                "PATIENT",
+                {
+                    {odil::registry::PatientBirthDate, 1},
+                    {odil::registry::OtherPatientIDs, 3},
+                }
+            }
+        });
+
+    odil::BasicDirectoryCreator const creator(
+        "root", {"a.dcm", "b.dcm"}, extra_records,
+        odil::Writer::ItemEncoding::UndefinedLength);
+    BOOST_REQUIRE_EQUAL(creator.root, "root");
+    BOOST_REQUIRE(
+        creator.files == std::vector<std::string>({"a.dcm", "b.dcm"}));
+    BOOST_REQUIRE(
+        creator.extra_record_keys == extra_records);
+    BOOST_REQUIRE(
+        creator.item_encoding == odil::Writer::ItemEncoding::UndefinedLength);
+}
+
+BOOST_AUTO_TEST_CASE(BasicDirectory)
+{
+    std::stringstream stream1;
+    stream1 <<  "{";
+
+    {
+        odil::DataSet data_set;
+        data_set.add("PatientID", {"DJ123"});
+        data_set.add("StudyDate", {"19100110"});
+        data_set.add("StudyTime", {"1234"});
+        data_set.add("StudyDescription", {"Study"});
+        data_set.add("StudyInstanceUID", {"1.2.3.4"});
+        data_set.add("StudyID", {"FOO"});
+        data_set.add("Modality", {"OT"});
+        data_set.add("SeriesInstanceUID", {"1.2.3.4.1"});
+        data_set.add("SeriesNumber", {1});
+        data_set.add("SeriesDescription", {"Series"});
+        data_set.add("InstanceNumber", {1});
+        data_set.add("SOPInstanceUID", {"1.2.3.4.1.1"});
+        data_set.add("SOPClassUID", {odil::registry::RawDataStorage});
+
+        std::ofstream stream("a.dcm");
+        odil::Writer::write_file(data_set, stream);
+    }
+
+    {
+        odil::DataSet data_set;
+        data_set.add("PatientID", {"DJ123"});
+        data_set.add("StudyDate", {"19100110"});
+        data_set.add("StudyTime", {"1234"});
+        data_set.add("StudyDescription", {"Study"});
+        data_set.add("StudyInstanceUID", {"1.2.3.4"});
+        data_set.add("StudyID", {"FOO"});
+        data_set.add("Modality", {"OT"});
+        data_set.add("SeriesInstanceUID", {"1.2.3.4.1"});
+        data_set.add("SeriesNumber", {1});
+        data_set.add("SeriesDescription", {"Series"});
+        data_set.add("InstanceNumber", {2});
+        data_set.add("SOPInstanceUID", {"1.2.3.4.1.2"});
+        data_set.add("SOPClassUID", {odil::registry::RawDataStorage});
+
+        std::ofstream stream("b.dcm");
+        odil::Writer::write_file(data_set, stream);
+    }
+
+    odil::BasicDirectoryCreator::RecordKeyMap const extra_records(
+        { { "SERIES", { {odil::registry::SeriesDescription, 1} } } });
+
+    odil::BasicDirectoryCreator const creator(
+        ".", {"a.dcm", "b.dcm"}, extra_records);
+    creator();
+
+    boost::filesystem::ifstream stream(boost::filesystem::path(".")/"DICOMDIR");
+    auto const dicomdir_and_header = odil::Reader::read_file(stream);
+
+    BOOST_REQUIRE(
+        dicomdir_and_header.first.as_string("MediaStorageSOPClassUID") ==
+            odil::Value::Strings({odil::registry::MediaStorageDirectoryStorage}));
+    BOOST_REQUIRE(
+        dicomdir_and_header.first.as_string("TransferSyntaxUID") ==
+            odil::Value::Strings({odil::registry::ExplicitVRLittleEndian}));
+
+    auto const & records =
+        dicomdir_and_header.second.as_data_set("DirectoryRecordSequence");
+    BOOST_REQUIRE_EQUAL(records.size(), 5);
+
+    BOOST_REQUIRE(
+        records[0].as_string("DirectoryRecordType") ==
+            odil::Value::Strings({"PATIENT"}));
+    BOOST_REQUIRE(
+        records[0].as_string("PatientID") ==
+            odil::Value::Strings({"DJ123"}));
+
+    BOOST_REQUIRE(
+        records[1].as_string("DirectoryRecordType") ==
+            odil::Value::Strings({"STUDY"}));
+    BOOST_REQUIRE(
+        records[1].as_string("StudyInstanceUID") ==
+            odil::Value::Strings({"1.2.3.4"}));
+    BOOST_REQUIRE(
+        records[1].as_string("StudyDescription") ==
+            odil::Value::Strings({"Study"}));
+
+    BOOST_REQUIRE(
+        records[2].as_string("DirectoryRecordType") ==
+            odil::Value::Strings({"SERIES"}));
+    BOOST_REQUIRE(
+        records[2].as_string("SeriesInstanceUID") ==
+            odil::Value::Strings({"1.2.3.4.1"}));
+    BOOST_REQUIRE(
+        records[2].as_string("SeriesDescription") ==
+            odil::Value::Strings({"Series"}));
+
+    BOOST_REQUIRE(
+        records[3].as_string("DirectoryRecordType") ==
+            odil::Value::Strings({"IMAGE"}));
+    BOOST_REQUIRE(
+        records[3].as_int("InstanceNumber") ==
+            odil::Value::Integers({1}));
+
+    BOOST_REQUIRE(
+        records[4].as_string("DirectoryRecordType") ==
+            odil::Value::Strings({"IMAGE"}));
+    BOOST_REQUIRE(
+        records[4].as_int("InstanceNumber") ==
+            odil::Value::Integers({2}));
+
+    boost::filesystem::remove("a.dcm");
+    boost::filesystem::remove("b.dcm");
+}
diff --git a/tests/code/CEchoRequest.cpp b/tests/code/CEchoRequest.cpp
deleted file mode 100644
index 8b2b7ba..0000000
--- a/tests/code/CEchoRequest.cpp
+++ /dev/null
@@ -1,56 +0,0 @@
-#define BOOST_TEST_MODULE CEchoRequest
-#include <boost/test/unit_test.hpp>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CEchoRequest.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CEchoRequest>
-{
-    dcmtkpp::DataSet command_set;
-
-    Fixture()
-    {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_ECHO_RQ});
-        this->command_set.add("MessageID", {1234});
-        this->command_set.add("AffectedSOPClassUID", {dcmtkpp::registry::VerificationSOPClass});
-    }
-
-    void check(dcmtkpp::CEchoRequest const & message)
-    {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_ECHO_RQ);
-        BOOST_CHECK_EQUAL(message.get_message_id(), 1234);
-        BOOST_CHECK_EQUAL(
-            message.get_affected_sop_class_uid(), dcmtkpp::registry::VerificationSOPClass);
-        BOOST_CHECK(!message.has_data_set());
-    }
-};
-
-BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
-{
-    dcmtkpp::CEchoRequest const message(1234, dcmtkpp::registry::VerificationSOPClass);
-    this->check(message);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
-{
-    this->check_message_constructor(this->command_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
-{
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RSP};
-    this->check_message_constructor_throw(this->command_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorMissingAffectSOPClass, Fixture)
-{
-    this->command_set.remove("AffectedSOPClassUID");
-    this->check_message_constructor_throw(this->command_set);
-}
diff --git a/tests/code/CEchoResponse.cpp b/tests/code/CEchoResponse.cpp
deleted file mode 100644
index 3dc26bd..0000000
--- a/tests/code/CEchoResponse.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#define BOOST_TEST_MODULE CEchoResponse
-#include <boost/test/unit_test.hpp>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CEchoResponse.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CEchoResponse>
-{
-    dcmtkpp::DataSet command_set;
-
-    Fixture()
-    {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_ECHO_RSP});
-        this->command_set.add("MessageIDBeingRespondedTo", {1234});
-        this->command_set.add("Status", {STATUS_Success});
-        this->command_set.add("AffectedSOPClassUID", {dcmtkpp::registry::VerificationSOPClass});
-    }
-
-    void check(dcmtkpp::CEchoResponse const & message)
-    {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_ECHO_RSP);
-        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
-        BOOST_CHECK_EQUAL(message.get_status(), STATUS_Success);
-        BOOST_CHECK_EQUAL(message.get_affected_sop_class_uid(), dcmtkpp::registry::VerificationSOPClass);
-        BOOST_CHECK(!message.has_data_set());
-    }
-};
-
-BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
-{
-    dcmtkpp::CEchoResponse const message(
-        1234, STATUS_Success, dcmtkpp::registry::VerificationSOPClass);
-    this->check(message);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
-{
-    this->check_message_constructor(this->command_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
-{
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RQ};
-    this->check_message_constructor_throw(this->command_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorMissingAffectSOPClass, Fixture)
-{
-    this->command_set.remove("AffectedSOPClassUID");
-    this->check_message_constructor_throw(this->command_set);
-}
diff --git a/tests/code/CFindResponse.cpp b/tests/code/CFindResponse.cpp
deleted file mode 100644
index 49bd561..0000000
--- a/tests/code/CFindResponse.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-#define BOOST_TEST_MODULE CFindResponse
-#include <boost/test/unit_test.hpp>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CFindResponse.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CFindResponse>
-{
-    dcmtkpp::DataSet command_set;
-    dcmtkpp::DataSet data_set;
-
-    Fixture()
-    {
-        command_set.add("CommandField", {dcmtkpp::Message::Command::C_FIND_RSP});
-        command_set.add("MessageIDBeingRespondedTo", {1234});
-        command_set.add("Status", {STATUS_Success});
-
-        command_set.add("MessageID", {5678});
-        command_set.add("AffectedSOPClassUID",
-            {dcmtkpp::registry::StudyRootQueryRetrieveInformationModelFIND});
-
-        data_set.add("PatientName", {"Doe^John"});
-        data_set.add("PatientID", {"DJ123"});
-        data_set.add("StudyDescription", {"Brain"});
-        data_set.add("StudyInstanceUID", {"1.2.3"});
-    }
-
-    virtual void check(dcmtkpp::CFindResponse const & message)
-    {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_FIND_RSP);
-        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
-        BOOST_CHECK_EQUAL(message.get_status(), STATUS_Success);
-
-        BOOST_CHECK(message.has_message_id());
-        BOOST_CHECK_EQUAL(message.get_message_id(), 5678);
-
-        BOOST_CHECK(message.has_affected_sop_class_uid());
-        BOOST_CHECK_EQUAL(
-            message.get_affected_sop_class_uid(),
-            dcmtkpp::registry::StudyRootQueryRetrieveInformationModelFIND);
-
-        BOOST_CHECK(message.has_data_set());
-        BOOST_CHECK(message.get_data_set() == this->data_set);
-    }
-};
-
-BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
-{
-    dcmtkpp::CFindResponse message(1234, STATUS_Success, this->data_set);
-    message.set_message_id(5678);
-    message.set_affected_sop_class_uid(
-        dcmtkpp::registry::StudyRootQueryRetrieveInformationModelFIND);
-    this->check(message);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
-{
-    this->check_message_constructor(this->command_set, this->data_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
-{
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RQ};
-    this->check_message_constructor_throw(this->command_set, this->data_set);
-}
diff --git a/tests/code/CGetResponse.cpp b/tests/code/CGetResponse.cpp
deleted file mode 100644
index 0f035e0..0000000
--- a/tests/code/CGetResponse.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-#define BOOST_TEST_MODULE CGetResponse
-#include <boost/test/unit_test.hpp>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CGetResponse.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CGetResponse>
-{
-    dcmtkpp::DataSet command_set;
-    dcmtkpp::DataSet data_set;
-
-    Fixture()
-    {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_GET_RSP});
-        this->command_set.add("MessageIDBeingRespondedTo", {1234});
-        this->command_set.add("Status", {STATUS_Success});
-
-        this->command_set.add("MessageID", {5678});
-        this->command_set.add("AffectedSOPClassUID",
-            {dcmtkpp::registry::StudyRootQueryRetrieveInformationModelGET});
-        this->command_set.add(dcmtkpp::registry::NumberOfRemainingSuboperations, {1});
-        this->command_set.add(dcmtkpp::registry::NumberOfCompletedSuboperations, {2});
-        this->command_set.add(dcmtkpp::registry::NumberOfFailedSuboperations, {3});
-        this->command_set.add(dcmtkpp::registry::NumberOfWarningSuboperations, {4});
-
-        this->data_set.add("PatientName", {"Doe^John"});
-        this->data_set.add("PatientID", {"DJ123"});
-        this->data_set.add("StudyDescription", {"Brain"});
-        this->data_set.add("StudyInstanceUID", {"1.2.3"});
-    }
-
-    virtual void check(dcmtkpp::CGetResponse const & message)
-    {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_GET_RSP);
-        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
-        BOOST_CHECK_EQUAL(message.get_status(), STATUS_Success);
-
-        BOOST_CHECK(message.has_message_id());
-        BOOST_CHECK_EQUAL(message.get_message_id(), 5678);
-
-        BOOST_CHECK(message.has_affected_sop_class_uid());
-        BOOST_CHECK_EQUAL(
-            message.get_affected_sop_class_uid(),
-            dcmtkpp::registry::StudyRootQueryRetrieveInformationModelGET);
-
-        BOOST_CHECK(message.has_number_of_remaining_sub_operations());
-        BOOST_CHECK_EQUAL(message.get_number_of_remaining_sub_operations(), 1);
-
-        BOOST_CHECK(message.has_number_of_completed_sub_operations());
-        BOOST_CHECK_EQUAL(message.get_number_of_completed_sub_operations(), 2);
-
-        BOOST_CHECK(message.has_number_of_failed_sub_operations());
-        BOOST_CHECK_EQUAL(message.get_number_of_failed_sub_operations(), 3);
-
-        BOOST_CHECK(message.has_number_of_warning_sub_operations());
-        BOOST_CHECK_EQUAL(message.get_number_of_warning_sub_operations(), 4);
-
-        BOOST_CHECK(message.has_data_set());
-        BOOST_CHECK(message.get_data_set() == this->data_set);
-    }
-};
-
-BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
-{
-    dcmtkpp::CGetResponse message(1234, STATUS_Success, this->data_set);
-    message.set_message_id(5678);
-    message.set_affected_sop_class_uid(
-        dcmtkpp::registry::StudyRootQueryRetrieveInformationModelGET);
-    message.set_number_of_remaining_sub_operations(1);
-    message.set_number_of_completed_sub_operations(2);
-    message.set_number_of_failed_sub_operations(3);
-    message.set_number_of_warning_sub_operations(4);
-
-    this->check(message);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
-{
-    this->check_message_constructor(this->command_set, this->data_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
-{
-    this->command_set.as_int(dcmtkpp::registry::CommandField) = { dcmtkpp::Message::Command::C_ECHO_RQ };
-    this->check_message_constructor_throw(this->command_set, this->data_set);
-}
diff --git a/tests/code/CMoveResponse.cpp b/tests/code/CMoveResponse.cpp
deleted file mode 100644
index 28677e9..0000000
--- a/tests/code/CMoveResponse.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-#define BOOST_TEST_MODULE CMoveResponse
-#include <boost/test/unit_test.hpp>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CMoveResponse.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CMoveResponse>
-{
-    dcmtkpp::DataSet command_set;
-    dcmtkpp::DataSet data_set;
-
-    Fixture()
-    {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_MOVE_RSP});
-        this->command_set.add("MessageIDBeingRespondedTo", {1234});
-        this->command_set.add("Status", {STATUS_Success});
-
-        this->command_set.add("MessageID", {5678});
-        this->command_set.add("AffectedSOPClassUID",
-            {dcmtkpp::registry::StudyRootQueryRetrieveInformationModelMOVE});
-        this->command_set.add(dcmtkpp::registry::NumberOfRemainingSuboperations, {1});
-        this->command_set.add(dcmtkpp::registry::NumberOfCompletedSuboperations, {2});
-        this->command_set.add(dcmtkpp::registry::NumberOfFailedSuboperations, {3});
-        this->command_set.add(dcmtkpp::registry::NumberOfWarningSuboperations, {4});
-
-        this->data_set.add("PatientName", {"Doe^John"});
-        this->data_set.add("PatientID", {"DJ123"});
-        this->data_set.add("StudyDescription", {"Brain"});
-        this->data_set.add("StudyInstanceUID", {"1.2.3"});
-    }
-
-    virtual void check(dcmtkpp::CMoveResponse const & message)
-    {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_MOVE_RSP);
-        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
-        BOOST_CHECK_EQUAL(message.get_status(), STATUS_Success);
-
-        BOOST_CHECK(message.has_message_id());
-        BOOST_CHECK_EQUAL(message.get_message_id(), 5678);
-
-        BOOST_CHECK(message.has_affected_sop_class_uid());
-        BOOST_CHECK_EQUAL(
-            message.get_affected_sop_class_uid(),
-            dcmtkpp::registry::StudyRootQueryRetrieveInformationModelMOVE);
-
-        BOOST_CHECK(message.has_number_of_remaining_sub_operations());
-        BOOST_CHECK_EQUAL(message.get_number_of_remaining_sub_operations(), 1);
-
-        BOOST_CHECK(message.has_number_of_completed_sub_operations());
-        BOOST_CHECK_EQUAL(message.get_number_of_completed_sub_operations(), 2);
-
-        BOOST_CHECK(message.has_number_of_failed_sub_operations());
-        BOOST_CHECK_EQUAL(message.get_number_of_failed_sub_operations(), 3);
-
-        BOOST_CHECK(message.has_number_of_warning_sub_operations());
-        BOOST_CHECK_EQUAL(message.get_number_of_warning_sub_operations(), 4);
-
-        BOOST_CHECK(message.has_data_set());
-        BOOST_CHECK(message.get_data_set() == this->data_set);
-    }
-};
-
-BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
-{
-    dcmtkpp::CMoveResponse message(1234, STATUS_Success, this->data_set);
-    message.set_message_id(5678);
-    message.set_affected_sop_class_uid(
-        dcmtkpp::registry::StudyRootQueryRetrieveInformationModelMOVE);
-    message.set_number_of_remaining_sub_operations(1);
-    message.set_number_of_completed_sub_operations(2);
-    message.set_number_of_failed_sub_operations(3);
-    message.set_number_of_warning_sub_operations(4);
-
-    this->check(message);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
-{
-    this->check_message_constructor(this->command_set, this->data_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
-{
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RQ};
-    this->check_message_constructor_throw(this->command_set, this->data_set);
-}
diff --git a/tests/code/CStoreResponse.cpp b/tests/code/CStoreResponse.cpp
deleted file mode 100644
index b22c920..0000000
--- a/tests/code/CStoreResponse.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#define BOOST_TEST_MODULE CStoreResponse
-#include <boost/test/unit_test.hpp>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/CStoreResponse.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
-
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CStoreResponse>
-{
-    dcmtkpp::DataSet command_set;
-
-    Fixture()
-    {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_STORE_RSP});
-        this->command_set.add("MessageIDBeingRespondedTo", {1234});
-        this->command_set.add("Status", {STATUS_Success});
-
-        this->command_set.add("MessageID", {5678});
-        this->command_set.add("AffectedSOPClassUID", {dcmtkpp::registry::MRImageStorage});
-        this->command_set.add("AffectedSOPInstanceUID", {"1.2.3.4"});
-    }
-
-    void check(dcmtkpp::CStoreResponse const & message)
-    {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_STORE_RSP);
-        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
-        BOOST_CHECK_EQUAL(message.get_status(), STATUS_Success);
-        BOOST_CHECK(!message.has_data_set());
-
-        BOOST_CHECK(message.has_message_id());
-        BOOST_CHECK_EQUAL(message.get_message_id(), 5678);
-
-        BOOST_CHECK(message.has_affected_sop_class_uid());
-        BOOST_CHECK_EQUAL(
-            message.get_affected_sop_class_uid(), dcmtkpp::registry::MRImageStorage);
-
-        BOOST_CHECK(message.has_affected_sop_instance_uid());
-        BOOST_CHECK_EQUAL(message.get_affected_sop_instance_uid(), "1.2.3.4");
-    }
-};
-
-BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
-{
-    dcmtkpp::CStoreResponse message(1234, STATUS_Success);
-    message.set_message_id(5678);
-    message.set_affected_sop_class_uid(dcmtkpp::registry::MRImageStorage);
-    message.set_affected_sop_instance_uid("1.2.3.4");
-
-    this->check(message);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
-{
-    this->check_message_constructor(this->command_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
-{
-    this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_ECHO_RQ});
-    this->check_message_constructor_throw(this->command_set);
-}
-
diff --git a/tests/code/Cancellation.cpp b/tests/code/Cancellation.cpp
deleted file mode 100644
index 5f2954f..0000000
--- a/tests/code/Cancellation.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#define BOOST_TEST_MODULE Cancellation
-#include <boost/test/unit_test.hpp>
-
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Cancellation.h"
-
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::Cancellation>
-{
-    dcmtkpp::DataSet command_set;
-    Fixture()
-    {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_CANCEL_RQ});
-        this->command_set.add("MessageIDBeingRespondedTo", {1234});
-    }
-
-    void check(dcmtkpp::Cancellation const & message)
-    {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_CANCEL_RQ);
-        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
-    }
-};
-
-BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
-{
-    dcmtkpp::Cancellation const message(1234);
-    this->check(message);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
-{
-    this->check_message_constructor(this->command_set);
-}
-
-BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
-{
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RQ};
-    this->check_message_constructor_throw(this->command_set);
-}
diff --git a/tests/code/DataSet.cpp b/tests/code/DataSet.cpp
index e6c516f..414bb78 100644
--- a/tests/code/DataSet.cpp
+++ b/tests/code/DataSet.cpp
@@ -1,88 +1,88 @@
 #define BOOST_TEST_MODULE DataSet
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
 
-#include "dcmtkpp/registry.h"
+#include "odil/registry.h"
 
 BOOST_AUTO_TEST_CASE(Empty)
 {
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     BOOST_CHECK(dataset.empty());
     BOOST_CHECK_EQUAL(dataset.size(), 0);
-    BOOST_CHECK(!dataset.has(dcmtkpp::Tag("PatientName")));
+    BOOST_CHECK(!dataset.has(odil::Tag("PatientName")));
 }
 
 BOOST_AUTO_TEST_CASE(AddExplicitVR)
 {
-    dcmtkpp::Tag const tag("PatientName");
+    odil::Tag const tag("PatientName");
 
-    dcmtkpp::DataSet dataset;
-    dataset.add(tag, dcmtkpp::VR::PN);
+    odil::DataSet dataset;
+    dataset.add(tag, odil::VR::PN);
 
     BOOST_CHECK(!dataset.empty());
     BOOST_CHECK_EQUAL(dataset.size(), 1);
     BOOST_CHECK(dataset.has(tag));
-    BOOST_CHECK(dataset.get_vr(tag) == dcmtkpp::VR::PN);
+    BOOST_CHECK(dataset.get_vr(tag) == odil::VR::PN);
 }
 
 BOOST_AUTO_TEST_CASE(AddImplicitVR)
 {
-    dcmtkpp::Tag const tag("PatientName");
+    odil::Tag const tag("PatientName");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
 
     BOOST_CHECK(!dataset.empty());
     BOOST_CHECK_EQUAL(dataset.size(), 1);
     BOOST_CHECK(dataset.has(tag));
-    BOOST_CHECK(dataset.get_vr(tag) == dcmtkpp::VR::PN);
+    BOOST_CHECK(dataset.get_vr(tag) == odil::VR::PN);
 }
 
 BOOST_AUTO_TEST_CASE(AddValueExplicitVR)
 {
-    dcmtkpp::Tag const tag("PatientName");
+    odil::Tag const tag("PatientName");
 
-    dcmtkpp::DataSet dataset;
-    dataset.add(tag, { "Doe^John"}, dcmtkpp::VR::PN);
+    odil::DataSet dataset;
+    dataset.add(tag, { "Doe^John"}, odil::VR::PN);
 
     BOOST_CHECK(!dataset.empty());
     BOOST_CHECK_EQUAL(dataset.size(), 1);
     BOOST_CHECK(dataset.has(tag));
-    BOOST_CHECK(dataset.get_vr(tag) == dcmtkpp::VR::PN);
-    BOOST_CHECK(dataset.as_string(tag) == dcmtkpp::Value::Strings({ "Doe^John" }));
+    BOOST_CHECK(dataset.get_vr(tag) == odil::VR::PN);
+    BOOST_CHECK(dataset.as_string(tag) == odil::Value::Strings({ "Doe^John" }));
 }
 
 BOOST_AUTO_TEST_CASE(AddValueImplicitVR)
 {
-    dcmtkpp::Tag const tag("PatientName");
+    odil::Tag const tag("PatientName");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag, { "Doe^John"});
 
     BOOST_CHECK(!dataset.empty());
     BOOST_CHECK_EQUAL(dataset.size(), 1);
     BOOST_CHECK(dataset.has(tag));
-    BOOST_CHECK(dataset.get_vr(tag) == dcmtkpp::VR::PN);
-    BOOST_CHECK(dataset.as_string(tag) == dcmtkpp::Value::Strings({ "Doe^John" }));
+    BOOST_CHECK(dataset.get_vr(tag) == odil::VR::PN);
+    BOOST_CHECK(dataset.as_string(tag) == odil::Value::Strings({ "Doe^John" }));
 }
 
 BOOST_AUTO_TEST_CASE(AddInvalidTag)
 {
-    dcmtkpp::Tag const tag(0xdead, 0xbeef);
-    dcmtkpp::DataSet dataset;
+    odil::Tag const tag(0xdead, 0xbeef);
+    odil::DataSet dataset;
 
-    BOOST_CHECK_THROW(dataset.add(tag), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(dataset.add(tag), odil::Exception);
 }
 
-BOOST_AUTO_TEST_CASE(AddInt)
+BOOST_AUTO_TEST_CASE(AddIntEmpty)
 {
-    dcmtkpp::Tag const tag("Rows");
+    odil::Tag const tag("Rows");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
 
     BOOST_CHECK(dataset.is_int(tag));
@@ -93,11 +93,11 @@ BOOST_AUTO_TEST_CASE(AddInt)
     BOOST_CHECK(value.empty());
 }
 
-BOOST_AUTO_TEST_CASE(AddDouble)
+BOOST_AUTO_TEST_CASE(AddDoubleEmpty)
 {
-    dcmtkpp::Tag const tag("SpacingBetweenSlices");
+    odil::Tag const tag("SpacingBetweenSlices");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
 
     BOOST_CHECK(dataset.is_real(tag));
@@ -108,11 +108,11 @@ BOOST_AUTO_TEST_CASE(AddDouble)
     BOOST_CHECK(value.empty());
 }
 
-BOOST_AUTO_TEST_CASE(AddString)
+BOOST_AUTO_TEST_CASE(AddStringEmpty)
 {
-    dcmtkpp::Tag const tag("PatientID");
+    odil::Tag const tag("PatientID");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
 
     BOOST_CHECK(dataset.is_string(tag));
@@ -123,26 +123,167 @@ BOOST_AUTO_TEST_CASE(AddString)
     BOOST_CHECK(value.empty());
 }
 
-BOOST_AUTO_TEST_CASE(AddDataSet)
+BOOST_AUTO_TEST_CASE(AddDataSetEmpty)
 {
-    dcmtkpp::Tag const tag("ReferencedStudySequence");
+    odil::Tag const tag("ReferencedStudySequence");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
 
     BOOST_CHECK(dataset.is_data_set(tag));
     BOOST_CHECK(dataset.empty(tag));
     BOOST_CHECK_EQUAL(dataset.size(tag), 0);
 
-    dcmtkpp::Value::DataSets const & value = dataset.as_data_set(tag);
+    odil::Value::DataSets const & value = dataset.as_data_set(tag);
     BOOST_CHECK(value.empty());
 }
 
+BOOST_AUTO_TEST_CASE(AddBinaryEmpty)
+{
+    odil::Tag const tag("BadPixelImage");
+
+    odil::DataSet dataset;
+    dataset.add(tag);
+
+    BOOST_CHECK(dataset.is_binary(tag));
+    BOOST_CHECK(dataset.empty(tag));
+    BOOST_CHECK_EQUAL(dataset.size(tag), 0);
+
+    auto const & value = dataset.as_binary(tag);
+    BOOST_CHECK(value.empty());
+}
+
+BOOST_AUTO_TEST_CASE(AddInt)
+{
+    odil::Tag const tag("Rows");
+
+    odil::DataSet dataset;
+    dataset.add(tag, odil::Value::Integers({123}));
+
+    BOOST_CHECK(dataset.is_int(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_int(tag) == odil::Value::Integers({123}));
+}
+
+BOOST_AUTO_TEST_CASE(AddDouble)
+{
+    odil::Tag const tag("SpacingBetweenSlices");
+
+    odil::DataSet dataset;
+    dataset.add(tag, odil::Value::Reals({123.456}));
+
+    BOOST_CHECK(dataset.is_real(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_real(tag) == odil::Value::Reals({123.456}));
+}
+
+BOOST_AUTO_TEST_CASE(AddString)
+{
+    odil::Tag const tag("PatientID");
+
+    odil::DataSet dataset;
+    dataset.add(tag, odil::Value::Strings({"DJ123"}));
+
+    BOOST_CHECK(dataset.is_string(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_string(tag) == odil::Value::Strings({"DJ123"}));
+}
+
+BOOST_AUTO_TEST_CASE(AddDataSet)
+{
+    odil::Tag const tag("ReferencedStudySequence");
+    odil::DataSet item;
+    item.add(odil::registry::StudyInstanceUID, {"1.2.3"});
+
+    odil::DataSet dataset;
+    dataset.add(tag, odil::Value::DataSets({item}));
+
+    BOOST_CHECK(dataset.is_data_set(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_data_set(tag, 0) == item);
+}
+
+BOOST_AUTO_TEST_CASE(AddIntInitializer1)
+{
+    odil::Tag const tag("Rows");
+
+    odil::DataSet dataset;
+    dataset.add(tag, {123});
+
+    BOOST_CHECK(dataset.is_int(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_int(tag) == odil::Value::Integers({123}));
+}
+
+BOOST_AUTO_TEST_CASE(AddIntInitializer2)
+{
+    odil::Tag const tag("Rows");
+
+    odil::DataSet dataset;
+    dataset.add(tag, {odil::Value::Integer(123)});
+
+    BOOST_CHECK(dataset.is_int(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_int(tag) == odil::Value::Integers({123}));
+}
+
+
+BOOST_AUTO_TEST_CASE(AddDoubleInitializer)
+{
+    odil::Tag const tag("SpacingBetweenSlices");
+
+    odil::DataSet dataset;
+    dataset.add(tag, {123.456});
+
+    BOOST_CHECK(dataset.is_real(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_real(tag) == odil::Value::Reals({123.456}));
+}
+
+BOOST_AUTO_TEST_CASE(AddStringInitializer)
+{
+    odil::Tag const tag("PatientID");
+
+    odil::DataSet dataset;
+    dataset.add(tag, {"DJ123"});
+
+    BOOST_CHECK(dataset.is_string(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_string(tag) == odil::Value::Strings({"DJ123"}));
+}
+
+BOOST_AUTO_TEST_CASE(AddDataSetInitializer)
+{
+    odil::Tag const tag("ReferencedStudySequence");
+    odil::DataSet item;
+    item.add(odil::registry::StudyInstanceUID, {"1.2.3"});
+
+    odil::DataSet dataset;
+    dataset.add(tag, {item});
+
+    BOOST_CHECK(dataset.is_data_set(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 1);
+    BOOST_REQUIRE(dataset.as_data_set(tag, 0) == item);
+}
+
+BOOST_AUTO_TEST_CASE(AddBinary)
+{
+    odil::Tag const tag("BadPixelImage");
+
+    odil::DataSet dataset;
+    dataset.add(tag, odil::Value::Binary({0x01, 0x02}));
+
+    BOOST_CHECK(dataset.is_binary(tag));
+    BOOST_REQUIRE_EQUAL(dataset.size(tag), 2);
+    BOOST_REQUIRE(
+        dataset.as_binary(tag) == odil::Value::Binary({ 0x01, 0x02 }));
+}
+
 BOOST_AUTO_TEST_CASE(ModifyInt)
 {
-    dcmtkpp::Tag const tag("Rows");
+    odil::Tag const tag("Rows");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
     dataset.as_int(tag).push_back(256);
 
@@ -153,9 +294,9 @@ BOOST_AUTO_TEST_CASE(ModifyInt)
 
 BOOST_AUTO_TEST_CASE(ModifyDouble)
 {
-    dcmtkpp::Tag const tag("SpacingBetweenSlices");
+    odil::Tag const tag("SpacingBetweenSlices");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
     dataset.as_real(tag).push_back(3.14);
 
@@ -166,9 +307,9 @@ BOOST_AUTO_TEST_CASE(ModifyDouble)
 
 BOOST_AUTO_TEST_CASE(ModifyString)
 {
-    dcmtkpp::Tag const tag("PatientID");
+    odil::Tag const tag("PatientID");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
     dataset.as_string(tag).push_back("FooBar");
 
@@ -179,52 +320,64 @@ BOOST_AUTO_TEST_CASE(ModifyString)
 
 BOOST_AUTO_TEST_CASE(ModifyDataSet)
 {
-    dcmtkpp::Tag const tag("ReferencedStudySequence");
+    odil::Tag const tag("ReferencedStudySequence");
 
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add(tag);
 
-    dcmtkpp::DataSet item;
+    odil::DataSet item;
     item.add("PatientID", {"DJ1234"});
     dataset.as_data_set(tag).push_back(item);
 
     BOOST_CHECK(!dataset.empty(tag));
     BOOST_CHECK_EQUAL(dataset.size(tag), 1);
 
-    dcmtkpp::Value::DataSets const & value = dataset.as_data_set(tag);
+    odil::Value::DataSets const & value = dataset.as_data_set(tag);
     BOOST_CHECK_EQUAL(value.size(), 1);
     BOOST_CHECK_EQUAL(value.size(), 1);
     BOOST_CHECK_EQUAL(value[0].size(), 1);
     BOOST_CHECK(value[0].has("PatientID"));
     BOOST_CHECK(
-        value[0].as_string("PatientID") == dcmtkpp::Value::Strings({"DJ1234"}));
+        value[0].as_string("PatientID") == odil::Value::Strings({"DJ1234"}));
+}
+
+BOOST_AUTO_TEST_CASE(ElementAccessor)
+{
+    odil::Tag const tag("PatientID");
+
+    odil::DataSet dataset;
+    dataset.add(tag, {"Foo^Bar"});
+    BOOST_REQUIRE(
+        dataset[tag].as_string() == odil::Value::Strings({"Foo^Bar"}));
+    BOOST_REQUIRE_THROW(
+        dataset[odil::registry::PatientName], odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(GetVRMissing)
 {
-    dcmtkpp::Tag const tag("PatientID");
-    dcmtkpp::DataSet dataset;
-    BOOST_CHECK_THROW(dataset.get_vr(tag), dcmtkpp::Exception);
+    odil::Tag const tag("PatientID");
+    odil::DataSet dataset;
+    BOOST_CHECK_THROW(dataset.get_vr(tag), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(TestEmptyMissing)
 {
-    dcmtkpp::Tag const tag("PatientID");
-    dcmtkpp::DataSet dataset;
-    BOOST_CHECK_THROW(dataset.empty(tag), dcmtkpp::Exception);
+    odil::Tag const tag("PatientID");
+    odil::DataSet dataset;
+    BOOST_CHECK_THROW(dataset.empty(tag), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(SizeMissing)
 {
-    dcmtkpp::Tag const tag("PatientID");
-    dcmtkpp::DataSet dataset;
-    BOOST_CHECK_THROW(dataset.size(tag), dcmtkpp::Exception);
+    odil::Tag const tag("PatientID");
+    odil::DataSet dataset;
+    BOOST_CHECK_THROW(dataset.size(tag), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(Remove)
 {
-    dcmtkpp::Tag const tag("PatientID");
-    dcmtkpp::DataSet dataset;
+    odil::Tag const tag("PatientID");
+    odil::DataSet dataset;
     dataset.add(tag);
 
     dataset.remove(tag);
@@ -233,26 +386,26 @@ BOOST_AUTO_TEST_CASE(Remove)
 
 BOOST_AUTO_TEST_CASE(RemoveMissing)
 {
-    dcmtkpp::Tag const tag("PatientID");
-    dcmtkpp::DataSet dataset;
+    odil::Tag const tag("PatientID");
+    odil::DataSet dataset;
     dataset.add(tag);
 
-    dcmtkpp::Tag const other("PatientName");
+    odil::Tag const other("PatientName");
 
     BOOST_CHECK_THROW(
         dataset.remove(other),
-        dcmtkpp::Exception);
+        odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(Equality)
 {
-    dcmtkpp::DataSet dataset1;
+    odil::DataSet dataset1;
     dataset1.add("PatientID", {"DJ1234"});
 
-    dcmtkpp::DataSet dataset2;
+    odil::DataSet dataset2;
     dataset2.add("PatientID", {"DJ1234"});
 
-    dcmtkpp::DataSet dataset3;
+    odil::DataSet dataset3;
     dataset3.add("PatientAge", {"042Y"});
 
     BOOST_CHECK(dataset1 == dataset2);
@@ -261,13 +414,13 @@ BOOST_AUTO_TEST_CASE(Equality)
 
 BOOST_AUTO_TEST_CASE(Difference)
 {
-    dcmtkpp::DataSet dataset1;
+    odil::DataSet dataset1;
     dataset1.add("PatientID", {"DJ1234"});
 
-    dcmtkpp::DataSet dataset2;
+    odil::DataSet dataset2;
     dataset2.add("PatientID", {"DJ1234"});
 
-    dcmtkpp::DataSet dataset3;
+    odil::DataSet dataset3;
     dataset3.add("PatientAge", {"042Y"});
 
     BOOST_CHECK(! (dataset1 != dataset2));
diff --git a/tests/code/DcmtkException.cpp b/tests/code/DcmtkException.cpp
new file mode 100644
index 0000000..61aab54
--- /dev/null
+++ b/tests/code/DcmtkException.cpp
@@ -0,0 +1,26 @@
+#define BOOST_TEST_MODULE DcmtkException
+#include <boost/test/unit_test.hpp>
+
+#include <dcmtk/config/osconfig.h>
+#include <dcmtk/ofstd/ofcond.h>
+
+#include "odil/dcmtk/Exception.h"
+
+BOOST_AUTO_TEST_CASE(StringConstructor)
+{
+    odil::dcmtk::Exception const exception("foo");
+    BOOST_REQUIRE(
+        exception.get_source() == odil::dcmtk::Exception::Source::Message);
+    BOOST_REQUIRE_EQUAL(exception.what(), "foo");
+    BOOST_REQUIRE_THROW(exception.get_condition(), odil::dcmtk::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(ConditionConstructor)
+{
+    odil::dcmtk::Exception const exception(EC_IllegalParameter);
+    BOOST_REQUIRE(
+        exception.get_source() == odil::dcmtk::Exception::Source::Condition);
+    BOOST_REQUIRE_EQUAL(
+        exception.what(), OFCondition(EC_IllegalParameter).text());
+    BOOST_REQUIRE(exception.get_condition() == EC_IllegalParameter);
+}
diff --git a/tests/code/EchoSCP.cpp b/tests/code/EchoSCP.cpp
new file mode 100644
index 0000000..8b29f65
--- /dev/null
+++ b/tests/code/EchoSCP.cpp
@@ -0,0 +1,122 @@
+#define BOOST_TEST_MODULE EchoSCP
+#include <boost/test/unit_test.hpp>
+
+#include <chrono>
+#include <cstdlib>
+#include <thread>
+
+#include <boost/asio.hpp>
+
+#include "odil/Association.h"
+#include "odil/EchoSCP.h"
+#include "odil/Exception.h"
+#include "odil/message/CEchoRequest.h"
+#include "odil/message/Response.h"
+
+struct Status
+{
+    int client;
+    std::string server;
+    bool called;
+};
+
+void run_server(Status * status)
+{
+    odil::Association association;
+    association.set_tcp_timeout(boost::posix_time::seconds(1));
+
+    try
+    {
+        association.receive_association(boost::asio::ip::tcp::v4(), 11113);
+
+        odil::EchoSCP echo_scp(association,
+            [status](odil::message::CEchoRequest const &)
+            {
+                status->called = true;
+                return odil::message::Response::Success;
+            });
+
+        // Get echo message
+        auto const message = association.receive_message();
+        echo_scp(message);
+        // Should throw with peer closing connection
+        association.receive_message();
+    }
+    catch(odil::AssociationAborted const &)
+    {
+        status->server = "abort";
+    }
+    catch(odil::AssociationReleased const &)
+    {
+        status->server = "release";
+    }
+    catch(odil::Exception const &)
+    {
+        status->server = "Other Odil exception";
+    }
+    catch(...)
+    {
+        status->server = "Other exception";
+    }
+}
+
+void run_client(Status * status, bool use_abort)
+{
+    std::string command = "echoscu";
+    if(use_abort)
+    {
+        command += " --abort";
+    }
+    command += " 127.0.0.1 11113";
+    status->client = system(command.c_str());
+}
+
+BOOST_AUTO_TEST_CASE(Callback)
+{
+    odil::Association association;
+    odil::EchoSCP scp(association);
+
+    bool called = false;
+    auto const callback =
+        [&called](odil::message::CEchoRequest const &)
+        {
+            called = true;
+            return odil::message::Response::Success;
+        };
+
+    scp.set_callback(callback);
+    scp.get_callback()(odil::message::CEchoRequest(1, ""));
+    BOOST_REQUIRE_EQUAL(called, true);
+}
+
+BOOST_AUTO_TEST_CASE(Release)
+{
+    Status status = { -1, "", false };
+
+    std::thread server(run_server, &status);
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    std::thread client(run_client, &status, false);
+
+    server.join();
+    client.join();
+
+    BOOST_REQUIRE_EQUAL(status.client, 0);
+    BOOST_REQUIRE_EQUAL(status.server, "release");
+    BOOST_REQUIRE_EQUAL(status.called, true);
+}
+
+BOOST_AUTO_TEST_CASE(Abort)
+{
+    Status status = { -1, "", false };
+
+    std::thread server(run_server, &status);
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    std::thread client(run_client, &status, true);
+
+    server.join();
+    client.join();
+
+    BOOST_REQUIRE_EQUAL(status.client, 0);
+    BOOST_REQUIRE_EQUAL(status.server, "abort");
+    BOOST_REQUIRE_EQUAL(status.called, true);
+}
diff --git a/tests/code/Element.cpp b/tests/code/Element.cpp
index 23c82e7..be392f7 100644
--- a/tests/code/Element.cpp
+++ b/tests/code/Element.cpp
@@ -1,20 +1,20 @@
 #define BOOST_TEST_MODULE Element
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Exception.h"
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/Exception.h"
 
 BOOST_AUTO_TEST_CASE(Empty)
 {
-    dcmtkpp::Element element;
+    odil::Element element;
     BOOST_CHECK(element.empty());
     BOOST_CHECK_EQUAL(element.size(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(Int)
 {
-    dcmtkpp::Element const element((dcmtkpp::Value::Integers()));
+    odil::Element const element((odil::Value::Integers()));
     BOOST_CHECK(element.is_int());
 
     std::vector<int64_t> const & value = element.as_int();
@@ -23,26 +23,38 @@ BOOST_AUTO_TEST_CASE(Int)
 
 BOOST_AUTO_TEST_CASE(ModifyInt)
 {
-    dcmtkpp::Element element({0});
+    odil::Element element({0});
     element.as_int().push_back(1);
 
     BOOST_CHECK(!element.empty());
     BOOST_CHECK_EQUAL(element.size(), 2);
 
     std::vector<int64_t> const & value = element.as_int();
-    BOOST_CHECK(value == dcmtkpp::Value::Integers({0, 1}));
+    BOOST_CHECK(value == odil::Value::Integers({0, 1}));
+}
+
+BOOST_AUTO_TEST_CASE(ModifyInt2)
+{
+    odil::Element element({odil::Value::Integer(0)});
+    element.as_int().push_back(1);
+
+    BOOST_CHECK(!element.empty());
+    BOOST_CHECK_EQUAL(element.size(), 2);
+
+    std::vector<int64_t> const & value = element.as_int();
+    BOOST_CHECK(value == odil::Value::Integers({0, 1}));
 }
 
 BOOST_AUTO_TEST_CASE(IntWrong)
 {
-    dcmtkpp::Element element((dcmtkpp::Value::Integers()));
-    BOOST_CHECK_THROW(element.as_real(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(element.as_string(), dcmtkpp::Exception);
+    odil::Element element((odil::Value::Integers()));
+    BOOST_CHECK_THROW(element.as_real(), odil::Exception);
+    BOOST_CHECK_THROW(element.as_string(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(Double)
 {
-    dcmtkpp::Element const element((dcmtkpp::Value::Reals()));
+    odil::Element const element((odil::Value::Reals()));
     BOOST_CHECK(element.is_real());
 
     std::vector<double> const & value = element.as_real();
@@ -51,27 +63,27 @@ BOOST_AUTO_TEST_CASE(Double)
 
 BOOST_AUTO_TEST_CASE(ModifyDouble)
 {
-    dcmtkpp::Element element({0.});
+    odil::Element element({0.});
     element.as_real().push_back(1.5);
 
     BOOST_CHECK(!element.empty());
     BOOST_CHECK_EQUAL(element.size(), 2);
 
     std::vector<double> const & value = element.as_real();
-    BOOST_CHECK(value == dcmtkpp::Value::Reals({0., 1.5}));
+    BOOST_CHECK(value == odil::Value::Reals({0., 1.5}));
 }
 
 BOOST_AUTO_TEST_CASE(DoubleWrong)
 {
-    dcmtkpp::Element element((dcmtkpp::Value::Reals()));
-    BOOST_CHECK_THROW(element.as_int(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(element.as_string(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(element.as_data_set(), dcmtkpp::Exception);
+    odil::Element element((odil::Value::Reals()));
+    BOOST_CHECK_THROW(element.as_int(), odil::Exception);
+    BOOST_CHECK_THROW(element.as_string(), odil::Exception);
+    BOOST_CHECK_THROW(element.as_data_set(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(String)
 {
-    dcmtkpp::Element const element((dcmtkpp::Value::Strings()));
+    odil::Element const element((odil::Value::Strings()));
     BOOST_CHECK(element.is_string());
 
     std::vector<std::string> const & value = element.as_string();
@@ -80,38 +92,38 @@ BOOST_AUTO_TEST_CASE(String)
 
 BOOST_AUTO_TEST_CASE(ModifyString)
 {
-    dcmtkpp::Element element({""});
+    odil::Element element({""});
     element.as_string().push_back("foo");
 
     BOOST_CHECK(!element.empty());
     BOOST_CHECK_EQUAL(element.size(), 2);
 
     std::vector<std::string> const & value = element.as_string();
-    BOOST_CHECK(value == dcmtkpp::Value::Strings({"", "foo"}));
+    BOOST_CHECK(value == odil::Value::Strings({"", "foo"}));
 }
 
 BOOST_AUTO_TEST_CASE(StringWrong)
 {
-    dcmtkpp::Element element((dcmtkpp::Value::Strings()));
-    BOOST_CHECK_THROW(element.as_int(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(element.as_real(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(element.as_data_set(), dcmtkpp::Exception);
+    odil::Element element((odil::Value::Strings()));
+    BOOST_CHECK_THROW(element.as_int(), odil::Exception);
+    BOOST_CHECK_THROW(element.as_real(), odil::Exception);
+    BOOST_CHECK_THROW(element.as_data_set(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(DataSet)
 {
-    dcmtkpp::Element const element((dcmtkpp::Value::DataSets()));
+    odil::Element const element((odil::Value::DataSets()));
     BOOST_CHECK(element.is_data_set());
 
-    dcmtkpp::Value::DataSets const & value = element.as_data_set();
+    odil::Value::DataSets const & value = element.as_data_set();
     BOOST_CHECK(value.empty());
 }
 
 BOOST_AUTO_TEST_CASE(ModifyDataSet)
 {
-    dcmtkpp::Element element((dcmtkpp::Value::DataSets()));
+    odil::Element element((odil::Value::DataSets()));
 
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add("PatientID");
     data_set.as_string("PatientID").push_back("DJ1234");
     element.as_data_set().push_back(data_set);
@@ -119,28 +131,28 @@ BOOST_AUTO_TEST_CASE(ModifyDataSet)
     BOOST_CHECK(!element.empty());
     BOOST_CHECK_EQUAL(element.size(), 1);
 
-    dcmtkpp::Value::DataSets const & value = element.as_data_set();
+    odil::Value::DataSets const & value = element.as_data_set();
     BOOST_CHECK_EQUAL(value.size(), 1);
     BOOST_CHECK_EQUAL(value[0].size(), 1);
     BOOST_CHECK(value[0].has("PatientID"));
     BOOST_CHECK(
-        value[0].as_string("PatientID") == dcmtkpp::Value::Strings({"DJ1234"}));
+        value[0].as_string("PatientID") == odil::Value::Strings({"DJ1234"}));
 }
 
 BOOST_AUTO_TEST_CASE(DataSetWrong)
 {
-    dcmtkpp::Element element((dcmtkpp::Value::DataSets()));
-    BOOST_CHECK_THROW(element.as_int(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(element.as_real(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(element.as_string(), dcmtkpp::Exception);
+    odil::Element element((odil::Value::DataSets()));
+    BOOST_CHECK_THROW(element.as_int(), odil::Exception);
+    BOOST_CHECK_THROW(element.as_real(), odil::Exception);
+    BOOST_CHECK_THROW(element.as_string(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(Equality)
 {
-    dcmtkpp::Element const element1({12,34}, dcmtkpp::VR::US);
-    dcmtkpp::Element const element2({12,34}, dcmtkpp::VR::US);
-    dcmtkpp::Element const element3({12.,34.}, dcmtkpp::VR::FL);
-    dcmtkpp::Element const element4({12,34}, dcmtkpp::VR::UL);
+    odil::Element const element1({12,34}, odil::VR::US);
+    odil::Element const element2({12,34}, odil::VR::US);
+    odil::Element const element3({12.,34.}, odil::VR::FL);
+    odil::Element const element4({12,34}, odil::VR::UL);
 
     BOOST_CHECK(element1 == element2);
     BOOST_CHECK(! (element1 == element3));
@@ -149,10 +161,10 @@ BOOST_AUTO_TEST_CASE(Equality)
 
 BOOST_AUTO_TEST_CASE(Difference)
 {
-    dcmtkpp::Element const element1({12,34}, dcmtkpp::VR::US);
-    dcmtkpp::Element const element2({12,34}, dcmtkpp::VR::US);
-    dcmtkpp::Element const element3({12.,34.}, dcmtkpp::VR::FL);
-    dcmtkpp::Element const element4({12,34}, dcmtkpp::VR::UL);
+    odil::Element const element1({12,34}, odil::VR::US);
+    odil::Element const element2({12,34}, odil::VR::US);
+    odil::Element const element3({12.,34.}, odil::VR::FL);
+    odil::Element const element4({12,34}, odil::VR::UL);
 
     BOOST_CHECK(! (element1 != element2));
     BOOST_CHECK(element1 != element3);
diff --git a/tests/code/ElementAccessor.cpp b/tests/code/ElementAccessor.cpp
index 4e8ac3e..04f9ded 100644
--- a/tests/code/ElementAccessor.cpp
+++ b/tests/code/ElementAccessor.cpp
@@ -6,8 +6,8 @@
 #include <dcmtk/dcmdata/dcdeftag.h>
 #include <dcmtk/dcmnet/dimse.h>
 
-#include "dcmtkpp/ElementAccessor.h"
-#include "dcmtkpp/VRTraits.h"
+#include "odil/dcmtk/ElementAccessor.h"
+#include "odil/dcmtk/VRTraits.h"
 
 struct Fixture
 {
@@ -19,13 +19,13 @@ struct Fixture
         condition = dataset.putAndInsertOFStringArray(DCM_PatientID, "DJ123");
         if(condition.bad())
         {
-            throw dcmtkpp::Exception(condition);
+            throw odil::dcmtk::Exception(condition);
         }
 
         condition = dataset.putAndInsertUint16(DCM_MessageID, 1234);
         if(condition.bad())
         {
-            throw dcmtkpp::Exception(condition);
+            throw odil::dcmtk::Exception(condition);
         }
     }
 };
@@ -33,15 +33,15 @@ struct Fixture
 BOOST_FIXTURE_TEST_CASE(GetCorrectTypeString, Fixture)
 {
     BOOST_CHECK_EQUAL(
-        dcmtkpp::ElementAccessor<std::string>::get(this->dataset, DCM_PatientID),
+        odil::dcmtk::ElementAccessor<std::string>::get(this->dataset, DCM_PatientID),
         "DJ123");
 }
 
 BOOST_FIXTURE_TEST_CASE(GetCorrectVRString, Fixture)
 {
     BOOST_CHECK_EQUAL(
-        dcmtkpp::ElementAccessor<
-            typename dcmtkpp::VRTraits<EVR_LO>::ValueType
+        odil::dcmtk::ElementAccessor<
+            typename odil::dcmtk::VRTraits<EVR_LO>::ValueType
         >::get(this->dataset, DCM_PatientID),
         "DJ123");
 }
@@ -49,15 +49,15 @@ BOOST_FIXTURE_TEST_CASE(GetCorrectVRString, Fixture)
 BOOST_FIXTURE_TEST_CASE(GetCorrectTypeNonString, Fixture)
 {
     BOOST_CHECK_EQUAL(
-        dcmtkpp::ElementAccessor<Uint16>::get(this->dataset, DCM_MessageID),
+        odil::dcmtk::ElementAccessor<Uint16>::get(this->dataset, DCM_MessageID),
         1234);
 }
 
 BOOST_FIXTURE_TEST_CASE(GetCorrectVRNonString, Fixture)
 {
     BOOST_CHECK_EQUAL(
-        dcmtkpp::ElementAccessor<
-            typename dcmtkpp::VRTraits<EVR_US>::ValueType
+        odil::dcmtk::ElementAccessor<
+            typename odil::dcmtk::VRTraits<EVR_US>::ValueType
         >::get(this->dataset, DCM_MessageID),
         1234);
 }
@@ -65,20 +65,20 @@ BOOST_FIXTURE_TEST_CASE(GetCorrectVRNonString, Fixture)
 BOOST_FIXTURE_TEST_CASE(GetWrongTypeString, Fixture)
 {
     BOOST_CHECK_THROW(
-        dcmtkpp::ElementAccessor<Float32>::get(this->dataset, DCM_PatientID),
-        dcmtkpp::Exception);
+        odil::dcmtk::ElementAccessor<Float32>::get(this->dataset, DCM_PatientID),
+        odil::dcmtk::Exception);
 }
 
 BOOST_FIXTURE_TEST_CASE(GetWrongTypeNonString, Fixture)
 {
     BOOST_CHECK_THROW(
-        dcmtkpp::ElementAccessor<Float32>::get(this->dataset, DCM_MessageID),
-        dcmtkpp::Exception);
+        odil::dcmtk::ElementAccessor<Float32>::get(this->dataset, DCM_MessageID),
+        odil::dcmtk::Exception);
 }
 
 BOOST_FIXTURE_TEST_CASE(SetEmpty, Fixture)
 {
-    dcmtkpp::ElementAccessor<std::string>::set(this->dataset, DCM_PatientName, "FOO");
+    odil::dcmtk::ElementAccessor<std::string>::set(this->dataset, DCM_PatientName, "FOO");
     OFString value;
     OFCondition const condition =
         this->dataset.findAndGetOFString(DCM_PatientName, value);
@@ -89,22 +89,22 @@ BOOST_FIXTURE_TEST_CASE(SetEmpty, Fixture)
 BOOST_FIXTURE_TEST_CASE(SetWrongTypeString, Fixture)
 {
     BOOST_CHECK_THROW(
-        dcmtkpp::ElementAccessor<Float32>::set(
+        odil::dcmtk::ElementAccessor<Float32>::set(
             this->dataset, DCM_PatientName, 1.234),
-        dcmtkpp::Exception);
+        odil::dcmtk::Exception);
 }
 
 BOOST_FIXTURE_TEST_CASE(SetWrongTypeNonString, Fixture)
 {
     BOOST_CHECK_THROW(
-        dcmtkpp::ElementAccessor<Float64>::set(
+        odil::dcmtk::ElementAccessor<Float64>::set(
             this->dataset, DCM_Status, STATUS_Success),
-        dcmtkpp::Exception);
+        odil::dcmtk::Exception);
 }
 
 BOOST_FIXTURE_TEST_CASE(SetExisting, Fixture)
 {
-    dcmtkpp::ElementAccessor<std::string>::set(this->dataset, DCM_PatientID, "FOO");
+    odil::dcmtk::ElementAccessor<std::string>::set(this->dataset, DCM_PatientID, "FOO");
     OFString value;
     OFCondition const condition =
         this->dataset.findAndGetOFString(DCM_PatientID, value);
@@ -115,11 +115,11 @@ BOOST_FIXTURE_TEST_CASE(SetExisting, Fixture)
 BOOST_FIXTURE_TEST_CASE(Has, Fixture)
 {
     BOOST_CHECK(
-        dcmtkpp::ElementAccessor<std::string>::has(this->dataset, DCM_PatientID));
+        odil::dcmtk::ElementAccessor<std::string>::has(this->dataset, DCM_PatientID));
 }
 
 BOOST_FIXTURE_TEST_CASE(HasNot, Fixture)
 {
     BOOST_CHECK(
-        !dcmtkpp::ElementAccessor<std::string>::has(this->dataset, DCM_PatientName));
+        !odil::dcmtk::ElementAccessor<std::string>::has(this->dataset, DCM_PatientName));
 }
diff --git a/tests/code/Exception.cpp b/tests/code/Exception.cpp
index c0939d5..1b46e41 100644
--- a/tests/code/Exception.cpp
+++ b/tests/code/Exception.cpp
@@ -1,36 +1,10 @@
-#define BOOST_TEST_MODULE Message
+#define BOOST_TEST_MODULE Exception
 #include <boost/test/unit_test.hpp>
 
-#include <ostream>
+#include "odil/Exception.h"
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/ofstd/ofcond.h>
-
-#include "dcmtkpp/Exception.h"
-
-namespace dcmtkpp
-{
-
-std::ostream & operator<<(std::ostream & stream, Exception::Source const & type)
-{
-    stream << int(type);
-    return stream;
-}
-
-}
-
-BOOST_AUTO_TEST_CASE(StringConstructor)
-{
-    dcmtkpp::Exception const exception("foo");
-    BOOST_CHECK_EQUAL(exception.get_source(), dcmtkpp::Exception::Source::Message);
-    BOOST_CHECK_EQUAL(exception.what(), "foo");
-    BOOST_CHECK_THROW(exception.get_condition(), dcmtkpp::Exception);
-}
-
-BOOST_AUTO_TEST_CASE(ConditionConstructor)
+BOOST_AUTO_TEST_CASE(Exception)
 {
-    dcmtkpp::Exception const exception(EC_IllegalParameter);
-    BOOST_CHECK_EQUAL(exception.get_source(), dcmtkpp::Exception::Source::Condition);
-    BOOST_CHECK_EQUAL(exception.what(), EC_IllegalParameter.text());
-    BOOST_CHECK(exception.get_condition() == EC_IllegalParameter);
+    odil::Exception const exception("foo");
+    BOOST_REQUIRE_EQUAL(exception.what(), "foo");
 }
diff --git a/tests/code/FindSCP.cpp b/tests/code/FindSCP.cpp
new file mode 100644
index 0000000..fdc4f6a
--- /dev/null
+++ b/tests/code/FindSCP.cpp
@@ -0,0 +1,166 @@
+#define BOOST_TEST_MODULE FindSCP
+#include <boost/test/unit_test.hpp>
+
+#include <chrono>
+#include <cstdlib>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include <boost/asio.hpp>
+#include <boost/filesystem.hpp>
+
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/FindSCP.h"
+#include "odil/Exception.h"
+#include "odil/SCP.h"
+#include "odil/Reader.h"
+#include "odil/message/Response.h"
+
+struct Status
+{
+    int client;
+    std::string server;
+    std::vector<odil::DataSet> responses;
+};
+
+class Generator: public odil::SCP::DataSetGenerator
+{
+public:
+    Generator()
+    {
+        // Nothing else.
+    }
+
+    virtual ~Generator()
+    {
+        // Nothing to do.
+    }
+
+    virtual void initialize(odil::message::Request const & )
+    {
+        odil::DataSet data_set_1;
+        data_set_1.add(odil::registry::PatientName, {"Hello^World"});
+        data_set_1.add(odil::registry::PatientID, {"1234"});
+        this->_responses.push_back(data_set_1);
+
+        odil::DataSet data_set_2;
+        data_set_2.add(odil::registry::PatientName, {"Doe^John"});
+        data_set_2.add(odil::registry::PatientID, {"5678"});
+        this->_responses.push_back(data_set_2);
+
+        this->_response_iterator = this->_responses.begin();
+    }
+
+    virtual bool done() const
+    {
+        return (this->_response_iterator == this->_responses.end());
+    }
+
+    virtual odil::DataSet get() const
+    {
+        return *this->_response_iterator;
+    }
+
+    virtual void next()
+    {
+        ++this->_response_iterator;
+    }
+
+
+private:
+    std::vector<odil::DataSet> _responses;
+    std::vector<odil::DataSet>::const_iterator _response_iterator;
+};
+
+void run_server(Status * status)
+{
+    odil::Association association;
+    association.set_tcp_timeout(boost::posix_time::seconds(1));
+
+    try
+    {
+        association.receive_association(boost::asio::ip::tcp::v4(), 11113);
+
+        odil::FindSCP find_scp(association);
+        auto const generator = std::make_shared<Generator>();
+        find_scp.set_generator(generator);
+
+        // Get echo message
+        auto const message = association.receive_message();
+        find_scp(message);
+        // Should throw with peer closing connection
+        association.receive_message();
+    }
+    catch(odil::AssociationAborted const &)
+    {
+        status->server = "abort";
+    }
+    catch(odil::AssociationReleased const &)
+    {
+        status->server = "release";
+    }
+    catch(odil::Exception const &)
+    {
+        status->server = "Other Odil exception";
+    }
+    catch(...)
+    {
+        status->server = "Other exception";
+    }
+}
+
+void run_client(Status * status)
+{
+    std::string command = "findscu "
+        "-P -k QueryRetrieveLevel=PATIENT "
+        "-k PatientID=* -k PatientName "
+        "-q -X "
+        "127.0.0.1 11113";
+    status->client = system(command.c_str());
+
+    boost::filesystem::directory_iterator end;
+    for(boost::filesystem::directory_iterator it("."); it != end; ++it )
+    {
+        if(!boost::filesystem::is_regular_file(it->status()))
+        {
+            continue;
+        }
+        auto const filename = it->path().stem().string();
+        if(filename.substr(0, 3) != "rsp")
+        {
+            continue;
+        }
+
+        std::ifstream stream(it->path().string());
+        auto const data_set = odil::Reader::read_file(stream).second;
+        status->responses.push_back(data_set);
+
+        boost::filesystem::remove(it->path());
+    }
+}
+
+BOOST_AUTO_TEST_CASE(Release)
+{
+    Status status = { -1, "", {} };
+
+    std::thread server(run_server, &status);
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    std::thread client(run_client, &status);
+
+    server.join();
+    client.join();
+
+    BOOST_REQUIRE_EQUAL(status.client, 0);
+    BOOST_REQUIRE_EQUAL(status.server, "release");
+    BOOST_REQUIRE_EQUAL(status.responses.size(), 2);
+
+    BOOST_REQUIRE_EQUAL(status.responses[0].size(), 2);
+    BOOST_REQUIRE_EQUAL(status.responses[0].as_string("PatientName", 0), "Hello^World");
+    BOOST_REQUIRE_EQUAL(status.responses[0].as_string("PatientID", 0), "1234");
+
+    BOOST_REQUIRE_EQUAL(status.responses[1].size(), 2);
+    BOOST_REQUIRE_EQUAL(status.responses[1].as_string("PatientName", 0), "Doe^John");
+    BOOST_REQUIRE_EQUAL(status.responses[1].as_string("PatientID", 0), "5678");
+}
diff --git a/tests/code/FindSCU.cpp b/tests/code/FindSCU.cpp
index 2328c8b..33d4fa7 100644
--- a/tests/code/FindSCU.cpp
+++ b/tests/code/FindSCU.cpp
@@ -1,9 +1,9 @@
 #define BOOST_TEST_MODULE FindSCU
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/FindSCU.h"
-#include "dcmtkpp/registry.h"
+#include "odil/DataSet.h"
+#include "odil/FindSCU.h"
+#include "odil/registry.h"
 
 #include "../PeerFixtureBase.h"
 
@@ -11,14 +11,13 @@ struct Fixture: public PeerFixtureBase
 {
     static bool called;
 
-    dcmtkpp::DataSet query;
+    odil::DataSet query;
 
     Fixture()
-    : PeerFixtureBase(NET_REQUESTOR, 104, 10,
-        {
+    : PeerFixtureBase({
             {
-                dcmtkpp::registry::PatientRootQueryRetrieveInformationModelFIND,
-                { dcmtkpp::registry::ImplicitVRLittleEndian }
+                1, odil::registry::PatientRootQueryRetrieveInformationModelFIND,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
             }
         })
     {
@@ -30,7 +29,7 @@ struct Fixture: public PeerFixtureBase
     }
 
 
-    static void callback(dcmtkpp::DataSet const &)
+    static void callback(odil::DataSet const &)
     {
         Fixture::called = true;
     }
@@ -40,26 +39,22 @@ bool Fixture::called = false;
 
 BOOST_FIXTURE_TEST_CASE(Find, Fixture)
 {
-    dcmtkpp::FindSCU scu;
-    scu.set_network(&this->network);
-    scu.set_association(&this->association);
+    odil::FindSCU scu(this->association);
 
-    scu.set_affected_sop_class(dcmtkpp::registry::PatientRootQueryRetrieveInformationModelFIND);
+    scu.set_affected_sop_class(odil::registry::PatientRootQueryRetrieveInformationModelFIND);
     auto const results = scu.find(this->query);
 
     BOOST_REQUIRE_EQUAL(results.size(), 1);
     BOOST_CHECK(
         results[0].as_string("PatientID") ==
-            dcmtkpp::Value::Strings({"DJ001"}));
+            odil::Value::Strings({"DJ001"}));
 }
 
 BOOST_FIXTURE_TEST_CASE(FindCallback, Fixture)
 {
-    dcmtkpp::FindSCU scu;
-    scu.set_network(&this->network);
-    scu.set_association(&this->association);
+    odil::FindSCU scu(this->association);
 
-    scu.set_affected_sop_class(dcmtkpp::registry::PatientRootQueryRetrieveInformationModelFIND);
+    scu.set_affected_sop_class(odil::registry::PatientRootQueryRetrieveInformationModelFIND);
     scu.find(this->query, Fixture::callback);
 
     BOOST_CHECK(Fixture::called);
diff --git a/tests/code/GetSCP.cpp b/tests/code/GetSCP.cpp
new file mode 100644
index 0000000..f1588f7
--- /dev/null
+++ b/tests/code/GetSCP.cpp
@@ -0,0 +1,201 @@
+#define BOOST_TEST_MODULE GetSCP
+#include <boost/test/unit_test.hpp>
+
+#include <algorithm>
+#include <chrono>
+#include <cstdlib>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include <boost/asio.hpp>
+#include <boost/filesystem.hpp>
+
+#include "odil/Association.h"
+#include "odil/DataSet.h"
+#include "odil/GetSCP.h"
+#include "odil/Exception.h"
+#include "odil/SCP.h"
+#include "odil/uid.h"
+#include "odil/Reader.h"
+#include "odil/message/Response.h"
+
+struct Status
+{
+    int client;
+    std::string server;
+    std::vector<odil::DataSet> responses;
+};
+
+class Generator: public odil::GetSCP::DataSetGenerator
+{
+public:
+    Generator()
+    {
+        // Nothing else.
+    }
+
+    virtual ~Generator()
+    {
+        // Nothing to do.
+    }
+
+    virtual void initialize(odil::message::Request const & )
+    {
+        odil::DataSet data_set_1;
+        data_set_1.add("SOPClassUID", {odil::registry::RawDataStorage});
+        data_set_1.add(
+            "SOPInstanceUID",
+            {"1.2.826.0.1.3680043.9.5560.3127449359877365688774406533090568532"});
+        data_set_1.add("PatientName", {"Hello^World"});
+        data_set_1.add("PatientID", {"1234"});
+        this->_responses.push_back(data_set_1);
+
+        odil::DataSet data_set_2;
+        data_set_2.add("SOPClassUID", {odil::registry::RawDataStorage});
+        data_set_2.add(
+            "SOPInstanceUID",
+            {"1.2.826.0.1.3680043.9.5560.3221615743193123463515381981101110692"});
+        data_set_2.add("PatientName", {"Doe^John"});
+        data_set_2.add("PatientID", {"5678"});
+        this->_responses.push_back(data_set_2);
+
+        this->_response_iterator = this->_responses.begin();
+    }
+
+    virtual bool done() const
+    {
+        return (this->_response_iterator == this->_responses.end());
+    }
+
+    virtual odil::DataSet get() const
+    {
+        return *this->_response_iterator;
+    }
+
+    virtual void next()
+    {
+        ++this->_response_iterator;
+    }
+
+    virtual unsigned int count() const
+    {
+        return 2;
+    }
+
+private:
+    std::vector<odil::DataSet> _responses;
+    std::vector<odil::DataSet>::const_iterator _response_iterator;
+};
+
+void run_server(Status * status)
+{
+    odil::Association association;
+    association.set_tcp_timeout(boost::posix_time::seconds(1));
+
+    try
+    {
+        association.receive_association(boost::asio::ip::tcp::v4(), 11113);
+
+        odil::GetSCP get_scp(association);
+        auto const generator = std::make_shared<Generator>();
+        get_scp.set_generator(generator);
+
+        // Get echo message
+        auto const message = association.receive_message();
+        get_scp(message);
+        // Should throw with peer closing connection
+        association.receive_message();
+    }
+    catch(odil::AssociationAborted const &)
+    {
+        status->server = "abort";
+    }
+    catch(odil::AssociationReleased const &)
+    {
+        status->server = "release";
+    }
+    catch(odil::Exception const &)
+    {
+        status->server = "Other Odil exception";
+    }
+    catch(...)
+    {
+        status->server = "Other exception";
+    }
+}
+
+void run_client(Status * status)
+{
+    std::string command = "getscu "
+        "-P -k QueryRetrieveLevel=PATIENT "
+        "-k PatientID=* -k PatientName "
+        "+B "
+        "127.0.0.1 11113";
+    status->client = system(command.c_str());
+
+    boost::filesystem::directory_iterator end;
+    for(boost::filesystem::directory_iterator it("."); it != end; ++it )
+    {
+        if(!boost::filesystem::is_regular_file(it->status()))
+        {
+            continue;
+        }
+        auto const filename = it->path().stem().string();
+        if(filename.substr(0, odil::uid_prefix.size()) != odil::uid_prefix)
+        {
+            continue;
+        }
+
+        std::ifstream stream(it->path().string());
+        auto const data_set = odil::Reader::read_file(stream).second;
+        status->responses.push_back(data_set);
+
+        boost::filesystem::remove(it->path());
+    }
+
+    std::sort(
+        status->responses.begin(), status->responses.end(),
+        [](odil::DataSet const & left, odil::DataSet const & right)
+        {
+            auto const & left_uid = left.as_string("SOPInstanceUID", 0);
+            auto const & right_uid = right.as_string("SOPInstanceUID", 0);
+            return (left_uid < right_uid);
+        });
+}
+
+BOOST_AUTO_TEST_CASE(Release)
+{
+    Status status = { -1, "", {} };
+
+    std::thread server(run_server, &status);
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    std::thread client(run_client, &status);
+
+    server.join();
+    client.join();
+
+    BOOST_REQUIRE_EQUAL(status.client, 0);
+    BOOST_REQUIRE_EQUAL(status.server, "release");
+    BOOST_REQUIRE_EQUAL(status.responses.size(), 2);
+
+    BOOST_REQUIRE_EQUAL(status.responses[0].size(), 4);
+    BOOST_REQUIRE_EQUAL(
+        status.responses[0].as_string("SOPInstanceUID", 0),
+        "1.2.826.0.1.3680043.9.5560.3127449359877365688774406533090568532");
+    BOOST_REQUIRE_EQUAL(
+        status.responses[0].as_string("SOPClassUID", 0),
+        odil::registry::RawDataStorage);
+    BOOST_REQUIRE_EQUAL(status.responses[0].as_string("PatientName", 0), "Hello^World");
+    BOOST_REQUIRE_EQUAL(status.responses[0].as_string("PatientID", 0), "1234");
+
+    BOOST_REQUIRE_EQUAL(status.responses[1].size(), 4);
+    BOOST_REQUIRE_EQUAL(
+        status.responses[1].as_string("SOPInstanceUID", 0),
+        "1.2.826.0.1.3680043.9.5560.3221615743193123463515381981101110692");
+    BOOST_REQUIRE_EQUAL(
+        status.responses[1].as_string("SOPClassUID", 0),
+        odil::registry::RawDataStorage);
+    BOOST_REQUIRE_EQUAL(status.responses[1].as_string("PatientName", 0), "Doe^John");
+    BOOST_REQUIRE_EQUAL(status.responses[1].as_string("PatientID", 0), "5678");
+}
diff --git a/tests/code/GetSCU.cpp b/tests/code/GetSCU.cpp
index 7f3f979..c71014b 100644
--- a/tests/code/GetSCU.cpp
+++ b/tests/code/GetSCU.cpp
@@ -1,9 +1,9 @@
 #define BOOST_TEST_MODULE GetSCU
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/GetSCU.h"
-#include "dcmtkpp/registry.h"
+#include "odil/DataSet.h"
+#include "odil/GetSCU.h"
+#include "odil/registry.h"
 
 #include "../PeerFixtureBase.h"
 
@@ -11,18 +11,17 @@ struct Fixture: public PeerFixtureBase
 {
     static bool called;
 
-    dcmtkpp::DataSet query;
+    odil::DataSet query;
 
     Fixture()
-    : PeerFixtureBase(NET_REQUESTOR, 104, 10,
-        {
+    : PeerFixtureBase({
             {
-                dcmtkpp::registry::PatientRootQueryRetrieveInformationModelGET,
-                { dcmtkpp::registry::ImplicitVRLittleEndian }
+                1, odil::registry::PatientRootQueryRetrieveInformationModelGET,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
             },
             {
-                dcmtkpp::registry::RawDataStorage, { dcmtkpp::registry::ImplicitVRLittleEndian },
-                ASC_SC_ROLE_SCP
+                3, odil::registry::RawDataStorage,
+                { odil::registry::ImplicitVRLittleEndian }, false, true
             }
         })
     {
@@ -33,7 +32,7 @@ struct Fixture: public PeerFixtureBase
     }
 
 
-    static void callback(dcmtkpp::DataSet const &)
+    static void callback(odil::DataSet const &)
     {
         Fixture::called = true;
     }
@@ -43,26 +42,25 @@ bool Fixture::called = false;
 
 BOOST_FIXTURE_TEST_CASE(Get, Fixture)
 {
-    dcmtkpp::GetSCU scu;
-    scu.set_network(&this->network);
-    scu.set_association(&this->association);
+    odil::GetSCU scu(this->association);
 
-    scu.set_affected_sop_class(dcmtkpp::registry::PatientRootQueryRetrieveInformationModelGET);
+    scu.set_affected_sop_class(
+        odil::registry::PatientRootQueryRetrieveInformationModelGET);
     auto const results = scu.get(this->query);
 
     BOOST_REQUIRE_EQUAL(results.size(), 1);
     BOOST_CHECK(
         results[0].as_string("SOPInstanceUID") ==
-            dcmtkpp::Value::Strings({"2.25.95090344942250266709587559073467305647"}));
+            odil::Value::Strings(
+                {"2.25.95090344942250266709587559073467305647"}));
 }
 
 BOOST_FIXTURE_TEST_CASE(GetCallback, Fixture)
 {
-    dcmtkpp::GetSCU scu;
-    scu.set_network(&this->network);
-    scu.set_association(&this->association);
+    odil::GetSCU scu(this->association);
 
-    scu.set_affected_sop_class(dcmtkpp::registry::PatientRootQueryRetrieveInformationModelGET);
+    scu.set_affected_sop_class(
+        odil::registry::PatientRootQueryRetrieveInformationModelGET);
     scu.get(this->query, Fixture::callback);
 
     BOOST_CHECK(Fixture::called);
diff --git a/tests/code/Message.cpp b/tests/code/Message.cpp
deleted file mode 100644
index 0b8e836..0000000
--- a/tests/code/Message.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#define BOOST_TEST_MODULE Message
-#include <boost/test/unit_test.hpp>
-
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-
-BOOST_AUTO_TEST_CASE(DefaultConstructor)
-{
-    dcmtkpp::Message const message;
-
-    // Command Set might not be empty (Command Group Length, Data Set Type)
-    BOOST_CHECK(!message.has_data_set());
-}
-
-BOOST_AUTO_TEST_CASE(Constructor)
-{
-    dcmtkpp::DataSet command_set;
-    command_set.add("CommandField", {dcmtkpp::Message::Command::C_ECHO_RQ});
-
-    dcmtkpp::DataSet data_set;
-
-    dcmtkpp::Message const message(command_set, data_set);
-
-    BOOST_CHECK_EQUAL(
-        message.get_command_set().as_int("CommandField", 0),
-        dcmtkpp::Message::Command::C_ECHO_RQ);
-
-    BOOST_CHECK(message.has_data_set());
-    BOOST_CHECK(message.get_data_set().empty());
-
-    BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_ECHO_RQ);
-}
-
-BOOST_AUTO_TEST_CASE(CommandField)
-{
-    dcmtkpp::Message message;
-    message.set_command_field(dcmtkpp::Message::Command::C_FIND_RSP);
-
-    BOOST_CHECK(
-        message.get_command_set().as_int("CommandField") ==
-            dcmtkpp::Value::Integers({dcmtkpp::Message::Command::C_FIND_RSP}));
-    BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_FIND_RSP);
-}
-
-BOOST_AUTO_TEST_CASE(DeleteDataSet)
-{
-    dcmtkpp::DataSet command_set;
-    dcmtkpp::DataSet data_set;
-
-    dcmtkpp::Message message(command_set, data_set);
-
-    BOOST_CHECK(message.has_data_set());
-
-    message.delete_data_set();
-
-    BOOST_CHECK(!message.has_data_set());
-}
diff --git a/tests/code/MoveSCP.cpp b/tests/code/MoveSCP.cpp
new file mode 100644
index 0000000..18fb15e
--- /dev/null
+++ b/tests/code/MoveSCP.cpp
@@ -0,0 +1,237 @@
+#define BOOST_TEST_MODULE MoveSCP
+#include <boost/test/unit_test.hpp>
+
+#include <chrono>
+#include <cstdlib>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include <boost/asio.hpp>
+#include <boost/filesystem.hpp>
+
+#include "odil/Association.h"
+#include "odil/AssociationParameters.h"
+#include "odil/DataSet.h"
+#include "odil/MoveSCP.h"
+#include "odil/Exception.h"
+#include "odil/SCP.h"
+#include "odil/uid.h"
+#include "odil/Reader.h"
+#include "odil/registry.h"
+#include "odil/message/Response.h"
+
+struct Status
+{
+    int client;
+    std::string server;
+    std::vector<odil::DataSet> responses;
+};
+
+class Generator: public odil::MoveSCP::DataSetGenerator
+{
+public:
+    Generator(odil::Association & association)
+    : _association(association)
+    {
+        // Nothing else.
+    }
+
+    virtual ~Generator()
+    {
+        // Nothing to do.
+    }
+
+    virtual void initialize(odil::message::Request const & )
+    {
+        odil::DataSet data_set_1;
+        data_set_1.add("SOPClassUID", {odil::registry::RawDataStorage});
+        data_set_1.add(
+            "SOPInstanceUID",
+            {"1.2.826.0.1.3680043.9.5560.3127449359877365688774406533090568532"});
+        data_set_1.add("PatientName", {"Hello^World"});
+        data_set_1.add("PatientID", {"1234"});
+        this->_responses.push_back(data_set_1);
+
+        odil::DataSet data_set_2;
+        data_set_2.add("SOPClassUID", {odil::registry::RawDataStorage});
+        data_set_2.add(
+            "SOPInstanceUID",
+            {"1.2.826.0.1.3680043.9.5560.3221615743193123463515381981101110692"});
+        data_set_2.add("PatientName", {"Doe^John"});
+        data_set_2.add("PatientID", {"5678"});
+        this->_responses.push_back(data_set_2);
+
+        this->_response_iterator = this->_responses.begin();
+    }
+
+    virtual bool done() const
+    {
+        return (this->_response_iterator == this->_responses.end());
+    }
+
+    virtual odil::DataSet get() const
+    {
+        return *this->_response_iterator;
+    }
+
+    virtual void next()
+    {
+        ++this->_response_iterator;
+    }
+
+    virtual unsigned int count() const
+    {
+        return 2;
+    }
+
+    virtual odil::Association get_association(
+        odil::message::CMoveRequest const & request) const
+    {
+        odil::Association move_association;
+        move_association.set_peer_host(this->_association.get_peer_host());
+        move_association.set_peer_port(11114);
+
+        // FIXME: max length, user_identity
+        std::vector<odil::AssociationParameters::PresentationContext>
+            presentation_contexts;
+        for(auto const & uid: odil::registry::uids_dictionary)
+        {
+            auto const & name = uid.second.name;
+            if(name.substr(name.size()-7, std::string::npos) == "Storage")
+            {
+                odil::AssociationParameters::PresentationContext
+                    presentation_context = {
+                        static_cast<uint8_t>(1+2*presentation_contexts.size()),
+                        uid.first,
+                        { odil::registry::ImplicitVRLittleEndian },
+                        true, false
+                };
+                presentation_contexts.push_back(presentation_context);
+            }
+        }
+        move_association.update_parameters()
+            .set_calling_ae_title(
+                this->_association.get_negotiated_parameters().get_called_ae_title())
+            .set_called_ae_title(request.get_move_destination())
+            .set_presentation_contexts(presentation_contexts);
+
+        return move_association;
+    }
+
+private:
+    std::vector<odil::DataSet> _responses;
+    std::vector<odil::DataSet>::const_iterator _response_iterator;
+    odil::Association & _association;
+};
+
+void run_server(Status * status)
+{
+    odil::Association association;
+    association.set_tcp_timeout(boost::posix_time::seconds(1));
+
+    try
+    {
+        association.receive_association(boost::asio::ip::tcp::v4(), 11113);
+
+        odil::MoveSCP move_scp(association);
+        auto const generator = std::make_shared<Generator>(association);
+        move_scp.set_generator(generator);
+
+        // Get move message
+        auto const message = association.receive_message();
+        move_scp(message);
+        // Should throw with peer closing connection
+        association.receive_message();
+    }
+    catch(odil::AssociationAborted const &)
+    {
+        status->server = "abort";
+    }
+    catch(odil::AssociationReleased const &)
+    {
+        status->server = "release";
+    }
+    catch(odil::Exception const &)
+    {
+        status->server = "Other Odil exception";
+    }
+    catch(...)
+    {
+        status->server = "Other exception";
+    }
+}
+
+void run_client(Status * status)
+{
+    std::string command = "movescu "
+        "-P -k QueryRetrieveLevel=PATIENT "
+        "-k PatientID=* -k PatientName "
+        "+P 11114 127.0.0.1 11113";
+    status->client = system(command.c_str());
+
+    boost::filesystem::directory_iterator end;
+    for(boost::filesystem::directory_iterator it("."); it != end; ++it )
+    {
+        if(!boost::filesystem::is_regular_file(it->status()))
+        {
+            continue;
+        }
+        auto const filename = it->path().stem().string();
+        if(filename.substr(0, 3) != "RAW")
+        {
+            continue;
+        }
+
+        std::ifstream stream(it->path().string());
+        auto const data_set = odil::Reader::read_file(stream).second;
+        status->responses.push_back(data_set);
+
+        boost::filesystem::remove(it->path());
+    }
+
+    std::sort(
+        status->responses.begin(), status->responses.end(),
+        [](odil::DataSet const & left, odil::DataSet const & right)
+        {
+            auto const & left_uid = left.as_string("SOPInstanceUID", 0);
+            auto const & right_uid = right.as_string("SOPInstanceUID", 0);
+            return (left_uid < right_uid);
+        });
+}
+
+BOOST_AUTO_TEST_CASE(Release)
+{
+    Status status = { -1, "", {} };
+
+    std::thread server(run_server, &status);
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    std::thread client(run_client, &status);
+
+    server.join();
+    client.join();
+
+    BOOST_REQUIRE_EQUAL(status.client, 0);
+    BOOST_REQUIRE_EQUAL(status.server, "release");
+    BOOST_REQUIRE_EQUAL(status.responses.size(), 2);
+
+    BOOST_REQUIRE_EQUAL(status.responses[0].size(), 4);
+    BOOST_REQUIRE_EQUAL(
+        status.responses[0].as_string("SOPInstanceUID", 0),
+        "1.2.826.0.1.3680043.9.5560.3127449359877365688774406533090568532");
+    BOOST_REQUIRE_EQUAL(
+        status.responses[0].as_string("SOPClassUID", 0),
+        odil::registry::RawDataStorage);
+    BOOST_REQUIRE_EQUAL(status.responses[0].as_string("PatientName", 0), "Hello^World");
+    BOOST_REQUIRE_EQUAL(status.responses[0].as_string("PatientID", 0), "1234");
+
+    BOOST_REQUIRE_EQUAL(status.responses[1].size(), 4);
+    BOOST_REQUIRE_EQUAL(
+        status.responses[1].as_string("SOPInstanceUID", 0),
+        "1.2.826.0.1.3680043.9.5560.3221615743193123463515381981101110692");
+    BOOST_REQUIRE_EQUAL(
+        status.responses[1].as_string("SOPClassUID", 0),
+        odil::registry::RawDataStorage);
+    BOOST_REQUIRE_EQUAL(status.responses[1].as_string("PatientName", 0), "Doe^John");
+    BOOST_REQUIRE_EQUAL(status.responses[1].as_string("PatientID", 0), "5678");
+}
diff --git a/tests/code/MoveSCU.cpp b/tests/code/MoveSCU.cpp
index 5a131e7..598c043 100644
--- a/tests/code/MoveSCU.cpp
+++ b/tests/code/MoveSCU.cpp
@@ -1,9 +1,9 @@
 #define BOOST_TEST_MODULE MoveSCU
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/MoveSCU.h"
-#include "dcmtkpp/registry.h"
+#include "odil/DataSet.h"
+#include "odil/MoveSCU.h"
+#include "odil/registry.h"
 
 #include "../PeerFixtureBase.h"
 
@@ -11,18 +11,17 @@ struct Fixture: public PeerFixtureBase
 {
     static bool called;
 
-    dcmtkpp::DataSet query;
+    odil::DataSet query;
 
     Fixture()
-    : PeerFixtureBase(NET_ACCEPTORREQUESTOR, 11113, 10,
-        {
+    : PeerFixtureBase({
             {
-                dcmtkpp::registry::PatientRootQueryRetrieveInformationModelMOVE,
-                { dcmtkpp::registry::ImplicitVRLittleEndian }
+                1, odil::registry::PatientRootQueryRetrieveInformationModelMOVE,
+                { odil::registry::ImplicitVRLittleEndian }, true, false
             },
             {
-                dcmtkpp::registry::RawDataStorage, { dcmtkpp::registry::ImplicitVRLittleEndian },
-                ASC_SC_ROLE_SCP
+                3, odil::registry::RawDataStorage,
+                { odil::registry::ImplicitVRLittleEndian }, false, true
             }
         })
     {
@@ -32,7 +31,7 @@ struct Fixture: public PeerFixtureBase
         this->query.add("PatientName", {"Doe^John"});
     }
 
-    static void callback(dcmtkpp::DataSet const &)
+    static void callback(odil::DataSet const &)
     {
         Fixture::called = true;
     }
@@ -40,43 +39,41 @@ struct Fixture: public PeerFixtureBase
 
 bool Fixture::called = false;
 
-BOOST_AUTO_TEST_CASE(DefaultConstructor)
+BOOST_FIXTURE_TEST_CASE(DefaultConstructor, Fixture)
 {
-    dcmtkpp::MoveSCU const scu;
+    odil::MoveSCU const scu(this->association);
     BOOST_CHECK_EQUAL(scu.get_move_destination(), "");
 }
 
-BOOST_AUTO_TEST_CASE(MoveDestination)
+BOOST_FIXTURE_TEST_CASE(MoveDestination, Fixture)
 {
-    dcmtkpp::MoveSCU scu;
+    odil::MoveSCU scu(this->association);
     scu.set_move_destination("remote");
     BOOST_CHECK_EQUAL(scu.get_move_destination(), "remote");
 }
 
 BOOST_FIXTURE_TEST_CASE(Move, Fixture)
 {
-    dcmtkpp::MoveSCU scu;
-    scu.set_network(&this->network);
-    scu.set_association(&this->association);
+    odil::MoveSCU scu(this->association);
     scu.set_move_destination("LOCAL");
 
-    scu.set_affected_sop_class(dcmtkpp::registry::PatientRootQueryRetrieveInformationModelMOVE);
+    scu.set_affected_sop_class(
+        odil::registry::PatientRootQueryRetrieveInformationModelMOVE);
     auto const results = scu.move(this->query);
 
     BOOST_REQUIRE_EQUAL(results.size(), 1);
     BOOST_CHECK(
         results[0].as_string("SOPInstanceUID") ==
-            dcmtkpp::Value::Strings{"2.25.95090344942250266709587559073467305647"});
+            odil::Value::Strings{"2.25.95090344942250266709587559073467305647"});
 }
 
 BOOST_FIXTURE_TEST_CASE(MoveCallback, Fixture)
 {
-    dcmtkpp::MoveSCU scu;
-    scu.set_network(&this->network);
-    scu.set_association(&this->association);
+    odil::MoveSCU scu(this->association);
     scu.set_move_destination("LOCAL");
 
-    scu.set_affected_sop_class(dcmtkpp::registry::PatientRootQueryRetrieveInformationModelMOVE);
+    scu.set_affected_sop_class(
+        odil::registry::PatientRootQueryRetrieveInformationModelMOVE);
     scu.move(this->query, Fixture::callback);
 
     BOOST_CHECK(Fixture::called);
diff --git a/tests/code/Network.cpp b/tests/code/Network.cpp
deleted file mode 100644
index f2594f9..0000000
--- a/tests/code/Network.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-#define BOOST_TEST_MODULE Network
-#include <boost/test/unit_test.hpp>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/assoc.h>
-
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Network.h"
-
-BOOST_AUTO_TEST_CASE(DefaultConstructor)
-{
-    dcmtkpp::Network network;
-
-    BOOST_CHECK_EQUAL(network.get_role(), NET_REQUESTOR);
-    BOOST_CHECK_EQUAL(network.get_port(), 0);
-    BOOST_CHECK_EQUAL(network.get_timeout(), 30);
-    BOOST_CHECK_EQUAL(network.get_options(), 0);
-
-    BOOST_CHECK(!network.is_initialized());
-    BOOST_CHECK_EQUAL(network.get_network(), static_cast<T_ASC_Network*>(NULL));
-}
-
-BOOST_AUTO_TEST_CASE(Constructor)
-{
-    dcmtkpp::Network network(NET_ACCEPTOR, 11112, 10, 1);
-
-    BOOST_CHECK_EQUAL(network.get_role(), NET_ACCEPTOR);
-    BOOST_CHECK_EQUAL(network.get_port(), 11112);
-    BOOST_CHECK_EQUAL(network.get_timeout(), 10);
-    BOOST_CHECK_EQUAL(network.get_options(), 1);
-
-    BOOST_CHECK(!network.is_initialized());
-    BOOST_CHECK_EQUAL(network.get_network(), static_cast<T_ASC_Network*>(NULL));
-}
-
-BOOST_AUTO_TEST_CASE(CopyConstructor)
-{
-    dcmtkpp::Network network(NET_ACCEPTOR, 11112, 10, 1);
-    dcmtkpp::Network other(network);
-
-    BOOST_CHECK_EQUAL(other.get_role(), network.get_role());
-    BOOST_CHECK_EQUAL(other.get_port(), network.get_port());
-    BOOST_CHECK_EQUAL(other.get_timeout(), network.get_timeout());
-    BOOST_CHECK_EQUAL(other.get_options(), network.get_options());
-    BOOST_CHECK_EQUAL(other.is_initialized(), network.is_initialized());
-    BOOST_CHECK_EQUAL(other.get_network(), network.get_network());
-}
-
-BOOST_AUTO_TEST_CASE(Assignment)
-{
-    dcmtkpp::Network network(NET_ACCEPTOR, 11112, 10, 1);
-    dcmtkpp::Network other(NET_REQUESTOR, 112, 30, 0);
-    other = network;
-
-    BOOST_CHECK_EQUAL(other.get_role(), network.get_role());
-    BOOST_CHECK_EQUAL(other.get_port(), network.get_port());
-    BOOST_CHECK_EQUAL(other.get_timeout(), network.get_timeout());
-    BOOST_CHECK_EQUAL(other.get_options(), network.get_options());
-    BOOST_CHECK_EQUAL(other.is_initialized(), network.is_initialized());
-    BOOST_CHECK_EQUAL(other.get_network(), network.get_network());
-}
-
-BOOST_AUTO_TEST_CASE(Role)
-{
-    dcmtkpp::Network network;
-    network.set_role(NET_ACCEPTOR);
-
-    BOOST_CHECK_EQUAL(network.get_role(), NET_ACCEPTOR);
-}
-
-BOOST_AUTO_TEST_CASE(RoleAssociated)
-{
-    dcmtkpp::Network network;
-    network.initialize();
-
-    BOOST_CHECK_THROW(network.set_role(NET_ACCEPTOR), dcmtkpp::Exception);
-}
-
-BOOST_AUTO_TEST_CASE(Port)
-{
-    dcmtkpp::Network network;
-    network.set_port(1234);
-
-    BOOST_CHECK_EQUAL(network.get_port(), 1234);
-}
-
-BOOST_AUTO_TEST_CASE(PortAssociated)
-{
-    dcmtkpp::Network network;
-    network.initialize();
-
-    BOOST_CHECK_THROW(network.set_port(1234), dcmtkpp::Exception);
-}
-
-BOOST_AUTO_TEST_CASE(Timeout)
-{
-    dcmtkpp::Network network;
-    network.set_timeout(10);
-
-    BOOST_CHECK_EQUAL(network.get_timeout(), 10);
-}
-
-BOOST_AUTO_TEST_CASE(TimeoutAssociated)
-{
-    dcmtkpp::Network network;
-    network.initialize();
-
-    BOOST_CHECK_THROW(network.set_timeout(10), dcmtkpp::Exception);
-}
-
-BOOST_AUTO_TEST_CASE(Options)
-{
-    dcmtkpp::Network network;
-    network.set_options(1);
-
-    BOOST_CHECK_EQUAL(network.get_options(), 1);
-}
-
-BOOST_AUTO_TEST_CASE(OptionsAssociated)
-{
-    dcmtkpp::Network network;
-    network.initialize();
-
-    BOOST_CHECK_THROW(network.set_options(1), dcmtkpp::Exception);
-}
-
-BOOST_AUTO_TEST_CASE(Initialize)
-{
-    dcmtkpp::Network network(NET_REQUESTOR, 11112, 1, 0);
-    network.initialize();
-
-    BOOST_CHECK(network.is_initialized());
-    BOOST_CHECK_NE(network.get_network(),  static_cast<T_ASC_Network*>(NULL));
-}
-
-BOOST_AUTO_TEST_CASE(Drop)
-{
-    dcmtkpp::Network network(NET_REQUESTOR, 11112, 1, 0);
-    network.initialize();
-    network.drop();
-    BOOST_CHECK(!network.is_initialized());
-    BOOST_CHECK_EQUAL(network.get_network(),  static_cast<T_ASC_Network*>(NULL));
-}
-
-BOOST_AUTO_TEST_CASE(InitializeTwice)
-{
-    dcmtkpp::Network network(NET_REQUESTOR, 11112, 1, 0);
-    network.initialize();
-    BOOST_REQUIRE_THROW(network.initialize(), dcmtkpp::Exception);
-}
-
-BOOST_AUTO_TEST_CASE(DropNotInitialized)
-{
-    dcmtkpp::Network network;
-    BOOST_REQUIRE_THROW(network.drop(), dcmtkpp::Exception);
-}
diff --git a/tests/code/Reader.cpp b/tests/code/Reader.cpp
index ebc71e6..2999031 100644
--- a/tests/code/Reader.cpp
+++ b/tests/code/Reader.cpp
@@ -1,7 +1,6 @@
 #define BOOST_TEST_MODULE Reader
 #include <boost/test/unit_test.hpp>
 
-#include <endian.h>
 #include <sstream>
 #include <tuple>
 
@@ -9,26 +8,28 @@
 #include <dcmtk/dcmdata/dctk.h>
 #include <dcmtk/dcmdata/dcostrmb.h>
 
-#include "dcmtkpp/conversion.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Reader.h"
-#include "dcmtkpp/VR.h"
+#include "odil/endian.h"
+#include "odil/Element.h"
+#include "odil/registry.h"
+#include "odil/Reader.h"
+#include "odil/VR.h"
+#include "odil/dcmtk/conversion.h"
 
 BOOST_AUTO_TEST_CASE(Constructor)
 {
     std::istringstream stream;
-    dcmtkpp::Reader const reader(stream, dcmtkpp::registry::ExplicitVRBigEndian_Retired);
-    BOOST_REQUIRE(reader.byte_ordering == BIG_ENDIAN);
+    odil::Reader const reader(stream, odil::registry::ExplicitVRBigEndian_Retired);
+    BOOST_REQUIRE(reader.byte_ordering == odil::ByteOrdering::BigEndian);
     BOOST_REQUIRE(reader.explicit_vr == true);
 }
 
 void do_test(
-    dcmtkpp::DataSet const & dcmtkpp_data_set, std::string transfer_syntax,
+    odil::DataSet const & odil_data_set, std::string transfer_syntax,
     E_EncodingType item_encoding, E_GrpLenEncoding group_length_encoding)
 {
     // Write input data set
-    auto const dcmtk_data_set = dynamic_cast<DcmDataset*>(dcmtkpp::convert(dcmtkpp_data_set));
+    auto const dcmtk_data_set =
+        dynamic_cast<DcmDataset*>(odil::dcmtk::convert(odil_data_set));
     std::string data(1000000, '\0');
     DcmOutputBufferStream dcmtk_stream(&data[0], data.size());
     dcmtk_data_set->transferInit();
@@ -43,19 +44,19 @@ void do_test(
     std::istringstream stream(data);
 
     // Read output data set
-    dcmtkpp::Reader reader(stream, transfer_syntax);
-    auto const other_dcmtkpp_data_set = reader.read_data_set();
+    odil::Reader reader(stream, transfer_syntax);
+    auto const other_odil_data_set = reader.read_data_set();
 
-    BOOST_REQUIRE(other_dcmtkpp_data_set == dcmtkpp_data_set);
+    BOOST_REQUIRE(other_odil_data_set == odil_data_set);
 }
 
-void do_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
+void do_test(odil::DataSet const & odil_data_set)
 {
     std::vector<std::string> const transfer_syntaxes =
         {
-            dcmtkpp::registry::ImplicitVRLittleEndian,
-            dcmtkpp::registry::ExplicitVRLittleEndian,
-            dcmtkpp::registry::ExplicitVRBigEndian_Retired
+            odil::registry::ImplicitVRLittleEndian,
+            odil::registry::ExplicitVRLittleEndian,
+            odil::registry::ExplicitVRBigEndian_Retired
         };
     std::vector<E_EncodingType> item_encodings =
         { EET_ExplicitLength, EET_UndefinedLength };
@@ -69,7 +70,7 @@ void do_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
             for(auto const & use_group_length: use_group_length_values)
             {
                 do_test(
-                    dcmtkpp_data_set, transfer_syntax,
+                    odil_data_set, transfer_syntax,
                     item_encoding, use_group_length);
             }
         }
@@ -78,171 +79,172 @@ void do_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
 
 BOOST_AUTO_TEST_CASE(AT)
 {
-    dcmtkpp::Element dcmtkpp_element({"12345678", "9abcdef0"}, dcmtkpp::VR::AT);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorATValue, dcmtkpp_element);
+    odil::Element odil_element({"12345678", "9abcdef0"}, odil::VR::AT);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorATValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(CS)
 {
-    dcmtkpp::Element dcmtkpp_element({"ABC", "DEF"}, dcmtkpp::VR::CS);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorCSValue, dcmtkpp_element);
+    odil::Element odil_element({"ABC", "DEF"}, odil::VR::CS);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorCSValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(DS)
 {
-    dcmtkpp::Element dcmtkpp_element({1.23, -4.56}, dcmtkpp::VR::DS);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorDSValue, dcmtkpp_element);
+    odil::Element odil_element({1.23, -4.56}, odil::VR::DS);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorDSValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(FD)
 {
-    dcmtkpp::Element dcmtkpp_element({1.23, -4.56}, dcmtkpp::VR::FD);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorFDValue, dcmtkpp_element);
+    odil::Element odil_element({1.23, -4.56}, odil::VR::FD);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorFDValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(FL)
 {
-    dcmtkpp::Element dcmtkpp_element({0.5, -0.125}, dcmtkpp::VR::FL);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorFLValue, dcmtkpp_element);
+    odil::Element odil_element({0.5, -0.125}, odil::VR::FL);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorFLValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(IS)
 {
-    dcmtkpp::Element dcmtkpp_element({123, -456}, dcmtkpp::VR::IS);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorISValue, dcmtkpp_element);
+    odil::Element odil_element({123, -456}, odil::VR::IS);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorISValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(OB)
 {
-    dcmtkpp::Element dcmtkpp_element(
+    odil::Element odil_element(
         std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04}),
-        dcmtkpp::VR::OB);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::EncapsulatedDocument, dcmtkpp_element);
+        odil::VR::OB);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::EncapsulatedDocument, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(OF)
 {
-    dcmtkpp::Element dcmtkpp_element(
+    odil::Element odil_element(
         std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}),
-        dcmtkpp::VR::OF);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::VectorGridData, dcmtkpp_element);
+        odil::VR::OF);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::VectorGridData, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(OW)
 {
-    dcmtkpp::Element dcmtkpp_element(
+    odil::Element odil_element(
         std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04}),
-        dcmtkpp::VR::OW);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::RedPaletteColorLookupTableData, dcmtkpp_element);
+        odil::VR::OW);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::RedPaletteColorLookupTableData, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(SL)
 {
-    dcmtkpp::Element dcmtkpp_element({12345678, -8765432}, dcmtkpp::VR::SL);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorSLValue, dcmtkpp_element);
+    odil::Element odil_element({12345678, -8765432}, odil::VR::SL);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorSLValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(SQ)
 {
-    dcmtkpp::DataSet item1;
+    odil::DataSet item1;
     item1.add(
-        dcmtkpp::registry::SelectorSLValue,
-        dcmtkpp::Element({12345678, -8765432}, dcmtkpp::VR::SL));
-    dcmtkpp::DataSet item2;
+        odil::registry::SelectorSLValue,
+        odil::Element({12345678, -8765432}, odil::VR::SL));
+    odil::DataSet item2;
     item2.add(
-        dcmtkpp::registry::SelectorFDValue,
-        dcmtkpp::Element({1.23, -4.56}, dcmtkpp::VR::FD));
+        odil::registry::SelectorFDValue,
+        odil::Element({1.23, -4.56}, odil::VR::FD));
 
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::FrameExtractionSequence,
-        dcmtkpp::Element({item1, item2}, dcmtkpp::VR::SQ));
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::FrameExtractionSequence,
+        odil::Element({item1, item2}, odil::VR::SQ));
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(SS)
 {
-    dcmtkpp::Element dcmtkpp_element({1234, -5678}, dcmtkpp::VR::SS);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorSSValue, dcmtkpp_element);
+    odil::Element odil_element({1234, -5678}, odil::VR::SS);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorSSValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(UI)
 {
-    dcmtkpp::Element dcmtkpp_element({"1.2", "3.4"}, dcmtkpp::VR::UI);
-    dcmtkpp::DataSet dcmtkpp_data_set;
+    odil::Element odil_element({"1.2", "3.4"}, odil::VR::UI);
+    odil::DataSet odil_data_set;
     // SelectorUIValue is not in current DCMTK
-    dcmtkpp_data_set.add(dcmtkpp::registry::SOPInstanceUID, dcmtkpp_element);
+    odil_data_set.add(odil::registry::SOPInstanceUID, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(UL)
 {
-    dcmtkpp::Element dcmtkpp_element({12345678, 8765432}, dcmtkpp::VR::UL);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorULValue, dcmtkpp_element);
+    odil::Element odil_element({12345678, 8765432}, odil::VR::UL);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorULValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(US)
 {
-    dcmtkpp::Element dcmtkpp_element({1234, 5678}, dcmtkpp::VR::US);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorUSValue, dcmtkpp_element);
+    odil::Element odil_element({1234, 5678}, odil::VR::US);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorUSValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 void do_file_test(
-    dcmtkpp::DataSet const & dcmtkpp_data_set, std::string transfer_syntax,
+    odil::DataSet const & odil_data_set, std::string transfer_syntax,
     E_EncodingType item_encoding, E_GrpLenEncoding group_length_encoding)
 {
     // Write input data set
-    auto const dcmtk_data_set = dynamic_cast<DcmDataset*>(dcmtkpp::convert(dcmtkpp_data_set));
+    auto const dcmtk_data_set =
+        dynamic_cast<DcmDataset*>(odil::dcmtk::convert(odil_data_set));
 
     DcmFileFormat file_format(dcmtk_data_set);
     file_format.getMetaInfo()->putAndInsertString(
         DCM_TransferSyntaxUID, transfer_syntax.c_str());
     file_format.getMetaInfo()->putAndInsertString(
         DCM_MediaStorageSOPClassUID,
-        dcmtkpp_data_set.as_string(dcmtkpp::registry::SOPClassUID)[0].c_str());
+        odil_data_set.as_string(odil::registry::SOPClassUID)[0].c_str());
     file_format.getMetaInfo()->putAndInsertString(
         DCM_MediaStorageSOPInstanceUID,
-        dcmtkpp_data_set.as_string(dcmtkpp::registry::SOPInstanceUID)[0].c_str());
+        odil_data_set.as_string(odil::registry::SOPInstanceUID)[0].c_str());
 
     std::string data(1000000, '\0');
     DcmOutputBufferStream dcmtk_stream(&data[0], data.size());
@@ -259,29 +261,29 @@ void do_file_test(
     std::istringstream stream(data);
 
     // Read output data set
-    dcmtkpp::DataSet meta_information, other_dcmtkpp_data_set;
-    std::tie(meta_information, other_dcmtkpp_data_set) = dcmtkpp::Reader::read_file(stream);
+    odil::DataSet meta_information, other_odil_data_set;
+    std::tie(meta_information, other_odil_data_set) = odil::Reader::read_file(stream);
 
-    BOOST_REQUIRE(other_dcmtkpp_data_set == dcmtkpp_data_set);
+    BOOST_REQUIRE(other_odil_data_set == odil_data_set);
 
     BOOST_REQUIRE(
-        meta_information.as_string(dcmtkpp::registry::TransferSyntaxUID) ==
-        dcmtkpp::Value::Strings({transfer_syntax}));
+        meta_information.as_string(odil::registry::TransferSyntaxUID) ==
+        odil::Value::Strings({transfer_syntax}));
     BOOST_REQUIRE(
-        meta_information.as_string(dcmtkpp::registry::MediaStorageSOPClassUID) ==
-        other_dcmtkpp_data_set.as_string(dcmtkpp::registry::SOPClassUID));
+        meta_information.as_string(odil::registry::MediaStorageSOPClassUID) ==
+        other_odil_data_set.as_string(odil::registry::SOPClassUID));
     BOOST_REQUIRE(
-        meta_information.as_string(dcmtkpp::registry::MediaStorageSOPInstanceUID) ==
-        other_dcmtkpp_data_set.as_string(dcmtkpp::registry::SOPInstanceUID));
+        meta_information.as_string(odil::registry::MediaStorageSOPInstanceUID) ==
+        other_odil_data_set.as_string(odil::registry::SOPInstanceUID));
 }
 
-void do_file_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
+void do_file_test(odil::DataSet const & odil_data_set)
 {
     std::vector<std::string> const transfer_syntaxes =
         {
-            dcmtkpp::registry::ImplicitVRLittleEndian,
-            dcmtkpp::registry::ExplicitVRLittleEndian,
-            dcmtkpp::registry::ExplicitVRBigEndian_Retired
+            odil::registry::ImplicitVRLittleEndian,
+            odil::registry::ExplicitVRLittleEndian,
+            odil::registry::ExplicitVRBigEndian_Retired
         };
     std::vector<E_EncodingType> item_encodings =
         { EET_ExplicitLength, EET_UndefinedLength };
@@ -295,7 +297,7 @@ void do_file_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
             for(auto const & use_group_length: use_group_length_values)
             {
                 do_file_test(
-                    dcmtkpp_data_set, transfer_syntax,
+                    odil_data_set, transfer_syntax,
                     item_encoding, use_group_length);
             }
         }
@@ -304,25 +306,25 @@ void do_file_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
 
 BOOST_AUTO_TEST_CASE(File)
 {
-    dcmtkpp::DataSet item1;
+    odil::DataSet item1;
     item1.add(
-        dcmtkpp::registry::SelectorSLValue,
-        dcmtkpp::Element({12345678, -8765432}, dcmtkpp::VR::SL));
-    dcmtkpp::DataSet item2;
+        odil::registry::SelectorSLValue,
+        odil::Element({12345678, -8765432}, odil::VR::SL));
+    odil::DataSet item2;
     item2.add(
-        dcmtkpp::registry::SelectorFDValue,
-        dcmtkpp::Element({1.23, -4.56}, dcmtkpp::VR::FD));
-
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(
-        dcmtkpp::registry::SOPClassUID,
-        {dcmtkpp::registry::RawDataStorage}, dcmtkpp::VR::UI);
-    dcmtkpp_data_set.add(
-        dcmtkpp::registry::SOPInstanceUID,
-        {"1.2.3.4"}, dcmtkpp::VR::UI);
-    dcmtkpp_data_set.add(
-        dcmtkpp::registry::FrameExtractionSequence,
-        dcmtkpp::Element({item1, item2}, dcmtkpp::VR::SQ));
-
-    do_file_test(dcmtkpp_data_set);
+        odil::registry::SelectorFDValue,
+        odil::Element({1.23, -4.56}, odil::VR::FD));
+
+    odil::DataSet odil_data_set;
+    odil_data_set.add(
+        odil::registry::SOPClassUID,
+        {odil::registry::RawDataStorage}, odil::VR::UI);
+    odil_data_set.add(
+        odil::registry::SOPInstanceUID,
+        {"1.2.3.4"}, odil::VR::UI);
+    odil_data_set.add(
+        odil::registry::FrameExtractionSequence,
+        odil::Element({item1, item2}, odil::VR::SQ));
+
+    do_file_test(odil_data_set);
 }
diff --git a/tests/code/Response.cpp b/tests/code/Response.cpp
deleted file mode 100644
index 85070a2..0000000
--- a/tests/code/Response.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#define BOOST_TEST_MODULE Response
-#include <boost/test/unit_test.hpp>
-
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
-
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/Response.h"
-
-BOOST_AUTO_TEST_CASE(Constructor)
-{
-    dcmtkpp::Response const message(1234, STATUS_Pending);
-
-    BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
-    BOOST_CHECK_EQUAL(message.get_status(), STATUS_Pending);
-}
-
-BOOST_AUTO_TEST_CASE(MessageConstructor)
-{
-    dcmtkpp::DataSet command_set;
-    command_set.add("MessageIDBeingRespondedTo", {1234});
-    command_set.add("Status", {STATUS_Pending});
-    dcmtkpp::Message const generic_message(command_set);
-
-    dcmtkpp::Response const message(generic_message);
-
-    BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
-    BOOST_CHECK_EQUAL(message.get_status(), STATUS_Pending);
-    BOOST_CHECK(!message.has_data_set());
-}
diff --git a/tests/code/SCPDispatcher.cpp b/tests/code/SCPDispatcher.cpp
new file mode 100644
index 0000000..f1d746c
--- /dev/null
+++ b/tests/code/SCPDispatcher.cpp
@@ -0,0 +1,132 @@
+#define BOOST_TEST_MODULE SCPDispatcher
+#include <boost/test/unit_test.hpp>
+
+#include <chrono>
+#include <memory>
+#include <thread>
+
+#include "odil/Association.h"
+#include "odil/EchoSCP.h"
+#include "odil/Exception.h"
+#include "odil/SCPDispatcher.h"
+#include "odil/message/Message.h"
+#include "odil/message/Response.h"
+
+struct Status
+{
+    int client;
+    std::string server;
+    bool called;
+};
+
+void run_server(Status * status, bool with_echo)
+{
+    odil::Association association;
+    association.set_tcp_timeout(boost::posix_time::seconds(1));
+
+    try
+    {
+        association.receive_association(boost::asio::ip::tcp::v4(), 11113);
+
+        auto echo_scp = std::make_shared<odil::EchoSCP>(association,
+            [&status](odil::message::CEchoRequest const &)
+            {
+                status->called = true;
+                return odil::message::Response::Success;
+            });
+
+        odil::SCPDispatcher dispatcher(association);
+
+        if(with_echo)
+        {
+            dispatcher.set_scp(
+                odil::message::Message::Command::C_ECHO_RQ, echo_scp);
+        }
+
+        dispatcher.dispatch();
+
+        // Should throw with peer closing connection
+        association.receive_message();
+    }
+    catch(odil::AssociationAborted const &)
+    {
+        status->server = "abort";
+    }
+    catch(odil::AssociationReleased const &)
+    {
+        status->server = "release";
+    }
+    catch(odil::Exception const & e)
+    {
+        status->server = e.what();
+    }
+    catch(...)
+    {
+        status->server = "Other exception";
+    }
+}
+
+void run_echo_client(Status * status)
+{
+    std::string command = "echoscu";
+    command += " 127.0.0.1 11113";
+    status->client = system(command.c_str());
+}
+
+BOOST_AUTO_TEST_CASE(Empty)
+{
+    odil::Association association;
+    odil::SCPDispatcher dispatcher(association);
+    BOOST_REQUIRE_EQUAL(
+        dispatcher.has_scp(odil::message::Message::Command::C_ECHO_RQ),
+        false);
+    BOOST_REQUIRE_THROW(
+        dispatcher.get_scp(odil::message::Message::Command::C_ECHO_RQ),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(SCP)
+{
+    odil::Association association;
+    odil::SCPDispatcher dispatcher(association);
+
+    auto scp = std::make_shared<odil::EchoSCP>(association);
+    dispatcher.set_scp(
+        odil::message::Message::Command::C_ECHO_RQ,
+        scp);
+
+    BOOST_REQUIRE_NO_THROW(
+        dispatcher.get_scp(odil::message::Message::Command::C_ECHO_RQ));
+}
+
+BOOST_AUTO_TEST_CASE(DispatchWithEcho)
+{
+    Status status = { -1, "", false };
+
+    std::thread server(run_server, &status, true);
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    std::thread client(run_echo_client, &status);
+
+    server.join();
+    client.join();
+
+    BOOST_REQUIRE_EQUAL(status.client, 0);
+    BOOST_REQUIRE_EQUAL(status.server, "release");
+    BOOST_REQUIRE_EQUAL(status.called, true);
+}
+
+BOOST_AUTO_TEST_CASE(DispatchWithoutEcho)
+{
+    Status status = { -1, "", false };
+
+    std::thread server(run_server, &status, false);
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    std::thread client(run_echo_client, &status);
+
+    server.join();
+    client.join();
+
+    BOOST_REQUIRE_EQUAL(status.client, 0);
+    BOOST_REQUIRE_EQUAL(status.server, "No such provider");
+    BOOST_REQUIRE_EQUAL(status.called, false);
+}
diff --git a/tests/code/SCU.cpp b/tests/code/SCU.cpp
index d21df99..090d0ed 100644
--- a/tests/code/SCU.cpp
+++ b/tests/code/SCU.cpp
@@ -1,42 +1,40 @@
 #define BOOST_TEST_MODULE SCU
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/SCU.h"
-#include "dcmtkpp/registry.h"
+#include "odil/SCU.h"
+#include "odil/registry.h"
 
 #include "../PeerFixtureBase.h"
 
 struct Fixture: public PeerFixtureBase
 {
     Fixture()
-    : PeerFixtureBase(NET_REQUESTOR, 104, 10,
+    : PeerFixtureBase({
         {
-            { dcmtkpp::registry::VerificationSOPClass,
-                {dcmtkpp::registry::ImplicitVRLittleEndian}
-            }
-        })
+            1, odil::registry::VerificationSOPClass,
+            {odil::registry::ImplicitVRLittleEndian}, true, false
+        }
+    })
     {
         // Nothing else
     }
 };
 
-BOOST_AUTO_TEST_CASE(DefaultConstructor)
+BOOST_FIXTURE_TEST_CASE(DefaultConstructor, Fixture)
 {
-    dcmtkpp::SCU const scu;
+    odil::SCU const scu(this->association);
     BOOST_CHECK_EQUAL(scu.get_affected_sop_class(), "");
 }
 
-BOOST_AUTO_TEST_CASE(AffectedSOPClassUID)
+BOOST_FIXTURE_TEST_CASE(AffectedSOPClassUID, Fixture)
 {
-    dcmtkpp::SCU scu;
+    odil::SCU scu(this->association);
     scu.set_affected_sop_class("1.2.3");
     BOOST_CHECK_EQUAL(scu.get_affected_sop_class(), "1.2.3");
 }
 
 BOOST_FIXTURE_TEST_CASE(Echo, Fixture)
 {
-    dcmtkpp::SCU scu;
-    scu.set_network(&this->network);
-    scu.set_association(&this->association);
+    odil::SCU scu(this->association);
     scu.echo();
 }
diff --git a/tests/code/ServiceRole.cpp b/tests/code/ServiceRole.cpp
deleted file mode 100644
index 609e244..0000000
--- a/tests/code/ServiceRole.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-#define BOOST_TEST_MODULE ServiceRole
-#include <boost/test/unit_test.hpp>
-
-#include "dcmtkpp/Association.h"
-#include "dcmtkpp/Network.h"
-#include "dcmtkpp/ServiceRole.h"
-
-BOOST_AUTO_TEST_CASE(DefaultConstructor)
-{
-    dcmtkpp::ServiceRole const role;
-    BOOST_CHECK_EQUAL(
-        role.get_network(), static_cast<dcmtkpp::Network *>(NULL));
-    BOOST_CHECK_EQUAL(
-        role.get_association(), static_cast<dcmtkpp::Association *>(NULL));
-}
-
-BOOST_AUTO_TEST_CASE(Network)
-{
-    dcmtkpp::ServiceRole role;
-
-    dcmtkpp::Network network;
-    role.set_network(&network);
-
-    BOOST_CHECK_EQUAL(role.get_network(), &network);
-}
-
-BOOST_AUTO_TEST_CASE(Association)
-{
-    dcmtkpp::ServiceRole role;
-
-    dcmtkpp::Association association;
-    role.set_association(&association);
-
-    BOOST_CHECK_EQUAL(role.get_association(), &association);
-}
-
-BOOST_AUTO_TEST_CASE(CopyConstructor)
-{
-    dcmtkpp::Network network;
-    dcmtkpp::Association association;
-    dcmtkpp::ServiceRole role;
-    role.set_network(&network);
-    role.set_association(&association);
-
-    dcmtkpp::ServiceRole const other(role);
-    BOOST_CHECK_EQUAL(other.get_network(), role.get_network());
-    BOOST_CHECK_EQUAL(other.get_association(), role.get_association());
-}
-
-BOOST_AUTO_TEST_CASE(Assignement)
-{
-    dcmtkpp::Network network;
-    dcmtkpp::Association association;
-    dcmtkpp::ServiceRole role;
-    role.set_network(&network);
-    role.set_association(&association);
-
-    dcmtkpp::ServiceRole other;
-    other = role;
-    BOOST_CHECK_EQUAL(other.get_network(), role.get_network());
-    BOOST_CHECK_EQUAL(other.get_association(), role.get_association());
-}
diff --git a/tests/code/StoreSCU.cpp b/tests/code/StoreSCU.cpp
index d1465ee..f4f6c86 100644
--- a/tests/code/StoreSCU.cpp
+++ b/tests/code/StoreSCU.cpp
@@ -1,80 +1,66 @@
 #define BOOST_TEST_MODULE StoreSCU
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/StoreSCU.h"
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/StoreSCU.h"
 
 #include "../PeerFixtureBase.h"
 
 struct Fixture: public PeerFixtureBase
 {
-    static bool called;
-
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
 
     Fixture()
-    : PeerFixtureBase(NET_REQUESTOR, 104, 10,
+    : PeerFixtureBase({
         {
-            { dcmtkpp::registry::RawDataStorage,
-                {dcmtkpp::registry::ImplicitVRLittleEndian}
-            }
-        })
+            1, odil::registry::RawDataStorage,
+            {odil::registry::ImplicitVRLittleEndian}, true, false
+        }
+    })
     {
-        Fixture::called = false;
-
         this->dataset.add("ImageType", {"ORIGINAL", "PRIMARY", "OTHER"});
         this->dataset.add("PatientID", {"1234"});
         this->dataset.add("StudyInstanceUID", {"2.25.386726390606491051215227596277040710"});
         this->dataset.add("SeriesInstanceUID", {"2.25.235367796740370588607388995952651763168"});
-        this->dataset.add("SOPClassUID", {dcmtkpp::registry::RawDataStorage});
+        this->dataset.add("SOPClassUID", {odil::registry::RawDataStorage});
         this->dataset.add("SOPInstanceUID", {"2.25.294312554735929033890522327215919068328"});
     }
 
-
-    static void callback(void*, unsigned long)
-    {
-        Fixture::called = true;
-    }
 };
 
-bool Fixture::called = false;
-
-BOOST_AUTO_TEST_CASE(AffectedSOPClassUID)
+BOOST_FIXTURE_TEST_CASE(AffectedSOPClassUID, Fixture)
 {
-    dcmtkpp::DataSet dataset;
-    dataset.add("SOPClassUID", {dcmtkpp::registry::RawDataStorage});
+    odil::DataSet dataset;
+    dataset.add("SOPClassUID", {odil::registry::RawDataStorage});
 
-    dcmtkpp::StoreSCU scu;
+    odil::StoreSCU scu(this->association);
     scu.set_affected_sop_class(dataset);
-    BOOST_CHECK_EQUAL(scu.get_affected_sop_class(), dcmtkpp::registry::RawDataStorage);
+    BOOST_CHECK_EQUAL(scu.get_affected_sop_class(), odil::registry::RawDataStorage);
 }
 
-BOOST_AUTO_TEST_CASE(AffectedSOPClassUIDNoSOPClassUID)
+BOOST_FIXTURE_TEST_CASE(AffectedSOPClassUIDNoSOPClassUID, Fixture)
 {
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
 
-    dcmtkpp::StoreSCU scu;
-    BOOST_CHECK_THROW(scu.set_affected_sop_class(dataset), dcmtkpp::Exception);
+    odil::StoreSCU scu(this->association);
+    BOOST_CHECK_THROW(scu.set_affected_sop_class(dataset), odil::Exception);
 }
 
-BOOST_AUTO_TEST_CASE(AffectedSOPClassUIDUnknownSOPClassUID)
+BOOST_FIXTURE_TEST_CASE(AffectedSOPClassUIDUnknownSOPClassUID, Fixture)
 {
-    dcmtkpp::DataSet dataset;
+    odil::DataSet dataset;
     dataset.add("SOPClassUID", {"invalid"});
 
-    dcmtkpp::StoreSCU scu;
-    BOOST_CHECK_THROW(scu.set_affected_sop_class(dataset), dcmtkpp::Exception);
+    odil::StoreSCU scu(this->association);
+    BOOST_CHECK_THROW(scu.set_affected_sop_class(dataset), odil::Exception);
 }
 
 BOOST_FIXTURE_TEST_CASE(Store, Fixture)
 {
-    dcmtkpp::StoreSCU scu;
-    scu.set_network(&this->network);
-    scu.set_association(&this->association);
+    odil::StoreSCU scu(this->association);
 
     scu.set_affected_sop_class(this->dataset);
-    scu.store(this->dataset, Fixture::callback);
-    BOOST_CHECK(this->called);
+    scu.store(this->dataset);
 }
diff --git a/tests/code/Tag.cpp b/tests/code/Tag.cpp
index 6c4e4a7..1c35855 100644
--- a/tests/code/Tag.cpp
+++ b/tests/code/Tag.cpp
@@ -1,83 +1,83 @@
 #define BOOST_TEST_MODULE Tag
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/Tag.h"
+#include "odil/Exception.h"
+#include "odil/Tag.h"
 
 BOOST_AUTO_TEST_CASE(TwoArgumentsConstructor)
 {
-    dcmtkpp::Tag const tag(0xdead, 0xbeef);
+    odil::Tag const tag(0xdead, 0xbeef);
     BOOST_CHECK_EQUAL(tag.group, 0xdead);
     BOOST_CHECK_EQUAL(tag.element, 0xbeef);
 }
 
 BOOST_AUTO_TEST_CASE(OneArgumentConstructor)
 {
-    dcmtkpp::Tag const tag(0xdeadbeef);
+    odil::Tag const tag(0xdeadbeef);
     BOOST_CHECK_EQUAL(tag.group, 0xdead);
     BOOST_CHECK_EQUAL(tag.element, 0xbeef);
 }
 
 BOOST_AUTO_TEST_CASE(StringConstructor)
 {
-    dcmtkpp::Tag const tag(std::string("PixelData"));
+    odil::Tag const tag(std::string("PixelData"));
     BOOST_CHECK_EQUAL(tag.group, 0x7fe0);
     BOOST_CHECK_EQUAL(tag.element, 0x0010);
 }
 
 BOOST_AUTO_TEST_CASE(CharConstructor)
 {
-    dcmtkpp::Tag const tag("PixelData");
+    odil::Tag const tag("PixelData");
     BOOST_CHECK_EQUAL(tag.group, 0x7fe0);
     BOOST_CHECK_EQUAL(tag.element, 0x0010);
 }
 
 BOOST_AUTO_TEST_CASE(StringConstructorNumeric)
 {
-    dcmtkpp::Tag const tag(std::string("7fe00010"));
+    odil::Tag const tag(std::string("7fe00010"));
     BOOST_CHECK_EQUAL(tag.group, 0x7fe0);
     BOOST_CHECK_EQUAL(tag.element, 0x0010);
 }
 
 BOOST_AUTO_TEST_CASE(CharConstructorNumeric)
 {
-    dcmtkpp::Tag const tag("7fe00010");
+    odil::Tag const tag("7fe00010");
     BOOST_CHECK_EQUAL(tag.group, 0x7fe0);
     BOOST_CHECK_EQUAL(tag.element, 0x0010);
 }
 
 BOOST_AUTO_TEST_CASE(StringConstructorWrong)
 {
-    BOOST_CHECK_THROW(dcmtkpp::Tag(std::string("Foobar")), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(odil::Tag(std::string("Foobar")), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(CharConstructorWrong)
 {
-    BOOST_CHECK_THROW(dcmtkpp::Tag("Foobar"), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(odil::Tag("Foobar"), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(StringConstructorNumericWrong)
 {
-    BOOST_CHECK_THROW(dcmtkpp::Tag(std::string("XXXXYYYY")), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(odil::Tag(std::string("XXXXYYYY")), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(CharConstructorNumericWrong)
 {
-    BOOST_CHECK_THROW(dcmtkpp::Tag("XXXXYYYY"), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(odil::Tag("XXXXYYYY"), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(CopyConstructor)
 {
-    dcmtkpp::Tag const tag(0xdead, 0xbeef);
-    dcmtkpp::Tag const other(tag);
+    odil::Tag const tag(0xdead, 0xbeef);
+    odil::Tag const other(tag);
     BOOST_CHECK_EQUAL(other.group, 0xdead);
     BOOST_CHECK_EQUAL(other.element, 0xbeef);
 }
 
 BOOST_AUTO_TEST_CASE(IsPrivate)
 {
-    dcmtkpp::Tag const tag1(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag2(0x7fe0, 0x0010);
+    odil::Tag const tag1(0xdead, 0xbeef);
+    odil::Tag const tag2(0x7fe0, 0x0010);
 
     BOOST_CHECK(tag1.is_private());
     BOOST_CHECK(!tag2.is_private());
@@ -85,22 +85,22 @@ BOOST_AUTO_TEST_CASE(IsPrivate)
 
 BOOST_AUTO_TEST_CASE(Name)
 {
-    dcmtkpp::Tag const tag(0x7fe0, 0x0010);
+    odil::Tag const tag(0x7fe0, 0x0010);
     std::string const name = tag.get_name();
     BOOST_CHECK_EQUAL(name, "PixelData");
 }
 
 BOOST_AUTO_TEST_CASE(NameWrong)
 {
-    dcmtkpp::Tag const tag(0xEEEE, 0xEEEE);
-    BOOST_CHECK_THROW(tag.get_name(), dcmtkpp::Exception);
+    odil::Tag const tag(0xEEEE, 0xEEEE);
+    BOOST_CHECK_THROW(tag.get_name(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(Equality)
 {
-    dcmtkpp::Tag const tag1(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag2(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag3(0xbeef, 0xf00d);
+    odil::Tag const tag1(0xdead, 0xbeef);
+    odil::Tag const tag2(0xdead, 0xbeef);
+    odil::Tag const tag3(0xbeef, 0xf00d);
 
     BOOST_CHECK(tag1 == tag2);
     BOOST_CHECK( ! (tag1 == tag3) );
@@ -108,9 +108,9 @@ BOOST_AUTO_TEST_CASE(Equality)
 
 BOOST_AUTO_TEST_CASE(Difference)
 {
-    dcmtkpp::Tag const tag1(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag2(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag3(0xbeef, 0xf00d);
+    odil::Tag const tag1(0xdead, 0xbeef);
+    odil::Tag const tag2(0xdead, 0xbeef);
+    odil::Tag const tag3(0xbeef, 0xf00d);
 
     BOOST_CHECK( ! (tag1 != tag2) );
     BOOST_CHECK(tag1 != tag3);
@@ -118,9 +118,9 @@ BOOST_AUTO_TEST_CASE(Difference)
 
 BOOST_AUTO_TEST_CASE(Inferior)
 {
-    dcmtkpp::Tag const tag1(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag2(0xdead, 0xf00d);
-    dcmtkpp::Tag const tag3(0xbeef, 0xf00d);
+    odil::Tag const tag1(0xdead, 0xbeef);
+    odil::Tag const tag2(0xdead, 0xf00d);
+    odil::Tag const tag3(0xbeef, 0xf00d);
 
     BOOST_CHECK(tag1 < tag2);
     BOOST_CHECK(tag3 < tag1);
@@ -131,9 +131,9 @@ BOOST_AUTO_TEST_CASE(Inferior)
 
 BOOST_AUTO_TEST_CASE(Superior)
 {
-    dcmtkpp::Tag const tag1(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag2(0xdead, 0xf00d);
-    dcmtkpp::Tag const tag3(0xbeef, 0xf00d);
+    odil::Tag const tag1(0xdead, 0xbeef);
+    odil::Tag const tag2(0xdead, 0xf00d);
+    odil::Tag const tag3(0xbeef, 0xf00d);
 
     BOOST_CHECK(tag2 > tag1);
     BOOST_CHECK(tag1 > tag3);
@@ -144,10 +144,10 @@ BOOST_AUTO_TEST_CASE(Superior)
 
 BOOST_AUTO_TEST_CASE(InferiorOrEqual)
 {
-    dcmtkpp::Tag const tag1(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag2(0xdead, 0xf00d);
-    dcmtkpp::Tag const tag3(0xbeef, 0xf00d);
-    dcmtkpp::Tag const tag4(0xdead, 0xbeef);
+    odil::Tag const tag1(0xdead, 0xbeef);
+    odil::Tag const tag2(0xdead, 0xf00d);
+    odil::Tag const tag3(0xbeef, 0xf00d);
+    odil::Tag const tag4(0xdead, 0xbeef);
 
     BOOST_CHECK(tag1 <= tag2);
     BOOST_CHECK(tag3 <= tag1);
@@ -159,10 +159,10 @@ BOOST_AUTO_TEST_CASE(InferiorOrEqual)
 
 BOOST_AUTO_TEST_CASE(SuperiorOrEqual)
 {
-    dcmtkpp::Tag const tag1(0xdead, 0xbeef);
-    dcmtkpp::Tag const tag2(0xdead, 0xf00d);
-    dcmtkpp::Tag const tag3(0xbeef, 0xf00d);
-    dcmtkpp::Tag const tag4(0xdead, 0xbeef);
+    odil::Tag const tag1(0xdead, 0xbeef);
+    odil::Tag const tag2(0xdead, 0xf00d);
+    odil::Tag const tag3(0xbeef, 0xf00d);
+    odil::Tag const tag4(0xdead, 0xbeef);
 
     BOOST_CHECK(tag2 >= tag1);
     BOOST_CHECK(tag1 >= tag3);
@@ -174,7 +174,7 @@ BOOST_AUTO_TEST_CASE(SuperiorOrEqual)
 
 BOOST_AUTO_TEST_CASE(StreamInsertion)
 {
-    dcmtkpp::Tag const tag(0xdead, 0xbeef);
+    odil::Tag const tag(0xdead, 0xbeef);
     std::ostringstream stream;
     stream << tag;
     BOOST_CHECK_EQUAL(stream.str(), "deadbeef");
@@ -182,6 +182,6 @@ BOOST_AUTO_TEST_CASE(StreamInsertion)
 
 BOOST_AUTO_TEST_CASE(StringConversion)
 {
-    dcmtkpp::Tag const tag(0xdead, 0xbeef);
+    odil::Tag const tag(0xdead, 0xbeef);
     BOOST_CHECK_EQUAL(std::string(tag), "deadbeef");
 }
diff --git a/tests/code/VR.cpp b/tests/code/VR.cpp
index f231879..bdd8a63 100644
--- a/tests/code/VR.cpp
+++ b/tests/code/VR.cpp
@@ -1,31 +1,31 @@
 #define BOOST_TEST_MODULE VR
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/Exception.h"
-#include "dcmtkpp/VR.h"
+#include "odil/Exception.h"
+#include "odil/VR.h"
 
 BOOST_AUTO_TEST_CASE(as_string)
 {
-    dcmtkpp::VR const vr(dcmtkpp::VR::AT);
-    std::string const string = dcmtkpp::as_string(vr);
+    odil::VR const vr(odil::VR::AT);
+    std::string const string = odil::as_string(vr);
     BOOST_CHECK_EQUAL(string, "AT");
 }
 
 BOOST_AUTO_TEST_CASE(as_string_invalid)
 {
-    dcmtkpp::VR const vr(dcmtkpp::VR::INVALID);
-    BOOST_CHECK_THROW(dcmtkpp::as_string(vr), dcmtkpp::Exception);
+    odil::VR const vr(odil::VR::INVALID);
+    BOOST_CHECK_THROW(odil::as_string(vr), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(as_vr)
 {
     std::string const string("AT");
-    dcmtkpp::VR const vr = dcmtkpp::as_vr(string);
-    BOOST_CHECK(vr == dcmtkpp::VR::AT);
+    odil::VR const vr = odil::as_vr(string);
+    BOOST_CHECK(vr == odil::VR::AT);
 }
 
 BOOST_AUTO_TEST_CASE(as_vr_wrong)
 {
     std::string const string("XX");
-    BOOST_CHECK_THROW(dcmtkpp::as_vr(string), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(odil::as_vr(string), odil::Exception);
 }
diff --git a/tests/code/VRFinder.cpp b/tests/code/VRFinder.cpp
new file mode 100644
index 0000000..8c78fc7
--- /dev/null
+++ b/tests/code/VRFinder.cpp
@@ -0,0 +1,177 @@
+#define BOOST_TEST_MODULE VRFinder
+#include <boost/test/unit_test.hpp>
+
+#include <string>
+
+#include "odil/DataSet.h"
+#include "odil/Exception.h"
+#include "odil/registry.h"
+#include "odil/Tag.h"
+#include "odil/VR.h"
+#include "odil/VRFinder.h"
+
+odil::VR
+user_defined_finder(
+    odil::Tag const & tag, odil::DataSet const &, std::string const &,
+    bool & called)
+{
+    if(tag == odil::Tag(0xeeee, 0xeeee))
+    {
+        called = true;
+        return odil::VR::UR;
+    }
+    else
+    {
+        throw odil::Exception("Not applicable");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(DefaultConstructor)
+{
+    odil::VRFinder finder;
+    BOOST_REQUIRE_EQUAL(finder.finders.empty(), true);
+}
+
+BOOST_AUTO_TEST_CASE(PublicDictionary)
+{
+    auto const vr = odil::VRFinder::public_dictionary(
+        odil::Tag(0x0010, 0x0010), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::PN);
+}
+
+BOOST_AUTO_TEST_CASE(PublicDictionaryNotApplicable)
+{
+    BOOST_REQUIRE_THROW(
+        odil::VRFinder::public_dictionary(
+            odil::Tag(0x0011, 0x0011), odil::DataSet(),
+            odil::registry::ImplicitVRLittleEndian),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(GroupLength)
+{
+    auto const vr = odil::VRFinder::group_length(
+        odil::Tag(0x0010, 0x0000), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UL);
+}
+
+BOOST_AUTO_TEST_CASE(GroupLengthNotApplicable)
+{
+    BOOST_REQUIRE_THROW(
+        odil::VRFinder::group_length(
+            odil::Tag(0x0010, 0x0010), odil::DataSet(),
+            odil::registry::ImplicitVRLittleEndian),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(PrivateTag)
+{
+    auto const vr = odil::VRFinder::private_tag(
+        odil::Tag(0x0011, 0x0011), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UN);
+}
+
+BOOST_AUTO_TEST_CASE(PrivateTagNotApplicable)
+{
+    BOOST_REQUIRE_THROW(
+        odil::VRFinder::private_tag(
+            odil::Tag(0x0010, 0x0010), odil::DataSet(),
+            odil::registry::ImplicitVRLittleEndian),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(ImplictiVRLittleEndian)
+{
+    auto const vr = odil::VRFinder::implicit_vr_little_endian(
+        odil::Tag(0x7fe0, 0x0010), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::OW);
+}
+
+BOOST_AUTO_TEST_CASE(ImplictiVRLittleEndianNotApplicableTag)
+{
+    BOOST_REQUIRE_THROW(
+        odil::VRFinder::implicit_vr_little_endian(
+            odil::Tag(0x0010, 0x0010), odil::DataSet(),
+            odil::registry::ImplicitVRLittleEndian),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(ImplictiVRLittleEndianNotApplicableVR)
+{
+    BOOST_REQUIRE_THROW(
+        odil::VRFinder::implicit_vr_little_endian(
+            odil::Tag(0x7fe0, 0x0010), odil::DataSet(),
+            odil::registry::ExplicitVRLittleEndian),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(PublicElement)
+{
+    odil::VRFinder finder;
+    auto const vr = finder(
+        odil::Tag(0x0010, 0x0010), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::PN);
+}
+
+BOOST_AUTO_TEST_CASE(GroupLengthElement)
+{
+    odil::VRFinder finder;
+    auto const vr = finder(
+        odil::Tag(0x0010, 0x0000), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UL);
+}
+
+BOOST_AUTO_TEST_CASE(PrivateElement)
+{
+    odil::VRFinder finder;
+    auto const vr = finder(
+        odil::Tag(0x0011, 0x0011), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::UN);
+}
+
+BOOST_AUTO_TEST_CASE(TSDependentElement)
+{
+    odil::VRFinder finder;
+    auto const vr = finder(
+        odil::Tag(0x7fe0, 0x0010), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian);
+    BOOST_REQUIRE(vr == odil::VR::OW);
+}
+
+BOOST_AUTO_TEST_CASE(UserFinder)
+{
+    bool called = false;
+    auto const my_finder_function =
+        [&called]
+        (
+            odil::Tag const & tag, odil::DataSet const & data_set,
+            std::string const & ts
+        )
+        {
+            return user_defined_finder(tag, data_set, ts, called);
+        };
+    odil::VRFinder finder;
+    finder.finders.push_back(my_finder_function);
+    finder(
+        odil::Tag(0xeeee, 0xeeee), odil::DataSet(),
+        odil::registry::ImplicitVRLittleEndian
+    );
+    BOOST_REQUIRE_EQUAL(called, true);
+}
+
+BOOST_AUTO_TEST_CASE(NoVRFound)
+{
+    odil::VRFinder finder;
+    BOOST_REQUIRE_THROW(
+        finder(
+            odil::Tag(0xcccc, 0xcccc), odil::DataSet(),
+            odil::registry::ImplicitVRLittleEndian
+        ), odil::Exception);
+}
diff --git a/tests/code/Value.cpp b/tests/code/Value.cpp
index d62017a..3365305 100644
--- a/tests/code/Value.cpp
+++ b/tests/code/Value.cpp
@@ -1,128 +1,128 @@
 #define BOOST_TEST_MODULE Value
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/Exception.h"
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/Exception.h"
 
 BOOST_AUTO_TEST_CASE(Empty)
 {
-    dcmtkpp::Value const value;
+    odil::Value const value;
 
-    BOOST_CHECK(value.get_type() == dcmtkpp::Value::Type::Empty);
-    BOOST_CHECK_THROW(value.as_integers(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_reals(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_strings(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_data_sets(), dcmtkpp::Exception);
+    BOOST_CHECK(value.get_type() == odil::Value::Type::Empty);
+    BOOST_CHECK_THROW(value.as_integers(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_reals(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_strings(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_data_sets(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(Integers)
 {
-    dcmtkpp::Value const value({1234});
+    odil::Value const value({1234});
 
-    BOOST_CHECK(value.get_type() == dcmtkpp::Value::Type::Integers);
-    BOOST_CHECK(value.as_integers() == dcmtkpp::Value::Integers({1234}));
+    BOOST_CHECK(value.get_type() == odil::Value::Type::Integers);
+    BOOST_CHECK(value.as_integers() == odil::Value::Integers({1234}));
 
-    BOOST_CHECK_THROW(value.as_reals(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_strings(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_data_sets(), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(value.as_reals(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_strings(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_data_sets(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(ModifyIntegers)
 {
-    dcmtkpp::Value value({1234});
+    odil::Value value({1234});
     value.as_integers().push_back(5678);
-    BOOST_CHECK(value.as_integers() == dcmtkpp::Value::Integers({1234, 5678}));
+    BOOST_CHECK(value.as_integers() == odil::Value::Integers({1234, 5678}));
 }
 
 BOOST_AUTO_TEST_CASE(Reals)
 {
-    dcmtkpp::Value const value({12.34});
+    odil::Value const value({12.34});
 
-    BOOST_CHECK(value.get_type() == dcmtkpp::Value::Type::Reals);
-    BOOST_CHECK(value.as_reals() == dcmtkpp::Value::Reals({12.34}));
+    BOOST_CHECK(value.get_type() == odil::Value::Type::Reals);
+    BOOST_CHECK(value.as_reals() == odil::Value::Reals({12.34}));
 
-    BOOST_CHECK_THROW(value.as_integers(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_strings(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_data_sets(), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(value.as_integers(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_strings(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_data_sets(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(ModifyReals)
 {
-    dcmtkpp::Value value({12.34});
+    odil::Value value({12.34});
     value.as_reals().push_back(56.78);
-    BOOST_CHECK(value.as_reals() == dcmtkpp::Value::Reals({12.34, 56.78}));
+    BOOST_CHECK(value.as_reals() == odil::Value::Reals({12.34, 56.78}));
 }
 
 BOOST_AUTO_TEST_CASE(Strings)
 {
-    dcmtkpp::Value const value({"foo"});
+    odil::Value const value({"foo"});
 
-    BOOST_CHECK(value.get_type() == dcmtkpp::Value::Type::Strings);
-    BOOST_CHECK(value.as_strings() == dcmtkpp::Value::Strings({"foo"}));
+    BOOST_CHECK(value.get_type() == odil::Value::Type::Strings);
+    BOOST_CHECK(value.as_strings() == odil::Value::Strings({"foo"}));
 
-    BOOST_CHECK_THROW(value.as_integers(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_reals(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_data_sets(), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(value.as_integers(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_reals(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_data_sets(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(ModifyStrings)
 {
-    dcmtkpp::Value value({"foo"});
+    odil::Value value({"foo"});
     value.as_strings().push_back("bar");
-    BOOST_CHECK(value.as_strings() == dcmtkpp::Value::Strings({"foo", "bar"}));
+    BOOST_CHECK(value.as_strings() == odil::Value::Strings({"foo", "bar"}));
 }
 
 BOOST_AUTO_TEST_CASE(DataSets)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add("PatientID", {"DJ1234"});
-    dcmtkpp::Value const value({data_set});
+    odil::Value const value({data_set});
 
-    BOOST_CHECK(value.get_type() == dcmtkpp::Value::Type::DataSets);
+    BOOST_CHECK(value.get_type() == odil::Value::Type::DataSets);
     BOOST_CHECK_EQUAL(value.as_data_sets().size(), 1);
     BOOST_CHECK(value.as_data_sets()[0].has("PatientID"));
     BOOST_CHECK(
         value.as_data_sets()[0].as_string("PatientID") ==
-            dcmtkpp::Value::Strings({"DJ1234"}));
+            odil::Value::Strings({"DJ1234"}));
 
-    BOOST_CHECK_THROW(value.as_integers(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_strings(), dcmtkpp::Exception);
-    BOOST_CHECK_THROW(value.as_reals(), dcmtkpp::Exception);
+    BOOST_CHECK_THROW(value.as_integers(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_strings(), odil::Exception);
+    BOOST_CHECK_THROW(value.as_reals(), odil::Exception);
 }
 
 BOOST_AUTO_TEST_CASE(ModifyDataSets)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add("PatientID");
     data_set.as_string("PatientID").push_back("DJ1234");
-    dcmtkpp::Value value({data_set});
+    odil::Value value({data_set});
 
     value.as_data_sets()[0].as_string("PatientID")[0] = "XXX";
 
-    BOOST_CHECK(value.get_type() == dcmtkpp::Value::Type::DataSets);
+    BOOST_CHECK(value.get_type() == odil::Value::Type::DataSets);
     BOOST_CHECK_EQUAL(value.as_data_sets().size(), 1);
     BOOST_CHECK(value.as_data_sets()[0].has("PatientID"));
     BOOST_CHECK(
         value.as_data_sets()[0].as_string("PatientID") ==
-            dcmtkpp::Value::Strings({"XXX"}));
+            odil::Value::Strings({"XXX"}));
 }
 
 BOOST_AUTO_TEST_CASE(EqualityEmpty)
 {
-    dcmtkpp::Value const value1;
-    dcmtkpp::Value const value2;
-    dcmtkpp::Value const value3((dcmtkpp::Value::Integers()));
+    odil::Value const value1;
+    odil::Value const value2;
+    odil::Value const value3((odil::Value::Integers()));
     BOOST_CHECK(value1 == value2);
     BOOST_CHECK( ! (value1 == value3));
 }
 
 BOOST_AUTO_TEST_CASE(EqualityIntegers)
 {
-    dcmtkpp::Value const value1({1,2});
-    dcmtkpp::Value const value2({1,2});
-    dcmtkpp::Value const value3({3,4});
-    dcmtkpp::Value const value4({3,4});
+    odil::Value const value1({1,2});
+    odil::Value const value2({1,2});
+    odil::Value const value3({3,4});
+    odil::Value const value4({3,4});
     BOOST_CHECK(value1 == value2);
     BOOST_CHECK( ! (value1 == value3));
     BOOST_CHECK( ! (value1 == value4));
@@ -130,10 +130,10 @@ BOOST_AUTO_TEST_CASE(EqualityIntegers)
 
 BOOST_AUTO_TEST_CASE(EqualityReals)
 {
-    dcmtkpp::Value const value1({1,2});
-    dcmtkpp::Value const value2({1,2});
-    dcmtkpp::Value const value3({3,4});
-    dcmtkpp::Value const value4({3,4});
+    odil::Value const value1({1,2});
+    odil::Value const value2({1,2});
+    odil::Value const value3({3,4});
+    odil::Value const value4({3,4});
     BOOST_CHECK(value1 == value2);
     BOOST_CHECK( ! (value1 == value3));
     BOOST_CHECK( ! (value1 == value4));
@@ -141,10 +141,10 @@ BOOST_AUTO_TEST_CASE(EqualityReals)
 
 BOOST_AUTO_TEST_CASE(EqualityStrings)
 {
-    dcmtkpp::Value const value1({"1","2"});
-    dcmtkpp::Value const value2({"1","2"});
-    dcmtkpp::Value const value3({"3","4"});
-    dcmtkpp::Value const value4({3,4});
+    odil::Value const value1({"1","2"});
+    odil::Value const value2({"1","2"});
+    odil::Value const value3({"3","4"});
+    odil::Value const value4({3,4});
     BOOST_CHECK(value1 == value2);
     BOOST_CHECK( ! (value1 == value3));
     BOOST_CHECK( ! (value1 == value4));
@@ -152,18 +152,18 @@ BOOST_AUTO_TEST_CASE(EqualityStrings)
 
 BOOST_AUTO_TEST_CASE(EqualityDataSets)
 {
-    dcmtkpp::DataSet dataset1;
+    odil::DataSet dataset1;
     dataset1.add("PatientID", {"DJ1234"});
     dataset1.add("PixelSpacing", {1.5, 2.5});
 
-    dcmtkpp::DataSet dataset2;
+    odil::DataSet dataset2;
     dataset1.add("PatientName", {"Doe^John"});
     dataset1.add("PatientAge", {"042Y"});
 
-    dcmtkpp::Value const value1({dataset1});
-    dcmtkpp::Value const value2({dataset1});
-    dcmtkpp::Value const value3({dataset2});
-    dcmtkpp::Value const value4({3,4});
+    odil::Value const value1({dataset1});
+    odil::Value const value2({dataset1});
+    odil::Value const value3({dataset2});
+    odil::Value const value4({3,4});
     BOOST_CHECK(value1 == value2);
     BOOST_CHECK( ! (value1 == value3));
     BOOST_CHECK( ! (value1 == value4));
@@ -171,19 +171,19 @@ BOOST_AUTO_TEST_CASE(EqualityDataSets)
 
 BOOST_AUTO_TEST_CASE(DifferenceEmpty)
 {
-    dcmtkpp::Value const value1;
-    dcmtkpp::Value const value2;
-    dcmtkpp::Value const value3((dcmtkpp::Value::Integers()));
+    odil::Value const value1;
+    odil::Value const value2;
+    odil::Value const value3((odil::Value::Integers()));
     BOOST_CHECK(! (value1 != value2));
     BOOST_CHECK(value1 != value3);
 }
 
 BOOST_AUTO_TEST_CASE(DifferenceIntegers)
 {
-    dcmtkpp::Value const value1({1,2});
-    dcmtkpp::Value const value2({1,2});
-    dcmtkpp::Value const value3({3,4});
-    dcmtkpp::Value const value4({3,4});
+    odil::Value const value1({1,2});
+    odil::Value const value2({1,2});
+    odil::Value const value3({3,4});
+    odil::Value const value4({3,4});
     BOOST_CHECK(! (value1 != value2));
     BOOST_CHECK(value1 != value3);
     BOOST_CHECK(value1 != value4);
@@ -191,10 +191,10 @@ BOOST_AUTO_TEST_CASE(DifferenceIntegers)
 
 BOOST_AUTO_TEST_CASE(DifferenceReals)
 {
-    dcmtkpp::Value const value1({1.,2.});
-    dcmtkpp::Value const value2({1.,2.});
-    dcmtkpp::Value const value3({3.,4.});
-    dcmtkpp::Value const value4({3,4});
+    odil::Value const value1({1.,2.});
+    odil::Value const value2({1.,2.});
+    odil::Value const value3({3.,4.});
+    odil::Value const value4({3,4});
     BOOST_CHECK(! (value1 != value2));
     BOOST_CHECK(value1 != value3);
     BOOST_CHECK(value1 != value4);
@@ -202,10 +202,10 @@ BOOST_AUTO_TEST_CASE(DifferenceReals)
 
 BOOST_AUTO_TEST_CASE(DifferenceStrings)
 {
-    dcmtkpp::Value const value1({"1","2"});
-    dcmtkpp::Value const value2({"1","2"});
-    dcmtkpp::Value const value3({"3","4"});
-    dcmtkpp::Value const value4({3,4});
+    odil::Value const value1({"1","2"});
+    odil::Value const value2({"1","2"});
+    odil::Value const value3({"3","4"});
+    odil::Value const value4({3,4});
     BOOST_CHECK(! (value1 != value2));
     BOOST_CHECK(value1 != value3);
     BOOST_CHECK(value1 != value4);
@@ -213,18 +213,18 @@ BOOST_AUTO_TEST_CASE(DifferenceStrings)
 
 BOOST_AUTO_TEST_CASE(DifferenceDataSets)
 {
-    dcmtkpp::DataSet dataset1;
+    odil::DataSet dataset1;
     dataset1.add("PatientID", {"DJ1234"});
     dataset1.add("PixelSpacing", {1.5, 2.5});
 
-    dcmtkpp::DataSet dataset2;
+    odil::DataSet dataset2;
     dataset1.add("PatientName", {"Doe^John"});
     dataset1.add("PatientAge", {"042Y"});
 
-    dcmtkpp::Value const value1({dataset1});
-    dcmtkpp::Value const value2({dataset1});
-    dcmtkpp::Value const value3({dataset2});
-    dcmtkpp::Value const value4({3,4});
+    odil::Value const value1({dataset1});
+    odil::Value const value2({dataset1});
+    odil::Value const value3({dataset2});
+    odil::Value const value4({3,4});
     BOOST_CHECK(! (value1 != value2));
     BOOST_CHECK(value1 != value3);
     BOOST_CHECK(value1 != value4);
@@ -232,19 +232,59 @@ BOOST_AUTO_TEST_CASE(DifferenceDataSets)
 
 struct Visitor
 {
-    typedef bool result_type;
+    typedef std::string result_type;
 
     template<typename T>
-    bool operator()(T const & container) const
+    result_type operator()(T const & container) const
     {
-        return true;
+        return typeid(container).name();
     }
 };
 
 BOOST_AUTO_TEST_CASE(VisitorEmpty)
 {
-    dcmtkpp::Value const value;
+    odil::Value const value;
     BOOST_CHECK_THROW(
-        dcmtkpp::apply_visitor(Visitor(), value),
-        dcmtkpp::Exception);
+        odil::apply_visitor(Visitor(), value),
+        odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(VisitorIntegers)
+{
+    odil::Value const value({1234});
+    BOOST_REQUIRE_EQUAL(
+        odil::apply_visitor(Visitor(), value),
+        typeid(odil::Value::Integers).name());
+}
+
+BOOST_AUTO_TEST_CASE(VisitorReals)
+{
+    odil::Value const value({12.34});
+    BOOST_REQUIRE_EQUAL(
+        odil::apply_visitor(Visitor(), value),
+        typeid(odil::Value::Reals).name());
+}
+
+BOOST_AUTO_TEST_CASE(VisitorStrings)
+{
+    odil::Value const value({"foo"});
+    BOOST_REQUIRE_EQUAL(
+        odil::apply_visitor(Visitor(), value),
+        typeid(odil::Value::Strings).name());
+}
+
+BOOST_AUTO_TEST_CASE(VisitorDataSets)
+{
+    odil::Value const value({odil::DataSet()});
+    BOOST_REQUIRE_EQUAL(
+        odil::apply_visitor(Visitor(), value),
+        typeid(odil::Value::DataSets).name());
+}
+
+BOOST_AUTO_TEST_CASE(VisitorBinary)
+{
+    odil::Value const value((odil::Value::Binary()));
+    BOOST_REQUIRE_EQUAL(
+        odil::apply_visitor(Visitor(), value),
+        typeid(odil::Value::Binary).name());
 }
diff --git a/tests/code/Writer.cpp b/tests/code/Writer.cpp
index 0c86f75..dc6a28b 100644
--- a/tests/code/Writer.cpp
+++ b/tests/code/Writer.cpp
@@ -1,44 +1,44 @@
 #define BOOST_TEST_MODULE Writer
 #include <boost/test/unit_test.hpp>
 
-#include <endian.h>
 #include <sstream>
 
 #include <dcmtk/config/osconfig.h>
 #include <dcmtk/dcmdata/dctk.h>
 #include <dcmtk/dcmdata/dcistrmb.h>
 
-#include "dcmtkpp/conversion.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Writer.h"
-#include "dcmtkpp/VR.h"
+#include "odil/endian.h"
+#include "odil/Element.h"
+#include "odil/registry.h"
+#include "odil/Writer.h"
+#include "odil/VR.h"
+#include "odil/dcmtk/conversion.h"
 
 BOOST_AUTO_TEST_CASE(Constructor)
 {
     std::ostringstream stream;
-    dcmtkpp::Writer const writer(stream, BIG_ENDIAN, true);
-    BOOST_REQUIRE(writer.byte_ordering == BIG_ENDIAN);
+    odil::Writer const writer(stream, odil::ByteOrdering::BigEndian, true);
+    BOOST_REQUIRE(writer.byte_ordering == odil::ByteOrdering::BigEndian);
     BOOST_REQUIRE(writer.explicit_vr == true);
 }
 
 BOOST_AUTO_TEST_CASE(ConstructorTransferSyntax)
 {
     std::ostringstream stream;
-    dcmtkpp::Writer const writer(stream, dcmtkpp::registry::ExplicitVRBigEndian_Retired);
-    BOOST_REQUIRE(writer.byte_ordering == BIG_ENDIAN);
+    odil::Writer const writer(stream, odil::registry::ExplicitVRBigEndian_Retired);
+    BOOST_REQUIRE(writer.byte_ordering == odil::ByteOrdering::BigEndian);
     BOOST_REQUIRE(writer.explicit_vr == true);
 }
 
 void do_test(
-    dcmtkpp::DataSet const & dcmtkpp_data_set, std::string transfer_syntax,
-    dcmtkpp::Writer::ItemEncoding item_encoding, bool use_group_length)
+    odil::DataSet const & odil_data_set, std::string transfer_syntax,
+    odil::Writer::ItemEncoding item_encoding, bool use_group_length)
 {
     // Write input data set
     std::stringstream stream;
-    dcmtkpp::Writer const writer(
+    odil::Writer const writer(
         stream, transfer_syntax, item_encoding, use_group_length);
-    writer.write_data_set(dcmtkpp_data_set);
+    writer.write_data_set(odil_data_set);
 
     // Store data in a DCMTK stream
     std::string const data = stream.str();
@@ -54,22 +54,22 @@ void do_test(
     BOOST_REQUIRE_EQUAL(dcmtk_stream.avail(), 0);
     dcmtk_data_set.transferEnd();
 
-    auto const other_dcmtkpp_dataset = dcmtkpp::convert(&dcmtk_data_set);
-    BOOST_REQUIRE(dcmtkpp_data_set == other_dcmtkpp_dataset);
+    auto const other_odil_dataset = odil::dcmtk::convert(&dcmtk_data_set);
+    BOOST_REQUIRE(odil_data_set == other_odil_dataset);
 }
 
-void do_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
+void do_test(odil::DataSet const & odil_data_set)
 {
     std::vector<std::string> const transfer_syntaxes =
         {
-            dcmtkpp::registry::ImplicitVRLittleEndian,
-            dcmtkpp::registry::ExplicitVRLittleEndian,
-            dcmtkpp::registry::ExplicitVRBigEndian_Retired
+            odil::registry::ImplicitVRLittleEndian,
+            odil::registry::ExplicitVRLittleEndian,
+            odil::registry::ExplicitVRBigEndian_Retired
         };
-    std::vector<dcmtkpp::Writer::ItemEncoding> item_encodings =
+    std::vector<odil::Writer::ItemEncoding> item_encodings =
         {
-            dcmtkpp::Writer::ItemEncoding::ExplicitLength,
-            dcmtkpp::Writer::ItemEncoding::UndefinedLength
+            odil::Writer::ItemEncoding::ExplicitLength,
+            odil::Writer::ItemEncoding::UndefinedLength
         };
     std::vector<bool> use_group_length_values = {/*true, */false};
 
@@ -80,164 +80,173 @@ void do_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
             for(auto const & use_group_length: use_group_length_values)
             {
                 do_test(
-                    dcmtkpp_data_set, transfer_syntax,
+                    odil_data_set, transfer_syntax,
                     item_encoding, use_group_length);
             }
         }
     }
 }
 
+BOOST_AUTO_TEST_CASE(AT)
+{
+    odil::Element odil_element({"00100020", "0008103e"}, odil::VR::AT);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorATValue, odil_element);
+
+    do_test(odil_data_set);
+}
+
 BOOST_AUTO_TEST_CASE(CS)
 {
-    dcmtkpp::Element dcmtkpp_element({"ABC", "DEF"}, dcmtkpp::VR::CS);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorCSValue, dcmtkpp_element);
+    odil::Element odil_element({"ABC", "DEF"}, odil::VR::CS);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorCSValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(DS)
 {
-    dcmtkpp::Element dcmtkpp_element({1.23, -4.56}, dcmtkpp::VR::DS);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorDSValue, dcmtkpp_element);
+    odil::Element odil_element({1.23, -4.56}, odil::VR::DS);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorDSValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(FD)
 {
-    dcmtkpp::Element dcmtkpp_element({1.23, -4.56}, dcmtkpp::VR::FD);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorFDValue, dcmtkpp_element);
+    odil::Element odil_element({1.23, -4.56}, odil::VR::FD);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorFDValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(FL)
 {
-    dcmtkpp::Element dcmtkpp_element({0.5, -0.125}, dcmtkpp::VR::FL);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorFLValue, dcmtkpp_element);
+    odil::Element odil_element({0.5, -0.125}, odil::VR::FL);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorFLValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(IS)
 {
-    dcmtkpp::Element dcmtkpp_element({123, -456}, dcmtkpp::VR::IS);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorISValue, dcmtkpp_element);
+    odil::Element odil_element({123, -456}, odil::VR::IS);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorISValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(OB)
 {
-    dcmtkpp::Element dcmtkpp_element(
+    odil::Element odil_element(
         std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04}),
-        dcmtkpp::VR::OB);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::EncapsulatedDocument, dcmtkpp_element);
+        odil::VR::OB);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::EncapsulatedDocument, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(OF)
 {
-    dcmtkpp::Element dcmtkpp_element(
+    odil::Element odil_element(
         std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}),
-        dcmtkpp::VR::OF);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::VectorGridData, dcmtkpp_element);
+        odil::VR::OF);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::VectorGridData, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(OW)
 {
-    dcmtkpp::Element dcmtkpp_element(
+    odil::Element odil_element(
         std::vector<uint8_t>({0x01, 0x02, 0x03, 0x04}),
-        dcmtkpp::VR::OW);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::RedPaletteColorLookupTableData, dcmtkpp_element);
+        odil::VR::OW);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::RedPaletteColorLookupTableData, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(SL)
 {
-    dcmtkpp::Element dcmtkpp_element({12345678, -8765432}, dcmtkpp::VR::SL);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorSLValue, dcmtkpp_element);
+    odil::Element odil_element({12345678, -8765432}, odil::VR::SL);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorSLValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(SQ)
 {
-    dcmtkpp::DataSet item1;
+    odil::DataSet item1;
     item1.add(
-        dcmtkpp::registry::SelectorSLValue,
-        dcmtkpp::Element({12345678, -8765432}, dcmtkpp::VR::SL));
-    dcmtkpp::DataSet item2;
+        odil::registry::SelectorSLValue,
+        odil::Element({12345678, -8765432}, odil::VR::SL));
+    odil::DataSet item2;
     item2.add(
-        dcmtkpp::registry::SelectorFDValue,
-        dcmtkpp::Element({1.23, -4.56}, dcmtkpp::VR::FD));
+        odil::registry::SelectorFDValue,
+        odil::Element({1.23, -4.56}, odil::VR::FD));
 
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::FrameExtractionSequence,
-        dcmtkpp::Element({item1, item2}, dcmtkpp::VR::SQ));
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::FrameExtractionSequence,
+        odil::Element({item1, item2}, odil::VR::SQ));
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(SS)
 {
-    dcmtkpp::Element dcmtkpp_element({1234, -5678}, dcmtkpp::VR::SS);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorSSValue, dcmtkpp_element);
+    odil::Element odil_element({1234, -5678}, odil::VR::SS);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorSSValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(UI)
 {
-    dcmtkpp::Element dcmtkpp_element({"1.2", "3.4"}, dcmtkpp::VR::UI);
-    dcmtkpp::DataSet dcmtkpp_data_set;
+    odil::Element odil_element({"1.2", "3.4"}, odil::VR::UI);
+    odil::DataSet odil_data_set;
     // SelectorUIValue is not in current DCMTK
-    dcmtkpp_data_set.add(dcmtkpp::registry::SOPInstanceUID, dcmtkpp_element);
+    odil_data_set.add(odil::registry::SOPInstanceUID, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(UL)
 {
-    dcmtkpp::Element dcmtkpp_element({12345678, 8765432}, dcmtkpp::VR::UL);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorULValue, dcmtkpp_element);
+    odil::Element odil_element({12345678, 8765432}, odil::VR::UL);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorULValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 BOOST_AUTO_TEST_CASE(US)
 {
-    dcmtkpp::Element dcmtkpp_element({1234, 5678}, dcmtkpp::VR::US);
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(dcmtkpp::registry::SelectorUSValue, dcmtkpp_element);
+    odil::Element odil_element({1234, 5678}, odil::VR::US);
+    odil::DataSet odil_data_set;
+    odil_data_set.add(odil::registry::SelectorUSValue, odil_element);
 
-    do_test(dcmtkpp_data_set);
+    do_test(odil_data_set);
 }
 
 void do_file_test(
-    dcmtkpp::DataSet const & dcmtkpp_data_set, std::string transfer_syntax,
-    dcmtkpp::Writer::ItemEncoding item_encoding, bool use_group_length)
+    odil::DataSet const & odil_data_set, std::string transfer_syntax,
+    odil::Writer::ItemEncoding item_encoding, bool use_group_length)
 {
     // Write input data set
     std::stringstream stream;
 
-    dcmtkpp::Writer::write_file(
-        dcmtkpp_data_set, stream, transfer_syntax,
+    odil::Writer::write_file(
+        odil_data_set, stream, odil::DataSet(), transfer_syntax,
         item_encoding, use_group_length);
 
     // Store data in a DCMTK stream
@@ -255,35 +264,36 @@ void do_file_test(
     format.transferEnd();
 
     DcmMetaInfo * dcmtk_meta_information = format.getMetaInfo();
-    auto const dcmtkpp_meta_information = dcmtkpp::convert(dcmtk_meta_information);
+    auto const odil_meta_information =
+        odil::dcmtk::convert(dcmtk_meta_information);
     BOOST_REQUIRE(
-        dcmtkpp_meta_information.as_string(dcmtkpp::registry::TransferSyntaxUID) ==
-            dcmtkpp::Value::Strings({ transfer_syntax }));
+        odil_meta_information.as_string(odil::registry::TransferSyntaxUID) ==
+            odil::Value::Strings({ transfer_syntax }));
 
     DcmDataset * dcmtk_data_set = format.getDataset();
-    auto const other_dcmtkpp_dataset = dcmtkpp::convert(dcmtk_data_set);
-    BOOST_REQUIRE(dcmtkpp_data_set == other_dcmtkpp_dataset);
+    auto const other_odil_dataset = odil::dcmtk::convert(dcmtk_data_set);
+    BOOST_REQUIRE(odil_data_set == other_odil_dataset);
 
     BOOST_REQUIRE(
-        dcmtkpp_meta_information.as_string(dcmtkpp::registry::MediaStorageSOPClassUID) ==
-            other_dcmtkpp_dataset.as_string(dcmtkpp::registry::SOPClassUID));
+        odil_meta_information.as_string(odil::registry::MediaStorageSOPClassUID) ==
+            other_odil_dataset.as_string(odil::registry::SOPClassUID));
     BOOST_REQUIRE(
-        dcmtkpp_meta_information.as_string(dcmtkpp::registry::MediaStorageSOPInstanceUID) ==
-            other_dcmtkpp_dataset.as_string(dcmtkpp::registry::SOPInstanceUID));
+        odil_meta_information.as_string(odil::registry::MediaStorageSOPInstanceUID) ==
+            other_odil_dataset.as_string(odil::registry::SOPInstanceUID));
 }
 
-void do_file_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
+void do_file_test(odil::DataSet const & odil_data_set)
 {
     std::vector<std::string> const transfer_syntaxes =
         {
-            dcmtkpp::registry::ImplicitVRLittleEndian,
-            dcmtkpp::registry::ExplicitVRLittleEndian,
-            dcmtkpp::registry::ExplicitVRBigEndian_Retired
+            odil::registry::ImplicitVRLittleEndian,
+            odil::registry::ExplicitVRLittleEndian,
+            odil::registry::ExplicitVRBigEndian_Retired
         };
-    std::vector<dcmtkpp::Writer::ItemEncoding> item_encodings =
+    std::vector<odil::Writer::ItemEncoding> item_encodings =
         {
-            dcmtkpp::Writer::ItemEncoding::ExplicitLength,
-            dcmtkpp::Writer::ItemEncoding::UndefinedLength
+            odil::Writer::ItemEncoding::ExplicitLength,
+            odil::Writer::ItemEncoding::UndefinedLength
         };
     std::vector<bool> use_group_length_values = {/*true, */false};
 
@@ -294,7 +304,7 @@ void do_file_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
             for(auto const & use_group_length: use_group_length_values)
             {
                 do_file_test(
-                    dcmtkpp_data_set, transfer_syntax,
+                    odil_data_set, transfer_syntax,
                     item_encoding, use_group_length);
             }
         }
@@ -303,25 +313,25 @@ void do_file_test(dcmtkpp::DataSet const & dcmtkpp_data_set)
 
 BOOST_AUTO_TEST_CASE(File)
 {
-    dcmtkpp::DataSet item1;
+    odil::DataSet item1;
     item1.add(
-        dcmtkpp::registry::SelectorSLValue,
-        dcmtkpp::Element({12345678, -8765432}, dcmtkpp::VR::SL));
-    dcmtkpp::DataSet item2;
+        odil::registry::SelectorSLValue,
+        odil::Element({12345678, -8765432}, odil::VR::SL));
+    odil::DataSet item2;
     item2.add(
-        dcmtkpp::registry::SelectorFDValue,
-        dcmtkpp::Element({1.23, -4.56}, dcmtkpp::VR::FD));
-
-    dcmtkpp::DataSet dcmtkpp_data_set;
-    dcmtkpp_data_set.add(
-        dcmtkpp::registry::SOPClassUID,
-        {dcmtkpp::registry::RawDataStorage}, dcmtkpp::VR::UI);
-    dcmtkpp_data_set.add(
-        dcmtkpp::registry::SOPInstanceUID,
-        {"1.2.3.4"}, dcmtkpp::VR::UI);
-    dcmtkpp_data_set.add(
-        dcmtkpp::registry::FrameExtractionSequence,
-        dcmtkpp::Element({item1, item2}, dcmtkpp::VR::SQ));
-
-    do_file_test(dcmtkpp_data_set);
+        odil::registry::SelectorFDValue,
+        odil::Element({1.23, -4.56}, odil::VR::FD));
+
+    odil::DataSet odil_data_set;
+    odil_data_set.add(
+        odil::registry::SOPClassUID,
+        {odil::registry::RawDataStorage}, odil::VR::UI);
+    odil_data_set.add(
+        odil::registry::SOPInstanceUID,
+        {"1.2.3.4"}, odil::VR::UI);
+    odil_data_set.add(
+        odil::registry::FrameExtractionSequence,
+        odil::Element({item1, item2}, odil::VR::SQ));
+
+    do_file_test(odil_data_set);
 }
diff --git a/tests/code/base64.cpp b/tests/code/base64.cpp
new file mode 100644
index 0000000..96830f2
--- /dev/null
+++ b/tests/code/base64.cpp
@@ -0,0 +1,75 @@
+#define BOOST_TEST_MODULE Base64
+#include <boost/test/unit_test.hpp>
+
+#include <iterator>
+#include <string>
+
+#include "odil/base64.h"
+
+void test_encode(std::string const & data, std::string const & expected)
+{
+    std::string encoded;
+    encoded.reserve(data.size()*4/3);
+
+    odil::base64::encode(
+        data.begin(), data.end(), std::back_inserter(encoded));
+
+    BOOST_REQUIRE_EQUAL(encoded, expected);
+}
+
+void test_decode(std::string const & data, std::string const & expected)
+{
+    std::string decoded;
+    decoded.reserve(data.size()*3/4);
+
+    odil::base64::decode(
+        data.begin(), data.end(), std::back_inserter(decoded));
+
+    BOOST_REQUIRE_EQUAL(decoded, expected);
+}
+
+BOOST_AUTO_TEST_CASE(EncodeEmpty)
+{
+    test_encode("", "");
+}
+
+BOOST_AUTO_TEST_CASE(Encode1)
+{
+    test_encode(
+        "Lorem ipsum dolor sit amet.", "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQu");
+}
+
+BOOST_AUTO_TEST_CASE(Encode2)
+{
+    test_encode(
+        "Lorem ipsum dolor sit amet", "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ=");
+}
+
+BOOST_AUTO_TEST_CASE(Encode3)
+{
+    test_encode(
+        "Lorem ipsum dolor sit ame", "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZQ==");
+}
+
+BOOST_AUTO_TEST_CASE(DecodeEmpty)
+{
+    test_decode("", "");
+}
+
+BOOST_AUTO_TEST_CASE(Decode1)
+{
+    test_decode(
+        "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQu", "Lorem ipsum dolor sit amet.");
+}
+
+BOOST_AUTO_TEST_CASE(Decode2)
+{
+    test_decode(
+        "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ=", "Lorem ipsum dolor sit amet");
+}
+
+BOOST_AUTO_TEST_CASE(Decode3)
+{
+    test_decode(
+        "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZQ==", "Lorem ipsum dolor sit ame");
+}
diff --git a/tests/code/conversion.cpp b/tests/code/conversion.cpp
index 109c2cb..7510479 100644
--- a/tests/code/conversion.cpp
+++ b/tests/code/conversion.cpp
@@ -4,18 +4,18 @@
 #include <dcmtk/config/osconfig.h>
 #include <dcmtk/dcmdata/dctk.h>
 
-#include "dcmtkpp/conversion.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/ElementAccessor.h"
-#include "dcmtkpp/Tag.h"
-#include "dcmtkpp/Value.h"
-#include "dcmtkpp/VR.h"
-#include "dcmtkpp/VRTraits.h"
+#include "odil/DataSet.h"
+#include "odil/Tag.h"
+#include "odil/Value.h"
+#include "odil/VR.h"
+#include "odil/dcmtk/VRTraits.h"
+#include "odil/dcmtk/conversion.h"
+#include "odil/dcmtk/ElementAccessor.h"
 
 BOOST_AUTO_TEST_CASE(TagFromDcmtkpp)
 {
-    dcmtkpp::Tag const source(0xdead, 0xbeef);
-    DcmTagKey const destination = dcmtkpp::convert(source);
+    odil::Tag const source(0xdead, 0xbeef);
+    DcmTagKey const destination = odil::dcmtk::convert(source);
 
     BOOST_CHECK_EQUAL(destination.getGroup(), 0xdead);
     BOOST_CHECK_EQUAL(destination.getElement(), 0xbeef);
@@ -24,7 +24,7 @@ BOOST_AUTO_TEST_CASE(TagFromDcmtkpp)
 BOOST_AUTO_TEST_CASE(TagFromDcmtk)
 {
     DcmTagKey const source(0xdead, 0xbeef);
-    dcmtkpp::Tag const destination = dcmtkpp::convert(source);
+    odil::Tag const destination = odil::dcmtk::convert(source);
 
     BOOST_CHECK_EQUAL(destination.group, 0xdead);
     BOOST_CHECK_EQUAL(destination.element, 0xbeef);
@@ -37,24 +37,24 @@ void compare(TValueType const & t1, TValueType const & t2)
 }
 
 template<>
-void compare<dcmtkpp::Value::Reals::value_type>(
-    dcmtkpp::Value::Reals::value_type const & t1,
-    dcmtkpp::Value::Reals::value_type const & t2)
+void compare<odil::Value::Reals::value_type>(
+    odil::Value::Reals::value_type const & t1,
+    odil::Value::Reals::value_type const & t2)
 {
     BOOST_CHECK_CLOSE(t1, t2, 1e-6);
 }
 
-template<dcmtkpp::VR VVR, DcmEVR VEVR, typename TInputType, typename TOutputType>
-void test_element_from_dcmtkpp(
+template<odil::VR VVR, DcmEVR VEVR, typename TInputType, typename TOutputType>
+void test_element_from_odil(
     TInputType const & source_value,
-    TInputType const & (dcmtkpp::Element::*getter)() const)
+    TInputType const & (odil::Element::*getter)() const)
 {
-    dcmtkpp::Tag const source_tag(0xdead, 0xbeef);
-    DcmTagKey const destination_tag = dcmtkpp::convert(source_tag);
+    odil::Tag const source_tag(0xdead, 0xbeef);
+    DcmTagKey const destination_tag = odil::dcmtk::convert(source_tag);
 
-    dcmtkpp::Element const source(source_value, VVR);
+    odil::Element const source(source_value, VVR);
 
-    DcmElement * destination = dcmtkpp::convert(source_tag, source);
+    DcmElement * destination = odil::dcmtk::convert(source_tag, source);
     BOOST_CHECK_NE(destination, (DcmElement const *)(NULL));
 
     BOOST_CHECK_EQUAL(destination->getVR(), VEVR);
@@ -64,30 +64,30 @@ void test_element_from_dcmtkpp(
     BOOST_CHECK_EQUAL(destination->getVM(), source.size());
     for(std::size_t i=0; i<source.size(); ++i)
     {
-        typedef typename dcmtkpp::VRTraits<VEVR>::ValueType ValueType;
-        if(typeid(TInputType) == typeid(dcmtkpp::Value::Reals))
+        typedef typename odil::dcmtk::VRTraits<VEVR>::ValueType ValueType;
+        if(typeid(TInputType) == typeid(odil::Value::Reals))
         {
             compare<ValueType>(
-                dcmtkpp::ElementAccessor<ValueType>::element_get(*destination, i),
+                odil::dcmtk::ElementAccessor<ValueType>::element_get(*destination, i),
                 (source.*getter)()[i]);
         }
         else
         {
             BOOST_CHECK_EQUAL(
-                dcmtkpp::ElementAccessor<ValueType>::element_get(*destination, i),
+                odil::dcmtk::ElementAccessor<ValueType>::element_get(*destination, i),
                 (source.*getter)()[i]);
         }
     }
 }
 
-template<dcmtkpp::VR VVR, DcmEVR VEVR, typename TInputType, typename TElementType>
-void test_element_to_dcmtkpp(
+template<odil::VR VVR, DcmEVR VEVR, typename TInputType, typename TElementType>
+void test_element_to_odil(
     TInputType const & source_value,
-    TInputType const & (dcmtkpp::Element::*getter)() const)
+    TInputType const & (odil::Element::*getter)() const)
 {
     DcmTag const source_tag(0xdead, 0xbeef, VEVR);
     TElementType source(source_tag);
-    if(typeid(TInputType) == typeid(dcmtkpp::Value::Strings) ||
+    if(typeid(TInputType) == typeid(odil::Value::Strings) ||
         VEVR == EVR_IS || VEVR == EVR_DS)
     {
         OFString value;
@@ -117,156 +117,156 @@ void test_element_to_dcmtkpp(
         for(unsigned int i=0; i<source_value.size(); ++i)
         {
             auto const & item = source_value[i];
-            dcmtkpp::ElementAccessor<typename dcmtkpp::VRTraits<VEVR>::ValueType>::element_set(
+            odil::dcmtk::ElementAccessor<typename odil::dcmtk::VRTraits<VEVR>::ValueType>::element_set(
                 source, item, i);
         }
     }
 
-    dcmtkpp::Element const destination = dcmtkpp::convert(&source);
+    odil::Element const destination = odil::dcmtk::convert(&source);
 
     BOOST_CHECK(VVR == destination.vr);
     BOOST_CHECK_EQUAL(source.getVM(), destination.size());
     for(std::size_t i=0; i<destination.size(); ++i)
     {
-        typedef typename dcmtkpp::VRTraits<VEVR>::ValueType ValueType;
-        if(typeid(TInputType) == typeid(dcmtkpp::Value::Reals))
+        typedef typename odil::dcmtk::VRTraits<VEVR>::ValueType ValueType;
+        if(typeid(TInputType) == typeid(odil::Value::Reals))
         {
             compare<ValueType>(
-                dcmtkpp::ElementAccessor<ValueType>::element_get(source, i),
+                odil::dcmtk::ElementAccessor<ValueType>::element_get(source, i),
                 (destination.*getter)()[i]);
         }
         else
         {
             BOOST_CHECK_EQUAL(
-                dcmtkpp::ElementAccessor<ValueType>::element_get(source, i),
+                odil::dcmtk::ElementAccessor<ValueType>::element_get(source, i),
                 (destination.*getter)()[i]);
         }
     }
 }
 
 #define ElementTest(vr, InputType, ElementType, value, getter) \
-BOOST_AUTO_TEST_CASE(vr##FromDcmtkpp) \
+BOOST_AUTO_TEST_CASE(vr##FromOdil) \
 { \
-    test_element_from_dcmtkpp< \
-        dcmtkpp::VR::vr, EVR_##vr, InputType, ElementType>(value, getter); \
+    test_element_from_odil< \
+        odil::VR::vr, EVR_##vr, InputType, ElementType>(value, getter); \
 } \
-BOOST_AUTO_TEST_CASE(vr##ToDcmtkpp) \
+BOOST_AUTO_TEST_CASE(vr##ToOdil) \
 { \
-    test_element_to_dcmtkpp< \
-        dcmtkpp::VR::vr, EVR_##vr, InputType, ElementType>(value, getter); \
+    test_element_to_odil< \
+        odil::VR::vr, EVR_##vr, InputType, ElementType>(value, getter); \
 }
 
 ElementTest(
-    AE, dcmtkpp::Value::Strings, DcmApplicationEntity,
-    dcmtkpp::Value::Strings({"foo", "bar"}), &dcmtkpp::Element::as_string);
+    AE, odil::Value::Strings, DcmApplicationEntity,
+    odil::Value::Strings({"foo", "bar"}), &odil::Element::as_string);
 
 ElementTest(
-    AS, dcmtkpp::Value::Strings, DcmAgeString,
-    dcmtkpp::Value::Strings({"012Y", "345D"}), &dcmtkpp::Element::as_string);
+    AS, odil::Value::Strings, DcmAgeString,
+    odil::Value::Strings({"012Y", "345D"}), &odil::Element::as_string);
 
 ElementTest(
-    CS, dcmtkpp::Value::Strings, DcmCodeString,
-    dcmtkpp::Value::Strings({"foo", "bar"}), &dcmtkpp::Element::as_string);
+    CS, odil::Value::Strings, DcmCodeString,
+    odil::Value::Strings({"foo", "bar"}), &odil::Element::as_string);
 
 ElementTest(
-    DA, dcmtkpp::Value::Strings, DcmDate,
-    dcmtkpp::Value::Strings({"19000101", "20131215"}),
-    &dcmtkpp::Element::as_string);
+    DA, odil::Value::Strings, DcmDate,
+    odil::Value::Strings({"19000101", "20131215"}),
+    &odil::Element::as_string);
 
 ElementTest(
-    DS, dcmtkpp::Value::Reals, DcmDecimalString,
-    dcmtkpp::Value::Reals({12.34, 56.78}), &dcmtkpp::Element::as_real);
+    DS, odil::Value::Reals, DcmDecimalString,
+    odil::Value::Reals({12.34, 56.78}), &odil::Element::as_real);
 
 ElementTest(
-    DT, dcmtkpp::Value::Strings, DcmDateTime,
-    dcmtkpp::Value::Strings({"19000101123456", "201312150123"}),
-    &dcmtkpp::Element::as_string);
+    DT, odil::Value::Strings, DcmDateTime,
+    odil::Value::Strings({"19000101123456", "201312150123"}),
+    &odil::Element::as_string);
 
 ElementTest(
-    FL, dcmtkpp::Value::Reals, DcmFloatingPointSingle,
-    dcmtkpp::Value::Reals({12.34, 56.78}), &dcmtkpp::Element::as_real);
+    FL, odil::Value::Reals, DcmFloatingPointSingle,
+    odil::Value::Reals({12.34, 56.78}), &odil::Element::as_real);
 
 ElementTest(
-    FD, dcmtkpp::Value::Reals, DcmFloatingPointDouble,
-    dcmtkpp::Value::Reals({12.34, 56.78}), &dcmtkpp::Element::as_real);
+    FD, odil::Value::Reals, DcmFloatingPointDouble,
+    odil::Value::Reals({12.34, 56.78}), &odil::Element::as_real);
 
 ElementTest(
-    IS, dcmtkpp::Value::Integers, DcmIntegerString,
-    dcmtkpp::Value::Integers({34567, -67890}), &dcmtkpp::Element::as_int);
+    IS, odil::Value::Integers, DcmIntegerString,
+    odil::Value::Integers({34567, -67890}), &odil::Element::as_int);
 
 ElementTest(
-    LO, dcmtkpp::Value::Strings, DcmLongString,
-    dcmtkpp::Value::Strings({"foo bar", "something else"}),
-    &dcmtkpp::Element::as_string);
+    LO, odil::Value::Strings, DcmLongString,
+    odil::Value::Strings({"foo bar", "something else"}),
+    &odil::Element::as_string);
 
 ElementTest(
-    LT, dcmtkpp::Value::Strings, DcmLongText,
-    dcmtkpp::Value::Strings({"foo\nbar\\something else"}),
-    &dcmtkpp::Element::as_string);
+    LT, odil::Value::Strings, DcmLongText,
+    odil::Value::Strings({"foo\nbar\\something else"}),
+    &odil::Element::as_string);
 
 // OB
 // OF
 // OW
 
 ElementTest(
-    PN, dcmtkpp::Value::Strings, DcmPersonName,
-    dcmtkpp::Value::Strings({"Doe^John", "^Bob^Dr."}),
-    &dcmtkpp::Element::as_string);
+    PN, odil::Value::Strings, DcmPersonName,
+    odil::Value::Strings({"Doe^John", "^Bob^Dr."}),
+    &odil::Element::as_string);
 
 ElementTest(
-    SH, dcmtkpp::Value::Strings, DcmShortString,
-    dcmtkpp::Value::Strings({"foo", "bar"}), &dcmtkpp::Element::as_string);
+    SH, odil::Value::Strings, DcmShortString,
+    odil::Value::Strings({"foo", "bar"}), &odil::Element::as_string);
 
 ElementTest(
-    SL, dcmtkpp::Value::Integers, DcmSignedLong,
-    dcmtkpp::Value::Integers({34567, -56789}), &dcmtkpp::Element::as_int);
+    SL, odil::Value::Integers, DcmSignedLong,
+    odil::Value::Integers({34567, -56789}), &odil::Element::as_int);
 
 ElementTest(
-    SS, dcmtkpp::Value::Integers, DcmSignedShort,
-    dcmtkpp::Value::Integers({1234, -5678}), &dcmtkpp::Element::as_int);
+    SS, odil::Value::Integers, DcmSignedShort,
+    odil::Value::Integers({1234, -5678}), &odil::Element::as_int);
 
 ElementTest(
-    ST, dcmtkpp::Value::Strings, DcmShortText,
-    dcmtkpp::Value::Strings({"foo\nbar\\something else"}),
-    &dcmtkpp::Element::as_string);
+    ST, odil::Value::Strings, DcmShortText,
+    odil::Value::Strings({"foo\nbar\\something else"}),
+    &odil::Element::as_string);
 
 ElementTest(
-    TM, dcmtkpp::Value::Strings, DcmTime,
-    dcmtkpp::Value::Strings({"123456", "0123"}),
-    &dcmtkpp::Element::as_string);
+    TM, odil::Value::Strings, DcmTime,
+    odil::Value::Strings({"123456", "0123"}),
+    &odil::Element::as_string);
 
 ElementTest(
-    UI, dcmtkpp::Value::Strings, DcmUniqueIdentifier,
-    dcmtkpp::Value::Strings(
+    UI, odil::Value::Strings, DcmUniqueIdentifier,
+    odil::Value::Strings(
         {"1.2.840.10008.5.1.4.1.1.4", "1.2.840.10008.5.1.4.1.1.4.1"}),
-        &dcmtkpp::Element::as_string);
+        &odil::Element::as_string);
 
 ElementTest(
-    UL, dcmtkpp::Value::Integers, DcmUnsignedLong,
-    dcmtkpp::Value::Integers({123456, 789012}), &dcmtkpp::Element::as_int);
+    UL, odil::Value::Integers, DcmUnsignedLong,
+    odil::Value::Integers({123456, 789012}), &odil::Element::as_int);
 
 // UN
 
 ElementTest(
-    US, dcmtkpp::Value::Integers, DcmUnsignedShort,
-    dcmtkpp::Value::Integers({12345, 6789}), &dcmtkpp::Element::as_int);
+    US, odil::Value::Integers, DcmUnsignedShort,
+    odil::Value::Integers({12345, 6789}), &odil::Element::as_int);
 
 ElementTest(
-    UT, dcmtkpp::Value::Strings, DcmUnlimitedText,
-    dcmtkpp::Value::Strings({"foo\nbar\\something else"}),
-    &dcmtkpp::Element::as_string);
+    UT, odil::Value::Strings, DcmUnlimitedText,
+    odil::Value::Strings({"foo\nbar\\something else"}),
+    &odil::Element::as_string);
 
 BOOST_AUTO_TEST_CASE(ATFromDcmtkpp)
 {
-    dcmtkpp::Element const source(
-        dcmtkpp::Value::Strings({"deadbeef", "beeff00d"}), dcmtkpp::VR::AT);
+    odil::Element const source(
+        odil::Value::Strings({"deadbeef", "beeff00d"}), odil::VR::AT);
 
-    DcmElement * destination = dcmtkpp::convert(
-        dcmtkpp::Tag(0x1234, 0x5678), source);
+    DcmElement * destination = odil::dcmtk::convert(
+        odil::Tag(0x1234, 0x5678), source);
 
     BOOST_CHECK_NE(destination, (DcmElement const *)(NULL));
 
-    BOOST_CHECK_EQUAL(destination->getVR(), dcmtkpp::convert(source.vr));
+    BOOST_CHECK_EQUAL(destination->getVR(), odil::dcmtk::convert(source.vr));
     BOOST_CHECK_NE(
         dynamic_cast<DcmAttributeTag *>(destination),
         (DcmAttributeTag *)(NULL));
@@ -274,13 +274,13 @@ BOOST_AUTO_TEST_CASE(ATFromDcmtkpp)
     BOOST_CHECK_EQUAL(destination->getVM(), source.size());
     for(std::size_t i=0; i<source.size(); ++i)
     {
-        dcmtkpp::Tag const & source_tag = source.as_string()[i];
+        odil::Tag const & source_tag = source.as_string()[i];
 
         DcmTagKey destination_tag;
         OFCondition const condition = destination->getTagVal(destination_tag, i);
         BOOST_CHECK(condition.good());
 
-        BOOST_CHECK(source_tag == dcmtkpp::convert(destination_tag));
+        BOOST_CHECK(source_tag == odil::dcmtk::convert(destination_tag));
     }
 }
 
@@ -290,36 +290,36 @@ BOOST_AUTO_TEST_CASE(ATToDcmtkpp)
     source.putTagVal(DcmTagKey(0xdead, 0xbeef), 0);
     source.putTagVal(DcmTagKey(0xbeef, 0xf00d), 1);
 
-    dcmtkpp::Element const destination = dcmtkpp::convert(&source);
+    odil::Element const destination = odil::dcmtk::convert(&source);
 
-    BOOST_CHECK(destination.vr == dcmtkpp::convert(source.getVR()));
+    BOOST_CHECK(destination.vr == odil::dcmtk::convert(source.getVR()));
     BOOST_CHECK_EQUAL(source.getVM(), destination.size());
     for(std::size_t i=0; i<destination.size(); ++i)
     {
         DcmTagKey source_tag;
         source.getTagVal(source_tag, i);
 
-        dcmtkpp::Tag const & destination_tag = destination.as_string()[i];
-        BOOST_CHECK(dcmtkpp::convert(source_tag) == destination_tag);
+        odil::Tag const & destination_tag = destination.as_string()[i];
+        BOOST_CHECK(odil::dcmtk::convert(source_tag) == destination_tag);
     }
 }
 
 BOOST_AUTO_TEST_CASE(SQFromDcmtkpp)
 {
-    dcmtkpp::DataSet item;
+    odil::DataSet item;
     item.add("PatientID");
     item.as_string("PatientID").push_back("DJ1234");
 
-    dcmtkpp::Element const source(
-        dcmtkpp::Value::DataSets({item}), dcmtkpp::VR::SQ);
+    odil::Element const source(
+        odil::Value::DataSets({item}), odil::VR::SQ);
 
-    dcmtkpp::Tag const source_tag(0xdead, 0xbeef);
+    odil::Tag const source_tag(0xdead, 0xbeef);
 
-    DcmElement * destination = dcmtkpp::convert(source_tag, source);
+    DcmElement * destination = odil::dcmtk::convert(source_tag, source);
 
     BOOST_CHECK_NE(destination, (DcmElement const *)(NULL));
 
-    BOOST_CHECK_EQUAL(destination->getVR(), dcmtkpp::convert(source.vr));
+    BOOST_CHECK_EQUAL(destination->getVR(), odil::dcmtk::convert(source.vr));
     BOOST_CHECK_NE(
         dynamic_cast<DcmSequenceOfItems *>(destination),
         (DcmSequenceOfItems *)(NULL));
@@ -327,9 +327,9 @@ BOOST_AUTO_TEST_CASE(SQFromDcmtkpp)
     BOOST_CHECK_EQUAL(destination->getVM(), source.size());
     for(std::size_t i=0; i<source.size(); ++i)
     {
-        dcmtkpp::DataSet const & source_item = source.as_data_set()[i];
+        odil::DataSet const & source_item = source.as_data_set()[i];
         DcmItem * destination_item = dynamic_cast<DcmSequenceOfItems *>(destination)->getItem(i);
-        BOOST_CHECK(source_item == dcmtkpp::convert(destination_item));
+        BOOST_CHECK(source_item == odil::dcmtk::convert(destination_item));
     }
 }
 
@@ -341,9 +341,9 @@ BOOST_AUTO_TEST_CASE(SQToDcmtkpp)
     DcmSequenceOfItems source(DcmTag(0xdead, 0xbeef, EVR_SQ));
     source.append(item);
 
-    dcmtkpp::Element const destination = dcmtkpp::convert(&source);
+    odil::Element const destination = odil::dcmtk::convert(&source);
 
-    BOOST_CHECK(destination.vr == dcmtkpp::convert(source.getVR()));
+    BOOST_CHECK(destination.vr == odil::dcmtk::convert(source.getVR()));
     BOOST_CHECK_EQUAL(source.getVM(), destination.size());
     for(std::size_t i=0; i<destination.size(); ++i)
     {
@@ -351,37 +351,37 @@ BOOST_AUTO_TEST_CASE(SQToDcmtkpp)
         DcmDataset * source_item = dynamic_cast<DcmDataset *>(item);
         BOOST_REQUIRE(source_item != NULL);
 
-        dcmtkpp::DataSet const & destination_item = destination.as_data_set()[i];
-        BOOST_CHECK(dcmtkpp::convert(source_item) == destination_item);
+        odil::DataSet const & destination_item = destination.as_data_set()[i];
+        BOOST_CHECK(odil::dcmtk::convert(source_item) == destination_item);
     }
 }
 
 BOOST_AUTO_TEST_CASE(EmptyDataSetFromDcmtkpp)
 {
-    dcmtkpp::DataSet empty;
-    DcmItem * result = dcmtkpp::convert(empty);
+    odil::DataSet empty;
+    DcmItem * result = odil::dcmtk::convert(empty);
     BOOST_CHECK_EQUAL(result->card(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(EmptyDataSetFromDcmtk)
 {
     DcmDataset empty;
-    dcmtkpp::DataSet const result = dcmtkpp::convert(&empty);
+    odil::DataSet const result = odil::dcmtk::convert(&empty);
     BOOST_CHECK(result.empty());
 }
 
 BOOST_AUTO_TEST_CASE(DataSetFromDcmtkpp)
 {
-    dcmtkpp::Element const patient_id_source(
-        dcmtkpp::Value::Strings({"DJ1234"}), dcmtkpp::VR::CS);
-    dcmtkpp::Element const pixel_spacing_source(
-        dcmtkpp::Value::Reals({1.23, 4.56}), dcmtkpp::VR::DS);
+    odil::Element const patient_id_source(
+        odil::Value::Strings({"DJ1234"}), odil::VR::CS);
+    odil::Element const pixel_spacing_source(
+        odil::Value::Reals({1.23, 4.56}), odil::VR::DS);
 
-    dcmtkpp::DataSet source;
-    source.add(dcmtkpp::Tag("PatientID"), patient_id_source);
-    source.add(dcmtkpp::Tag("PixelSpacing"), pixel_spacing_source);
+    odil::DataSet source;
+    source.add(odil::Tag("PatientID"), patient_id_source);
+    source.add(odil::Tag("PixelSpacing"), pixel_spacing_source);
 
-    DcmItem * result = dcmtkpp::convert(source);
+    DcmItem * result = odil::dcmtk::convert(source);
     BOOST_CHECK_EQUAL(result->card(), 2);
 
     DcmElement * patient_id;
@@ -389,7 +389,7 @@ BOOST_AUTO_TEST_CASE(DataSetFromDcmtkpp)
         result->findAndGetElement(DCM_PatientID, patient_id);
     BOOST_CHECK(patient_id_ok.good());
     BOOST_CHECK(
-        dcmtkpp::convert(patient_id).as_string() ==
+        odil::dcmtk::convert(patient_id).as_string() ==
             patient_id_source.as_string());
 
     DcmElement * pixel_spacing;
@@ -397,27 +397,27 @@ BOOST_AUTO_TEST_CASE(DataSetFromDcmtkpp)
         result->findAndGetElement(DCM_PixelSpacing, pixel_spacing);
     BOOST_CHECK(pixel_spacing_ok.good());
     BOOST_CHECK(
-        dcmtkpp::convert(pixel_spacing).as_real() ==
+        odil::dcmtk::convert(pixel_spacing).as_real() ==
             pixel_spacing_source.as_real());
 }
 
 BOOST_AUTO_TEST_CASE(DataSetFromDcmtk)
 {
-    dcmtkpp::Element const patient_id_source(
-        dcmtkpp::Value::Strings({"DJ1234"}), dcmtkpp::VR::CS);
-    dcmtkpp::Element const pixel_spacing_source(
-        dcmtkpp::Value::Reals({1.23, 4.56}), dcmtkpp::VR::DS);
+    odil::Element const patient_id_source(
+        odil::Value::Strings({"DJ1234"}), odil::VR::CS);
+    odil::Element const pixel_spacing_source(
+        odil::Value::Reals({1.23, 4.56}), odil::VR::DS);
 
     DcmDataset source;
-    source.insert(dcmtkpp::convert(dcmtkpp::Tag("PatientID"), patient_id_source));
-    source.insert(dcmtkpp::convert(dcmtkpp::Tag("PixelSpacing"), pixel_spacing_source));
+    source.insert(odil::dcmtk::convert(odil::Tag("PatientID"), patient_id_source));
+    source.insert(odil::dcmtk::convert(odil::Tag("PixelSpacing"), pixel_spacing_source));
 
-    dcmtkpp::DataSet const result = dcmtkpp::convert(&source);
+    odil::DataSet const result = odil::dcmtk::convert(&source);
     BOOST_CHECK_EQUAL(result.size(), 2);
     BOOST_CHECK(
-        result.as_string(dcmtkpp::Tag("PatientID")) ==
+        result.as_string(odil::Tag("PatientID")) ==
             patient_id_source.as_string());
     BOOST_CHECK(
-        result.as_real(dcmtkpp::Tag("PixelSpacing")) ==
+        result.as_real(odil::Tag("PixelSpacing")) ==
             pixel_spacing_source.as_real());
 }
diff --git a/tests/code/dul/StateMachine.cpp b/tests/code/dul/StateMachine.cpp
new file mode 100644
index 0000000..0b6a9ad
--- /dev/null
+++ b/tests/code/dul/StateMachine.cpp
@@ -0,0 +1,22 @@
+#define BOOST_TEST_MODULE StateMachine
+#include <boost/test/unit_test.hpp>
+
+#include "odil/Exception.h"
+#include "odil/dul/EventData.h"
+#include "odil/dul/StateMachine.h"
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::dul::StateMachine state_machine;
+    BOOST_REQUIRE(
+        state_machine.get_state() == odil::dul::StateMachine::State::Sta1);
+}
+
+BOOST_AUTO_TEST_CASE(WrongTransition)
+{
+    odil::dul::StateMachine state_machine;
+    odil::dul::EventData data;
+    BOOST_REQUIRE_THROW(
+        state_machine.transition(odil::dul::StateMachine::Event::None, data),
+        odil::Exception);
+}
diff --git a/tests/code/dul/Transport.cpp b/tests/code/dul/Transport.cpp
new file mode 100644
index 0000000..fd50fea
--- /dev/null
+++ b/tests/code/dul/Transport.cpp
@@ -0,0 +1,45 @@
+#define BOOST_TEST_MODULE Transport
+#include <boost/test/unit_test.hpp>
+
+#include <chrono>
+#include <boost/asio.hpp>
+
+#include "odil/Exception.h"
+#include "odil/dul/Transport.h"
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::dul::Transport transport;
+    BOOST_REQUIRE(!transport.is_open());
+}
+
+BOOST_AUTO_TEST_CASE(Connect)
+{
+    odil::dul::Transport transport;
+
+    boost::asio::ip::tcp::resolver resolver(transport.get_service());
+    boost::asio::ip::tcp::resolver::query const query(
+        odil::dul::Transport::Socket::protocol_type::v4(),
+        "www.example.com", "80");
+    auto const endpoint_it = resolver.resolve(query);
+
+    transport.connect(*endpoint_it);
+    BOOST_REQUIRE(transport.is_open());
+
+    transport.write("HEAD / HTTP/1.1\r\nHost: www.example.com\r\n\r\n");
+
+    auto const response = transport.read(128);
+    BOOST_REQUIRE(!response.empty());
+
+    transport.close();
+    BOOST_REQUIRE(!transport.is_open());
+}
+
+BOOST_AUTO_TEST_CASE(NotConnected)
+{
+    odil::dul::Transport transport;
+
+    BOOST_REQUIRE_THROW(transport.write("..."), odil::Exception);
+    BOOST_REQUIRE_THROW(transport.read(1), odil::Exception);
+    BOOST_REQUIRE_THROW(transport.close(), odil::Exception);
+}
diff --git a/tests/code/endian.cpp b/tests/code/endian.cpp
new file mode 100644
index 0000000..c02bf11
--- /dev/null
+++ b/tests/code/endian.cpp
@@ -0,0 +1,89 @@
+#define BOOST_TEST_MODULE endian
+#include <boost/test/unit_test.hpp>
+
+#include <cstdint>
+#include "odil/endian.h"
+
+BOOST_AUTO_TEST_CASE(ToLittleEndian16)
+{
+    uint16_t const input = 0x1234;
+    std::string const expected("\x34\x12");
+    BOOST_REQUIRE_EQUAL(
+        odil::host_to_little_endian(input),
+        *reinterpret_cast<uint16_t const *>(&expected[0])
+    );
+}
+
+BOOST_AUTO_TEST_CASE(ToLittleEndian32)
+{
+    uint32_t const input = 0x12345678;
+    std::string const expected("\x78\x56\x34\x12");
+    BOOST_REQUIRE_EQUAL(
+        odil::host_to_little_endian(input),
+        *reinterpret_cast<uint32_t const *>(&expected[0])
+    );
+}
+
+BOOST_AUTO_TEST_CASE(ToBigEndian16)
+{
+    uint16_t const input = 0x1234;
+    std::string const expected("\x12\x34");
+    BOOST_REQUIRE_EQUAL(
+        odil::host_to_big_endian(input),
+        *reinterpret_cast<uint16_t const *>(&expected[0])
+    );
+}
+
+BOOST_AUTO_TEST_CASE(ToBigEndian32)
+{
+    uint32_t const input = 0x12345678;
+    std::string const expected("\x12\x34\x56\x78");
+    BOOST_REQUIRE_EQUAL(
+        odil::host_to_big_endian(input),
+        *reinterpret_cast<uint32_t const *>(&expected[0])
+    );
+}
+
+BOOST_AUTO_TEST_CASE(FromLittleEndian16)
+{
+    std::string const input("\x34\x12");
+    uint16_t const expected = 0x1234;
+    BOOST_REQUIRE_EQUAL(
+        odil::little_endian_to_host(
+            *reinterpret_cast<uint16_t const *>(&input[0])),
+        expected
+    );
+}
+
+BOOST_AUTO_TEST_CASE(FromLittleEndian32)
+{
+    std::string const input("\x78\x56\x34\x12");
+    uint32_t const expected = 0x12345678;
+    BOOST_REQUIRE_EQUAL(
+        odil::little_endian_to_host(
+            *reinterpret_cast<uint32_t const *>(&input[0])),
+        expected
+    );
+}
+
+BOOST_AUTO_TEST_CASE(FromBigEndian16)
+{
+    std::string const input("\x12\x34");
+    uint16_t const expected = 0x1234;
+    BOOST_REQUIRE_EQUAL(
+        odil::big_endian_to_host(
+            *reinterpret_cast<uint16_t const *>(&input[0])),
+        expected
+    );
+}
+
+BOOST_AUTO_TEST_CASE(FromBigEndian32)
+{
+    std::string const input("\x12\x34\x56\x78");
+    uint32_t const expected = 0x12345678;
+    BOOST_REQUIRE_EQUAL(
+        odil::big_endian_to_host(
+            *reinterpret_cast<uint32_t const *>(&input[0])),
+        expected
+    );
+}
diff --git a/tests/code/json_converter.cpp b/tests/code/json_converter.cpp
index 8e1200a..c4f9607 100644
--- a/tests/code/json_converter.cpp
+++ b/tests/code/json_converter.cpp
@@ -5,18 +5,18 @@
 #include <string>
 #include <vector>
 
-#include <jsoncpp/json/json.h>
+#include <json/json.h>
 
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Element.h"
-#include "dcmtkpp/json_converter.h"
-#include "dcmtkpp/Value.h"
-#include "dcmtkpp/VR.h"
+#include "odil/DataSet.h"
+#include "odil/Element.h"
+#include "odil/json_converter.h"
+#include "odil/Value.h"
+#include "odil/VR.h"
 
-BOOST_AUTO_TEST_CASE(AsJSONEmpty)
+BOOST_AUTO_TEST_CASE(AsJSONEmptyDataSet)
 {
-    dcmtkpp::DataSet data_set;
-    auto const json = dcmtkpp::as_json(data_set);
+    odil::DataSet data_set;
+    auto const json = odil::as_json(data_set);
     BOOST_REQUIRE(json.empty());
 }
 
@@ -49,12 +49,22 @@ void check_json_string(Json::Value const & object, std::string const & expected_
     BOOST_REQUIRE_EQUAL(object.asString(), expected_value);
 }
 
+BOOST_AUTO_TEST_CASE(AsJSONEmptyElement)
+{
+    odil::DataSet data_set;
+    data_set.add(0xdeadbeef, odil::VR::SS);
+    auto const json = odil::as_json(data_set);
+    check_json_object(json, {"deadbeef"});
+    check_json_object(json["deadbeef"], {"vr"});
+    check_json_string(json["deadbeef"]["vr"], "SS");
+}
+
 BOOST_AUTO_TEST_CASE(AsJSONIntegers)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0xdeadbeef,
-        dcmtkpp::Element(dcmtkpp::Value::Integers({1, 2}), dcmtkpp::VR::SS));
-    auto const json = dcmtkpp::as_json(data_set);
+        odil::Element(odil::Value::Integers({1, 2}), odil::VR::SS));
+    auto const json = odil::as_json(data_set);
     check_json_object(json, {"deadbeef"});
     check_json_object(json["deadbeef"], {"vr", "Value"});
     check_json_string(json["deadbeef"]["vr"], "SS");
@@ -64,10 +74,10 @@ BOOST_AUTO_TEST_CASE(AsJSONIntegers)
 
 BOOST_AUTO_TEST_CASE(AsJSONReals)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0xdeadbeef,
-        dcmtkpp::Element(dcmtkpp::Value::Reals({1.2, 3.4}), dcmtkpp::VR::FL));
-    auto const json = dcmtkpp::as_json(data_set);
+        odil::Element(odil::Value::Reals({1.2, 3.4}), odil::VR::FL));
+    auto const json = odil::as_json(data_set);
     check_json_object(json, {"deadbeef"});
     check_json_object(json["deadbeef"], {"vr", "Value"});
     check_json_string(json["deadbeef"]["vr"], "FL");
@@ -77,12 +87,12 @@ BOOST_AUTO_TEST_CASE(AsJSONReals)
 
 BOOST_AUTO_TEST_CASE(AsJSONStrings)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0xdeadbeef,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings({"FOO", "BAR"}),
-            dcmtkpp::VR::CS));
-    auto const json = dcmtkpp::as_json(data_set);
+        odil::Element(
+            odil::Value::Strings({"FOO", "BAR"}),
+            odil::VR::CS));
+    auto const json = odil::as_json(data_set);
     check_json_object(json, {"deadbeef"});
     check_json_object(json["deadbeef"], {"vr", "Value"});
     check_json_string(json["deadbeef"]["vr"], "CS");
@@ -92,12 +102,12 @@ BOOST_AUTO_TEST_CASE(AsJSONStrings)
 
 BOOST_AUTO_TEST_CASE(AsJSONPersonName)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0xdeadbeef,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings({"Alpha^Betic=Ideo^Graphic=Pho^Netic"}),
-            dcmtkpp::VR::PN));
-    auto const json = dcmtkpp::as_json(data_set);
+        odil::Element(
+            odil::Value::Strings({"Alpha^Betic=Ideo^Graphic=Pho^Netic"}),
+            odil::VR::PN));
+    auto const json = odil::as_json(data_set);
     check_json_object(json, {"deadbeef"});
     check_json_object(json["deadbeef"], {"vr", "Value"});
     check_json_string(json["deadbeef"]["vr"], "PN");
@@ -113,16 +123,16 @@ BOOST_AUTO_TEST_CASE(AsJSONPersonName)
 
 BOOST_AUTO_TEST_CASE(AsJSONDataSets)
 {
-    dcmtkpp::DataSet item;
+    odil::DataSet item;
     item.add(0xbeeff00d,
-        dcmtkpp::Element(dcmtkpp::Value::Integers({1,2}), dcmtkpp::VR::SS));
-    dcmtkpp::DataSet data_set;
+        odil::Element(odil::Value::Integers({1,2}), odil::VR::SS));
+    odil::DataSet data_set;
     data_set.add(0xdeadbeef,
-        dcmtkpp::Element(
-            dcmtkpp::Value::DataSets({item}),
-            dcmtkpp::VR::SQ));
+        odil::Element(
+            odil::Value::DataSets({item}),
+            odil::VR::SQ));
 
-    auto const json = dcmtkpp::as_json(data_set);
+    auto const json = odil::as_json(data_set);
 
     check_json_object(json, {"deadbeef"});
     check_json_object(json["deadbeef"], {"vr", "Value"});
@@ -140,13 +150,13 @@ BOOST_AUTO_TEST_CASE(AsJSONDataSets)
 
 BOOST_AUTO_TEST_CASE(AsJSONBinary)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0xdeadbeef,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Binary({0x1, 0x2, 0x3, 0x4, 0x5}),
-            dcmtkpp::VR::OB));
+        odil::Element(
+            odil::Value::Binary({0x1, 0x2, 0x3, 0x4, 0x5}),
+            odil::VR::OB));
 
-    auto const json = dcmtkpp::as_json(data_set);
+    auto const json = odil::as_json(data_set);
 
     check_json_object(json, {"deadbeef"});
     check_json_object(json["deadbeef"], {"vr", "InlineBinary"});
@@ -161,7 +171,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetEmpty)
     Json::Value json;
     data >> json;
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(json);
+    odil::DataSet const data_set = odil::as_dataset(json);
     BOOST_REQUIRE(data_set.empty());
 }
 
@@ -172,12 +182,12 @@ BOOST_AUTO_TEST_CASE(AsDataSetIntegers)
     Json::Value json;
     data >> json;
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(json);
+    odil::DataSet const data_set = odil::as_dataset(json);
     BOOST_REQUIRE_EQUAL(data_set.size(), 1);
     BOOST_REQUIRE(data_set.has("deadbeef"));
-    BOOST_REQUIRE(data_set.get_vr("deadbeef") == dcmtkpp::VR::SS);
+    BOOST_REQUIRE(data_set.get_vr("deadbeef") == odil::VR::SS);
     BOOST_REQUIRE(data_set.is_int("deadbeef"));
-    BOOST_REQUIRE(data_set.as_int("deadbeef") == dcmtkpp::Value::Integers({1, 2}));
+    BOOST_REQUIRE(data_set.as_int("deadbeef") == odil::Value::Integers({1, 2}));
 }
 
 BOOST_AUTO_TEST_CASE(AsDataSetReals)
@@ -187,12 +197,12 @@ BOOST_AUTO_TEST_CASE(AsDataSetReals)
     Json::Value json;
     data >> json;
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(json);
+    odil::DataSet const data_set = odil::as_dataset(json);
     BOOST_REQUIRE_EQUAL(data_set.size(), 1);
     BOOST_REQUIRE(data_set.has("deadbeef"));
-    BOOST_REQUIRE(data_set.get_vr("deadbeef") == dcmtkpp::VR::FL);
+    BOOST_REQUIRE(data_set.get_vr("deadbeef") == odil::VR::FL);
     BOOST_REQUIRE(data_set.is_real("deadbeef"));
-    BOOST_REQUIRE(data_set.as_real("deadbeef") == dcmtkpp::Value::Reals({1.2, 3.4}));
+    BOOST_REQUIRE(data_set.as_real("deadbeef") == odil::Value::Reals({1.2, 3.4}));
 }
 
 BOOST_AUTO_TEST_CASE(AsDataSetStrings)
@@ -202,12 +212,12 @@ BOOST_AUTO_TEST_CASE(AsDataSetStrings)
     Json::Value json;
     data >> json;
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(json);
+    odil::DataSet const data_set = odil::as_dataset(json);
     BOOST_REQUIRE_EQUAL(data_set.size(), 1);
     BOOST_REQUIRE(data_set.has("deadbeef"));
-    BOOST_REQUIRE(data_set.get_vr("deadbeef") == dcmtkpp::VR::CS);
+    BOOST_REQUIRE(data_set.get_vr("deadbeef") == odil::VR::CS);
     BOOST_REQUIRE(data_set.is_string("deadbeef"));
-    BOOST_REQUIRE(data_set.as_string("deadbeef") == dcmtkpp::Value::Strings({"FOO", "BAR"}));
+    BOOST_REQUIRE(data_set.as_string("deadbeef") == odil::Value::Strings({"FOO", "BAR"}));
 }
 
 BOOST_AUTO_TEST_CASE(AsDataSetPersonName)
@@ -221,12 +231,12 @@ BOOST_AUTO_TEST_CASE(AsDataSetPersonName)
     Json::Value json;
     data >> json;
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(json);
+    odil::DataSet const data_set = odil::as_dataset(json);
     BOOST_REQUIRE_EQUAL(data_set.size(), 1);
     BOOST_REQUIRE(data_set.has("deadbeef"));
-    BOOST_REQUIRE(data_set.get_vr("deadbeef") == dcmtkpp::VR::PN);
+    BOOST_REQUIRE(data_set.get_vr("deadbeef") == odil::VR::PN);
     BOOST_REQUIRE(data_set.is_string("deadbeef"));
-    BOOST_REQUIRE(data_set.as_string("deadbeef") == dcmtkpp::Value::Strings(
+    BOOST_REQUIRE(data_set.as_string("deadbeef") == odil::Value::Strings(
         {"Alpha^Betic=Ideo^Graphic=Pho^Netic"}));
 }
 
@@ -239,16 +249,16 @@ BOOST_AUTO_TEST_CASE(AsDataSetDataSets)
     Json::Value json;
     data >> json;
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(json);
+    odil::DataSet const data_set = odil::as_dataset(json);
     BOOST_REQUIRE_EQUAL(data_set.size(), 1);
     BOOST_REQUIRE(data_set.has("deadbeef"));
-    BOOST_REQUIRE(data_set.get_vr("deadbeef") == dcmtkpp::VR::SQ);
+    BOOST_REQUIRE(data_set.get_vr("deadbeef") == odil::VR::SQ);
     BOOST_REQUIRE(data_set.is_data_set("deadbeef"));
 
-    dcmtkpp::DataSet item;
+    odil::DataSet item;
     item.add(0xbeeff00d,
-        dcmtkpp::Element(dcmtkpp::Value::Integers({1,2}), dcmtkpp::VR::SS));
-    BOOST_REQUIRE(data_set.as_data_set("deadbeef") == dcmtkpp::Value::DataSets({item}));
+        odil::Element(odil::Value::Integers({1,2}), odil::VR::SS));
+    BOOST_REQUIRE(data_set.as_data_set("deadbeef") == odil::Value::DataSets({item}));
 }
 
 
@@ -259,11 +269,11 @@ BOOST_AUTO_TEST_CASE(AsDataSetBinary)
     Json::Value json;
     data >> json;
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(json);
+    odil::DataSet const data_set = odil::as_dataset(json);
     BOOST_REQUIRE_EQUAL(data_set.size(), 1);
     BOOST_REQUIRE(data_set.has("deadbeef"));
-    BOOST_REQUIRE(data_set.get_vr("deadbeef") == dcmtkpp::VR::OB);
+    BOOST_REQUIRE(data_set.get_vr("deadbeef") == odil::VR::OB);
     BOOST_REQUIRE(data_set.is_binary("deadbeef"));
-    BOOST_REQUIRE(data_set.as_binary("deadbeef") == dcmtkpp::Value::Binary(
+    BOOST_REQUIRE(data_set.as_binary("deadbeef") == odil::Value::Binary(
         {0x1, 0x2, 0x3, 0x4, 0x5}));
 }
diff --git a/tests/code/message/CEchoRequest.cpp b/tests/code/message/CEchoRequest.cpp
new file mode 100644
index 0000000..5748aaa
--- /dev/null
+++ b/tests/code/message/CEchoRequest.cpp
@@ -0,0 +1,60 @@
+#define BOOST_TEST_MODULE CEchoRequest
+#include <boost/test/unit_test.hpp>
+
+#include "odil/message/CEchoRequest.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+
+#include "../../MessageFixtureBase.h"
+
+struct Fixture: public MessageFixtureBase<odil::message::CEchoRequest>
+{
+    odil::DataSet command_set;
+
+    Fixture()
+    {
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_ECHO_RQ});
+        this->command_set.add("MessageID", {1234});
+        this->command_set.add(
+            "AffectedSOPClassUID", {odil::registry::VerificationSOPClass});
+    }
+
+    void check(odil::message::CEchoRequest const & message)
+    {
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_ECHO_RQ);
+        BOOST_CHECK_EQUAL(message.get_message_id(), 1234);
+        BOOST_CHECK_EQUAL(
+            message.get_affected_sop_class_uid(),
+            odil::registry::VerificationSOPClass);
+        BOOST_CHECK(!message.has_data_set());
+    }
+};
+
+BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
+{
+    odil::message::CEchoRequest const message(
+        1234, odil::registry::VerificationSOPClass);
+    this->check(message);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
+{
+    this->check_message_constructor(this->command_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
+{
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RSP};
+    this->check_message_constructor_throw(this->command_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorMissingAffectSOPClass, Fixture)
+{
+    this->command_set.remove("AffectedSOPClassUID");
+    this->check_message_constructor_throw(this->command_set);
+}
diff --git a/tests/code/message/CEchoResponse.cpp b/tests/code/message/CEchoResponse.cpp
new file mode 100644
index 0000000..82cdeec
--- /dev/null
+++ b/tests/code/message/CEchoResponse.cpp
@@ -0,0 +1,64 @@
+#define BOOST_TEST_MODULE CEchoResponse
+#include <boost/test/unit_test.hpp>
+
+#include "odil/message/CEchoResponse.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+
+#include "../../MessageFixtureBase.h"
+
+struct Fixture: public MessageFixtureBase<odil::message::CEchoResponse>
+{
+    odil::DataSet command_set;
+
+    Fixture()
+    {
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_ECHO_RSP});
+        this->command_set.add("MessageIDBeingRespondedTo", {1234});
+        this->command_set.add("Status", {odil::message::Response::Success});
+        this->command_set.add(
+            "AffectedSOPClassUID", {odil::registry::VerificationSOPClass});
+    }
+
+    void check(odil::message::CEchoResponse const & message)
+    {
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_ECHO_RSP);
+        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
+        BOOST_CHECK_EQUAL(
+            message.get_status(), odil::message::Response::Success);
+        BOOST_CHECK_EQUAL(
+            message.get_affected_sop_class_uid(),
+            odil::registry::VerificationSOPClass);
+        BOOST_CHECK(!message.has_data_set());
+    }
+};
+
+BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
+{
+    odil::message::CEchoResponse const message(
+        1234, odil::message::Response::Success,
+        odil::registry::VerificationSOPClass);
+    this->check(message);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
+{
+    this->check_message_constructor(this->command_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
+{
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RQ};
+    this->check_message_constructor_throw(this->command_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorMissingAffectSOPClass, Fixture)
+{
+    this->command_set.remove("AffectedSOPClassUID");
+    this->check_message_constructor_throw(this->command_set);
+}
diff --git a/tests/code/CFindRequest.cpp b/tests/code/message/CFindRequest.cpp
similarity index 58%
rename from tests/code/CFindRequest.cpp
rename to tests/code/message/CFindRequest.cpp
index 32f7fac..6054e2b 100644
--- a/tests/code/CFindRequest.cpp
+++ b/tests/code/message/CFindRequest.cpp
@@ -1,50 +1,51 @@
 #define BOOST_TEST_MODULE CFindRequest
 #include <boost/test/unit_test.hpp>
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
+#include "odil/message/CFindRequest.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
 
-#include "dcmtkpp/CFindRequest.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
+#include "../../MessageFixtureBase.h"
 
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CFindRequest>
+struct Fixture: public MessageFixtureBase<odil::message::CFindRequest>
 {
-    dcmtkpp::DataSet command_set;
-    dcmtkpp::DataSet query;
+    odil::DataSet command_set;
+    odil::DataSet query;
 
     Fixture()
     {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_FIND_RQ});
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_FIND_RQ});
         this->command_set.add("MessageID", {1234});
         this->command_set.add("AffectedSOPClassUID",
-            {dcmtkpp::registry::PatientRootQueryRetrieveInformationModelFIND});
-        this->command_set.add("Priority", {DIMSE_PRIORITY_MEDIUM});
+            {odil::registry::PatientRootQueryRetrieveInformationModelFIND});
+        this->command_set.add(
+            "Priority", {odil::message::Message::Priority::MEDIUM});
 
         this->query.add("PatientName", {"Doe^John"});
         this->query.add("StudyDescription", {"Brain"});
         this->query.add("QueryRetrieveLevel", {"STUDY"});
     }
 
-    virtual void check(dcmtkpp::CFindRequest const & message)
+    virtual void check(odil::message::CFindRequest const & message)
     {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_FIND_RQ);
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_FIND_RQ);
         BOOST_CHECK_EQUAL(message.get_message_id(), 1234);
         BOOST_CHECK_EQUAL(
             message.get_affected_sop_class_uid(),
-            dcmtkpp::registry::PatientRootQueryRetrieveInformationModelFIND);
+            odil::registry::PatientRootQueryRetrieveInformationModelFIND);
         BOOST_CHECK(message.get_data_set() == this->query);
     }
 };
 
 BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
 {
-    dcmtkpp::CFindRequest const message(
-        1234, dcmtkpp::registry::PatientRootQueryRetrieveInformationModelFIND,
-        DIMSE_PRIORITY_MEDIUM, this->query);
+    odil::message::CFindRequest const message(
+        1234, odil::registry::PatientRootQueryRetrieveInformationModelFIND,
+        odil::message::Message::Priority::MEDIUM, this->query);
     this->check(message);
 }
 
@@ -55,7 +56,8 @@ BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
 
 BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
 {
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RQ};
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RQ};
     this->check_message_constructor_throw(this->command_set, this->query);
 }
 
@@ -78,6 +80,6 @@ BOOST_FIXTURE_TEST_CASE(MessageConstructorMissingQuery, Fixture)
 
 BOOST_FIXTURE_TEST_CASE(MessageConstructorEmptyQuery, Fixture)
 {
-    dcmtkpp::DataSet empty;
+    odil::DataSet empty;
     this->check_message_constructor_throw(this->command_set, empty);
 }
diff --git a/tests/code/message/CFindResponse.cpp b/tests/code/message/CFindResponse.cpp
new file mode 100644
index 0000000..cd160f5
--- /dev/null
+++ b/tests/code/message/CFindResponse.cpp
@@ -0,0 +1,107 @@
+#define BOOST_TEST_MODULE CFindResponse
+#include <boost/test/unit_test.hpp>
+
+#include "odil/message/CFindResponse.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+
+#include "../../MessageFixtureBase.h"
+
+struct Fixture: public MessageFixtureBase<odil::message::CFindResponse>
+{
+    odil::DataSet command_set;
+    odil::DataSet data_set;
+
+    Fixture()
+    {
+        command_set.add(
+            "CommandField", {odil::message::Message::Command::C_FIND_RSP});
+        command_set.add("MessageIDBeingRespondedTo", {1234});
+        command_set.add("Status", {odil::message::Response::Success});
+
+        command_set.add("MessageID", {5678});
+        command_set.add("AffectedSOPClassUID",
+            {odil::registry::StudyRootQueryRetrieveInformationModelFIND});
+
+        data_set.add("PatientName", {"Doe^John"});
+        data_set.add("PatientID", {"DJ123"});
+        data_set.add("StudyDescription", {"Brain"});
+        data_set.add("StudyInstanceUID", {"1.2.3"});
+    }
+
+    virtual void check(odil::message::CFindResponse const & message)
+    {
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_FIND_RSP);
+        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
+        BOOST_CHECK_EQUAL(
+            message.get_status(), odil::message::Response::Success);
+
+        BOOST_CHECK(message.has_message_id());
+        BOOST_CHECK_EQUAL(message.get_message_id(), 5678);
+
+        BOOST_CHECK(message.has_affected_sop_class_uid());
+        BOOST_CHECK_EQUAL(
+            message.get_affected_sop_class_uid(),
+            odil::registry::StudyRootQueryRetrieveInformationModelFIND);
+
+        BOOST_CHECK(message.has_data_set());
+        BOOST_CHECK(message.get_data_set() == this->data_set);
+    }
+};
+
+BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
+{
+    odil::message::CFindResponse message(
+        1234, odil::message::Response::Success, this->data_set);
+    message.set_message_id(5678);
+    message.set_affected_sop_class_uid(
+        odil::registry::StudyRootQueryRetrieveInformationModelFIND);
+    this->check(message);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
+{
+    this->check_message_constructor(this->command_set, this->data_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
+{
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RQ};
+    this->check_message_constructor_throw(this->command_set, this->data_set);
+}
+
+BOOST_AUTO_TEST_CASE(StatusPending)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::CFindResponse::PendingWarningOptionalKeysNotSupported,
+    };
+
+    for(auto const status:statuses)
+    {
+        odil::message::CFindResponse response(1234, status);
+        BOOST_REQUIRE(response.is_pending());
+        BOOST_REQUIRE(!response.is_warning());
+        BOOST_REQUIRE(!response.is_failure());
+    }
+}
+
+BOOST_AUTO_TEST_CASE(StatusFailure)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::CFindResponse::RefusedOutOfResources,
+        odil::message::CFindResponse::IdentifierDoesNotMatchSOPClass,
+        odil::message::CFindResponse::UnableToProcess
+    };
+
+    for(auto const status:statuses)
+    {
+        odil::message::CFindResponse response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(!response.is_warning());
+        BOOST_REQUIRE(response.is_failure());
+    }
+}
diff --git a/tests/code/CGetRequest.cpp b/tests/code/message/CGetRequest.cpp
similarity index 56%
rename from tests/code/CGetRequest.cpp
rename to tests/code/message/CGetRequest.cpp
index 5e63da4..f820f03 100644
--- a/tests/code/CGetRequest.cpp
+++ b/tests/code/message/CGetRequest.cpp
@@ -1,42 +1,43 @@
 #define BOOST_TEST_MODULE CGetRequest
 #include <boost/test/unit_test.hpp>
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
+#include "odil/message/CGetRequest.h"
+#include "odil/DataSet.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
 
-#include "dcmtkpp/CGetRequest.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
+#include "../../MessageFixtureBase.h"
 
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CGetRequest>
+struct Fixture: public MessageFixtureBase<odil::message::CGetRequest>
 {
-    dcmtkpp::DataSet command_set;
-    dcmtkpp::DataSet query;
+    odil::DataSet command_set;
+    odil::DataSet query;
 
     Fixture()
     {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_GET_RQ});
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_GET_RQ});
         this->command_set.add("MessageID", {1234});
         this->command_set.add("AffectedSOPClassUID",
-            {dcmtkpp::registry::PatientRootQueryRetrieveInformationModelGET});
-        this->command_set.add("Priority", {DIMSE_PRIORITY_MEDIUM});
+            {odil::registry::PatientRootQueryRetrieveInformationModelGET});
+        this->command_set.add(
+            "Priority", {odil::message::Message::Priority::MEDIUM});
 
         this->query.add("PatientName", {"Doe^John"});
         this->query.add("StudyDescription", {"Brain"});
         this->query.add("QueryRetrieveLevel", {"STUDY"});
     }
 
-    virtual void check(dcmtkpp::CGetRequest const & message)
+    virtual void check(odil::message::CGetRequest const & message)
     {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_GET_RQ);
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_GET_RQ);
         BOOST_CHECK_EQUAL(message.get_message_id(), 1234);
         BOOST_CHECK_EQUAL(
             message.get_affected_sop_class_uid(),
-            dcmtkpp::registry::PatientRootQueryRetrieveInformationModelGET);
+            odil::registry::PatientRootQueryRetrieveInformationModelGET);
         BOOST_CHECK(message.has_data_set());
         BOOST_CHECK(message.get_data_set() == this->query);
     }
@@ -44,9 +45,9 @@ struct Fixture: public MessageFixtureBase<dcmtkpp::CGetRequest>
 
 BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
 {
-    dcmtkpp::CGetRequest const message(
-        1234, dcmtkpp::registry::PatientRootQueryRetrieveInformationModelGET,
-        DIMSE_PRIORITY_MEDIUM, this->query);
+    odil::message::CGetRequest const message(
+        1234, odil::registry::PatientRootQueryRetrieveInformationModelGET,
+        odil::message::Message::Priority::MEDIUM, this->query);
     this->check(message);
 }
 
@@ -57,7 +58,8 @@ BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
 
 BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
 {
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RQ};
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RQ};
     this->check_message_constructor_throw(this->command_set, this->query);
 }
 
@@ -75,6 +77,6 @@ BOOST_FIXTURE_TEST_CASE(MessageConstructorMissingPriority, Fixture)
 
 BOOST_FIXTURE_TEST_CASE(MessageConstructorEmptyQuery, Fixture)
 {
-    dcmtkpp::DataSet empty;
+    odil::DataSet empty;
     this->check_message_constructor_throw(this->command_set, empty);
 }
diff --git a/tests/code/message/CGetResponse.cpp b/tests/code/message/CGetResponse.cpp
new file mode 100644
index 0000000..d99a97e
--- /dev/null
+++ b/tests/code/message/CGetResponse.cpp
@@ -0,0 +1,129 @@
+#define BOOST_TEST_MODULE CGetResponse
+#include <boost/test/unit_test.hpp>
+
+#include "odil/message/CGetResponse.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+
+#include "../../MessageFixtureBase.h"
+
+struct Fixture: public MessageFixtureBase<odil::message::CGetResponse>
+{
+    odil::DataSet command_set;
+    odil::DataSet data_set;
+
+    Fixture()
+    {
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_GET_RSP});
+        this->command_set.add("MessageIDBeingRespondedTo", {1234});
+        this->command_set.add("Status", {odil::message::Response::Success});
+
+        this->command_set.add("MessageID", {5678});
+        this->command_set.add("AffectedSOPClassUID",
+            {odil::registry::StudyRootQueryRetrieveInformationModelGET});
+        this->command_set.add(odil::registry::NumberOfRemainingSuboperations, {1});
+        this->command_set.add(odil::registry::NumberOfCompletedSuboperations, {2});
+        this->command_set.add(odil::registry::NumberOfFailedSuboperations, {3});
+        this->command_set.add(odil::registry::NumberOfWarningSuboperations, {4});
+
+        this->data_set.add("PatientName", {"Doe^John"});
+        this->data_set.add("PatientID", {"DJ123"});
+        this->data_set.add("StudyDescription", {"Brain"});
+        this->data_set.add("StudyInstanceUID", {"1.2.3"});
+    }
+
+    virtual void check(odil::message::CGetResponse const & message)
+    {
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_GET_RSP);
+        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
+        BOOST_CHECK_EQUAL(
+            message.get_status(), odil::message::Response::Success);
+
+        BOOST_CHECK(message.has_message_id());
+        BOOST_CHECK_EQUAL(message.get_message_id(), 5678);
+
+        BOOST_CHECK(message.has_affected_sop_class_uid());
+        BOOST_CHECK_EQUAL(
+            message.get_affected_sop_class_uid(),
+            odil::registry::StudyRootQueryRetrieveInformationModelGET);
+
+        BOOST_CHECK(message.has_number_of_remaining_sub_operations());
+        BOOST_CHECK_EQUAL(message.get_number_of_remaining_sub_operations(), 1);
+
+        BOOST_CHECK(message.has_number_of_completed_sub_operations());
+        BOOST_CHECK_EQUAL(message.get_number_of_completed_sub_operations(), 2);
+
+        BOOST_CHECK(message.has_number_of_failed_sub_operations());
+        BOOST_CHECK_EQUAL(message.get_number_of_failed_sub_operations(), 3);
+
+        BOOST_CHECK(message.has_number_of_warning_sub_operations());
+        BOOST_CHECK_EQUAL(message.get_number_of_warning_sub_operations(), 4);
+
+        BOOST_CHECK(message.has_data_set());
+        BOOST_CHECK(message.get_data_set() == this->data_set);
+    }
+};
+
+BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
+{
+    odil::message::CGetResponse message(
+        1234, odil::message::Response::Success, this->data_set);
+    message.set_message_id(5678);
+    message.set_affected_sop_class_uid(
+        odil::registry::StudyRootQueryRetrieveInformationModelGET);
+    message.set_number_of_remaining_sub_operations(1);
+    message.set_number_of_completed_sub_operations(2);
+    message.set_number_of_failed_sub_operations(3);
+    message.set_number_of_warning_sub_operations(4);
+
+    this->check(message);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
+{
+    this->check_message_constructor(this->command_set, this->data_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
+{
+    this->command_set.as_int(odil::registry::CommandField) = {
+        odil::message::Message::Command::C_ECHO_RQ };
+    this->check_message_constructor_throw(this->command_set, this->data_set);
+}
+
+BOOST_AUTO_TEST_CASE(StatusWarning)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::CGetResponse::SubOperationsCompleteOneOrMoreFailuresOrWarnings
+    };
+
+    for(auto const status:statuses)
+    {
+        odil::message::CGetResponse response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(response.is_warning());
+        BOOST_REQUIRE(!response.is_failure());
+    }
+}
+
+BOOST_AUTO_TEST_CASE(StatusFailure)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::CGetResponse::RefusedOutOfResourcesUnableToCalculateNumberOfMatches,
+        odil::message::CGetResponse::RefusedOutOfResourcesUnableToPerformSubOperations,
+        odil::message::CGetResponse::IdentifierDoesNotMatchSOPClass,
+        odil::message::CGetResponse::UnableToProcess
+    };
+
+    for(auto const status:statuses)
+    {
+        odil::message::CGetResponse response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(!response.is_warning());
+        BOOST_REQUIRE(response.is_failure());
+    }
+}
diff --git a/tests/code/CMoveRequest.cpp b/tests/code/message/CMoveRequest.cpp
similarity index 58%
rename from tests/code/CMoveRequest.cpp
rename to tests/code/message/CMoveRequest.cpp
index 5d9d10f..fc71965 100644
--- a/tests/code/CMoveRequest.cpp
+++ b/tests/code/message/CMoveRequest.cpp
@@ -1,28 +1,27 @@
 #define BOOST_TEST_MODULE CMoveRequest
 #include <boost/test/unit_test.hpp>
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
+#include "odil/message/CMoveRequest.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
 
-#include "dcmtkpp/CMoveRequest.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
+#include "../../MessageFixtureBase.h"
 
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CMoveRequest>
+struct Fixture: public MessageFixtureBase<odil::message::CMoveRequest>
 {
-    dcmtkpp::DataSet command_set;
-    dcmtkpp::DataSet query;
+    odil::DataSet command_set;
+    odil::DataSet query;
 
     Fixture()
     {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_MOVE_RQ});
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_MOVE_RQ});
         this->command_set.add("MessageID", {1234});
         this->command_set.add("AffectedSOPClassUID",
-            {dcmtkpp::registry::PatientRootQueryRetrieveInformationModelMOVE});
-        this->command_set.add("Priority", {DIMSE_PRIORITY_MEDIUM});
+            {odil::registry::PatientRootQueryRetrieveInformationModelMOVE});
+        this->command_set.add(
+            "Priority", {odil::message::Message::Priority::MEDIUM});
         this->command_set.add("MoveDestination", {"destination"});
 
         this->query.add("PatientName", {"Doe^John"});
@@ -30,13 +29,15 @@ struct Fixture: public MessageFixtureBase<dcmtkpp::CMoveRequest>
         this->query.add("QueryRetrieveLevel", {"STUDY"});
     }
 
-    virtual void check(dcmtkpp::CMoveRequest const & message)
+    virtual void check(odil::message::CMoveRequest const & message)
     {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_MOVE_RQ);
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_MOVE_RQ);
         BOOST_CHECK_EQUAL(message.get_message_id(), 1234);
         BOOST_CHECK_EQUAL(
             message.get_affected_sop_class_uid(),
-            dcmtkpp::registry::PatientRootQueryRetrieveInformationModelMOVE);
+            odil::registry::PatientRootQueryRetrieveInformationModelMOVE);
         BOOST_CHECK_EQUAL(message.get_move_destination(), "destination");
         BOOST_CHECK(message.has_data_set());
         BOOST_CHECK(message.get_data_set() == this->query);
@@ -45,9 +46,10 @@ struct Fixture: public MessageFixtureBase<dcmtkpp::CMoveRequest>
 
 BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
 {
-    dcmtkpp::CMoveRequest const message(
-        1234, dcmtkpp::registry::PatientRootQueryRetrieveInformationModelMOVE,
-        DIMSE_PRIORITY_MEDIUM, "destination", this->query);
+    odil::message::CMoveRequest const message(
+        1234, odil::registry::PatientRootQueryRetrieveInformationModelMOVE,
+        odil::message::Message::Priority::MEDIUM, "destination",
+        this->query);
     this->check(message);
 }
 
@@ -58,7 +60,8 @@ BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
 
 BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
 {
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RQ};
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RQ};
     this->check_message_constructor_throw(this->command_set, this->query);
 }
 
@@ -76,6 +79,6 @@ BOOST_FIXTURE_TEST_CASE(MessageConstructorMissingPriority, Fixture)
 
 BOOST_FIXTURE_TEST_CASE(MessageConstructorEmptyQuery, Fixture)
 {
-    dcmtkpp::DataSet empty;
+    odil::DataSet empty;
     this->check_message_constructor_throw(this->command_set, empty);
 }
diff --git a/tests/code/message/CMoveResponse.cpp b/tests/code/message/CMoveResponse.cpp
new file mode 100644
index 0000000..593aa6e
--- /dev/null
+++ b/tests/code/message/CMoveResponse.cpp
@@ -0,0 +1,130 @@
+#define BOOST_TEST_MODULE CMoveResponse
+#include <boost/test/unit_test.hpp>
+
+#include "odil/message/CMoveResponse.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+
+#include "../../MessageFixtureBase.h"
+
+struct Fixture: public MessageFixtureBase<odil::message::CMoveResponse>
+{
+    odil::DataSet command_set;
+    odil::DataSet data_set;
+
+    Fixture()
+    {
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_MOVE_RSP});
+        this->command_set.add("MessageIDBeingRespondedTo", {1234});
+        this->command_set.add("Status", {odil::message::Response::Success});
+
+        this->command_set.add("MessageID", {5678});
+        this->command_set.add("AffectedSOPClassUID",
+            {odil::registry::StudyRootQueryRetrieveInformationModelMOVE});
+        this->command_set.add(odil::registry::NumberOfRemainingSuboperations, {1});
+        this->command_set.add(odil::registry::NumberOfCompletedSuboperations, {2});
+        this->command_set.add(odil::registry::NumberOfFailedSuboperations, {3});
+        this->command_set.add(odil::registry::NumberOfWarningSuboperations, {4});
+
+        this->data_set.add("PatientName", {"Doe^John"});
+        this->data_set.add("PatientID", {"DJ123"});
+        this->data_set.add("StudyDescription", {"Brain"});
+        this->data_set.add("StudyInstanceUID", {"1.2.3"});
+    }
+
+    virtual void check(odil::message::CMoveResponse const & message)
+    {
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_MOVE_RSP);
+        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
+        BOOST_CHECK_EQUAL(
+            message.get_status(), odil::message::Response::Success);
+
+        BOOST_CHECK(message.has_message_id());
+        BOOST_CHECK_EQUAL(message.get_message_id(), 5678);
+
+        BOOST_CHECK(message.has_affected_sop_class_uid());
+        BOOST_CHECK_EQUAL(
+            message.get_affected_sop_class_uid(),
+            odil::registry::StudyRootQueryRetrieveInformationModelMOVE);
+
+        BOOST_CHECK(message.has_number_of_remaining_sub_operations());
+        BOOST_CHECK_EQUAL(message.get_number_of_remaining_sub_operations(), 1);
+
+        BOOST_CHECK(message.has_number_of_completed_sub_operations());
+        BOOST_CHECK_EQUAL(message.get_number_of_completed_sub_operations(), 2);
+
+        BOOST_CHECK(message.has_number_of_failed_sub_operations());
+        BOOST_CHECK_EQUAL(message.get_number_of_failed_sub_operations(), 3);
+
+        BOOST_CHECK(message.has_number_of_warning_sub_operations());
+        BOOST_CHECK_EQUAL(message.get_number_of_warning_sub_operations(), 4);
+
+        BOOST_CHECK(message.has_data_set());
+        BOOST_CHECK(message.get_data_set() == this->data_set);
+    }
+};
+
+BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
+{
+    odil::message::CMoveResponse message(
+        1234, odil::message::Response::Success, this->data_set);
+    message.set_message_id(5678);
+    message.set_affected_sop_class_uid(
+        odil::registry::StudyRootQueryRetrieveInformationModelMOVE);
+    message.set_number_of_remaining_sub_operations(1);
+    message.set_number_of_completed_sub_operations(2);
+    message.set_number_of_failed_sub_operations(3);
+    message.set_number_of_warning_sub_operations(4);
+
+    this->check(message);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
+{
+    this->check_message_constructor(this->command_set, this->data_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
+{
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RQ};
+    this->check_message_constructor_throw(this->command_set, this->data_set);
+}
+
+BOOST_AUTO_TEST_CASE(StatusWarning)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::CMoveResponse::SubOperationsCompleteOneOrMoreFailuresOrWarnings
+    };
+
+    for(auto const status:statuses)
+    {
+        odil::message::CMoveResponse response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(response.is_warning());
+        BOOST_REQUIRE(!response.is_failure());
+    }
+}
+
+BOOST_AUTO_TEST_CASE(StatusFailure)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::CMoveResponse::RefusedOutOfResourcesUnableToCalculateNumberOfMatches,
+        odil::message::CMoveResponse::RefusedOutOfResourcesUnableToPerformSubOperations,
+        odil::message::CMoveResponse::RefusedMoveDestinationUnknown,
+        odil::message::CMoveResponse::IdentifierDoesNotMatchSOPClass,
+        odil::message::CMoveResponse::UnableToProcess
+    };
+
+    for(auto const status:statuses)
+    {
+        odil::message::CMoveResponse response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(!response.is_warning());
+        BOOST_REQUIRE(response.is_failure());
+    }
+}
diff --git a/tests/code/CStoreRequest.cpp b/tests/code/message/CStoreRequest.cpp
similarity index 65%
rename from tests/code/CStoreRequest.cpp
rename to tests/code/message/CStoreRequest.cpp
index fd926db..9d0162f 100644
--- a/tests/code/CStoreRequest.cpp
+++ b/tests/code/message/CStoreRequest.cpp
@@ -1,28 +1,28 @@
 #define BOOST_TEST_MODULE CStoreRequest
 #include <boost/test/unit_test.hpp>
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmnet/dimse.h>
+#include "odil/message/CStoreRequest.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
 
-#include "dcmtkpp/CStoreRequest.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/registry.h"
+#include "../../MessageFixtureBase.h"
 
-#include "../MessageFixtureBase.h"
-
-struct Fixture: public MessageFixtureBase<dcmtkpp::CStoreRequest>
+struct Fixture: public MessageFixtureBase<odil::message::CStoreRequest>
 {
-    dcmtkpp::DataSet command_set;
-    dcmtkpp::DataSet data_set;
+    odil::DataSet command_set;
+    odil::DataSet data_set;
 
     Fixture()
     {
-        this->command_set.add("CommandField", {dcmtkpp::Message::Command::C_STORE_RQ});
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_STORE_RQ});
         this->command_set.add("MessageID", {1234});
-        this->command_set.add("AffectedSOPClassUID", {dcmtkpp::registry::MRImageStorage});
+        this->command_set.add(
+            "AffectedSOPClassUID", {odil::registry::MRImageStorage});
         this->command_set.add("AffectedSOPInstanceUID", {"1.2.3.4"});
-        this->command_set.add("Priority", {DIMSE_PRIORITY_MEDIUM});
+        this->command_set.add(
+            "Priority", {odil::message::Message::Priority::MEDIUM});
 
         this->command_set.add("MoveOriginatorApplicationEntityTitle", {"origin"});
         this->command_set.add("MoveOriginatorMessageID", {5678});
@@ -33,12 +33,14 @@ struct Fixture: public MessageFixtureBase<dcmtkpp::CStoreRequest>
         this->data_set.add("StudyInstanceUID", {"1.2.3"});
     }
 
-    void check(dcmtkpp::CStoreRequest const & message)
+    void check(odil::message::CStoreRequest const & message)
     {
-        BOOST_CHECK_EQUAL(message.get_command_field(), dcmtkpp::Message::Command::C_STORE_RQ);
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_STORE_RQ);
         BOOST_CHECK_EQUAL(message.get_message_id(), 1234);
         BOOST_CHECK_EQUAL(
-            message.get_affected_sop_class_uid(), dcmtkpp::registry::MRImageStorage);
+            message.get_affected_sop_class_uid(), odil::registry::MRImageStorage);
         BOOST_CHECK_EQUAL(
             message.get_affected_sop_instance_uid(), "1.2.3.4");
 
@@ -55,8 +57,9 @@ struct Fixture: public MessageFixtureBase<dcmtkpp::CStoreRequest>
 
 BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
 {
-    dcmtkpp::CStoreRequest message(
-        1234, dcmtkpp::registry::MRImageStorage, "1.2.3.4", DIMSE_PRIORITY_MEDIUM,
+    odil::message::CStoreRequest message(
+        1234, odil::registry::MRImageStorage, "1.2.3.4",
+        odil::message::Message::Priority::MEDIUM,
         this->data_set);
     message.set_move_originator_ae_title("origin");
     message.set_move_originator_message_id(5678);
@@ -71,7 +74,8 @@ BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
 
 BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
 {
-    this->command_set.as_int("CommandField") = {dcmtkpp::Message::Command::C_ECHO_RSP};
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RSP};
     this->check_message_constructor_throw(this->command_set, this->data_set);
 }
 
diff --git a/tests/code/message/CStoreResponse.cpp b/tests/code/message/CStoreResponse.cpp
new file mode 100644
index 0000000..369e1e7
--- /dev/null
+++ b/tests/code/message/CStoreResponse.cpp
@@ -0,0 +1,106 @@
+#define BOOST_TEST_MODULE CStoreResponse
+#include <boost/test/unit_test.hpp>
+
+#include "odil/message/CStoreResponse.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/registry.h"
+
+#include "../../MessageFixtureBase.h"
+
+struct Fixture: public MessageFixtureBase<odil::message::CStoreResponse>
+{
+    odil::DataSet command_set;
+
+    Fixture()
+    {
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_STORE_RSP});
+        this->command_set.add("MessageIDBeingRespondedTo", {1234});
+        this->command_set.add("Status", {odil::message::Response::Success});
+
+        this->command_set.add("MessageID", {5678});
+        this->command_set.add(
+            "AffectedSOPClassUID", {odil::registry::MRImageStorage});
+        this->command_set.add("AffectedSOPInstanceUID", {"1.2.3.4"});
+    }
+
+    void check(odil::message::CStoreResponse const & message)
+    {
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_STORE_RSP);
+        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
+        BOOST_CHECK_EQUAL(
+            message.get_status(), odil::message::Response::Success);
+        BOOST_CHECK(!message.has_data_set());
+
+        BOOST_CHECK(message.has_message_id());
+        BOOST_CHECK_EQUAL(message.get_message_id(), 5678);
+
+        BOOST_CHECK(message.has_affected_sop_class_uid());
+        BOOST_CHECK_EQUAL(
+            message.get_affected_sop_class_uid(), odil::registry::MRImageStorage);
+
+        BOOST_CHECK(message.has_affected_sop_instance_uid());
+        BOOST_CHECK_EQUAL(message.get_affected_sop_instance_uid(), "1.2.3.4");
+    }
+};
+
+BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
+{
+    odil::message::CStoreResponse message(
+        1234, odil::message::Response::Success);
+    message.set_message_id(5678);
+    message.set_affected_sop_class_uid(odil::registry::MRImageStorage);
+    message.set_affected_sop_instance_uid("1.2.3.4");
+
+    this->check(message);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
+{
+    this->check_message_constructor(this->command_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
+{
+    this->command_set.add(
+        "CommandField", {odil::message::Message::Command::C_ECHO_RQ});
+    this->check_message_constructor_throw(this->command_set);
+}
+
+BOOST_AUTO_TEST_CASE(StatusWarning)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::CStoreResponse::CoercionOfDataElements,
+        odil::message::CStoreResponse::DataSetDoesNotMatchSOPClass,
+        odil::message::CStoreResponse::ElementsDiscarded
+    };
+
+    for(auto const status:statuses)
+    {
+        odil::message::CStoreResponse response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(response.is_warning());
+        BOOST_REQUIRE(!response.is_failure());
+    }
+}
+
+
+BOOST_AUTO_TEST_CASE(StatusFailure)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::CStoreResponse::RefusedOutOfResources,
+        odil::message::CStoreResponse::ErrorDataSetDoesNotMatchSOPClass,
+        odil::message::CStoreResponse::ErrorCannotUnderstand
+    };
+
+    for(auto const status:statuses)
+    {
+        odil::message::CStoreResponse response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(!response.is_warning());
+        BOOST_REQUIRE(response.is_failure());
+    }
+}
diff --git a/tests/code/message/Cancellation.cpp b/tests/code/message/Cancellation.cpp
new file mode 100644
index 0000000..c193924
--- /dev/null
+++ b/tests/code/message/Cancellation.cpp
@@ -0,0 +1,44 @@
+#define BOOST_TEST_MODULE Cancellation
+#include <boost/test/unit_test.hpp>
+
+#include "odil/DataSet.h"
+#include "odil/message/Cancellation.h"
+
+#include "../../MessageFixtureBase.h"
+
+struct Fixture: public MessageFixtureBase<odil::message::Cancellation>
+{
+    odil::DataSet command_set;
+    Fixture()
+    {
+        this->command_set.add(
+            "CommandField", {odil::message::Message::Command::C_CANCEL_RQ});
+        this->command_set.add("MessageIDBeingRespondedTo", {1234});
+    }
+
+    void check(odil::message::Cancellation const & message)
+    {
+        BOOST_CHECK_EQUAL(
+            message.get_command_field(),
+            odil::message::Message::Command::C_CANCEL_RQ);
+        BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
+    }
+};
+
+BOOST_FIXTURE_TEST_CASE(Constructor, Fixture)
+{
+    odil::message::Cancellation const message(1234);
+    this->check(message);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructor, Fixture)
+{
+    this->check_message_constructor(this->command_set);
+}
+
+BOOST_FIXTURE_TEST_CASE(MessageConstructorWrongCommandField, Fixture)
+{
+    this->command_set.as_int("CommandField") = {
+        odil::message::Message::Command::C_ECHO_RQ};
+    this->check_message_constructor_throw(this->command_set);
+}
diff --git a/tests/code/message/Message.cpp b/tests/code/message/Message.cpp
new file mode 100644
index 0000000..08f9172
--- /dev/null
+++ b/tests/code/message/Message.cpp
@@ -0,0 +1,63 @@
+#define BOOST_TEST_MODULE Message
+#include <boost/test/unit_test.hpp>
+
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+
+BOOST_AUTO_TEST_CASE(DefaultConstructor)
+{
+    odil::message::Message const message;
+
+    // Command Set might not be empty (Command Group Length, Data Set Type)
+    BOOST_CHECK(!message.has_data_set());
+}
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::DataSet command_set;
+    command_set.add(
+        "CommandField", {odil::message::Message::Command::C_ECHO_RQ});
+
+    odil::DataSet data_set;
+
+    odil::message::Message const message(command_set, data_set);
+
+    BOOST_CHECK_EQUAL(
+        message.get_command_set().as_int("CommandField", 0),
+        odil::message::Message::Command::C_ECHO_RQ);
+
+    BOOST_CHECK(message.has_data_set());
+    BOOST_CHECK(message.get_data_set().empty());
+
+    BOOST_CHECK_EQUAL(
+        message.get_command_field(),
+        odil::message::Message::Command::C_ECHO_RQ);
+}
+
+BOOST_AUTO_TEST_CASE(CommandField)
+{
+    odil::message::Message message;
+    message.set_command_field(odil::message::Message::Command::C_FIND_RSP);
+
+    BOOST_CHECK(
+        message.get_command_set().as_int("CommandField") ==
+            odil::Value::Integers(
+                {odil::message::Message::Command::C_FIND_RSP}));
+    BOOST_CHECK_EQUAL(
+        message.get_command_field(),
+        odil::message::Message::Command::C_FIND_RSP);
+}
+
+BOOST_AUTO_TEST_CASE(DeleteDataSet)
+{
+    odil::DataSet command_set;
+    odil::DataSet data_set;
+
+    odil::message::Message message(command_set, data_set);
+
+    BOOST_CHECK(message.has_data_set());
+
+    message.delete_data_set();
+
+    BOOST_CHECK(!message.has_data_set());
+}
diff --git a/tests/code/Request.cpp b/tests/code/message/Request.cpp
similarity index 53%
rename from tests/code/Request.cpp
rename to tests/code/message/Request.cpp
index c040689..b17af64 100644
--- a/tests/code/Request.cpp
+++ b/tests/code/message/Request.cpp
@@ -1,26 +1,24 @@
 #define BOOST_TEST_MODULE Request
 #include <boost/test/unit_test.hpp>
 
-#include <dcmtk/config/osconfig.h>
-
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/Message.h"
-#include "dcmtkpp/Request.h"
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/message/Request.h"
 
 BOOST_AUTO_TEST_CASE(Constructor)
 {
-    dcmtkpp::Request const message(1234);
+    odil::message::Request const message(1234);
 
     BOOST_CHECK_EQUAL(message.get_message_id(), 1234);
 }
 
 BOOST_AUTO_TEST_CASE(MessageConstructor)
 {
-    dcmtkpp::DataSet command_set;
+    odil::DataSet command_set;
     command_set.add("MessageID", {1234});
-    dcmtkpp::Message const generic_message(command_set);
+    odil::message::Message const generic_message(command_set);
 
-    dcmtkpp::Request const message(generic_message);
+    odil::message::Request const message(generic_message);
 
     BOOST_CHECK_EQUAL(message.get_message_id(), 1234);
     BOOST_CHECK(!message.has_data_set());
diff --git a/tests/code/message/Response.cpp b/tests/code/message/Response.cpp
new file mode 100644
index 0000000..616772c
--- /dev/null
+++ b/tests/code/message/Response.cpp
@@ -0,0 +1,112 @@
+#define BOOST_TEST_MODULE Response
+#include <boost/test/unit_test.hpp>
+
+#include <vector>
+
+#include "odil/DataSet.h"
+#include "odil/message/Message.h"
+#include "odil/message/Response.h"
+#include "odil/Value.h"
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::message::Response const message(
+        1234, odil::message::Response::Pending);
+
+    BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
+    BOOST_CHECK_EQUAL(message.get_status(),
+        odil::message::Response::Pending);
+}
+
+BOOST_AUTO_TEST_CASE(MessageConstructor)
+{
+    odil::DataSet command_set;
+    command_set.add("MessageIDBeingRespondedTo", {1234});
+    command_set.add("Status", {odil::message::Response::Pending});
+    odil::message::Message const generic_message(command_set);
+
+    odil::message::Response const message(generic_message);
+
+    BOOST_CHECK_EQUAL(message.get_message_id_being_responded_to(), 1234);
+    BOOST_CHECK_EQUAL(
+        message.get_status(), odil::message::Response::Pending);
+    BOOST_CHECK(!message.has_data_set());
+}
+
+BOOST_AUTO_TEST_CASE(StatusPending)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::Response::Pending,
+    };
+
+    for(auto const status:statuses)
+    {
+        BOOST_REQUIRE(odil::message::Response::is_pending(status));
+        BOOST_REQUIRE(!odil::message::Response::is_warning(status));
+        BOOST_REQUIRE(!odil::message::Response::is_failure(status));
+
+        odil::message::Response response(1234, status);
+        BOOST_REQUIRE(response.is_pending());
+        BOOST_REQUIRE(!response.is_warning());
+        BOOST_REQUIRE(!response.is_failure());
+    }
+}
+
+
+BOOST_AUTO_TEST_CASE(StatusWarning)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::Response::AttributeListError,
+        odil::message::Response::AttributeValueOutOfRange
+    };
+
+    for(auto const status:statuses)
+    {
+        BOOST_REQUIRE(!odil::message::Response::is_pending(status));
+        BOOST_REQUIRE(odil::message::Response::is_warning(status));
+        BOOST_REQUIRE(!odil::message::Response::is_failure(status));
+
+        odil::message::Response response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(response.is_warning());
+        BOOST_REQUIRE(!response.is_failure());
+    }
+}
+
+BOOST_AUTO_TEST_CASE(StatusFailure)
+{
+    std::vector<odil::Value::Integer> const statuses = {
+        odil::message::Response::SOPClassNotSupported,
+        odil::message::Response::ClassInstanceConflict,
+        odil::message::Response::DuplicateSOPInstance,
+        odil::message::Response::DuplicateInvocation,
+        odil::message::Response::InvalidArgumentValue,
+        odil::message::Response::InvalidAttributeValue,
+        odil::message::Response::InvalidObjectInstance,
+        odil::message::Response::MissingAttribute,
+        odil::message::Response::MissingAttributeValue,
+        odil::message::Response::MistypedArgument,
+        odil::message::Response::NoSuchArgument,
+        odil::message::Response::NoSuchAttribute,
+        odil::message::Response::NoSuchEventType,
+        odil::message::Response::NoSuchSOPInstance,
+        odil::message::Response::NoSuchSOPClass,
+        odil::message::Response::ProcessingFailure,
+        odil::message::Response::ResourceLimitation,
+        odil::message::Response::UnrecognizedOperation,
+        odil::message::Response::NoSuchActionType,
+        odil::message::Response::RefusedNotAuthorized
+    };
+
+    for(auto const status:statuses)
+    {
+        BOOST_REQUIRE(!odil::message::Response::is_pending(status));
+        BOOST_REQUIRE(!odil::message::Response::is_warning(status));
+        BOOST_REQUIRE(odil::message::Response::is_failure(status));
+
+        odil::message::Response response(1234, status);
+        BOOST_REQUIRE(!response.is_pending());
+        BOOST_REQUIRE(!response.is_warning());
+        BOOST_REQUIRE(response.is_failure());
+    }
+}
diff --git a/tests/code/pdu/AAbort.cpp b/tests/code/pdu/AAbort.cpp
new file mode 100644
index 0000000..bbe1765
--- /dev/null
+++ b/tests/code/pdu/AAbort.cpp
@@ -0,0 +1,51 @@
+#define BOOST_TEST_MODULE pdu::AAbort
+#include <boost/test/unit_test.hpp>
+
+#include <istream>
+#include <memory>
+
+#include "odil/pdu/AAbort.h"
+#include "odil/Exception.h"
+
+std::string const data = {
+    0x07, 0x00,
+    0x00, 0x00, 0x00, 0x04,
+    0x00, 0x00,
+    0x01, 0x02
+};
+
+BOOST_AUTO_TEST_CASE(ConstructorFields)
+{
+    odil::pdu::AAbort const pdu(1, 2);
+    BOOST_REQUIRE_EQUAL(pdu.get_source(), 1);
+    BOOST_REQUIRE_EQUAL(pdu.get_reason(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStream)
+{
+    std::istringstream stream(data);
+    odil::pdu::AAbort const pdu(stream);
+    BOOST_REQUIRE_EQUAL(pdu.get_source(), 1);
+    BOOST_REQUIRE_EQUAL(pdu.get_reason(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::AAbort const pdu(1, 2);
+    std::ostringstream stream;
+    stream << pdu;
+
+    BOOST_REQUIRE(stream.str() == data);
+}
+
+BOOST_AUTO_TEST_CASE(WrongSource)
+{
+    odil::pdu::AAbort pdu(1, 2);
+    BOOST_REQUIRE_THROW(pdu.set_source(3), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(WrongReason)
+{
+    odil::pdu::AAbort pdu(1, 2);
+    BOOST_REQUIRE_THROW(pdu.set_reason(9), odil::Exception);
+}
diff --git a/tests/code/pdu/AAssociateAC.cpp b/tests/code/pdu/AAssociateAC.cpp
new file mode 100644
index 0000000..2e44a82
--- /dev/null
+++ b/tests/code/pdu/AAssociateAC.cpp
@@ -0,0 +1,279 @@
+#define BOOST_TEST_MODULE AAssociateAC
+#include <boost/test/unit_test.hpp>
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "odil/pdu/AAssociateAC.h"
+#include "odil/pdu/ApplicationContext.h"
+#include "odil/pdu/PresentationContextAC.h"
+#include "odil/pdu/UserIdentityAC.h"
+#include "odil/pdu/UserInformation.h"
+#include "odil/Exception.h"
+
+struct Fixture
+{
+public:
+    static std::string const read_data;
+    static std::string const write_data;
+
+    odil::pdu::ApplicationContext application_context;
+    std::vector<odil::pdu::PresentationContextAC> presentation_contexts;
+    odil::pdu::UserInformation user_information;
+
+    Fixture()
+    : application_context("foo")
+    {
+        odil::pdu::PresentationContextAC pc1(3, "transfer_syntax", 1);
+        odil::pdu::PresentationContextAC pc2(5, "transfer_syntax_2", 2);
+        this->presentation_contexts = {pc1, pc2};
+
+        this->user_information.set_sub_items<odil::pdu::MaximumLength>(
+            { { 0x12345678 } });
+        this->user_information.set_sub_items<odil::pdu::UserIdentityAC>(
+            { { "bar" } });
+    }
+
+    void check_application_context(
+        odil::pdu::ApplicationContext const & context) const
+    {
+        BOOST_REQUIRE_EQUAL(
+            context.get_name(), this->application_context.get_name());
+    }
+
+    void check_presentation_contexts(
+        std::vector<odil::pdu::PresentationContextAC> const & contexts)
+    {
+        BOOST_REQUIRE_EQUAL(contexts.size(), presentation_contexts.size());
+        for(int i=0; i<contexts.size(); ++i)
+        {
+            BOOST_REQUIRE_EQUAL(
+                contexts[i].get_item_type(),
+                presentation_contexts[i].get_item_type());
+            BOOST_REQUIRE_EQUAL(
+                contexts[i].get_id(),
+                presentation_contexts[i].get_id());
+            BOOST_REQUIRE_EQUAL(
+                contexts[i].get_result_reason(),
+                presentation_contexts[i].get_result_reason());
+        }
+    }
+
+    void check_user_information(
+        odil::pdu::UserInformation const & user_information) const
+    {
+        BOOST_REQUIRE(
+            !user_information.get_sub_items<odil::pdu::MaximumLength>().empty());
+        BOOST_REQUIRE_EQUAL(
+            user_information.get_sub_items<odil::pdu::MaximumLength>()[0].get_maximum_length(),
+            0x12345678);
+
+        BOOST_REQUIRE(
+            !user_information.get_sub_items<odil::pdu::UserIdentityAC>().empty());
+        BOOST_REQUIRE_EQUAL(
+            user_information.get_sub_items<odil::pdu::UserIdentityAC>()[0].get_server_response(),
+            "bar");
+    }
+};
+
+std::string const
+Fixture
+::read_data(
+    // Header, 6 bytes
+
+    "\x02\x00"
+    "\x00\x00\x00\x98"
+
+    // Items: 68 bytes
+
+    "\x00\x01\x00\x00"
+    "       CALLED_AE"
+    "CALLING_AE      "
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+
+    // Application Context, 7 bytes
+
+    "\x10\x00\x00\x03"
+    "foo"
+
+    // Presentation Contexts, 27+29 bytes
+
+    "\x21\x00\x00\x17"
+    "\x03\x00\x01\x00"
+    "\x40\x00\x00\x0f""transfer_syntax"
+
+    "\x21\x00\x00\x19"
+    "\x05\x00\x02\x00"
+    "\x40\x00\x00\x11""transfer_syntax_2"
+
+    // User Information, 21 bytes
+
+    "\x50\x00\x00\x11"
+    "\x51\x00\x00\x04"
+    "\x12\x34\x56\x78"
+    "\x59\x00\x00\x05"
+    "\x00\x03" "bar"
+    ,
+    158
+);
+
+std::string const
+Fixture
+::write_data(
+    // Header, 6 bytes
+
+    "\x02\x00"
+    "\x00\x00\x00\x98"
+
+    // Items: 68 bytes
+
+    "\x00\x01\x00\x00"
+    "CALLED_AE       "
+    "CALLING_AE      "
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+
+    // Application Context, 7 bytes
+
+    "\x10\x00\x00\x03"
+    "foo"
+
+    // Presentation Contexts, 27+29 bytes
+
+    "\x21\x00\x00\x17"
+    "\x03\x00\x01\x00"
+    "\x40\x00\x00\x0f""transfer_syntax"
+
+    "\x21\x00\x00\x19"
+    "\x05\x00\x02\x00"
+    "\x40\x00\x00\x11""transfer_syntax_2"
+
+    // User Information, 21 bytes
+
+    "\x50\x00\x00\x11"
+    "\x51\x00\x00\x04"
+    "\x12\x34\x56\x78"
+    "\x59\x00\x00\x05"
+    "\x00\x03" "bar"
+    ,
+    158
+);
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::pdu::AAssociateAC const pdu;
+
+    BOOST_REQUIRE_EQUAL(pdu.get_called_ae_title(), "");
+    BOOST_REQUIRE_EQUAL(pdu.get_calling_ae_title(), "");
+    BOOST_REQUIRE_EQUAL(pdu.get_protocol_version(), 0);
+    BOOST_REQUIRE_THROW(pdu.get_application_context(), odil::Exception);
+    BOOST_REQUIRE(pdu.get_presentation_contexts().empty());
+    BOOST_REQUIRE_THROW(pdu.get_user_information(), odil::Exception);
+}
+
+BOOST_FIXTURE_TEST_CASE(ConstructorStream, Fixture)
+{
+    std::istringstream stream(read_data);
+    odil::pdu::AAssociateAC const pdu(stream);
+
+    BOOST_REQUIRE_EQUAL(pdu.get_called_ae_title(), "CALLED_AE");
+    BOOST_REQUIRE_EQUAL(pdu.get_calling_ae_title(), "CALLING_AE");
+    BOOST_REQUIRE_EQUAL(pdu.get_protocol_version(), 1);
+    this->check_application_context(pdu.get_application_context());
+    this->check_presentation_contexts(pdu.get_presentation_contexts());
+    this->check_user_information(pdu.get_user_information());
+}
+
+BOOST_AUTO_TEST_CASE(ProtocolVersion)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_EQUAL(pdu.get_protocol_version(), 0);
+    pdu.set_protocol_version(2);
+    BOOST_REQUIRE_EQUAL(pdu.get_protocol_version(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(CalledAETitle)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_EQUAL(pdu.get_called_ae_title(), "");
+    pdu.set_called_ae_title("called");
+    BOOST_REQUIRE_EQUAL(pdu.get_called_ae_title(), "called");
+}
+
+BOOST_AUTO_TEST_CASE(CallingAETitle)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_EQUAL(pdu.get_calling_ae_title(), "");
+    pdu.set_calling_ae_title("calling");
+    BOOST_REQUIRE_EQUAL(pdu.get_calling_ae_title(), "calling");
+}
+
+BOOST_FIXTURE_TEST_CASE(ApplicationContext, Fixture)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_THROW(pdu.get_application_context(), odil::Exception);
+    pdu.set_application_context(application_context);
+    this->check_application_context(pdu.get_application_context());
+}
+
+BOOST_FIXTURE_TEST_CASE(PresentationContexts, Fixture)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE(pdu.get_presentation_contexts().empty());
+    pdu.set_presentation_contexts(presentation_contexts);
+    this->check_presentation_contexts(pdu.get_presentation_contexts());
+}
+
+BOOST_FIXTURE_TEST_CASE(UserInformation, Fixture)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_THROW(pdu.get_user_information(), odil::Exception);
+    pdu.set_user_information(user_information);
+    this->check_user_information(pdu.get_user_information());
+}
+
+BOOST_FIXTURE_TEST_CASE(Write, Fixture)
+{
+    odil::pdu::AAssociateAC pdu;
+    pdu.set_protocol_version(1);
+    pdu.set_called_ae_title("CALLED_AE");
+    pdu.set_calling_ae_title("CALLING_AE");
+
+    pdu.set_application_context(application_context);
+    pdu.set_presentation_contexts(presentation_contexts);
+    pdu.set_user_information(user_information);
+
+    std::ostringstream stream;
+    stream << pdu;
+
+    BOOST_REQUIRE(stream.str() == write_data);
+}
+
+BOOST_AUTO_TEST_CASE(CalledAETitleEmpty)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_THROW(pdu.set_called_ae_title(""), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(CalledAETitleTooLong)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_THROW(
+        pdu.set_called_ae_title("123456789abcdef01"), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(CallingAETitleEmpty)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_THROW(pdu.set_calling_ae_title(""), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(CallingAETitleTooLong)
+{
+    odil::pdu::AAssociateAC pdu;
+    BOOST_REQUIRE_THROW(
+        pdu.set_calling_ae_title("123456789abcdef01"), odil::Exception);
+}
diff --git a/tests/code/pdu/AAssociateRJ.cpp b/tests/code/pdu/AAssociateRJ.cpp
new file mode 100644
index 0000000..b857ee0
--- /dev/null
+++ b/tests/code/pdu/AAssociateRJ.cpp
@@ -0,0 +1,59 @@
+#define BOOST_TEST_MODULE pdu::AssociateRJ
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+
+#include "odil/pdu/AAssociateRJ.h"
+#include "odil/Exception.h"
+
+std::string const data = {
+    0x03, 0x00,
+    0x00, 0x00, 0x00, 0x04,
+    0x00, 0x01,
+    0x02, 0x03
+};
+
+BOOST_AUTO_TEST_CASE(ConstructorFields)
+{
+    odil::pdu::AAssociateRJ const pdu(1, 2, 3);
+    BOOST_REQUIRE_EQUAL(pdu.get_result(), 1);
+    BOOST_REQUIRE_EQUAL(pdu.get_source(), 2);
+    BOOST_REQUIRE_EQUAL(pdu.get_reason(), 3);
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStream)
+{
+    std::istringstream stream(data);
+    odil::pdu::AAssociateRJ const pdu(stream);
+    BOOST_REQUIRE_EQUAL(pdu.get_result(), 1);
+    BOOST_REQUIRE_EQUAL(pdu.get_source(), 2);
+    BOOST_REQUIRE_EQUAL(pdu.get_reason(), 3);
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::AAssociateRJ const pdu(1, 2, 3);
+    std::ostringstream stream;
+    stream << pdu;
+
+    BOOST_REQUIRE(stream.str() == data);
+}
+
+BOOST_AUTO_TEST_CASE(WrongResult)
+{
+    odil::pdu::AAssociateRJ pdu(1, 2, 3);
+    BOOST_REQUIRE_THROW(pdu.set_result(4), odil::Exception);
+}
+
+
+BOOST_AUTO_TEST_CASE(WrongSource)
+{
+    odil::pdu::AAssociateRJ pdu(1, 2, 3);
+    BOOST_REQUIRE_THROW(pdu.set_source(4), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(WrongReason)
+{
+    odil::pdu::AAssociateRJ pdu(1, 2, 3);
+    BOOST_REQUIRE_THROW(pdu.set_reason(25), odil::Exception);
+}
diff --git a/tests/code/pdu/AAssociateRQ.cpp b/tests/code/pdu/AAssociateRQ.cpp
new file mode 100644
index 0000000..3fc2156
--- /dev/null
+++ b/tests/code/pdu/AAssociateRQ.cpp
@@ -0,0 +1,290 @@
+#define BOOST_TEST_MODULE AAssociateRQ
+#include <boost/test/unit_test.hpp>
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "odil/pdu/AAssociateRQ.h"
+#include "odil/pdu/ApplicationContext.h"
+#include "odil/pdu/PresentationContextRQ.h"
+#include "odil/pdu/UserIdentityRQ.h"
+#include "odil/pdu/UserInformation.h"
+#include "odil/Exception.h"
+
+struct Fixture
+{
+public:
+    static std::string const read_data;
+    static std::string const write_data;
+
+    odil::pdu::ApplicationContext application_context;
+    std::vector<odil::pdu::PresentationContextRQ> presentation_contexts;
+    odil::pdu::UserInformation user_information;
+
+    Fixture()
+    : application_context("foo")
+    {
+        odil::pdu::PresentationContextRQ pc1(3, "abstract_syntax", {"ts1", "ts2"});
+        this->presentation_contexts = {pc1};
+
+        this->user_information.set_sub_items<odil::pdu::MaximumLength>(
+            { { 0x12345678 } });
+        odil::pdu::UserIdentityRQ user_identity;
+        user_identity.set_username_and_passcode("foo", "bar");
+        user_identity.set_positive_response_requested(true);
+        this->user_information.set_sub_items<odil::pdu::UserIdentityRQ>(
+            { user_identity });
+    }
+
+    void check_application_context(
+        odil::pdu::ApplicationContext const & context) const
+    {
+        BOOST_REQUIRE_EQUAL(
+            context.get_name(), this->application_context.get_name());
+    }
+
+    void check_presentation_contexts(
+        std::vector<odil::pdu::PresentationContextRQ> const & contexts)
+    {
+        BOOST_REQUIRE_EQUAL(contexts.size(), presentation_contexts.size());
+        for(int i=0; i<contexts.size(); ++i)
+        {
+            BOOST_REQUIRE_EQUAL(
+                contexts[i].get_item_type(),
+                presentation_contexts[i].get_item_type());
+            BOOST_REQUIRE_EQUAL(
+                contexts[i].get_id(),
+                presentation_contexts[i].get_id());
+            BOOST_REQUIRE_EQUAL(
+                contexts[i].get_abstract_syntax(),
+                presentation_contexts[i].get_abstract_syntax());
+        }
+    }
+
+    void check_user_information(
+        odil::pdu::UserInformation const & user_information) const
+    {
+        BOOST_REQUIRE(
+            !user_information.get_sub_items<odil::pdu::MaximumLength>().empty());
+        BOOST_REQUIRE_EQUAL(
+            user_information.get_sub_items<odil::pdu::MaximumLength>()[0].get_maximum_length(),
+            0x12345678);
+
+        BOOST_REQUIRE(
+            !user_information.get_sub_items<odil::pdu::UserIdentityRQ>().empty());
+        BOOST_REQUIRE_EQUAL(
+            user_information.get_sub_items<odil::pdu::UserIdentityRQ>()[0].get_type(),
+            2);
+        BOOST_REQUIRE_EQUAL(
+            user_information.get_sub_items<odil::pdu::UserIdentityRQ>()[0].get_positive_response_requested(),
+            true);
+        BOOST_REQUIRE_EQUAL(
+            user_information.get_sub_items<odil::pdu::UserIdentityRQ>()[0].get_primary_field(),
+            "foo");
+        BOOST_REQUIRE_EQUAL(
+            user_information.get_sub_items<odil::pdu::UserIdentityRQ>()[0].get_secondary_field(),
+            "bar");
+    }
+};
+
+std::string const
+Fixture
+::read_data(
+    // Header, 6 bytes
+
+    "\x01\x00"
+    "\x00\x00\x00\x90"
+
+    // Items: 68 bytes
+
+    "\x00\x01\x00\x00"
+    "       CALLED_AE"
+    "CALLING_AE      "
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+
+    // Application Context, 7 bytes
+
+    "\x10\x00\x00\x03"
+    "foo"
+
+    // Presentation Contexts, 41 bytes
+
+    "\x20\x00\x00\x25"
+    "\x03\x00\x00\x00"
+    "\x30\x00\x00\x0f""abstract_syntax"
+    "\x40\x00\x00\x03""ts1"
+    "\x40\x00\x00\x03""ts2"
+
+    // User Information, 28 bytes
+
+    "\x50\x00\x00\x18"
+    "\x51\x00\x00\x04"
+    "\x12\x34\x56\x78"
+    "\x58\x00\x00\x0c"
+    "\x02\x01"
+    "\x00\x03" "foo"
+    "\x00\x03" "bar"
+    ,
+    150
+);
+
+std::string const
+Fixture
+::write_data(
+    // Header, 6 bytes
+
+    "\x01\x00"
+    "\x00\x00\x00\x90"
+
+    // Items: 68 bytes
+
+    "\x00\x01\x00\x00"
+    "CALLED_AE       "
+    "CALLING_AE      "
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+
+    // Application Context, 7 bytes
+
+    "\x10\x00\x00\x03"
+    "foo"
+
+    // Presentation Contexts, 41 bytes
+
+    "\x20\x00\x00\x25"
+    "\x03\x00\x00\x00"
+    "\x30\x00\x00\x0f""abstract_syntax"
+    "\x40\x00\x00\x03""ts1"
+    "\x40\x00\x00\x03""ts2"
+
+    // User Information, 28 bytes
+
+    "\x50\x00\x00\x18"
+    "\x51\x00\x00\x04"
+    "\x12\x34\x56\x78"
+    "\x58\x00\x00\x0c"
+    "\x02\x01"
+    "\x00\x03" "foo"
+    "\x00\x03" "bar"
+    ,
+    150
+);
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::pdu::AAssociateRQ const pdu;
+
+    BOOST_REQUIRE_EQUAL(pdu.get_called_ae_title(), "");
+    BOOST_REQUIRE_EQUAL(pdu.get_calling_ae_title(), "");
+    BOOST_REQUIRE_EQUAL(pdu.get_protocol_version(), 0);
+    BOOST_REQUIRE_THROW(pdu.get_application_context(), odil::Exception);
+    BOOST_REQUIRE(pdu.get_presentation_contexts().empty());
+    BOOST_REQUIRE_THROW(pdu.get_user_information(), odil::Exception);
+}
+
+BOOST_FIXTURE_TEST_CASE(ConstructorStream, Fixture)
+{
+    std::istringstream stream(read_data);
+    odil::pdu::AAssociateRQ const pdu(stream);
+
+    BOOST_REQUIRE_EQUAL(pdu.get_called_ae_title(), "CALLED_AE");
+    BOOST_REQUIRE_EQUAL(pdu.get_calling_ae_title(), "CALLING_AE");
+    BOOST_REQUIRE_EQUAL(pdu.get_protocol_version(), 1);
+    this->check_application_context(pdu.get_application_context());
+    this->check_presentation_contexts(pdu.get_presentation_contexts());
+    this->check_user_information(pdu.get_user_information());
+}
+
+BOOST_AUTO_TEST_CASE(ProtocolVersion)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_EQUAL(pdu.get_protocol_version(), 0);
+    pdu.set_protocol_version(2);
+    BOOST_REQUIRE_EQUAL(pdu.get_protocol_version(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(CalledAETitle)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_EQUAL(pdu.get_called_ae_title(), "");
+    pdu.set_called_ae_title("called");
+    BOOST_REQUIRE_EQUAL(pdu.get_called_ae_title(), "called");
+}
+
+BOOST_AUTO_TEST_CASE(CallingAETitle)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_EQUAL(pdu.get_calling_ae_title(), "");
+    pdu.set_calling_ae_title("calling");
+    BOOST_REQUIRE_EQUAL(pdu.get_calling_ae_title(), "calling");
+}
+
+BOOST_FIXTURE_TEST_CASE(ApplicationContext, Fixture)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_THROW(pdu.get_application_context(), odil::Exception);
+    pdu.set_application_context(application_context);
+    this->check_application_context(pdu.get_application_context());
+}
+
+BOOST_FIXTURE_TEST_CASE(PresentationContexts, Fixture)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE(pdu.get_presentation_contexts().empty());
+    pdu.set_presentation_contexts(presentation_contexts);
+    this->check_presentation_contexts(pdu.get_presentation_contexts());
+}
+
+BOOST_FIXTURE_TEST_CASE(UserInformation, Fixture)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_THROW(pdu.get_user_information(), odil::Exception);
+    pdu.set_user_information(user_information);
+    this->check_user_information(pdu.get_user_information());
+}
+
+BOOST_FIXTURE_TEST_CASE(Write, Fixture)
+{
+    odil::pdu::AAssociateRQ pdu;
+    pdu.set_protocol_version(1);
+    pdu.set_called_ae_title("CALLED_AE");
+    pdu.set_calling_ae_title("CALLING_AE");
+
+    pdu.set_application_context(application_context);
+    pdu.set_presentation_contexts(presentation_contexts);
+    pdu.set_user_information(user_information);
+
+    std::ostringstream stream;
+    stream << pdu;
+
+    BOOST_REQUIRE(stream.str() == write_data);
+}
+
+BOOST_AUTO_TEST_CASE(CalledAETitleEmpty)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_THROW(pdu.set_called_ae_title(""), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(CalledAETitleTooLong)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_THROW(
+        pdu.set_called_ae_title("123456789abcdef01"), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(CallingAETitleEmpty)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_THROW(pdu.set_calling_ae_title(""), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(CallingAETitleTooLong)
+{
+    odil::pdu::AAssociateRQ pdu;
+    BOOST_REQUIRE_THROW(
+        pdu.set_calling_ae_title("123456789abcdef01"), odil::Exception);
+}
diff --git a/tests/code/pdu/AReleaseRP.cpp b/tests/code/pdu/AReleaseRP.cpp
new file mode 100644
index 0000000..c1df04f
--- /dev/null
+++ b/tests/code/pdu/AReleaseRP.cpp
@@ -0,0 +1,34 @@
+#define BOOST_TEST_MODULE AReleaseRPPDU
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/pdu/AReleaseRP.h"
+
+std::string const data = {
+    0x06, 0x00,
+    0x00, 0x00, 0x00, 0x04,
+    0x00, 0x00,
+    0x00, 0x00
+};
+
+BOOST_AUTO_TEST_CASE(ConstructorFields)
+{
+    odil::pdu::AReleaseRP const pdu;
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStream)
+{
+    std::istringstream stream(data);
+    odil::pdu::AReleaseRP const pdu(stream);
+}
+
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::AReleaseRP const pdu;
+    std::ostringstream stream;
+    stream << pdu;
+    BOOST_REQUIRE(stream.str() == data);
+}
diff --git a/tests/code/pdu/AReleaseRQ.cpp b/tests/code/pdu/AReleaseRQ.cpp
new file mode 100644
index 0000000..44cb275
--- /dev/null
+++ b/tests/code/pdu/AReleaseRQ.cpp
@@ -0,0 +1,34 @@
+#define BOOST_TEST_MODULE AReleaseRPPDU
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/pdu/AReleaseRQ.h"
+
+std::string const data = {
+    0x05, 0x00,
+    0x00, 0x00, 0x00, 0x04,
+    0x00, 0x00,
+    0x00, 0x00
+};
+
+BOOST_AUTO_TEST_CASE(ConstructorFields)
+{
+    odil::pdu::AReleaseRQ const pdu;
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStream)
+{
+    std::istringstream stream(data);
+    odil::pdu::AReleaseRQ const pdu(stream);
+}
+
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::AReleaseRQ const pdu;
+    std::ostringstream stream;
+    stream << pdu;
+    BOOST_REQUIRE(stream.str() == data);
+}
diff --git a/tests/code/pdu/ApplicationContext.cpp b/tests/code/pdu/ApplicationContext.cpp
new file mode 100644
index 0000000..2001189
--- /dev/null
+++ b/tests/code/pdu/ApplicationContext.cpp
@@ -0,0 +1,44 @@
+#define BOOST_TEST_MODULE ApplicationContext
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/ApplicationContext.h"
+
+std::string const data(
+    "\x10\x00\x00\x03"
+    "foo",
+    7
+);
+
+BOOST_AUTO_TEST_CASE(FromString)
+{
+    odil::pdu::ApplicationContext const context("foo");
+    BOOST_REQUIRE_EQUAL(context.get_name(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(FromStream)
+{
+    std::istringstream stream(data);
+
+    odil::pdu::ApplicationContext const context(stream);
+    BOOST_REQUIRE_EQUAL(context.get_name(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(Name)
+{
+    odil::pdu::ApplicationContext context("foo");
+    context.set_name("other");
+    BOOST_REQUIRE_EQUAL(context.get_name(), "other");
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::ApplicationContext const context("foo");
+    std::ostringstream stream;
+    stream << context;
+
+    BOOST_REQUIRE_EQUAL(stream.str(), data);
+}
diff --git a/tests/code/pdu/ImplementationClassUID.cpp b/tests/code/pdu/ImplementationClassUID.cpp
new file mode 100644
index 0000000..8492159
--- /dev/null
+++ b/tests/code/pdu/ImplementationClassUID.cpp
@@ -0,0 +1,43 @@
+#define BOOST_TEST_MODULE ImplementationClassUID
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/pdu/ImplementationClassUID.h"
+
+std::string const data(
+    "\x52\x00\x00\x03"
+    "foo",
+    7
+);
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::pdu::ImplementationClassUID const item("foo");
+    BOOST_REQUIRE_EQUAL(item.get_implementation_class_uid(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(FromStream)
+{
+    std::istringstream stream(data);
+    odil::pdu::ImplementationClassUID const item(stream);
+
+    BOOST_REQUIRE_EQUAL(item.get_implementation_class_uid(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(ImplementationClassUID)
+{
+    odil::pdu::ImplementationClassUID item("foo");
+    item.set_implementation_class_uid("bar");
+    BOOST_REQUIRE_EQUAL(item.get_implementation_class_uid(), "bar");
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::ImplementationClassUID const item("foo");
+    std::ostringstream stream;
+    stream << item;
+
+    BOOST_REQUIRE_EQUAL(stream.str(), data);
+}
diff --git a/tests/code/pdu/ImplementationVersionName.cpp b/tests/code/pdu/ImplementationVersionName.cpp
new file mode 100644
index 0000000..a9f71c9
--- /dev/null
+++ b/tests/code/pdu/ImplementationVersionName.cpp
@@ -0,0 +1,59 @@
+#define BOOST_TEST_MODULE ImplementationVersionName
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/ImplementationVersionName.h"
+
+std::string const data(
+    "\x55\x00\x00\x03"
+    "foo",
+    7
+);
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::pdu::ImplementationVersionName const item("foo");
+    BOOST_REQUIRE_EQUAL(item.get_implementation_version_name(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(FromStream)
+{
+    std::istringstream stream(data);
+    odil::pdu::ImplementationVersionName const item(stream);
+
+    BOOST_REQUIRE_EQUAL(item.get_implementation_version_name(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(VersionName)
+{
+    odil::pdu::ImplementationVersionName item("foo");
+    item.set_implementation_version_name("bar");
+    BOOST_REQUIRE_EQUAL(item.get_implementation_version_name(), "bar");
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::ImplementationVersionName const item("foo");
+    std::ostringstream stream;
+    stream << item;
+
+    BOOST_REQUIRE_EQUAL(stream.str(), data);
+}
+
+BOOST_AUTO_TEST_CASE(Empty)
+{
+    odil::pdu::ImplementationVersionName item("foo");
+    BOOST_REQUIRE_THROW(
+        item.set_implementation_version_name(""), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(TooLong)
+{
+    odil::pdu::ImplementationVersionName item("foo");
+    BOOST_REQUIRE_THROW(
+        item.set_implementation_version_name("1234567890abcdef01"),
+        odil::Exception);
+}
diff --git a/tests/code/pdu/Item.cpp b/tests/code/pdu/Item.cpp
new file mode 100644
index 0000000..70ee680
--- /dev/null
+++ b/tests/code/pdu/Item.cpp
@@ -0,0 +1,128 @@
+#define BOOST_TEST_MODULE pdu::Item
+#include <boost/test/unit_test.hpp>
+
+#include <cstdint>
+
+#include "odil/pdu/Item.h"
+#include "odil/Exception.h"
+
+BOOST_AUTO_TEST_CASE(FieldUInt8)
+{
+    odil::pdu::Item::Field const field(uint8_t(123));
+
+    BOOST_REQUIRE(
+        field.get_type() == odil::pdu::Item::Field::Type::unsigned_int_8);
+    BOOST_REQUIRE_EQUAL(field.as_unsigned_int_8(), 123);
+}
+
+BOOST_AUTO_TEST_CASE(FieldUInt16)
+{
+    odil::pdu::Item::Field const field(uint16_t(1234));
+
+    BOOST_REQUIRE(
+        field.get_type() == odil::pdu::Item::Field::Type::unsigned_int_16);
+    BOOST_REQUIRE_EQUAL(field.as_unsigned_int_16(), 1234);
+}
+
+BOOST_AUTO_TEST_CASE(FieldUInt32)
+{
+    odil::pdu::Item::Field const field(uint32_t(123456));
+
+    BOOST_REQUIRE(
+        field.get_type() == odil::pdu::Item::Field::Type::unsigned_int_32);
+    BOOST_REQUIRE_EQUAL(field.as_unsigned_int_32(), 123456);
+}
+
+BOOST_AUTO_TEST_CASE(FieldString)
+{
+    odil::pdu::Item::Field const field("abcdef");
+
+    BOOST_REQUIRE(field.get_type() == odil::pdu::Item::Field::Type::string);
+    BOOST_REQUIRE_EQUAL(field.as_string(), "abcdef");
+}
+
+BOOST_AUTO_TEST_CASE(FieldItems)
+{
+    odil::pdu::Item const item_1({{"foo", uint8_t(123)}});
+    odil::pdu::Item const item_2({{"bar", std::string("abcdef")}});
+    odil::pdu::Item::Field const field(
+        std::vector<odil::pdu::Item>({item_1, item_2}));
+
+    BOOST_REQUIRE(field.get_type() == odil::pdu::Item::Field::Type::items);
+    BOOST_REQUIRE_EQUAL(field.as_items().size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(DefaultConstructor)
+{
+    odil::pdu::Item const item;
+    BOOST_REQUIRE(item.empty());
+    BOOST_REQUIRE_EQUAL(item.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FieldsConstructor)
+{
+    odil::pdu::Item const item({
+        {std::string("foo"), uint8_t(123)},
+        {std::string("bar"), std::string("abcd")}
+    });
+    BOOST_REQUIRE_EQUAL(item.size(), 2);
+    BOOST_REQUIRE_EQUAL(item.as_unsigned_int_8("foo"), 123);
+    BOOST_REQUIRE_EQUAL(item.as_string("bar"), "abcd");
+}
+
+BOOST_AUTO_TEST_CASE(UInt8)
+{
+    odil::pdu::Item item;
+    item.add("foo", uint8_t(123));
+    BOOST_REQUIRE_EQUAL(item.size(), 1);
+    BOOST_REQUIRE_EQUAL(item.has_field("foo"), true);
+    BOOST_REQUIRE_EQUAL(item.as_unsigned_int_8("foo"), 123);
+    BOOST_REQUIRE_THROW(item.as_unsigned_int_16("foo"), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(UInt16)
+{
+    odil::pdu::Item item;
+    item.add("foo", uint16_t(1234));
+    BOOST_REQUIRE_EQUAL(item.size(), 1);
+    BOOST_REQUIRE_EQUAL(item.as_unsigned_int_16("foo"), 1234);
+    BOOST_REQUIRE_THROW(item.as_unsigned_int_32("foo"), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(UInt32)
+{
+    odil::pdu::Item item;
+    item.add("foo", uint32_t(123456));
+    BOOST_REQUIRE_EQUAL(item.size(), 1);
+    BOOST_REQUIRE_EQUAL(item.as_unsigned_int_32("foo"), 123456);
+    BOOST_REQUIRE_THROW(item.as_string("foo"), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(String)
+{
+    odil::pdu::Item item;
+    item.add("foo", std::string("abcd"));
+    BOOST_REQUIRE_EQUAL(item.size(), 1);
+    BOOST_REQUIRE_EQUAL(item.as_string("foo"), "abcd");
+    BOOST_REQUIRE_THROW(item.as_items("foo"), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(Items)
+{
+    odil::pdu::Item const sub_item(
+        {{"bar", std::string("abcdef")}});
+    odil::pdu::Item item;
+    item.add("foo", std::vector<odil::pdu::Item>({sub_item}));
+    BOOST_REQUIRE_EQUAL(item.size(), 1);
+    BOOST_REQUIRE_EQUAL(item.as_items("foo").size(), 1);
+    BOOST_REQUIRE_THROW(item.as_unsigned_int_8("foo"), odil::Exception);
+}
+
+BOOST_AUTO_TEST_CASE(FieldAccessor)
+{
+    odil::pdu::Item item;
+    item.add("foo", std::string("abcd"));
+    BOOST_REQUIRE_NO_THROW(item["foo"]);
+    BOOST_REQUIRE_THROW(item["bar"], odil::Exception);
+
+}
diff --git a/tests/code/pdu/MaximumLength.cpp b/tests/code/pdu/MaximumLength.cpp
new file mode 100644
index 0000000..5eb274b
--- /dev/null
+++ b/tests/code/pdu/MaximumLength.cpp
@@ -0,0 +1,56 @@
+#define BOOST_TEST_MODULE MaximumLength
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/MaximumLength.h"
+
+BOOST_AUTO_TEST_CASE(ConstructorDefault)
+{
+    odil::pdu::MaximumLength const maximum_length;
+    BOOST_REQUIRE_EQUAL(maximum_length.get_maximum_length(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorInt)
+{
+    odil::pdu::MaximumLength const maximum_length(123);
+    BOOST_REQUIRE_EQUAL(maximum_length.get_maximum_length(), 123);
+}
+
+BOOST_AUTO_TEST_CASE(FromStream)
+{
+    std::string const data(
+        "\x51\x00\x00\x04"
+        "\x12\x34\x56\x78",
+        8
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::MaximumLength const maximum_length(stream);
+
+    BOOST_REQUIRE_EQUAL(maximum_length.get_maximum_length(), 0x12345678);
+}
+
+BOOST_AUTO_TEST_CASE(MaximumLength)
+{
+    odil::pdu::MaximumLength maximum_length;
+    maximum_length.set_maximum_length(123);
+    BOOST_REQUIRE_EQUAL(maximum_length.get_maximum_length(), 123);
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::MaximumLength const maximum_length(0x12345678);
+    std::ostringstream data;
+    data << maximum_length;
+
+    std::string const expected(
+        "\x51\x00\x00\x04"
+        "\x12\x34\x56\x78",
+        8
+    );
+
+    BOOST_REQUIRE_EQUAL(data.str(), expected);
+}
diff --git a/tests/code/pdu/PDataTF.cpp b/tests/code/pdu/PDataTF.cpp
new file mode 100644
index 0000000..c66bed3
--- /dev/null
+++ b/tests/code/pdu/PDataTF.cpp
@@ -0,0 +1,74 @@
+#define BOOST_TEST_MODULE pdu::PDataTF
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/PDataTF.h"
+
+std::string const data = {
+    0x04, 0x00,
+    0x00, 0x00, 0x00, 0x1e,
+
+    0x00, 0x00, 0x00, 0x0a,
+    0x01, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+
+    0x00, 0x00, 0x00, 0x0c,
+    0x03, 0x02, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12
+};
+
+std::vector<odil::pdu::PDataTF::PresentationDataValueItem> const pdv_items = {
+    { 1, 0x01, "\x01\x02\x03\x04\x05\x06\x07\x08" },
+    { 3, 0x02, "\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12" },
+};
+
+namespace odil
+{
+
+namespace pdu
+{
+
+bool operator==(
+    PDataTF::PresentationDataValueItem const & lhs,
+    PDataTF::PresentationDataValueItem const & rhs)
+{
+    return (
+        lhs.get_presentation_context_id() == rhs.get_presentation_context_id() &&
+        lhs.get_control_header() == rhs.get_control_header() &&
+        lhs.get_fragment() == rhs.get_fragment());
+}
+
+}
+
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorPDV)
+{
+    odil::pdu::PDataTF const pdu(pdv_items);
+    BOOST_REQUIRE(pdu.get_pdv_items() == pdv_items);
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStream)
+{
+    std::istringstream stream(data);
+    odil::pdu::PDataTF const pdu(stream);
+    BOOST_REQUIRE(pdu.get_pdv_items() == pdv_items);
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::PDataTF const pdu(pdv_items);
+    std::ostringstream stream;
+    stream << pdu;
+    BOOST_REQUIRE(stream.str() == data);
+}
+
+BOOST_AUTO_TEST_CASE(WrongPresentationContextID)
+{
+    BOOST_REQUIRE_THROW(
+        odil::pdu::PDataTF::PresentationDataValueItem(
+            2, pdv_items[0].get_control_header(), pdv_items[0].get_fragment()),
+        odil::Exception);
+}
diff --git a/tests/code/pdu/PresentationContextAC.cpp b/tests/code/pdu/PresentationContextAC.cpp
new file mode 100644
index 0000000..6c77224
--- /dev/null
+++ b/tests/code/pdu/PresentationContextAC.cpp
@@ -0,0 +1,62 @@
+#define BOOST_TEST_MODULE PresentationContextAC
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/PresentationContextAC.h"
+
+std::string const data(
+    "\x21\x00\x00\x17"
+    "\x03\x00\x01\x00"
+    "\x40\x00\x00\x0f""transfer_syntax",
+    27
+);
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::pdu::PresentationContextAC const context(1, "transfer_syntax", 2);
+
+    BOOST_REQUIRE_EQUAL(context.get_item_type(), 0x21);
+    BOOST_REQUIRE_EQUAL(context.get_id(), 0x1);
+    BOOST_REQUIRE_EQUAL(context.get_result_reason(), 0x2);
+    BOOST_REQUIRE_EQUAL(context.get_transfer_syntax(), "transfer_syntax");
+}
+
+BOOST_AUTO_TEST_CASE(Id)
+{
+    odil::pdu::PresentationContextAC context(1, "transfer_syntax", 1);
+    context.set_id(123);
+    BOOST_REQUIRE_EQUAL(context.get_id(), 123);
+}
+
+BOOST_AUTO_TEST_CASE(TransferSyntax)
+{
+    odil::pdu::PresentationContextAC context(1, "transfer_syntax", 1);
+    context.set_transfer_syntax("foo");
+    BOOST_REQUIRE_EQUAL(context.get_transfer_syntax(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::PresentationContextAC context(3, "transfer_syntax", 1);
+
+    std::ostringstream stream;
+    stream << context;
+
+    BOOST_REQUIRE_EQUAL(stream.str(), data);
+}
+
+BOOST_AUTO_TEST_CASE(Read)
+{
+    std::istringstream stream(data);
+
+    odil::pdu::PresentationContextAC const context(stream);
+
+    BOOST_REQUIRE_EQUAL(context.get_item_type(), 0x21);
+    BOOST_REQUIRE_EQUAL(context.get_id(), 3);
+    BOOST_REQUIRE_EQUAL(context.get_result_reason(), 1);
+    BOOST_REQUIRE_EQUAL(context.get_transfer_syntax(), "transfer_syntax");
+}
diff --git a/tests/code/pdu/PresentationContextRQ.cpp b/tests/code/pdu/PresentationContextRQ.cpp
new file mode 100644
index 0000000..9954401
--- /dev/null
+++ b/tests/code/pdu/PresentationContextRQ.cpp
@@ -0,0 +1,82 @@
+#define BOOST_TEST_MODULE PresentationContextRQ
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "odil/Exception.h"
+#include "odil/pdu/PresentationContextRQ.h"
+
+std::string const data(
+    "\x20\x00\x00\x25"
+    "\x03\x00\x00\x00"
+    "\x30\x00\x00\x0f""abstract_syntax"
+    "\x40\x00\x00\x03""ts1"
+    "\x40\x00\x00\x03""ts2",
+    41
+);
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::pdu::PresentationContextRQ const context(
+        1, "abstract_syntax", {"ts1", "ts2"});
+
+    BOOST_REQUIRE_EQUAL(context.get_item_type(), 0x20);
+    BOOST_REQUIRE_EQUAL(context.get_id(), 1);
+    BOOST_REQUIRE_EQUAL(context.get_abstract_syntax(), "abstract_syntax");
+    BOOST_REQUIRE(
+        context.get_transfer_syntaxes() ==
+            std::vector<std::string>({"ts1", "ts2"}));
+}
+
+BOOST_AUTO_TEST_CASE(Id)
+{
+    odil::pdu::PresentationContextRQ context(
+        1, "abstract_syntax", {"ts1", "ts2"});
+    context.set_id(123);
+    BOOST_REQUIRE_EQUAL(context.get_id(), 123);
+}
+
+BOOST_AUTO_TEST_CASE(AbstractSyntax)
+{
+    odil::pdu::PresentationContextRQ context(
+        1, "abstract_syntax", {"ts1", "ts2"});
+    context.set_abstract_syntax("foo");
+    BOOST_REQUIRE_EQUAL(context.get_abstract_syntax(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(TransferSyntaxes)
+{
+    odil::pdu::PresentationContextRQ context(
+        1, "abstract_syntax", {"ts1", "ts2"});
+    context.set_transfer_syntaxes({"foo", "bar"});
+    BOOST_REQUIRE(
+        context.get_transfer_syntaxes() ==
+            std::vector<std::string>({"foo", "bar"}));
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::PresentationContextRQ context(
+        3, "abstract_syntax", {"ts1", "ts2"});
+
+    std::ostringstream stream;
+    stream << context;
+
+    BOOST_REQUIRE_EQUAL(stream.str(), data);
+}
+
+BOOST_AUTO_TEST_CASE(Read)
+{
+    std::istringstream stream(data);
+
+    odil::pdu::PresentationContextRQ const context(stream);
+
+    BOOST_REQUIRE_EQUAL(context.get_item_type(), 0x20);
+    BOOST_REQUIRE_EQUAL(context.get_id(), 3);
+    BOOST_REQUIRE_EQUAL(context.get_abstract_syntax(), "abstract_syntax");
+    BOOST_REQUIRE(
+        context.get_transfer_syntaxes() ==
+            std::vector<std::string>({"ts1", "ts2"}));
+}
diff --git a/tests/code/pdu/RoleSelection.cpp b/tests/code/pdu/RoleSelection.cpp
new file mode 100644
index 0000000..986d402
--- /dev/null
+++ b/tests/code/pdu/RoleSelection.cpp
@@ -0,0 +1,74 @@
+#define BOOST_TEST_MODULE RoleSelection
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/pdu/RoleSelection.h"
+
+#include <iostream>
+
+std::string const data(
+    "\x54\x00\x00\x0b"
+    "\x00\x07" "1.2.3.4"
+    "\x01\x01",
+    15
+);
+
+BOOST_AUTO_TEST_CASE(DefaultConstructor)
+{
+    odil::pdu::RoleSelection const item;
+    BOOST_REQUIRE_EQUAL(item.get_sop_class_uid(), "");
+    BOOST_REQUIRE_EQUAL(item.get_scu_role_support(), false);
+    BOOST_REQUIRE_EQUAL(item.get_scp_role_support(), false);
+}
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::pdu::RoleSelection const item("1.2.3.4", true, true);
+    BOOST_REQUIRE_EQUAL(item.get_sop_class_uid(), "1.2.3.4");
+    BOOST_REQUIRE_EQUAL(item.get_scu_role_support(), true);
+    BOOST_REQUIRE_EQUAL(item.get_scp_role_support(), true);
+}
+
+BOOST_AUTO_TEST_CASE(FromStream)
+{
+    std::istringstream stream(data);
+    odil::pdu::RoleSelection const item(stream);
+
+    BOOST_REQUIRE_EQUAL(item.get_sop_class_uid(), "1.2.3.4");
+    BOOST_REQUIRE_EQUAL(item.get_scu_role_support(), true);
+    BOOST_REQUIRE_EQUAL(item.get_scp_role_support(), true);
+}
+
+BOOST_AUTO_TEST_CASE(SOPClassUID)
+{
+    odil::pdu::RoleSelection item;
+    item.set_sop_class_uid("1.2.3.4");
+    BOOST_REQUIRE_EQUAL(item.get_sop_class_uid(), "1.2.3.4");
+}
+
+BOOST_AUTO_TEST_CASE(SCURoleSupport)
+{
+    odil::pdu::RoleSelection item;
+    item.set_scu_role_support(true);
+    BOOST_REQUIRE_EQUAL(item.get_scu_role_support(), true);
+}
+
+BOOST_AUTO_TEST_CASE(SCPRoleSupport)
+{
+    odil::pdu::RoleSelection item;
+    item.set_scp_role_support(true);
+    BOOST_REQUIRE_EQUAL(item.get_scp_role_support(), true);
+}
+
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::RoleSelection item("1.2.3.4", true, true);
+
+    std::ostringstream stream;
+    stream << item;
+
+    BOOST_REQUIRE_EQUAL(stream.str(), data);
+}
diff --git a/tests/code/pdu/UserIdentityAC.cpp b/tests/code/pdu/UserIdentityAC.cpp
new file mode 100644
index 0000000..22d4efa
--- /dev/null
+++ b/tests/code/pdu/UserIdentityAC.cpp
@@ -0,0 +1,58 @@
+#define BOOST_TEST_MODULE UserIdentityAC
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/UserIdentityAC.h"
+
+BOOST_AUTO_TEST_CASE(ConstructorDefault)
+{
+    odil::pdu::UserIdentityAC const user_identity;
+    BOOST_REQUIRE_EQUAL(user_identity.get_server_response(), "");
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorString)
+{
+    odil::pdu::UserIdentityAC const user_identity("foo");
+    BOOST_REQUIRE_EQUAL(user_identity.get_server_response(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(FromStream)
+{
+    std::string const data(
+        "\x59\x00\x00\x05"
+        "\x00\x03" "foo",
+        9
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::UserIdentityAC const user_identity(stream);
+
+    BOOST_REQUIRE_EQUAL(user_identity.get_server_response(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(Type)
+{
+    odil::pdu::UserIdentityAC user_identity;
+    user_identity.set_server_response("foo");
+    BOOST_REQUIRE_EQUAL(user_identity.get_server_response(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::UserIdentityAC user_identity;
+    user_identity.set_server_response("foo");
+
+    std::ostringstream data;
+    data << user_identity;
+
+    std::string const expected(
+        "\x59\x00\x00\x05"
+        "\x00\x03" "foo",
+        9
+    );
+
+    BOOST_REQUIRE_EQUAL(data.str(), expected);
+}
diff --git a/tests/code/pdu/UserIdentityRQ.cpp b/tests/code/pdu/UserIdentityRQ.cpp
new file mode 100644
index 0000000..be34f8e
--- /dev/null
+++ b/tests/code/pdu/UserIdentityRQ.cpp
@@ -0,0 +1,124 @@
+#define BOOST_TEST_MODULE UserIdentityRQ
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+#include <string>
+
+#include "odil/Exception.h"
+#include "odil/pdu/UserIdentityRQ.h"
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+    odil::pdu::UserIdentityRQ const user_identity;
+    BOOST_REQUIRE_EQUAL(user_identity.get_type(), 1);
+    BOOST_REQUIRE_EQUAL(user_identity.get_positive_response_requested(), false);
+    BOOST_REQUIRE_EQUAL(user_identity.get_primary_field(), "");
+    BOOST_REQUIRE_EQUAL(user_identity.get_secondary_field(), "");
+}
+
+BOOST_AUTO_TEST_CASE(FromStream)
+{
+    std::string const data(
+        "\x58\x00\x00\x0c"
+        "\x02\x01"
+        "\x00\x03" "foo"
+        "\x00\x03" "bar",
+        16
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::UserIdentityRQ const user_identity(stream);
+
+    BOOST_REQUIRE_EQUAL(user_identity.get_type(), 2);
+    BOOST_REQUIRE_EQUAL(user_identity.get_positive_response_requested(), true);
+    BOOST_REQUIRE_EQUAL(user_identity.get_primary_field(), "foo");
+    BOOST_REQUIRE_EQUAL(user_identity.get_secondary_field(), "bar");
+}
+
+BOOST_AUTO_TEST_CASE(Type)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_type(2);
+    BOOST_REQUIRE_EQUAL(user_identity.get_type(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(PositiveResponseRequested)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_positive_response_requested(true);
+    BOOST_REQUIRE_EQUAL(user_identity.get_positive_response_requested(), true);
+}
+
+BOOST_AUTO_TEST_CASE(PrimaryField)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_primary_field("foo");
+    BOOST_REQUIRE_EQUAL(user_identity.get_primary_field(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(SecondaryField)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_secondary_field("foo");
+    BOOST_REQUIRE_EQUAL(user_identity.get_secondary_field(), "foo");
+}
+
+BOOST_AUTO_TEST_CASE(Username)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_username("foo");
+
+    BOOST_REQUIRE_EQUAL(user_identity.get_type(), 1);
+    BOOST_REQUIRE_EQUAL(user_identity.get_primary_field(), "foo");
+    BOOST_REQUIRE_EQUAL(user_identity.get_secondary_field(), "");
+}
+
+BOOST_AUTO_TEST_CASE(UsernameAndPasscode)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_username_and_passcode("foo", "bar");
+
+    BOOST_REQUIRE_EQUAL(user_identity.get_type(), 2);
+    BOOST_REQUIRE_EQUAL(user_identity.get_primary_field(), "foo");
+    BOOST_REQUIRE_EQUAL(user_identity.get_secondary_field(), "bar");
+}
+
+BOOST_AUTO_TEST_CASE(KerberosTicket)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_kerberos_service_ticket("1234");
+
+    BOOST_REQUIRE_EQUAL(user_identity.get_type(), 3);
+    BOOST_REQUIRE_EQUAL(user_identity.get_primary_field(), "1234");
+    BOOST_REQUIRE_EQUAL(user_identity.get_secondary_field(), "");
+}
+
+BOOST_AUTO_TEST_CASE(SAMLAssertion)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_saml_assertion("1234");
+
+    BOOST_REQUIRE_EQUAL(user_identity.get_type(), 4);
+    BOOST_REQUIRE_EQUAL(user_identity.get_primary_field(), "1234");
+    BOOST_REQUIRE_EQUAL(user_identity.get_secondary_field(), "");
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::UserIdentityRQ user_identity;
+    user_identity.set_username_and_passcode("foo", "bar");
+    user_identity.set_positive_response_requested(true);
+
+    std::ostringstream data;
+    data << user_identity;
+
+    std::string const expected(
+        "\x58\x00\x00\x0c"
+        "\x02\x01"
+        "\x00\x03" "foo"
+        "\x00\x03" "bar",
+        16
+    );
+
+    BOOST_REQUIRE_EQUAL(data.str(), expected);
+}
diff --git a/tests/code/pdu/UserInformation.cpp b/tests/code/pdu/UserInformation.cpp
new file mode 100644
index 0000000..a9bec4e
--- /dev/null
+++ b/tests/code/pdu/UserInformation.cpp
@@ -0,0 +1,160 @@
+#define BOOST_TEST_MODULE UserInformation
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+
+#include "odil/Exception.h"
+#include "odil/pdu/ImplementationClassUID.h"
+#include "odil/pdu/ImplementationVersionName.h"
+#include "odil/pdu/MaximumLength.h"
+#include "odil/pdu/RoleSelection.h"
+#include "odil/pdu/UserInformation.h"
+#include "odil/pdu/UserIdentityAC.h"
+#include "odil/pdu/UserIdentityRQ.h"
+
+BOOST_AUTO_TEST_CASE(ConstructorDefault)
+{
+    odil::pdu::UserInformation const item;
+    BOOST_REQUIRE(item.get_sub_items<odil::pdu::MaximumLength>().empty());
+    BOOST_REQUIRE(item.get_sub_items<odil::pdu::UserIdentityRQ>().empty());
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStreamMaximumLength)
+{
+    std::string const data(
+        "\x50\x00\x00\x08"
+        "\x51\x00\x00\x04"
+        "\x12\x34\x56\x78",
+        12
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::UserInformation const item(stream);
+    BOOST_REQUIRE_EQUAL(
+        item.get_sub_items<odil::pdu::MaximumLength>().size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStreamImplementationClassUID)
+{
+    std::string const data(
+        "\x50\x00\x00\x07"
+        "\x52\x00\x00\x03" "foo",
+        11
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::UserInformation const item(stream);
+    BOOST_REQUIRE_EQUAL(
+        item.get_sub_items<odil::pdu::ImplementationClassUID>().size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStreamRoleSelection)
+{
+    std::string const data(
+        "\x50\x00\x00\x0f"
+        "\x54\x00\x00\x0b"
+        "\x00\x07" "1.2.3.4"
+        "\x01\x01",
+        19
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::UserInformation const item(stream);
+    BOOST_REQUIRE_EQUAL(
+        item.get_sub_items<odil::pdu::RoleSelection>().size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStreamImplementationVersionName)
+{
+    std::string const data(
+        "\x50\x00\x00\x07"
+        "\x55\x00\x00\x03" "foo",
+        11
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::UserInformation const item(stream);
+    BOOST_REQUIRE_EQUAL(
+        item.get_sub_items<odil::pdu::ImplementationVersionName>().size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStreamUserIdentityRQ)
+{
+    std::string const data(
+        "\x50\x00\x00\x10"
+
+        "\x58\x00\x00\x0c"
+        "\x02\x01"
+        "\x00\x03" "foo"
+        "\x00\x03" "bar",
+        20
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::UserInformation const item(stream);
+    BOOST_REQUIRE_EQUAL(
+        item.get_sub_items<odil::pdu::UserIdentityRQ>().size(), 1);
+
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorStreamUserIdentityAC)
+{
+    std::string const data(
+        "\x50\x00\x00\x0a"
+
+        "\x59\x00\x00\x06"
+        "\x00\x04" "abcd",
+        14
+    );
+    std::istringstream stream(data);
+
+    odil::pdu::UserInformation const item(stream);
+    BOOST_REQUIRE_EQUAL(
+        item.get_sub_items<odil::pdu::UserIdentityAC>().size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(SetSubItems)
+{
+    odil::pdu::UserInformation item;
+    BOOST_REQUIRE(item.get_sub_items<odil::pdu::MaximumLength>().empty());
+
+    item.set_sub_items<odil::pdu::MaximumLength>({{0x12345678}});
+
+    BOOST_REQUIRE_EQUAL(
+        item.get_sub_items<odil::pdu::MaximumLength>().size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(DeleteSubItems)
+{
+    odil::pdu::UserInformation item;
+    BOOST_REQUIRE(item.get_sub_items<odil::pdu::MaximumLength>().empty());
+
+    item.set_sub_items<odil::pdu::MaximumLength>({{0x12345678}});
+
+    BOOST_REQUIRE_EQUAL(
+        item.get_sub_items<odil::pdu::MaximumLength>().size(), 1);
+
+    item.delete_sub_items<odil::pdu::MaximumLength>();
+
+    BOOST_REQUIRE(item.get_sub_items<odil::pdu::MaximumLength>().empty());
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+    odil::pdu::UserInformation item;
+    item.set_sub_items<odil::pdu::MaximumLength>({{0x12345678}});
+
+    std::ostringstream data;
+    data << item;
+
+    std::string const expected(
+        "\x50\x00\x00\x08"
+
+        "\x51\x00\x00\x04"
+        "\x12\x34\x56\x78",
+        12
+    );
+
+    BOOST_REQUIRE_EQUAL(data.str(), expected);
+}
+
diff --git a/tests/code/registry.cpp b/tests/code/registry.cpp
index 12e7b25..aef0026 100644
--- a/tests/code/registry.cpp
+++ b/tests/code/registry.cpp
@@ -1,14 +1,14 @@
 #define BOOST_TEST_MODULE registry
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/registry.h"
-#include "dcmtkpp/Tag.h"
+#include "odil/registry.h"
+#include "odil/Tag.h"
 
 BOOST_AUTO_TEST_CASE(PublicDictionary)
 {
-    auto const iterator = dcmtkpp::registry::public_dictionary.find(
-        dcmtkpp::registry::PatientName);
-    BOOST_REQUIRE(iterator != dcmtkpp::registry::public_dictionary.end());
+    auto const iterator = odil::registry::public_dictionary.find(
+        odil::registry::PatientName);
+    BOOST_REQUIRE(iterator != odil::registry::public_dictionary.end());
     auto const & entry = iterator->second;
     BOOST_REQUIRE_EQUAL(entry.name, "Patient's Name");
     BOOST_REQUIRE_EQUAL(entry.keyword, "PatientName");
@@ -18,9 +18,9 @@ BOOST_AUTO_TEST_CASE(PublicDictionary)
 
 BOOST_AUTO_TEST_CASE(UIDsDictionary)
 {
-    auto const iterator = dcmtkpp::registry::uids_dictionary.find(
-        dcmtkpp::registry::MRImageStorage);
-    BOOST_REQUIRE(iterator != dcmtkpp::registry::uids_dictionary.end());
+    auto const iterator = odil::registry::uids_dictionary.find(
+        odil::registry::MRImageStorage);
+    BOOST_REQUIRE(iterator != odil::registry::uids_dictionary.end());
     auto const & entry = iterator->second;
     BOOST_REQUIRE_EQUAL(entry.name, "MR Image Storage");
     BOOST_REQUIRE_EQUAL(entry.keyword, "MRImageStorage");
diff --git a/tests/code/uid.cpp b/tests/code/uid.cpp
index 517b76d..7c729be 100644
--- a/tests/code/uid.cpp
+++ b/tests/code/uid.cpp
@@ -1,11 +1,11 @@
 #define BOOST_TEST_MODULE UID
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/uid.h"
+#include "odil/uid.h"
 
 BOOST_AUTO_TEST_CASE(generate)
 {
-    std::string const uid = dcmtkpp::generate_uid();
+    std::string const uid = odil::generate_uid();
     BOOST_REQUIRE_LE(uid.size(), 64);
     for(auto const & c: uid)
     {
diff --git a/tests/code/unicode.cpp b/tests/code/unicode.cpp
index 5d23016..5b540e2 100644
--- a/tests/code/unicode.cpp
+++ b/tests/code/unicode.cpp
@@ -1,16 +1,13 @@
 #define BOOST_TEST_MODULE unicode
 #include <boost/test/unit_test.hpp>
 
-#include <dcmtk/config/osconfig.h>
-#include <dcmtk/dcmdata/dctk.h>
-
-#include "dcmtkpp/conversion.h"
-#include "dcmtkpp/DataSet.h"
-#include "dcmtkpp/unicode.h"
+#include "odil/DataSet.h"
+#include "odil/unicode.h"
+#include "odil/dcmtk/conversion.h"
 
 BOOST_AUTO_TEST_CASE(SCSARAB)
 {
-    dcmtkpp::Value::Strings const specific_character_set = { "ISO_IR 127" };
+    odil::Value::Strings const specific_character_set = { "ISO_IR 127" };
     std::string const source =
         "\xe2\xc8\xc7\xe6\xea" "^" "\xe4\xe6\xd2\xc7\xd1";
     std::string const expected =
@@ -18,51 +15,51 @@ BOOST_AUTO_TEST_CASE(SCSARAB)
         "^"
         "\xd9\x84\xd9\x86\xd8\xb2\xd8\xa7\xd8\xb1";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSFREN)
 {
-    dcmtkpp::Value::Strings const specific_character_set = { "ISO_IR 100" };
+    odil::Value::Strings const specific_character_set = { "ISO_IR 100" };
     std::string const source = "Buc" "^" "J\xe9r\xf4me";
     std::string const expected =
         "Buc" "^" "J\xc3\xa9r\xc3\xb4me";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSGERM)
 {
-    dcmtkpp::Value::Strings const specific_character_set = { "ISO_IR 100" };
+    odil::Value::Strings const specific_character_set = { "ISO_IR 100" };
     std::string const source =
         "\xc4neas" "^" "R\xfc" "diger";
     std::string const expected =
         "\xc3\x84neas" "^" "R\xc3\xbc" "diger";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSGREEK)
 {
-    dcmtkpp::Value::Strings const specific_character_set = { "ISO_IR 126" };
+    odil::Value::Strings const specific_character_set = { "ISO_IR 126" };
     std::string const source = "\xc4\xe9\xef\xed\xf5\xf3\xe9\xef\xf2";
     std::string const expected =
         "\xce\x94\xce\xb9\xce\xbf\xce\xbd\xcf\x85\xcf\x83\xce\xb9\xce\xbf\xcf\x82";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSH31)
 {
-    dcmtkpp::Value::Strings const specific_character_set =
+    odil::Value::Strings const specific_character_set =
         { "", "ISO 2022 IR 87" };
     std::string const source =
         "Yamada" "^" "Tarou"
@@ -80,14 +77,14 @@ BOOST_AUTO_TEST_CASE(SCSH31)
         "\xe3\x82\x84\xe3\x81\xbe\xe3\x81\xa0"
             "^" "\xe3\x81\x9f\xe3\x82\x8d\xe3\x81\x86";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSH32)
 {
-    dcmtkpp::Value::Strings const specific_character_set =
+    odil::Value::Strings const specific_character_set =
         { "ISO 2022 IR 13", "ISO 2022 IR 87" };
     std::string const source =
         "\xd4\xcf\xc0\xde" "^" "\xc0\xdb\xb3"
@@ -107,27 +104,27 @@ BOOST_AUTO_TEST_CASE(SCSH32)
         "\xe3\x82\x84\xe3\x81\xbe\xe3\x81\xa0"
             "^" "\xe3\x81\x9f\xe3\x82\x8d\xe3\x81\x86";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSHBRW)
 {
-    dcmtkpp::Value::Strings const specific_character_set = { "ISO_IR 138" };
+    odil::Value::Strings const specific_character_set = { "ISO_IR 138" };
     std::string const source = "\xf9\xf8\xe5\xef" "^" "\xe3\xe1\xe5\xf8\xe4";
     std::string const expected =
         "\xd7\xa9\xd7\xa8\xd7\x95\xd7\x9f"
             "^" "\xd7\x93\xd7\x91\xd7\x95\xd7\xa8\xd7\x94";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSI2)
 {
-    dcmtkpp::Value::Strings const specific_character_set =
+    odil::Value::Strings const specific_character_set =
         { "", "ISO 2022 IR 149" };
 
     std::string const source =
@@ -143,26 +140,26 @@ BOOST_AUTO_TEST_CASE(SCSI2)
         "="
         "\xed\x99\x8d" "^" "\xea\xb8\xb8\xeb\x8f\x99";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSRUSS)
 {
-    dcmtkpp::Value::Strings const specific_character_set = { "ISO_IR 144" };
+    odil::Value::Strings const specific_character_set = { "ISO_IR 144" };
     std::string const source = "\xbb\xee\xda\x63\x65\xdc\xd1\x79\x70\xd3";
     std::string const expected =
         "\xd0\x9b\xd1\x8e\xd0\xba\x63\x65\xd0\xbc\xd0\xb1\x79\x70\xd0\xb3";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSX1)
 {
-    dcmtkpp::Value::Strings const specific_character_set = { "ISO_IR 192" };
+    odil::Value::Strings const specific_character_set = { "ISO_IR 192" };
     std::string const source =
         "Wang" "^" "XiaoDong"
         "="
@@ -173,14 +170,14 @@ BOOST_AUTO_TEST_CASE(SCSX1)
         "\x3d\xe7\x8e\x8b" "^" "\xe5\xb0\x8f\xe6\x9d\xb1"
         "=";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
 
 BOOST_AUTO_TEST_CASE(SCSX2)
 {
-    dcmtkpp::Value::Strings const specific_character_set = { "GB18030" };
+    odil::Value::Strings const specific_character_set = { "GB18030" };
     std::string const source =
         "Wang" "^" "XiaoDong"
         "=" ""
@@ -192,67 +189,7 @@ BOOST_AUTO_TEST_CASE(SCSX2)
         "\xe7\x8e\x8b" "^" "\xe5\xb0\x8f\xe4\xb8\x9c"
         "=";
 
-    std::string const utf8 = dcmtkpp::as_utf8(
+    std::string const utf8 = odil::as_utf8(
         source, specific_character_set, true);
     BOOST_REQUIRE_EQUAL(utf8, expected);
 }
-
-/*
-dcmtkpp::DataSet read(std::string const & filename)
-{
-    DcmFileFormat format;
-
-    OFCondition const condition = format.loadFile(filename.c_str());
-    if(condition.bad())
-    {
-        throw dcmtkpp::Exception(condition);
-    }
-
-    dcmtkpp::DataSet const result = dcmtkpp::convert(format.getDataset());
-
-    return result;
-}
-
-
-int main(int argc, char ** argv)
-{
-    for(char** argument=argv+1; argument != argv+argc; ++argument)
-    {
-        std::cout << "Reading " << *argument << std::endl;
-
-        dcmtkpp::DataSet data_set;
-        try
-        {
-            data_set = read(*argument);
-        }
-        catch(dcmtkpp::Exception const & e)
-        {
-            std::cerr << "Could not read " << *argument << ": " << e.what() << "\n";
-            continue;
-        }
-
-        std::string patient_name;
-        try
-        {
-            patient_name = dcmtkpp::as_utf8(
-                data_set.as_string("PatientName")[0],
-                data_set.as_string("SpecificCharacterSet"), true);
-        }
-        catch(dcmtkpp::Exception const & e)
-        {
-            std::cerr << "Could not convert PatientName to UTF-8: " << e.what() << "\n";
-            continue;
-        }
-
-        std::cout << "    dcmtkpp::Value::Strings const specific_character_set = {\n";
-        for(auto const & c: data_set.as_string("SpecificCharacterSet"))
-        {
-            std::cout << "        " << "\"" << c << "\",\n";
-        }
-        std::cout << "    };\n";
-        std::cout << "Patient Name: \"" << patient_name << "\"\n";
-    }
-
-    return 0;
-}
-*/
diff --git a/tests/code/xml_converter.cpp b/tests/code/xml_converter.cpp
index 259837a..840a7a7 100644
--- a/tests/code/xml_converter.cpp
+++ b/tests/code/xml_converter.cpp
@@ -1,9 +1,7 @@
 #define BOOST_TEST_MODULE xml_converter
 #include <boost/test/unit_test.hpp>
 
-#include "dcmtkpp/xml_converter.h"
-
-#include <boost/property_tree/xml_parser.hpp>
+#include "odil/xml_converter.h"
 
 void check_attributes(boost::property_tree::ptree const & xml,
                       std::string const & tag,
@@ -99,8 +97,8 @@ void check_response(boost::property_tree::ptree const & xml,
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsXMLEmpty)
 {
-    dcmtkpp::DataSet data_set;
-    auto const xml = dcmtkpp::as_xml(data_set);
+    odil::DataSet data_set;
+    auto const xml = odil::as_xml(data_set);
 
     // Expected result:
     // <NativeDicomModel />
@@ -113,10 +111,10 @@ BOOST_AUTO_TEST_CASE(AsXMLEmpty)
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsXMLIntegers)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00280010,
-        dcmtkpp::Element(dcmtkpp::Value::Integers({1, 2}), dcmtkpp::VR::US));
-    auto const xml = dcmtkpp::as_xml(data_set);
+        odil::Element(odil::Value::Integers({1, 2}), odil::VR::US));
+    auto const xml = odil::as_xml(data_set);
 
     // Expected result:
     // <NativeDicomModel>
@@ -127,16 +125,16 @@ BOOST_AUTO_TEST_CASE(AsXMLIntegers)
     // </NativeDicomModel>
 
     check_response(xml, "00280010", "US", "Rows",
-                   dcmtkpp::Value::Integers({1, 2}));
+                   odil::Value::Integers({1, 2}));
 }
 
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsXMLReals)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00089459,
-        dcmtkpp::Element(dcmtkpp::Value::Reals({1.2, 3.4}), dcmtkpp::VR::FL));
-    auto const xml = dcmtkpp::as_xml(data_set);
+        odil::Element(odil::Value::Reals({1.2, 3.4}), odil::VR::FL));
+    auto const xml = odil::as_xml(data_set);
 
     // Expected result:
     // <NativeDicomModel>
@@ -148,18 +146,18 @@ BOOST_AUTO_TEST_CASE(AsXMLReals)
     // </NativeDicomModel>
 
     check_response(xml, "00089459", "FL", "RecommendedDisplayFrameRateInFloat",
-                   dcmtkpp::Value::Reals({1.2, 3.4}));
+                   odil::Value::Reals({1.2, 3.4}));
 }
 
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsXMLStrings)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00080060,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings({"FOO", "BAR"}),
-            dcmtkpp::VR::CS));
-    auto const xml = dcmtkpp::as_xml(data_set);
+        odil::Element(
+            odil::Value::Strings({"FOO", "BAR"}),
+            odil::VR::CS));
+    auto const xml = odil::as_xml(data_set);
 
     // Expected result:
     // <NativeDicomModel>
@@ -170,19 +168,19 @@ BOOST_AUTO_TEST_CASE(AsXMLStrings)
     // </NativeDicomModel>
 
     check_response(xml, "00080060", "CS", "Modality",
-                   dcmtkpp::Value::Strings({"FOO", "BAR"}));
+                   odil::Value::Strings({"FOO", "BAR"}));
 }
 
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsXMLPersonName)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00100010,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings({"Alpha^Betic=Ideo^Graphic=Pho^Netic",
+        odil::Element(
+            odil::Value::Strings({"Alpha^Betic=Ideo^Graphic=Pho^Netic",
                                      "family^given^middle^prefix^suffix"}),
-            dcmtkpp::VR::PN));
-    auto const xml = dcmtkpp::as_xml(data_set);
+            odil::VR::PN));
+    auto const xml = odil::as_xml(data_set);
 
     // Expected result:
     // <NativeDicomModel>
@@ -367,24 +365,24 @@ BOOST_AUTO_TEST_CASE(AsXMLPersonName)
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsXMLDataSets)
 {
-    dcmtkpp::DataSet item1;
+    odil::DataSet item1;
     item1.add(0x00100020,
-        dcmtkpp::Element(dcmtkpp::Value::Strings({"valueLO1"}),
-                         dcmtkpp::VR::LO));
+        odil::Element(odil::Value::Strings({"valueLO1"}),
+                         odil::VR::LO));
     item1.add(0x00100022,
-        dcmtkpp::Element(dcmtkpp::Value::Strings({"valueCS1"}),
-                         dcmtkpp::VR::CS));
-    dcmtkpp::DataSet item2;
+        odil::Element(odil::Value::Strings({"valueCS1"}),
+                         odil::VR::CS));
+    odil::DataSet item2;
     item2.add(0x00100022,
-        dcmtkpp::Element(dcmtkpp::Value::Strings({"valueCS2"}),
-                         dcmtkpp::VR::CS));
-    dcmtkpp::DataSet data_set;
+        odil::Element(odil::Value::Strings({"valueCS2"}),
+                         odil::VR::CS));
+    odil::DataSet data_set;
     data_set.add(0x00101002,
-        dcmtkpp::Element(
-            dcmtkpp::Value::DataSets({item1, item2}),
-            dcmtkpp::VR::SQ));
+        odil::Element(
+            odil::Value::DataSets({item1, item2}),
+            odil::VR::SQ));
 
-    auto const xml = dcmtkpp::as_xml(data_set);
+    auto const xml = odil::as_xml(data_set);
 
     // Expected result:
     // <NativeDicomModel>
@@ -417,7 +415,7 @@ BOOST_AUTO_TEST_CASE(AsXMLDataSets)
     BOOST_CHECK_EQUAL(native_dicom_model.front().first, "DicomAttribute");
     BOOST_REQUIRE_EQUAL(native_dicom_model.front().second.size(), 3);
 
-    dcmtkpp::Value::DataSets expected_values({item1, item2});
+    odil::Value::DataSets expected_values({item1, item2});
 
     int count = 0;
     for (auto it = native_dicom_model.front().second.begin();
@@ -471,7 +469,7 @@ BOOST_AUTO_TEST_CASE(AsXMLDataSets)
                         {
                             check_attributes((*it_dicomattr).second,
                                              std::string(current_value->first),
-                                             dcmtkpp::as_string(
+                                             odil::as_string(
                                                  current_value->second.vr),
                                              current_value->first.get_name());
                         }
@@ -514,13 +512,13 @@ BOOST_AUTO_TEST_CASE(AsXMLDataSets)
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsXMLBinary)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00660023,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Binary({0x1, 0x2, 0x3, 0x4, 0x5}),
-            dcmtkpp::VR::OW));
+        odil::Element(
+            odil::Value::Binary({0x1, 0x2, 0x3, 0x4, 0x5}),
+            odil::VR::OW));
 
-    auto const xml = dcmtkpp::as_xml(data_set);
+    auto const xml = odil::as_xml(data_set);
 
     // Expected result:
     // <NativeDicomModel>
@@ -573,12 +571,12 @@ BOOST_AUTO_TEST_CASE(AsXMLBinary)
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsXMLEmptyElement)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00080060,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings({}),
-            dcmtkpp::VR::CS));
-    auto const xml = dcmtkpp::as_xml(data_set);
+        odil::Element(
+            odil::Value::Strings({}),
+            odil::VR::CS));
+    auto const xml = odil::as_xml(data_set);
 
     // Expected result:
     // <NativeDicomModel>
@@ -586,7 +584,7 @@ BOOST_AUTO_TEST_CASE(AsXMLEmptyElement)
     // </NativeDicomModel>
 
     check_response(xml, "00080060", "CS", "Modality",
-                   dcmtkpp::Value::Strings({}));
+                   odil::Value::Strings({}));
 }
 
 /******************************* TEST Error ************************************/
@@ -594,50 +592,50 @@ BOOST_AUTO_TEST_CASE(AsXMLInvalidPersonName)
 {
     // Too many values separate by '^'
     {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00100010,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings({"Alpha^Betic^Ideo^Graphic^Pho^Netic"}),
-            dcmtkpp::VR::PN));
-    BOOST_REQUIRE_THROW(dcmtkpp::as_xml(data_set), dcmtkpp::Exception);
+        odil::Element(
+            odil::Value::Strings({"Alpha^Betic^Ideo^Graphic^Pho^Netic"}),
+            odil::VR::PN));
+    BOOST_REQUIRE_THROW(odil::as_xml(data_set), odil::Exception);
     }
 
     // Too many values separate by '='
     {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00100010,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings(
+        odil::Element(
+            odil::Value::Strings(
                 {"Alpha^Betic=Ideo^Graphic=Pho^Netic=Bad^Value"}),
-            dcmtkpp::VR::PN));
-    BOOST_REQUIRE_THROW(dcmtkpp::as_xml(data_set), dcmtkpp::Exception);
+            odil::VR::PN));
+    BOOST_REQUIRE_THROW(odil::as_xml(data_set), odil::Exception);
     }
 }
 
 /******************************* TEST Error ************************************/
 BOOST_AUTO_TEST_CASE(AsXMLInvalidDICOMTag)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0xbad00bad,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings({"value"}),
-            dcmtkpp::VR::CS));
-    BOOST_REQUIRE_THROW(dcmtkpp::as_xml(data_set), dcmtkpp::Exception);
+        odil::Element(
+            odil::Value::Strings({"value"}),
+            odil::VR::CS));
+    BOOST_REQUIRE_THROW(odil::as_xml(data_set), odil::Exception);
 }
 
 /******************************* TEST Error ************************************/
 BOOST_AUTO_TEST_CASE(AsXMLBadVR)
 {
-    dcmtkpp::DataSet data_set;
+    odil::DataSet data_set;
     data_set.add(0x00080060,
-        dcmtkpp::Element(
-            dcmtkpp::Value::Strings({"value"}),
-            dcmtkpp::VR::UNKNOWN));
-    BOOST_REQUIRE_THROW(dcmtkpp::as_xml(data_set), dcmtkpp::Exception);
+        odil::Element(
+            odil::Value::Strings({"value"}),
+            odil::VR::UNKNOWN));
+    BOOST_REQUIRE_THROW(odil::as_xml(data_set), odil::Exception);
 }
 
 template<typename TValueType>
-dcmtkpp::DataSet create_dataset(std::string const & tag, std::string const & vr,
+odil::DataSet create_dataset(std::string const & tag, std::string const & vr,
                                 std::string const & keyword,
                                 TValueType const & values,
                                 std::vector<int> const & order)
@@ -661,7 +659,7 @@ dcmtkpp::DataSet create_dataset(std::string const & tag, std::string const & vr,
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-    return dcmtkpp::as_dataset(dataset_xml);
+    return odil::as_dataset(dataset_xml);
 }
 
 template<typename T>
@@ -686,7 +684,7 @@ void create_dataset_error(std::string const & tag, std::string const & vr,
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
     // throw Exception
-    dcmtkpp::as_dataset(dataset_xml);
+    odil::as_dataset(dataset_xml);
 }
 
 /******************************* TEST Nominal **********************************/
@@ -695,14 +693,14 @@ BOOST_AUTO_TEST_CASE(AsDataSetEmpty)
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", boost::property_tree::ptree());
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(dataset_xml);
+    odil::DataSet const data_set = odil::as_dataset(dataset_xml);
     BOOST_REQUIRE(data_set.empty());
 }
 
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsDataSetIntegers)
 {
-    dcmtkpp::Value::Integers values({128, 256});
+    odil::Value::Integers values({128, 256});
 
     // Tag Value sorted by number and not sorted
     std::vector<std::vector<int> > orders = {{0, 1}, {1, 0}};
@@ -712,7 +710,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetIntegers)
                                              values, order);
         BOOST_REQUIRE_EQUAL(data_set.size(), 1);
         BOOST_REQUIRE(data_set.has("00280010"));
-        BOOST_REQUIRE(data_set.get_vr("00280010") == dcmtkpp::VR::US);
+        BOOST_REQUIRE(data_set.get_vr("00280010") == odil::VR::US);
         BOOST_REQUIRE(data_set.is_int("00280010"));
         BOOST_REQUIRE(data_set.as_int("00280010") == values);
     }
@@ -721,7 +719,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetIntegers)
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsDataSetReals)
 {
-    dcmtkpp::Value::Reals values({1.2, 3.4});
+    odil::Value::Reals values({1.2, 3.4});
 
     // Tag Value sorted by number and not sorted
     std::vector<std::vector<int> > orders = {{0, 1}, {1, 0}};
@@ -732,7 +730,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetReals)
                     values, order);
         BOOST_REQUIRE_EQUAL(data_set.size(), 1);
         BOOST_REQUIRE(data_set.has("00089459"));
-        BOOST_REQUIRE(data_set.get_vr("00089459") == dcmtkpp::VR::FL);
+        BOOST_REQUIRE(data_set.get_vr("00089459") == odil::VR::FL);
         BOOST_REQUIRE(data_set.is_real("00089459"));
         BOOST_REQUIRE(data_set.as_real("00089459") == values);
     }
@@ -741,7 +739,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetReals)
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsDataSetStrings)
 {
-    dcmtkpp::Value::Strings values({"FOO", "BAR"});
+    odil::Value::Strings values({"FOO", "BAR"});
 
     // Tag Value sorted by number and not sorted
     std::vector<std::vector<int> > orders = {{0, 1}, {1, 0}};
@@ -751,7 +749,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetStrings)
                                              values, order);
         BOOST_REQUIRE_EQUAL(data_set.size(), 1);
         BOOST_REQUIRE(data_set.has("00080060"));
-        BOOST_REQUIRE(data_set.get_vr("00080060") == dcmtkpp::VR::CS);
+        BOOST_REQUIRE(data_set.get_vr("00080060") == odil::VR::CS);
         BOOST_REQUIRE(data_set.is_string("00080060"));
         BOOST_REQUIRE(data_set.as_string("00080060") == values);
     }
@@ -829,12 +827,12 @@ BOOST_AUTO_TEST_CASE(AsDataSetPersonName)
         boost::property_tree::ptree dataset_xml;
         dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-        dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(dataset_xml);
+        odil::DataSet const data_set = odil::as_dataset(dataset_xml);
         BOOST_REQUIRE_EQUAL(data_set.size(), 1);
         BOOST_REQUIRE(data_set.has("00100010"));
-        BOOST_REQUIRE(data_set.get_vr("00100010") == dcmtkpp::VR::PN);
+        BOOST_REQUIRE(data_set.get_vr("00100010") == odil::VR::PN);
         BOOST_REQUIRE(data_set.is_string("00100010"));
-        BOOST_REQUIRE(data_set.as_string("00100010") == dcmtkpp::Value::Strings(
+        BOOST_REQUIRE(data_set.as_string("00100010") == odil::Value::Strings(
             {"family^given^middle^prefix^suffix", "family=family=family"}));
     }
 }
@@ -842,16 +840,16 @@ BOOST_AUTO_TEST_CASE(AsDataSetPersonName)
 /******************************* TEST Nominal **********************************/
 BOOST_AUTO_TEST_CASE(AsDataSetDataSets)
 {
-    dcmtkpp::DataSet item1;
+    odil::DataSet item1;
     item1.add(0x00100020,
-        dcmtkpp::Element(dcmtkpp::Value::Strings({"FOO"}), dcmtkpp::VR::LO));
+        odil::Element(odil::Value::Strings({"FOO"}), odil::VR::LO));
     item1.add(0x00100022,
-        dcmtkpp::Element(dcmtkpp::Value::Strings({"BAR"}), dcmtkpp::VR::CS));
-    dcmtkpp::DataSet item2;
+        odil::Element(odil::Value::Strings({"BAR"}), odil::VR::CS));
+    odil::DataSet item2;
     item2.add(0x00100020,
-        dcmtkpp::Element(dcmtkpp::Value::Strings({"OTHER"}), dcmtkpp::VR::LO));
+        odil::Element(odil::Value::Strings({"OTHER"}), odil::VR::LO));
 
-    dcmtkpp::Value::DataSets expected_result({item1, item2});
+    odil::Value::DataSets expected_result({item1, item2});
 
     std::vector<std::vector<int> > orders = {{0, 1}, {1, 0}};
     for (auto order : orders)
@@ -871,7 +869,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetDataSets)
             {
                 boost::property_tree::ptree subdicomattribute;
                 subdicomattribute.put("<xmlattr>.vr",
-                                      dcmtkpp::as_string(it->second.vr));
+                                      odil::as_string(it->second.vr));
                 subdicomattribute.put("<xmlattr>.tag", std::string(it->first));
                 subdicomattribute.put("<xmlattr>.keyword", it->first.get_name());
 
@@ -896,15 +894,10 @@ BOOST_AUTO_TEST_CASE(AsDataSetDataSets)
         boost::property_tree::ptree dataset_xml;
         dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-        std::stringstream xmldataset;
-        boost::property_tree::xml_writer_settings<char> settings(' ', 4);
-        boost::property_tree::write_xml(xmldataset, dataset_xml, settings);
-
-        std::cout << xmldataset.str() << std::endl;
-        dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(dataset_xml);
+        odil::DataSet const data_set = odil::as_dataset(dataset_xml);
         BOOST_REQUIRE_EQUAL(data_set.size(), 1);
         BOOST_REQUIRE(data_set.has("00101002"));
-        BOOST_REQUIRE(data_set.get_vr("00101002") == dcmtkpp::VR::SQ);
+        BOOST_REQUIRE(data_set.get_vr("00101002") == odil::VR::SQ);
         BOOST_REQUIRE(data_set.is_data_set("00101002"));
 
         BOOST_REQUIRE(data_set.as_data_set("00101002") == expected_result);
@@ -930,20 +923,20 @@ BOOST_AUTO_TEST_CASE(AsDataSetBinary)
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-    dcmtkpp::DataSet const data_set = dcmtkpp::as_dataset(dataset_xml);
+    odil::DataSet const data_set = odil::as_dataset(dataset_xml);
     BOOST_REQUIRE_EQUAL(data_set.size(), 1);
     BOOST_REQUIRE(data_set.has("00660023"));
-    BOOST_REQUIRE(data_set.get_vr("00660023") == dcmtkpp::VR::OW);
+    BOOST_REQUIRE(data_set.get_vr("00660023") == odil::VR::OW);
     BOOST_REQUIRE(data_set.is_binary("00660023"));
-    BOOST_REQUIRE(data_set.as_binary("00660023") == dcmtkpp::Value::Binary(
+    BOOST_REQUIRE(data_set.as_binary("00660023") == odil::Value::Binary(
         {0x1, 0x2, 0x3, 0x4, 0x5}));
 }
 
 /******************************* TEST Error ************************************/
 BOOST_AUTO_TEST_CASE(AsDataSetMissingRootNode)
 {
-    BOOST_REQUIRE_THROW(dcmtkpp::as_dataset(boost::property_tree::ptree()),
-                        dcmtkpp::Exception);
+    BOOST_REQUIRE_THROW(odil::as_dataset(boost::property_tree::ptree()),
+                        odil::Exception);
 }
 
 /******************************* TEST Error ************************************/
@@ -955,7 +948,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetBadDICOMNode)
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-    BOOST_REQUIRE_THROW(dcmtkpp::as_dataset(dataset_xml), dcmtkpp::Exception);
+    BOOST_REQUIRE_THROW(odil::as_dataset(dataset_xml), odil::Exception);
 }
 
 /******************************* TEST Error ************************************/
@@ -964,7 +957,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetBadDicomAttributeSubNode)
     // String value
     BOOST_REQUIRE_THROW(create_dataset_error("00080060", "CS",
                                              "Modality", "FOO"),
-                        dcmtkpp::Exception);
+                        odil::Exception);
 
     { // Person Name
     boost::property_tree::ptree dicomattribute;
@@ -987,18 +980,18 @@ BOOST_AUTO_TEST_CASE(AsDataSetBadDicomAttributeSubNode)
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-    BOOST_REQUIRE_THROW(dcmtkpp::as_dataset(dataset_xml), dcmtkpp::Exception);
+    BOOST_REQUIRE_THROW(odil::as_dataset(dataset_xml), odil::Exception);
     }
 
     // Real value
     BOOST_REQUIRE_THROW(create_dataset_error(
                             "00089459", "FL",
                             "RecommendedDisplayFrameRateInFloat", 1.2),
-                        dcmtkpp::Exception);
+                        odil::Exception);
 
     // Integer value
     BOOST_REQUIRE_THROW(create_dataset_error("00280010", "US", "Rows", 1),
-                        dcmtkpp::Exception);
+                        odil::Exception);
 
     { // Sequence
     boost::property_tree::ptree dicomattribute;
@@ -1016,14 +1009,14 @@ BOOST_AUTO_TEST_CASE(AsDataSetBadDicomAttributeSubNode)
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-    BOOST_REQUIRE_THROW(dcmtkpp::as_dataset(dataset_xml), dcmtkpp::Exception);
+    BOOST_REQUIRE_THROW(odil::as_dataset(dataset_xml), odil::Exception);
     }
 
     // Binary
     BOOST_REQUIRE_THROW(create_dataset_error(
                             "00660023", "OW",
                             "TrianglePointIndexList", "AQIDBAU="),
-                        dcmtkpp::Exception);
+                        odil::Exception);
 }
 
 /******************************* TEST Error ************************************/
@@ -1031,7 +1024,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetUnknownVR)
 {
     BOOST_REQUIRE_THROW(create_dataset_error("00080060", "UR",
                                              "Modality", "FOO"),
-                        dcmtkpp::Exception);
+                        odil::Exception);
 }
 
 /******************************* TEST Error ************************************/
@@ -1057,7 +1050,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetBadPersonNameSubNode)
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-    BOOST_REQUIRE_THROW(dcmtkpp::as_dataset(dataset_xml), dcmtkpp::Exception);
+    BOOST_REQUIRE_THROW(odil::as_dataset(dataset_xml), odil::Exception);
 }
 
 /******************************* TEST Error ************************************/
@@ -1083,7 +1076,7 @@ BOOST_AUTO_TEST_CASE(AsDataSetBadAlphabeticSubNode)
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-    BOOST_REQUIRE_THROW(dcmtkpp::as_dataset(dataset_xml), dcmtkpp::Exception);
+    BOOST_REQUIRE_THROW(odil::as_dataset(dataset_xml), odil::Exception);
 }
 
 /******************************* TEST Error ************************************/
@@ -1110,5 +1103,5 @@ BOOST_AUTO_TEST_CASE(AsDataSetTooManyInlineBinaryNode)
     boost::property_tree::ptree dataset_xml;
     dataset_xml.add_child("NativeDicomModel", nativedicommodel);
 
-    BOOST_REQUIRE_THROW(dcmtkpp::as_dataset(dataset_xml), dcmtkpp::Exception);
+    BOOST_REQUIRE_THROW(odil::as_dataset(dataset_xml), odil::Exception);
 }
diff --git a/tests/run.sh b/tests/run.sh
index fa8a490..1177ea8 100755
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -40,17 +40,17 @@ clean() {
     rm -rf ${directory}
 }
 
-directory=$(mktemp -d dcmtkpp.XXX)
+directory=$(mktemp -d odil.XXX)
 configure
 start_scp
 add_data
 
-export DCMTKPP_OWN_AET=LOCAL
-export DCMTKPP_PEER_HOST_NAME=localhost
-export DCMTKPP_PEER_PORT=11112
-export DCMTKPP_PEER_AET=REMOTE
+export ODIL_OWN_AET=LOCAL
+export ODIL_PEER_HOST_NAME=127.0.0.1
+export ODIL_PEER_PORT=11112
+export ODIL_PEER_AET=REMOTE
 
-ctest --no-compress-output -T Test || true
+ctest --no-compress-output -T Test $@ || true
 
 stop_scp
 clean
diff --git a/tests/tools/CMakeLists.txt b/tests/tools/CMakeLists.txt
new file mode 100644
index 0000000..9895042
--- /dev/null
+++ b/tests/tools/CMakeLists.txt
@@ -0,0 +1,11 @@
+find_package(DCMTK REQUIRED)
+
+add_definitions(${DCMTK_DEFINITIONS})
+include_directories(${DCMTK_INCLUDE_DIRS})
+link_directories(${DCMTK_LIBRARY_DIRS})
+
+file(GLOB headers *.h)
+file(GLOB files "*.cc")
+
+add_executable(getscu ${files} ${headers})
+target_link_libraries(getscu ${DCMTK_LIBRARIES})
diff --git a/tests/tools/dndefine.h b/tests/tools/dndefine.h
new file mode 100644
index 0000000..136535a
--- /dev/null
+++ b/tests/tools/dndefine.h
@@ -0,0 +1,80 @@
+/*
+ *
+ *  Copyright (C) 2011, OFFIS e.V.
+ *  All rights reserved.  See COPYRIGHT file for details.
+ *
+ *  This software and supporting documentation were developed by
+ *
+ *    OFFIS e.V.
+ *    R&D Division Health
+ *    Escherweg 2
+ *    D-26121 Oldenburg, Germany
+ *
+ *
+ *  Module:  dcnmnet
+ *
+ *  Author:  Uli Schlachter
+ *
+ *  Purpose: Contains preprocessor definitions
+ *
+ *  Last Update:      $Author: uli $
+ *  Update Date:      $Date: 2011-12-14 11:45:14 $
+ *  CVS/RCS Revision: $Revision: 1.1 $
+ *  Status:           $State: Exp $
+ *
+ *  CVS/RCS Log at end of file
+ *
+ */
+
+
+#ifndef DNDEFINE_H
+#define DNDEFINE_H
+
+#include "dcmtk/config/osconfig.h"
+
+#include "dcmtk/ofstd/ofdefine.h"
+
+// MODIFICATION: Add definitions
+#ifdef DCMTK_SHARED
+#ifdef _WIN32
+/* Defines needed for building DLLs on windows */
+#define DCMTK_DECL_EXPORT __declspec(dllexport)
+#define DCMTK_DECL_IMPORT __declspec(dllimport)
+#elif defined(HAVE_HIDDEN_VISIBILITY)
+/* GCC hides everything when given -fvisibility=hidden. The symbols which
+ * should be visible have to get a default visibility again.
+ */
+#define DCMTK_DECL_EXPORT __attribute__ ((visibility("default")))
+#endif
+#endif
+
+#ifndef DCMTK_DECL_EXPORT
+#define DCMTK_DECL_EXPORT
+#endif
+
+#ifndef DCMTK_DECL_IMPORT
+#define DCMTK_DECL_IMPORT
+#endif
+// END Modification
+
+#ifdef dcmnet_EXPORTS
+#define DCMTK_DCMNET_EXPORT DCMTK_DECL_EXPORT
+#else
+#define DCMTK_DCMNET_EXPORT DCMTK_DECL_IMPORT
+#endif
+
+
+#endif
+
+
+/*
+ * CVS/RCS Log:
+ * $Log: dndefine.h,v $
+ * Revision 1.1  2011-12-14 11:45:14  uli
+ * Make it possible to perfectly build dcmnet and dcmtls a DLLs.
+ *
+ * Revision 1.1  2011-12-14 09:50:21  uli
+ * Make it possible to properly build dcmimgle as a DLL.
+ *
+ *
+ */
diff --git a/tests/tools/getscu.cc b/tests/tools/getscu.cc
new file mode 100644
index 0000000..88349a4
--- /dev/null
+++ b/tests/tools/getscu.cc
@@ -0,0 +1,657 @@
+/*
+ *
+ *  Copyright (C) 2011, OFFIS e.V.
+ *  All rights reserved.  See COPYRIGHT file for details.
+ *
+ *  This software and supporting documentation were developed by
+ *
+ *    OFFIS e.V.
+ *    R&D Division Health
+ *    Escherweg 2
+ *    D-26121 Oldenburg, Germany
+ *
+ *
+ *  Module:  dcmnet
+ *
+ *  Author:  Michael Onken
+ *
+ *  Purpose: Query/Retrieve Service Class User (C-GET operation)
+ *
+ *  Last Update:      $Author: onken $
+ *  Update Date:      $Date: 2012-02-10 14:54:38 $
+ *  CVS/RCS Revision: $Revision: 1.10 $
+ *  Status:           $State: Exp $
+ *
+ *  CVS/RCS Log at end of file
+ *
+ */
+
+#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
+
+#ifdef HAVE_GUSI_H
+#include <GUSI.h>
+#endif
+
+#include "dcmtk/ofstd/ofconapp.h"
+#include "dcmtk/ofstd/oflist.h"
+// MODIFICATION: Use local header file
+#include "scu.h"
+// END Modification
+#include "dcmtk/dcmdata/dcuid.h"      /* for dcmtk version name */
+#include "dcmtk/dcmdata/dcostrmz.h"   /* for dcmZlibCompressionLevel */
+#include "dcmtk/dcmdata/dcpath.h"     /* for DcmPathProcessor */
+
+#ifdef WITH_ZLIB
+#include <zlib.h>     /* for zlibVersion() */
+#endif
+
+#define OFFIS_CONSOLE_APPLICATION "getscu"
+
+static OFLogger getscuLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);
+
+static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
+  OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";
+
+/* default application titles */
+#define APPLICATIONTITLE        "GETSCU"
+#define PEERAPPLICATIONTITLE    "ANY-SCP"
+
+typedef enum {
+  QMPatientRoot = 0,
+  QMStudyRoot = 1,
+  QMPatientStudyOnly = 2
+} QueryModel;
+
+static const char* querySyntax[3] = {
+  UID_GETPatientRootQueryRetrieveInformationModel,
+  UID_GETStudyRootQueryRetrieveInformationModel,
+  UID_RETIRED_GETPatientStudyOnlyQueryRetrieveInformationModel
+};
+
+OFCmdUnsignedInt        opt_maxPDU = ASC_DEFAULTMAXPDU;
+E_TransferSyntax        opt_store_networkTransferSyntax = EXS_Unknown;
+E_TransferSyntax        opt_get_networkTransferSyntax = EXS_Unknown;
+DcmStorageMode          opt_storageMode = DCMSCU_STORAGE_DISK;
+OFBool                  opt_showPresentationContexts = OFFalse;
+OFBool                  opt_abortAssociation = OFFalse;
+OFCmdUnsignedInt        opt_repeatCount = 1;
+QueryModel              opt_queryModel = QMPatientRoot;
+T_DIMSE_BlockingMode    opt_blockMode = DIMSE_BLOCKING;
+int                     opt_dimse_timeout = 0;
+int                     opt_acse_timeout = 30;
+OFString                opt_outputDirectory = ".";
+static OFList<OFString> overrideKeys;
+
+static void prepareTS(E_TransferSyntax ts,
+                      OFList<OFString>& syntaxes);
+
+static void applyOverrideKeys(DcmDataset *dataset);
+
+#define SHORTCOL 4
+#define LONGCOL 21
+
+int
+main(int argc, char *argv[])
+{
+  const char *opt_peer;
+  OFCmdUnsignedInt opt_port = 104;;
+  const char *opt_peerTitle = PEERAPPLICATIONTITLE;
+  const char *opt_ourTitle = APPLICATIONTITLE;
+  OFList<OFString> fileNameList;
+
+#ifdef HAVE_GUSI_H
+  /* needed for Macintosh */
+  GUSISetup(GUSIwithSIOUXSockets);
+  GUSISetup(GUSIwithInternetSockets);
+#endif
+
+  char tempstr[20];
+  OFString temp_str;
+  OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION , "DICOM retrieve (C-GET) SCU", rcsid);
+  OFCommandLine cmd;
+
+  cmd.setParamColumn(LONGCOL + SHORTCOL + 4);
+  cmd.addParam("peer", "hostname of DICOM peer");
+  cmd.addParam("port", "tcp/ip port number of peer");
+  cmd.addParam("dcmfile-in", "DICOM query file(s)", OFCmdParam::PM_MultiOptional);
+
+  cmd.setOptionColumns(LONGCOL, SHORTCOL);
+  cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
+   cmd.addOption("--help",                   "-h",      "print this help text and exit", OFCommandLine::AF_Exclusive);
+   cmd.addOption("--version",                           "print version information and exit", OFCommandLine::AF_Exclusive);
+   OFLog::addOptions(cmd);
+   cmd.addOption("--verbose-pc",             "+v",      "show presentation contexts in verbose mode");
+
+  cmd.addGroup("network options:");
+    cmd.addSubGroup("override matching keys:");
+      cmd.addOption("--key",                 "-k",   1, "[k]ey: gggg,eeee=\"str\", path or dic. name=\"str\"",
+                                                        "override matching key");
+    cmd.addSubGroup("retrieve information model:");
+      cmd.addOption("--patient",             "-P",      "use patient root information model (default)");
+      cmd.addOption("--study",               "-S",      "use study root information model");
+      cmd.addOption("--psonly",              "-O",      "use patient/study only information model");
+    cmd.addSubGroup("application entity titles:");
+      OFString opt1 = "set my calling AE title (default: ";
+      opt1 += APPLICATIONTITLE;
+      opt1 += ")";
+      cmd.addOption("--aetitle",             "-aet", 1, "[a]etitle: string", opt1.c_str());
+      OFString opt2 = "set called AE title of peer (default: ";
+      opt2 += PEERAPPLICATIONTITLE;
+      opt2 += ")";
+      cmd.addOption("--call",                "-aec", 1, "[a]etitle: string", opt2.c_str());
+    cmd.addSubGroup("preferred storage transfer syntaxes (incoming associations):");
+      cmd.addOption("--prefer-uncompr",      "+x=",     "prefer explicit VR local byte order (default)");
+      cmd.addOption("--prefer-little",       "+xe",     "prefer explicit VR little endian TS");
+      cmd.addOption("--prefer-big",          "+xb",     "prefer explicit VR big endian TS");
+      cmd.addOption("--prefer-lossless",     "+xs",     "prefer default JPEG lossless TS");
+      cmd.addOption("--prefer-jpeg8",        "+xy",     "prefer default JPEG lossy TS for 8 bit data");
+      cmd.addOption("--prefer-jpeg12",       "+xx",     "prefer default JPEG lossy TS for 12 bit data");
+      cmd.addOption("--prefer-j2k-lossless", "+xv",     "prefer JPEG 2000 lossless TS");
+      cmd.addOption("--prefer-j2k-lossy",    "+xw",     "prefer JPEG 2000 lossy TS");
+      cmd.addOption("--prefer-jls-lossless", "+xt",     "prefer JPEG-LS lossless TS");
+      cmd.addOption("--prefer-jls-lossy",    "+xu",     "prefer JPEG-LS lossy TS");
+      cmd.addOption("--prefer-mpeg2",        "+xm",     "prefer MPEG2 Main Profile @ Main Level TS");
+      cmd.addOption("--prefer-mpeg2-high",   "+xh",     "prefer MPEG2 Main Profile @ High Level TS");
+      cmd.addOption("--prefer-mpeg4",        "+xn",     "prefer MPEG4 AVC/H.264 HP / Level 4.1 TS");
+      cmd.addOption("--prefer-mpeg4-bd",     "+xl",     "prefer MPEG4 AVC/H.264 BD-compatible TS");
+      cmd.addOption("--prefer-rle",          "+xr",     "prefer RLE lossless TS");
+#ifdef WITH_ZLIB
+      cmd.addOption("--prefer-deflated",     "+xd",     "prefer deflated explicit VR little endian TS");
+#endif
+      cmd.addOption("--implicit",            "+xi",     "accept implicit VR little endian TS only");
+    cmd.addSubGroup("proposed retrieve transfer syntaxes (outgoing associations):");
+      cmd.addOption("--propose-uncompr",     "-x=",     "propose all uncompressed TS, explicit VR\nwith local byte ordering first (default)");
+      cmd.addOption("--propose-little",      "-xe",     "propose all uncompressed TS, explicit VR\nlittle endian first");
+      cmd.addOption("--propose-big",         "-xb",     "propose all uncompressed TS, explicit VR\nbig endian first");
+#ifdef WITH_ZLIB
+      cmd.addOption("--propose-deflated",    "-xd",     "propose deflated explicit VR little endian TS\nand all uncompressed transfer syntaxes");
+#endif
+      cmd.addOption("--propose-implicit",    "-xi",     "propose implicit VR little endian TS only");
+    cmd.addSubGroup("other network options:");
+      cmd.addOption("--timeout",             "-to",  1, "[s]econds: integer (default: unlimited)", "timeout for connection requests");
+      cmd.addOption("--acse-timeout",        "-ta",  1, "[s]econds: integer (default: 30)", "timeout for ACSE messages");
+      cmd.addOption("--dimse-timeout",       "-td",  1, "[s]econds: integer (default: unlimited)", "timeout for DIMSE messages");
+
+      OFString opt3 = "set max receive pdu to n bytes (default: ";
+      sprintf(tempstr, "%ld", OFstatic_cast(long, ASC_DEFAULTMAXPDU));
+      opt3 += tempstr;
+      opt3 += ")";
+      OFString opt4 = "[n]umber of bytes: integer (";
+      sprintf(tempstr, "%ld", OFstatic_cast(long, ASC_MINIMUMPDUSIZE));
+      opt4 += tempstr;
+      opt4 += "..";
+      sprintf(tempstr, "%ld", OFstatic_cast(long, ASC_MAXIMUMPDUSIZE));
+      opt4 += tempstr;
+      opt4 += ")";
+      cmd.addOption("--max-pdu",             "-pdu", 1, opt4.c_str(), opt3.c_str());
+      cmd.addOption("--repeat",                      1, "[n]umber: integer", "repeat n times");
+      cmd.addOption("--abort",                          "abort association instead of releasing it");
+  cmd.addGroup("output options:");
+    cmd.addSubGroup("general:");
+      cmd.addOption("--output-directory",    "-od",  1, "[d]irectory: string (default: \".\")", "write received objects to existing directory d");
+    cmd.addSubGroup("storage mode:");
+      cmd.addOption("--normal",              "-B",      "receive in memory, then write to disk (default)");
+      cmd.addOption("--bit-preserving",      "+B",      "receive directly to disk");
+      cmd.addOption("--ignore",                         "ignore store data, receive but do not store");
+
+  /* evaluate command line */
+  prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
+  if (!app.parseCommandLine(cmd, argc, argv, OFCommandLine::PF_ExpandWildcards))
+  {
+    exit(1);
+  }
+  /* check exclusive options first */
+  if (cmd.hasExclusiveOption())
+  {
+    if (cmd.findOption("--version"))
+    {
+      app.printHeader(OFTrue /*print host identifier*/);
+      COUT << OFendl << "External libraries used:";
+#ifdef WITH_ZLIB
+      COUT << OFendl << "- ZLIB, Version " << zlibVersion() << OFendl;
+#else
+      COUT << " none" << OFendl;
+#endif
+      return 0;
+    }
+  }
+
+  /* command line parameters */
+
+  cmd.getParam(1, opt_peer);
+  app.checkParam(cmd.getParamAndCheckMinMax(2, opt_port, 1, 65535));
+
+  OFLog::configureFromCommandLine(cmd, app);
+  if (cmd.findOption("--verbose-pc"))
+  {
+    app.checkDependence("--verbose-pc", "verbose mode", getscuLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL));
+    opt_showPresentationContexts = OFTrue;
+  }
+
+  if (cmd.findOption("--key", 0, OFCommandLine::FOM_FirstFromLeft))
+  {
+    const char *ovKey = NULL;
+    do
+    {
+      app.checkValue(cmd.getValue(ovKey));
+      overrideKeys.push_back(ovKey);
+    } while (cmd.findOption("--key", 0, OFCommandLine::FOM_NextFromLeft));
+  }
+
+  cmd.beginOptionBlock();
+  if (cmd.findOption("--patient")) opt_queryModel = QMPatientRoot;
+  if (cmd.findOption("--study")) opt_queryModel = QMStudyRoot;
+  if (cmd.findOption("--psonly")) opt_queryModel = QMPatientStudyOnly;
+  cmd.endOptionBlock();
+
+  if (cmd.findOption("--aetitle")) app.checkValue(cmd.getValue(opt_ourTitle));
+  if (cmd.findOption("--call")) app.checkValue(cmd.getValue(opt_peerTitle));
+
+  cmd.beginOptionBlock();
+  if (cmd.findOption("--prefer-uncompr"))
+  {
+    opt_store_networkTransferSyntax = EXS_Unknown;
+  }
+// MODIFICATION: Comment undefined TransferSyntax
+  if (cmd.findOption("--prefer-little")) opt_store_networkTransferSyntax = EXS_LittleEndianExplicit;
+  if (cmd.findOption("--prefer-big")) opt_store_networkTransferSyntax = EXS_BigEndianExplicit;
+//  if (cmd.findOption("--prefer-lossless")) opt_store_networkTransferSyntax = EXS_JPEGProcess14SV1;
+//  if (cmd.findOption("--prefer-jpeg8")) opt_store_networkTransferSyntax = EXS_JPEGProcess1;
+//  if (cmd.findOption("--prefer-jpeg12")) opt_store_networkTransferSyntax = EXS_JPEGProcess2_4;
+  if (cmd.findOption("--prefer-j2k-lossless")) opt_store_networkTransferSyntax = EXS_JPEG2000LosslessOnly;
+  if (cmd.findOption("--prefer-j2k-lossy")) opt_store_networkTransferSyntax = EXS_JPEG2000;
+  if (cmd.findOption("--prefer-jls-lossless")) opt_store_networkTransferSyntax = EXS_JPEGLSLossless;
+  if (cmd.findOption("--prefer-jls-lossy")) opt_store_networkTransferSyntax = EXS_JPEGLSLossy;
+  if (cmd.findOption("--prefer-mpeg2")) opt_store_networkTransferSyntax = EXS_MPEG2MainProfileAtMainLevel;
+  if (cmd.findOption("--prefer-mpeg2-high")) opt_store_networkTransferSyntax = EXS_MPEG2MainProfileAtHighLevel;
+//  if (cmd.findOption("--prefer-mpeg4")) opt_store_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_1;
+//  if (cmd.findOption("--prefer-mpeg4-bd")) opt_store_networkTransferSyntax = EXS_MPEG4BDcompatibleHighProfileLevel4_1;
+  if (cmd.findOption("--prefer-rle")) opt_store_networkTransferSyntax = EXS_RLELossless;
+// END Modification
+#ifdef WITH_ZLIB
+  if (cmd.findOption("--prefer-deflated")) opt_store_networkTransferSyntax = EXS_DeflatedLittleEndianExplicit;
+#endif
+  if (cmd.findOption("--implicit")) opt_store_networkTransferSyntax = EXS_LittleEndianImplicit;
+  cmd.endOptionBlock();
+
+  cmd.beginOptionBlock();
+  if (cmd.findOption("--propose-uncompr")) opt_get_networkTransferSyntax = EXS_Unknown;
+  if (cmd.findOption("--propose-little")) opt_get_networkTransferSyntax = EXS_LittleEndianExplicit;
+  if (cmd.findOption("--propose-big")) opt_get_networkTransferSyntax = EXS_BigEndianExplicit;
+  if (cmd.findOption("--propose-implicit")) opt_get_networkTransferSyntax = EXS_LittleEndianImplicit;
+#ifdef WITH_ZLIB
+  if (cmd.findOption("--propose-deflated")) opt_get_networkTransferSyntax = EXS_DeflatedLittleEndianExplicit;
+#endif
+  cmd.endOptionBlock();
+
+  if (cmd.findOption("--timeout"))
+  {
+    OFCmdSignedInt opt_timeout = 0;
+    app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
+    dcmConnectionTimeout.set(OFstatic_cast(Sint32, opt_timeout));
+  }
+
+  if (cmd.findOption("--acse-timeout"))
+  {
+    OFCmdSignedInt opt_timeout = 0;
+    app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
+    opt_acse_timeout = OFstatic_cast(int, opt_timeout);
+  }
+
+  if (cmd.findOption("--dimse-timeout"))
+  {
+    OFCmdSignedInt opt_timeout = 0;
+    app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
+    opt_dimse_timeout = OFstatic_cast(int, opt_timeout);
+    opt_blockMode = DIMSE_NONBLOCKING;
+  }
+
+  if (cmd.findOption("--max-pdu")) app.checkValue(cmd.getValueAndCheckMinMax(opt_maxPDU, ASC_MINIMUMPDUSIZE, ASC_MAXIMUMPDUSIZE));
+  if (cmd.findOption("--repeat"))  app.checkValue(cmd.getValueAndCheckMin(opt_repeatCount, 1));
+  if (cmd.findOption("--abort"))   opt_abortAssociation = OFTrue;
+  if (cmd.findOption("--ignore"))  opt_storageMode = DCMSCU_STORAGE_IGNORE;
+
+  if (cmd.findOption("--output-directory"))
+  {
+    app.checkValue(cmd.getValue(opt_outputDirectory));
+    app.checkConflict("--output-directory", "--ignore", opt_storageMode == DCMSCU_STORAGE_IGNORE);
+  }
+
+  cmd.beginOptionBlock();
+  if (cmd.findOption("--bit-preserving"))
+  {
+    app.checkConflict("--bit-preserving", "--ignore", opt_storageMode == DCMSCU_STORAGE_IGNORE);
+    opt_storageMode = DCMSCU_STORAGE_BIT_PRESERVING;
+  }
+  if (cmd.findOption("--normal"))
+  {
+    app.checkConflict("--normal", "--bit-preserving", opt_storageMode == DCMSCU_STORAGE_BIT_PRESERVING);
+    app.checkConflict("--normal", "--ignore", opt_storageMode == DCMSCU_STORAGE_IGNORE);
+    opt_storageMode = DCMSCU_STORAGE_DISK;
+  }
+  cmd.endOptionBlock();
+
+  /* finally parse filenames */
+  int paramCount = cmd.getParamCount();
+  const char *currentFilename = NULL;
+  OFString errormsg;
+
+  for (int i=3; i <= paramCount; i++)
+  {
+    cmd.getParam(i, currentFilename);
+    if (access(currentFilename, R_OK) < 0)
+    {
+      errormsg = "cannot access file: ";
+      errormsg += currentFilename;
+      app.printError(errormsg.c_str());
+    }
+    fileNameList.push_back(currentFilename);
+  }
+
+  if (fileNameList.empty() && overrideKeys.empty())
+  {
+    app.printError("either query file or override keys (or both) must be specified");
+  }
+
+  /* print resource identifier */
+  OFLOG_DEBUG(getscuLogger, rcsid << OFendl);
+
+  /* make sure data dictionary is loaded */
+  if (!dcmDataDict.isDictionaryLoaded())
+  {
+    OFLOG_WARN(getscuLogger, "no data dictionary loaded, check environment variable: "
+      << DCM_DICT_ENVIRONMENT_VARIABLE);
+  }
+
+  /* make sure output directory exists and is writeable */
+
+  if (!OFStandard::dirExists(opt_outputDirectory))
+  {
+    OFLOG_FATAL(getscuLogger, "specified output directory does not exist");
+    return 1;
+  }
+  else if (!OFStandard::isWriteable(opt_outputDirectory))
+  {
+    OFLOG_FATAL(getscuLogger, "specified output directory is not writeable");
+    return 1;
+  }
+
+  // Setup SCU
+  OFList<OFString> syntaxes;
+  prepareTS(opt_get_networkTransferSyntax, syntaxes);
+  DcmSCU scu;
+  scu.setMaxReceivePDULength(opt_maxPDU);
+  scu.setACSETimeout(opt_acse_timeout);
+  scu.setDIMSEBlockingMode(opt_blockMode);
+  scu.setDIMSETimeout(opt_dimse_timeout);
+  scu.setAETitle(opt_ourTitle);
+  scu.setPeerHostName(opt_peer);
+  scu.setPeerPort(OFstatic_cast(Uint16, opt_port));
+  scu.setPeerAETitle(opt_peerTitle);
+  scu.setVerbosePCMode(opt_showPresentationContexts);
+
+  // Add presentation contexts for get and find (we do not actually need find...)
+  // (only uncompressed)
+  scu.addPresentationContext(querySyntax[opt_queryModel], syntaxes);
+
+  // Add storage presentation contexts (long list of storage SOP classes, uncompressed)
+  syntaxes.clear();
+  prepareTS(opt_store_networkTransferSyntax, syntaxes);
+  for (Uint16 j = 0; j < numberOfDcmLongSCUStorageSOPClassUIDs; j++)
+  {
+    scu.addPresentationContext(dcmLongSCUStorageSOPClassUIDs[j], syntaxes, ASC_SC_ROLE_SCP);
+  }
+
+  // Set the storage mode
+  scu.setStorageMode(opt_storageMode);
+  if (opt_storageMode != DCMSCU_STORAGE_IGNORE)
+  {
+    scu.setStorageDir(opt_outputDirectory);
+  }
+
+  // Initialize network and negotiate association
+  OFCondition cond = scu.initNetwork();
+  if (cond.bad())
+  {
+    OFLOG_FATAL(getscuLogger, DimseCondition::dump(temp_str, cond));
+    exit(1);
+  }
+  cond = scu.negotiateAssociation();
+  if (cond.bad())
+  {
+    OFLOG_FATAL(getscuLogger, "No Acceptable Presentation Contexts");
+    exit(1);
+  }
+  cond = EC_Normal;
+  T_ASC_PresentationContextID pcid = scu.findPresentationContextID(querySyntax[opt_queryModel], "");
+  if (pcid == 0)
+  {
+    OFLOG_FATAL(getscuLogger, "No adequate Presentation Contexts for sending C-GET");
+    exit(1);
+  }
+
+  /* Do the real work, i.e. send C-GET requests and receive objects */
+  for (Uint16 repeat = 0; repeat < opt_repeatCount; repeat++)
+  {
+    Uint16 numRuns = 1;
+    DcmFileFormat dcmff;
+    DcmDataset *dset = dcmff.getDataset();
+    OFListConstIterator(OFString) it;
+    /* Load first file, if there is one */
+    if (!fileNameList.empty())
+    {
+      numRuns = fileNameList.size();
+      it = fileNameList.begin();
+      cond = dcmff.loadFile((*it).c_str());
+      if (cond.bad())
+      {
+        OFLOG_FATAL(getscuLogger, DimseCondition::dump(temp_str, cond));
+        exit(1);
+      }
+      dset = dcmff.getDataset();
+    }
+    OFList<RetrieveResponse*> responses;
+    /* For all files (or at least one run from override keys) */
+    for (Uint16 i = 0; i < numRuns; i++)
+    {
+      applyOverrideKeys(dset);
+      cond = scu.sendCGETRequest(pcid, dset, &responses);
+      if (cond.bad())
+      {
+        exit(1);
+      }
+      /* Load next file if there is one */
+      if (numRuns > 1)
+      {
+        it++;
+        cond = dcmff.loadFile((*it).c_str());
+        if (cond.bad())
+        {
+          OFLOG_FATAL(getscuLogger, DimseCondition::dump(temp_str, cond));
+          exit(1);
+        }
+        dset = dcmff.getDataset();
+      }
+    }
+    if (!responses.empty())
+    {
+      /* Output final status report */
+      OFLOG_INFO(getscuLogger, "Final status report from last C-GET message:");
+      (*(--responses.end()))->print();
+      /* Delete responses */
+      OFListIterator(RetrieveResponse*) iter = responses.begin();
+      OFListConstIterator(RetrieveResponse*) last = responses.end();
+      while (iter != last)
+      {
+        delete (*iter);
+        iter = responses.erase(iter);
+      }
+    }
+  }
+
+  /* tear down association */
+  if (cond == EC_Normal)
+  {
+    if (opt_abortAssociation)
+    {
+      OFLOG_INFO(getscuLogger, "Aborting Association");
+      scu.closeAssociation(DCMSCU_ABORT_ASSOCIATION);
+      exit(0);
+    } else
+    {
+      /* release association */
+      scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
+      exit(0);
+    }
+  }
+  else if (cond == DUL_PEERREQUESTEDRELEASE)
+  {
+    OFLOG_ERROR(getscuLogger, "Protocol Error: Peer requested release (Aborting)");
+    scu.closeAssociation(DCMSCU_ABORT_ASSOCIATION);
+    exit(1);
+  }
+  else if (cond == DUL_PEERABORTEDASSOCIATION)
+  {
+    OFLOG_INFO(getscuLogger, "Peer Aborted Association");
+  }
+  else
+  {
+    OFLOG_ERROR(getscuLogger, "GET SCU Failed: " << DimseCondition::dump(temp_str, cond));
+    OFLOG_INFO(getscuLogger, "Aborting Association");
+    scu.closeAssociation(DCMSCU_ABORT_ASSOCIATION);
+    exit(1);
+  }
+
+#ifdef HAVE_WINSOCK_H
+  WSACleanup();
+#endif
+
+  return 0;
+}
+
+static void applyOverrideKeys(DcmDataset *dataset)
+{
+  /* replace specific keys by those in overrideKeys */
+  OFListConstIterator(OFString) path = overrideKeys.begin();
+  OFListConstIterator(OFString) endOfList = overrideKeys.end();
+  DcmPathProcessor proc;
+  proc.setItemWildcardSupport(OFFalse);
+  proc.checkPrivateReservations(OFFalse);
+  OFCondition cond;
+  while (path != endOfList)
+  {
+    cond = proc.applyPathWithValue(dataset, *path);
+    if (cond.bad())
+    {
+      OFLOG_ERROR(getscuLogger, "Bad override key/path: " << *path << ": " << cond.text());
+    }
+    path++;
+  }
+}
+
+static void prepareTS(E_TransferSyntax ts,
+                      OFList<OFString>& syntaxes)
+{
+  /*
+  ** We prefer to use Explicitly encoded transfer syntaxes.
+  ** If we are running on a Little Endian machine we prefer
+  ** LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
+  ** Some SCP implementations will just select the first transfer
+  ** syntax they support (this is not part of the standard) so
+  ** organise the proposed transfer syntaxes to take advantage
+  ** of such behaviour.
+  **
+  ** The presentation contexts proposed here are only used for
+  ** C-FIND and C-MOVE, so there is no need to support compressed
+  ** transmission.
+  */
+
+  switch (ts)
+  {
+    case EXS_LittleEndianImplicit:
+      /* we only support Little Endian Implicit */
+      syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+      break;
+    case EXS_LittleEndianExplicit:
+      /* we prefer Little Endian Explicit */
+      syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+      syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
+      syntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
+      break;
+    case EXS_BigEndianExplicit:
+      /* we prefer Big Endian Explicit */
+      syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
+      syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+      syntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
+      break;
+#ifdef WITH_ZLIB
+    case EXS_DeflatedLittleEndianExplicit:
+      /* we prefer Deflated Little Endian Explicit */
+      syntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax);
+      syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+      syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
+      syntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
+      break;
+#endif
+    default:
+      DcmXfer xfer(ts);
+      if (xfer.isEncapsulated())
+      {
+        syntaxes.push_back(xfer.getXferID());
+      }
+      /* We prefer explicit transfer syntaxes.
+       * If we are running on a Little Endian machine we prefer
+       * LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
+       */
+      if (gLocalByteOrder == EBO_LittleEndian)  /* defined in dcxfer.h */
+      {
+        syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+        syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
+      } else
+      {
+        syntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
+        syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+      }
+      syntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
+      break;
+  }
+}
+
+
+/*
+** CVS Log
+** $Log: getscu.cc,v $
+** Revision 1.10  2012-02-10 14:54:38  onken
+** Removed duplicate log message when closing an association.
+**
+** Revision 1.9  2011-12-16 16:36:44  meichel
+** Minor changes for MSVC6 compatibility
+**
+** Revision 1.8  2011-09-26 08:13:53  joergr
+** Moved --verbose-pc option to the end of the "general options" section.
+**
+** Revision 1.7  2011-09-21 12:57:47  joergr
+** Removed TCP wrapper support (libwrap) which is not really useful for an SCU.
+**
+** Revision 1.6  2011-09-21 11:06:28  joergr
+** Removed option --disable-host-lookup which is not really useful for an SCU.
+**
+** Revision 1.5  2011-09-09 08:58:11  joergr
+** Replaced local list of supported storage SOP classes by the global variable
+** dcmLongSCUStorageSOPClassUIDs, which is maintained at a central location.
+**
+** Revision 1.4  2011-09-07 12:40:45  joergr
+** Made more clear that option --key also supports path expressions.
+**
+** Revision 1.3  2011-08-25 15:05:04  joergr
+** Changed data structure for Q/R responses from OFVector to OFList. Also fixed
+** some possible memory leaks and made the FIND/MOVE/GET code more consistent.
+**
+** Revision 1.2  2011-08-25 12:51:22  joergr
+** Added "verbose-pc" mode that shows the presentation contexts in verbose mode.
+**
+** Revision 1.1  2011-08-25 09:31:32  onken
+** Added C-GET functionality to DcmSCU class and accompanying getscu
+** commandline application.
+**
+*/
diff --git a/tests/tools/scu.cc b/tests/tools/scu.cc
new file mode 100644
index 0000000..6c45384
--- /dev/null
+++ b/tests/tools/scu.cc
@@ -0,0 +1,2590 @@
+/*
+ *
+ *  Copyright (C) 2008-2012, OFFIS e.V.
+ *  All rights reserved.  See COPYRIGHT file for details.
+ *
+ *  This software and supporting documentation were developed by
+ *
+ *    OFFIS e.V.
+ *    R&D Division Health
+ *    Escherweg 2
+ *    D-26121 Oldenburg, Germany
+ *
+ *
+ *  Module:  dcmnet
+ *
+ *  Author:  Michael Onken
+ *
+ *  Purpose: Base class for Service Class Users (SCUs)
+ *
+ *  Last Update:      $Author: onken $
+ *  Update Date:      $Date: 2012-05-14 10:42:54 $
+ *  CVS/RCS Revision: $Revision: 1.60 $
+ *  Status:           $State: Exp $
+ *
+ *  CVS/RCS Log at end of file
+ *
+ */
+
+#include "dcmtk/config/osconfig.h"  /* make sure OS specific configuration is included first */
+
+// MODIFICATION: Take the local scu.h
+#include "scu.h"
+// END Modification
+#include "dcmtk/dcmnet/diutil.h"    /* for dcmnet logger */
+#include "dcmtk/dcmdata/dcuid.h"    /* for dcmFindUIDName() */
+#include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */
+
+
+#ifdef WITH_ZLIB
+#include <zlib.h>                   /* for zlibVersion() */
+#endif
+
+#if OFFIS_DCMTK_VERSION_NUMBER <= 360
+
+// MODIFICATION: Add missing Errors
+const OFConditionConst NET_EC_InvalidSOPClassUID(OFM_dcmnet, 1000, OF_error, "Invalid SOP Class UID");
+const OFConditionConst NET_EC_UnknownStorageSOPClass(OFM_dcmnet, 1001, OF_error, "Unknown Storage SOP Class");
+const OFConditionConst NET_EC_InvalidSOPInstanceUID(OFM_dcmnet, 1002, OF_error, "Invalid SOP Instance UID");
+const OFConditionConst NET_EC_InvalidTransferSyntaxUID(OFM_dcmnet, 1003, OF_error, "Invalid Transfer Syntax UID");
+const OFConditionConst NET_EC_UnknownTransferSyntax(OFM_dcmnet, 1004, OF_error, "Unknown Transfer Syntax");
+const OFConditionConst NET_EC_NoPresentationContextsDefined(OFM_dcmnet, 1005, OF_error, "No Presentation Contexts defined");
+const OFConditionConst NET_EC_NoAcceptablePresentationContexts(OFM_dcmnet, 1006, OF_error, "No acceptable Presentation Contexts");
+const OFConditionConst NET_EC_NoSOPInstancesToSend(OFM_dcmnet, 1007, OF_error, "No SOP instances to send");
+const OFConditionConst NET_EC_NoSuchSOPInstance(OFM_dcmnet, 1008, OF_error, "No such SOP instance");
+const OFConditionConst NET_EC_InvalidDatasetPointer(OFM_dcmnet, 1009, OF_error, "Invalid dataset pointer");
+const OFConditionConst NET_EC_AlreadyConnected(OFM_dcmnet, 1010, OF_error, "Already connected");
+const OFConditionConst NET_EC_InsufficientPortPrivileges(OFM_dcmnet, 1023, OF_error, "Insufficient port privileges");
+// END Modification
+
+#endif
+
+DcmSCU::DcmSCU() :
+  m_assoc(NULL),
+  m_net(NULL),
+  m_params(NULL),
+  m_assocConfigFilename(),
+  m_assocConfigProfile(),
+  m_presContexts(),
+  m_assocConfigFile(),
+  m_openDIMSERequest(NULL),
+  m_maxReceivePDULength(ASC_DEFAULTMAXPDU),
+  m_blockMode(DIMSE_BLOCKING),
+  m_ourAETitle("ANY-SCU"),
+  m_peer(),
+  m_peerAETitle("ANY-SCP"),
+  m_peerPort(104),
+  m_dimseTimeout(0),
+  m_acseTimeout(30),
+  m_storageDir(),
+  m_storageMode(DCMSCU_STORAGE_DISK),
+  m_verbosePCMode(OFFalse),
+  m_datasetConversionMode(OFFalse),
+  m_progressNotificationMode(OFTrue)
+{
+
+#ifdef HAVE_GUSI_H
+  GUSISetup(GUSIwithSIOUXSockets);
+  GUSISetup(GUSIwithInternetSockets);
+#endif
+
+#ifdef HAVE_WINSOCK_H
+  WSAData winSockData;
+  /* we need at least version 1.1 */
+  WORD winSockVersionNeeded = MAKEWORD( 1, 1 );
+  WSAStartup(winSockVersionNeeded, &winSockData); // TODO: check with multiple SCU instances whether this is harmful
+#endif
+}
+
+void DcmSCU::freeNetwork()
+{
+  if ((m_assoc != NULL) || (m_net != NULL) || (m_params != NULL))
+    DCMNET_DEBUG("Cleaning up internal association and network structures");
+  /* destroy association parameters, i.e. free memory of T_ASC_Parameters.
+     Usually this is done in ASC_destroyAssociation; however, if we already
+     have association parameters but not yet an association (e.g. after calling
+     initNetwork() and negotiateAssociation()), the latter approach may fail.
+  */
+  if (m_params)
+  {
+    ASC_destroyAssociationParameters(&m_params);
+    m_params = NULL;
+    // make sure destroyAssocation does not try to free params a second time
+    // (happens in case we have already have an association structure)
+    if (m_assoc)
+      m_assoc->params = NULL;
+  }
+  // destroy the association, i.e. free memory of T_ASC_Association* structure.
+  ASC_destroyAssociation(&m_assoc);
+  // drop the network, i.e. free memory of T_ASC_Network* structure.
+  ASC_dropNetwork(&m_net);
+  // Cleanup old DIMSE request if any
+  delete m_openDIMSERequest;
+  m_openDIMSERequest = NULL;
+}
+
+
+DcmSCU::~DcmSCU()
+{
+  // abort association (if any) and destroy dcmnet data structures
+  if (isConnected())
+  {
+    closeAssociation(DCMSCU_ABORT_ASSOCIATION); // also frees network
+  } else {
+    freeNetwork();
+  }
+
+#ifdef HAVE_WINSOCK_H
+  WSACleanup(); // TODO: check with multiple SCU instances whether this is harmful
+#endif
+}
+
+
+OFCondition DcmSCU::initNetwork()
+{
+  /* Return if SCU is already connected */
+  if (isConnected())
+    return NET_EC_AlreadyConnected;
+
+  /* Be sure internal network structures are clean (delete old) */
+  freeNetwork();
+
+  OFString tempStr;
+  /* initialize network, i.e. create an instance of T_ASC_Network*. */
+  OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, m_acseTimeout, &m_net);
+  if (cond.bad())
+  {
+    DimseCondition::dump(tempStr, cond);
+    DCMNET_ERROR(tempStr);
+    return cond;
+  }
+
+  /* initialize asscociation parameters, i.e. create an instance of T_ASC_Parameters*. */
+  cond = ASC_createAssociationParameters(&m_params, m_maxReceivePDULength);
+  if (cond.bad())
+  {
+    DCMNET_ERROR(DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  /* sets this application's title and the called application's title in the params */
+  /* structure. The default values are "ANY-SCU" and "ANY-SCP". */
+  ASC_setAPTitles(m_params, m_ourAETitle.c_str(), m_peerAETitle.c_str(), NULL);
+
+  /* Figure out the presentation addresses and copy the */
+  /* corresponding values into the association parameters.*/
+  DIC_NODENAME localHost;
+  DIC_NODENAME peerHost;
+  gethostname(localHost, sizeof(localHost) - 1);
+  /* Since the underlying dcmnet structures reserve only 64 bytes for peer
+     as well as local host name, we check here for buffer overflow.
+   */
+  if ((m_peer.length() + 5 /* max 65535 */) + 1 /* for ":" */ > 63)
+  {
+    DCMNET_ERROR("Maximum length of peer host name '" << m_peer << "' is longer than maximum of 57 characters");
+    return EC_IllegalCall; // TODO: need to find better error code
+  }
+  if (strlen(localHost) + 1 > 63)
+  {
+    DCMNET_ERROR("Maximum length of local host name '" << localHost << "' is longer than maximum of 62 characters");
+    return EC_IllegalCall; // TODO: need to find better error code
+  }
+  sprintf(peerHost, "%s:%d", m_peer.c_str(), OFstatic_cast(int, m_peerPort));
+  ASC_setPresentationAddresses(m_params, localHost, peerHost);
+
+  /* Add presentation contexts */
+
+  // First, import from config file, if specified
+  OFCondition result;
+  if (!m_assocConfigFilename.empty())
+  {
+    DcmAssociationConfiguration assocConfig;
+    result = DcmAssociationConfigurationFile::initialize(assocConfig, m_assocConfigFilename.c_str());
+    if (result.bad())
+    {
+      DCMNET_WARN("Unable to parse association configuration file " << m_assocConfigFilename
+        << " (ignored): " << result.text());
+      return result;
+    }
+    else
+    {
+      /* perform name mangling for config file key */
+      OFString profileName;
+      const unsigned char *c = OFreinterpret_cast(const unsigned char *, m_assocConfigProfile.c_str());
+      while (*c)
+      {
+        if (! isspace(*c)) profileName += OFstatic_cast(char, toupper(*c));
+        ++c;
+      }
+
+      result = assocConfig.setAssociationParameters(profileName.c_str(), *m_params);
+      if (result.bad())
+      {
+        DCMNET_WARN("Unable to apply association configuration file " << m_assocConfigFilename
+          <<" (ignored): " << result.text());
+        return result;
+      }
+    }
+  }
+
+  // Adapt presentation context ID to existing presentation contexts.
+  // It's important that presentation context IDs are numerated 1,3,5,7...!
+  Uint32 nextFreePresID = 257;
+  Uint32 numContexts = ASC_countPresentationContexts(m_params);
+  if (numContexts <= 127)
+  {
+    // Need Uint16 to avoid overflow in currPresID (unsigned char)
+    nextFreePresID = 2 * numContexts + 1; /* add 1 to point to the next free ID*/
+  }
+  // Print warning if number of overall presentation contexts exceeds 128
+  if ((numContexts + m_presContexts.size()) > 128)
+  {
+    DCMNET_WARN("Number of presentation contexts exceeds 128 (" << numContexts + m_presContexts.size()
+      << "). Some contexts will not be negotiated");
+  }
+  else
+  {
+    DCMNET_TRACE("Configured " << numContexts << " presentation contexts from config file");
+    if (m_presContexts.size() > 0)
+        DCMNET_TRACE("Adding another " << m_presContexts.size() << " presentation contexts configured for SCU");
+  }
+
+  // Add presentation contexts not originating from config file
+  OFListIterator(DcmSCUPresContext) contIt = m_presContexts.begin();
+  OFListConstIterator(DcmSCUPresContext) endOfContList = m_presContexts.end();
+  while ((contIt != endOfContList) && (nextFreePresID <= 255))
+  {
+    const Uint16 numTransferSyntaxes = OFstatic_cast(Uint16, (*contIt).transferSyntaxes.size());
+    const char** transferSyntaxes = new const char*[numTransferSyntaxes];
+
+    // Iterate over transfer syntaxes within one presentation context
+    OFListIterator(OFString) syntaxIt = (*contIt).transferSyntaxes.begin();
+    OFListIterator(OFString) endOfSyntaxList = (*contIt).transferSyntaxes.end();
+    Uint16 sNum = 0;
+    // copy all transfer syntaxes to array
+    while (syntaxIt != endOfSyntaxList)
+    {
+      transferSyntaxes[sNum] = (*syntaxIt).c_str();
+      ++syntaxIt;
+      ++sNum;
+    }
+
+    // add the presentation context
+    cond = ASC_addPresentationContext(m_params, OFstatic_cast(Uint8, nextFreePresID),
+      (*contIt).abstractSyntaxName.c_str(), transferSyntaxes, numTransferSyntaxes,(*contIt).roleSelect);
+    // if adding was successfull, prepare presentation context ID for next addition
+    delete[] transferSyntaxes;
+    transferSyntaxes = NULL;
+    if (cond.bad())
+      return cond;
+    contIt++;
+    // goto next free number, only odd presentation context IDs permitted
+    nextFreePresID += 2;
+  }
+
+  numContexts = ASC_countPresentationContexts(m_params);
+  if (numContexts == 0)
+  {
+    DCMNET_ERROR("Cannot initialize network: No presentation contexts defined");
+    return NET_EC_NoPresentationContextsDefined;
+  }
+  DCMNET_DEBUG("Configured a total of " << numContexts << " presentation contexts for SCU");
+
+  return cond;
+}
+
+
+OFCondition DcmSCU::negotiateAssociation()
+{
+  /* Return error if SCU is already connected */
+  if (isConnected())
+    return NET_EC_AlreadyConnected;
+
+  /* dump presentation contexts if required */
+  OFString tempStr;
+  if (m_verbosePCMode)
+    DCMNET_INFO("Request Parameters:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_RQ));
+  else
+    DCMNET_DEBUG("Request Parameters:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_RQ));
+
+  /* create association, i.e. try to establish a network connection to another */
+  /* DICOM application. This call creates an instance of T_ASC_Association*. */
+  DCMNET_INFO("Requesting Association");
+  OFCondition cond = ASC_requestAssociation(m_net, m_params, &m_assoc);
+  if (cond.bad())
+  {
+    if (cond == DUL_ASSOCIATIONREJECTED)
+    {
+      T_ASC_RejectParameters rej;
+      ASC_getRejectParameters(m_params, &rej);
+      DCMNET_DEBUG("Association Rejected:" << OFendl << ASC_printRejectParameters(tempStr, &rej));
+      return cond;
+    }
+    else
+    {
+      DCMNET_DEBUG("Association Request Failed: " << DimseCondition::dump(tempStr, cond));
+      return cond;
+    }
+  }
+
+  /* dump the presentation contexts which have been accepted/refused */
+  if (m_verbosePCMode)
+    DCMNET_INFO("Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_AC));
+  else
+    DCMNET_DEBUG("Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_AC));
+
+  /* count the presentation contexts which have been accepted by the SCP */
+  /* If there are none, finish the execution */
+  if (ASC_countAcceptedPresentationContexts(m_params) == 0)
+  {
+    DCMNET_ERROR("No Acceptable Presentation Contexts");
+    return NET_EC_NoAcceptablePresentationContexts;
+  }
+
+  /* dump general information concerning the establishment of the network connection if required */
+  DCMNET_INFO("Association Accepted (Max Send PDV: " << OFstatic_cast(unsigned long, m_assoc->sendPDVLength) << ")");
+  return EC_Normal;
+}
+
+
+OFCondition DcmSCU::addPresentationContext(const OFString &abstractSyntax,
+                                           const OFList<OFString> &xferSyntaxes,
+                                           const T_ASC_SC_ROLE role)
+
+{
+
+  DcmSCUPresContext presContext;
+  presContext.abstractSyntaxName = abstractSyntax;
+  OFListConstIterator(OFString) it = xferSyntaxes.begin();
+  OFListConstIterator(OFString) endOfList = xferSyntaxes.end();
+  while (it != endOfList)
+  {
+    presContext.transferSyntaxes.push_back(*it);
+    it++;
+  }
+  presContext.roleSelect = role;
+  m_presContexts.push_back(presContext);
+  return EC_Normal;
+}
+
+
+OFCondition DcmSCU::useSecureConnection(DcmTransportLayer *tlayer)
+{
+  OFCondition cond = ASC_setTransportLayer(m_net, tlayer, OFFalse /* do not take over ownership */);
+  if (cond.good())
+    cond = ASC_setTransportLayerType(m_params, OFTrue /* use TLS */);
+  return cond;
+}
+
+
+void DcmSCU::clearPresentationContexts()
+{
+  m_presContexts.clear();
+  m_assocConfigFilename.clear();
+  m_assocConfigProfile.clear();
+}
+
+
+// Returns usable presentation context ID for a given abstract syntax UID and
+// transfer syntax UID. 0 if none matches.
+T_ASC_PresentationContextID DcmSCU::findPresentationContextID(const OFString &abstractSyntax,
+                                                              const OFString &transferSyntax)
+{
+  if (!isConnected())
+    return 0;
+
+  DUL_PRESENTATIONCONTEXT *pc;
+  LST_HEAD **l;
+  OFBool found = OFFalse;
+
+  if (abstractSyntax.empty()) return 0;
+
+  /* first of all we look for a presentation context
+   * matching both abstract and transfer syntax
+   */
+  l = &m_assoc->params->DULparams.acceptedPresentationContext;
+  pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
+  (void)LST_Position(l, (LST_NODE*)pc);
+  while (pc && !found)
+  {
+    found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0);
+    found &= (pc->result == ASC_P_ACCEPTANCE);
+    if (!transferSyntax.empty())  // ignore transfer syntax if not specified
+      found &= (strcmp(pc->acceptedTransferSyntax, transferSyntax.c_str()) == 0);
+    if (!found) pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
+  }
+  if (found)
+    return pc->presentationContextID;
+
+  return 0;   /* not found */
+}
+
+// Returns the presentation context ID that best matches the given abstract syntax UID and
+// transfer syntax UID.
+T_ASC_PresentationContextID DcmSCU::findAnyPresentationContextID(const OFString &abstractSyntax,
+                                                                 const OFString &transferSyntax)
+{
+  if (m_assoc == NULL)
+    return 0;
+
+  DUL_PRESENTATIONCONTEXT *pc;
+  LST_HEAD **l;
+  OFBool found = OFFalse;
+
+  if (abstractSyntax.empty()) return 0;
+
+  /* first of all we look for a presentation context
+   * matching both abstract and transfer syntax
+   */
+  l = &m_assoc->params->DULparams.acceptedPresentationContext;
+  pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
+  (void)LST_Position(l, (LST_NODE*)pc);
+  while (pc && !found)
+  {
+    found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0);
+    found &= (pc->result == ASC_P_ACCEPTANCE);
+    if (!transferSyntax.empty())  // ignore transfer syntax if not specified
+      found &= (strcmp(pc->acceptedTransferSyntax, transferSyntax.c_str()) == 0);
+    if (!found) pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
+  }
+  if (found) return pc->presentationContextID;
+
+  /* now we look for an explicit VR uncompressed PC. */
+  l = &m_assoc->params->DULparams.acceptedPresentationContext;
+  pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
+  (void)LST_Position(l, (LST_NODE*)pc);
+  while (pc && !found)
+  {
+    found =  (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0)
+          && (pc->result == ASC_P_ACCEPTANCE)
+          && ((strcmp(pc->acceptedTransferSyntax, UID_LittleEndianExplicitTransferSyntax) == 0)
+          || (strcmp(pc->acceptedTransferSyntax, UID_BigEndianExplicitTransferSyntax) == 0));
+    if (!found) pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
+  }
+  if (found) return pc->presentationContextID;
+
+  /* now we look for an implicit VR uncompressed PC. */
+  l = &m_assoc->params->DULparams.acceptedPresentationContext;
+  pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
+  (void)LST_Position(l, (LST_NODE*)pc);
+  while (pc && !found)
+  {
+    found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0)
+          && (pc->result == ASC_P_ACCEPTANCE)
+          && (strcmp(pc->acceptedTransferSyntax, UID_LittleEndianImplicitTransferSyntax) == 0);
+    if (!found) pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
+  }
+  if (found) return pc->presentationContextID;
+
+  /* finally we accept everything we get.
+     returns 0 if abstract syntax is not supported
+  */
+  return ASC_findAcceptedPresentationContextID(m_assoc, abstractSyntax.c_str());
+}
+
+void DcmSCU::findPresentationContext(const T_ASC_PresentationContextID presID,
+                                     OFString &abstractSyntax,
+                                     OFString &transferSyntax)
+{
+  transferSyntax.clear();
+  abstractSyntax.clear();
+  if (m_assoc == NULL)
+    return;
+
+  DUL_PRESENTATIONCONTEXT *pc;
+  LST_HEAD **l;
+
+  /* we look for a presentation context matching
+   * both abstract and transfer syntax
+   */
+  l = &m_assoc->params->DULparams.acceptedPresentationContext;
+  pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
+  (void)LST_Position(l, (LST_NODE*)pc);
+  while (pc)
+  {
+    if (presID == pc->presentationContextID)
+    {
+      if (pc->result == ASC_P_ACCEPTANCE)
+      {
+        // found a match
+        transferSyntax = pc->acceptedTransferSyntax;
+        abstractSyntax = pc->abstractSyntax;
+      }
+      break;
+    }
+    pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
+  }
+}
+
+
+Uint16 DcmSCU::nextMessageID()
+{
+  if (!isConnected())
+    return 0;
+  else
+    return m_assoc->nextMsgID++;
+}
+
+
+void DcmSCU::closeAssociation(const DcmCloseAssociationType closeType)
+{
+  if (!isConnected())
+  {
+    DCMNET_WARN("Closing of association request but no association active (ignored)");
+    return;
+  }
+
+  OFCondition cond;
+  OFString tempStr;
+
+  /* tear down association, i.e. terminate network connection to SCP */
+  switch (closeType)
+  {
+    case DCMSCU_RELEASE_ASSOCIATION:
+      /* release association */
+      DCMNET_INFO("Releasing Association");
+      cond = ASC_releaseAssociation(m_assoc);
+      if (cond.bad())
+      {
+        DCMNET_ERROR("Association Release Failed: " << DimseCondition::dump(tempStr, cond));
+        return; // TODO: do we really need this?
+      }
+      break;
+    case DCMSCU_ABORT_ASSOCIATION:
+      /* abort association */
+      DCMNET_INFO("Aborting Association");
+      cond = ASC_abortAssociation(m_assoc);
+      if (cond.bad())
+      {
+        DCMNET_ERROR("Association Abort Failed: " << DimseCondition::dump(tempStr, cond));
+      }
+      break;
+    case DCMSCU_PEER_REQUESTED_RELEASE:
+      /* peer requested release */
+      DCMNET_ERROR("Protocol Error: Peer requested release (Aborting)");
+      DCMNET_INFO("Aborting Association");
+      cond = ASC_abortAssociation(m_assoc);
+      if (cond.bad())
+      {
+        DCMNET_ERROR("Association Abort Failed: " << DimseCondition::dump(tempStr, cond));
+      }
+      break;
+    case DCMSCU_PEER_ABORTED_ASSOCIATION:
+      /* peer aborted association */
+      DCMNET_INFO("Peer Aborted Association");
+      break;
+  }
+
+  // destroy and free memory of internal association and network structures
+  freeNetwork();
+}
+
+
+/* ************************************************************************* */
+/*                            C-ECHO functionality                           */
+/* ************************************************************************* */
+
+// Sends C-ECHO request to another DICOM application
+OFCondition DcmSCU::sendECHORequest(const T_ASC_PresentationContextID presID)
+{
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+
+  OFCondition cond;
+  T_ASC_PresentationContextID pcid = presID;
+
+  /* If necessary, find appropriate presentation context */
+  if (pcid == 0)
+    pcid = findPresentationContextID(UID_VerificationSOPClass, UID_LittleEndianExplicitTransferSyntax);
+  if (pcid == 0)
+    pcid = findPresentationContextID(UID_VerificationSOPClass, UID_BigEndianExplicitTransferSyntax);
+  if (pcid == 0)
+    pcid = findPresentationContextID(UID_VerificationSOPClass, UID_LittleEndianImplicitTransferSyntax);
+  if (pcid == 0)
+  {
+    DCMNET_ERROR("No presentation context found for sending C-ECHO with SOP Class / Transfer Syntax: "
+      << dcmFindNameOfUID(UID_VerificationSOPClass, "") << " / "
+      << DcmXfer(UID_LittleEndianImplicitTransferSyntax).getXferName());
+    return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+  }
+
+  /* Now, assemble DIMSE message */
+  T_DIMSE_Message msg;
+  T_DIMSE_C_EchoRQ* req = &(msg.msg.CEchoRQ);
+  // Set type of message
+  msg.CommandField = DIMSE_C_ECHO_RQ;
+  // Set message ID
+  req->MessageID = nextMessageID();
+  // Announce no dataset
+  req->DataSetType = DIMSE_DATASET_NULL;
+  // Set affected SOP Class UID (always Verification SOP Class)
+  OFStandard::strlcpy(req->AffectedSOPClassUID, UID_VerificationSOPClass, sizeof(req->AffectedSOPClassUID));
+
+  /* Send request */
+  OFString tempStr;
+  DCMNET_INFO("Sending C-ECHO Request (MsgID " << req->MessageID << ")");
+
+  cond = sendDIMSEMessage(pcid, &msg, NULL /*dataObject*/);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending C-ECHO request: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  /* Receive response */
+  T_DIMSE_Message rsp;
+  DcmDataset* statusDetail = NULL;
+  cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+  /* Check whether we received C-ECHO response, otherwise print error */
+  if (rsp.CommandField == DIMSE_C_ECHO_RSP)
+  {
+    DCMNET_INFO("Received C-ECHO Response (" << rsp.msg.CEchoRSP.DimseStatus << ")");
+
+  } else {
+    DCMNET_ERROR("Expected C-ECHO response but received DIMSE command 0x"
+      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+      << OFstatic_cast(unsigned int, rsp.CommandField));
+    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
+    delete statusDetail;
+    return DIMSE_BADCOMMANDTYPE;
+  }
+  /* Print status detail if it was received */
+  if (statusDetail != NULL)
+  {
+    DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
+    delete statusDetail;
+  }
+  return EC_Normal;
+}
+
+/* ************************************************************************* */
+/*                            C-STORE functionality                          */
+/* ************************************************************************* */
+
+// Sends C-STORE request to another DICOM application
+OFCondition DcmSCU::sendSTORERequest(const T_ASC_PresentationContextID presID,
+                                     const OFString &dicomFile,
+                                     DcmDataset *dataset,
+                                     Uint16 &rspStatusCode)
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+
+  OFCondition cond;
+  OFString tempStr;
+  T_ASC_PresentationContextID pcid = presID;
+  DcmDataset* statusDetail = NULL;
+  T_DIMSE_Message msg;
+  T_DIMSE_C_StoreRQ* req = &(msg.msg.CStoreRQ);
+
+  // Set type of message
+  msg.CommandField = DIMSE_C_STORE_RQ;
+  /* Set message ID */
+  req->MessageID = nextMessageID();
+  /* Load file if necessary */
+  DcmFileFormat *fileformat = NULL;
+  if (!dicomFile.empty())
+  {
+    fileformat = new DcmFileFormat();
+    if (fileformat == NULL)
+      return EC_MemoryExhausted;
+    cond = fileformat->loadFile(dicomFile.c_str());
+    if (cond.bad())
+    {
+      delete fileformat;
+      return cond;
+    }
+    dataset = fileformat->getDataset();
+  }
+
+  /* Fill message according to dataset to be sent */
+  OFString sopClassUID;
+  OFString sopInstanceUID;
+  E_TransferSyntax xferSyntax = EXS_Unknown;
+  cond = getDatasetInfo(dataset, sopClassUID, sopInstanceUID, xferSyntax);
+  DcmXfer xfer(xferSyntax);
+  /* Check whether the information is sufficient */
+  if (sopClassUID.empty() || sopInstanceUID.empty() || ((pcid == 0) && (xferSyntax == EXS_Unknown)))
+  {
+    DCMNET_ERROR("Cannot send SOP instance, missing information:");
+    if (!dicomFile.empty())
+      DCMNET_ERROR("  DICOM Filename   : " << dicomFile);
+    DCMNET_ERROR("  SOP Class UID    : " << sopClassUID);
+    DCMNET_ERROR("  SOP Instance UID : " << sopInstanceUID);
+    DCMNET_ERROR("  Transfer Syntax  : " << xfer.getXferName());
+    if (pcid == 0)
+      DCMNET_ERROR("  Pres. Context ID : 0 (find via SOP Class and Transfer Syntax)");
+    else
+      DCMNET_ERROR("  Pres. Context ID : " << OFstatic_cast(unsigned int, pcid));
+    delete fileformat;
+    return cond;
+  }
+  OFStandard::strlcpy(req->AffectedSOPClassUID, sopClassUID.c_str(), sizeof(req->AffectedSOPClassUID));
+  OFStandard::strlcpy(req->AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(req->AffectedSOPInstanceUID));
+  req->DataSetType = DIMSE_DATASET_PRESENT;
+  req->Priority = DIMSE_PRIORITY_LOW;
+
+  /* If no presentation context is specified by the caller ... */
+  if (pcid == 0)
+  {
+      /* ... try to find an appropriate presentation context automatically */
+    pcid = findPresentationContextID(sopClassUID, xfer.getXferID());
+  }
+  else if (m_datasetConversionMode)
+  {
+    /* Convert dataset to network transfer syntax (if required) */
+    OFString abstractSyntax, transferSyntax;
+    findPresentationContext(pcid, abstractSyntax, transferSyntax);
+    /* Check whether given presentation context was accepted by the peer */
+    if (abstractSyntax.empty() || transferSyntax.empty())
+    {
+      /* Mark presentation context as invalid */
+      pcid = 0;
+    } else {
+      if (abstractSyntax != sopClassUID)
+      {
+        DCMNET_WARN("Inappropriate presentation context with ID " << OFstatic_cast(unsigned int, pcid)
+          << ": abstract syntax does not match SOP class UID");
+      }
+      /* Try to convert to the negotiated transfer syntax */
+      DcmXfer netXfer = DcmXfer(transferSyntax.c_str()).getXfer();
+      if (netXfer.getXfer() != xferSyntax)
+      {
+        DCMNET_INFO("Converting transfer syntax: " << xfer.getXferName() << " -> "
+          << netXfer.getXferName());
+        dataset->chooseRepresentation(netXfer.getXfer(), NULL);
+      }
+    }
+  }
+  /* No appropriate presentation context for sending */
+  if (pcid == 0)
+  {
+    OFString sopClassName = dcmFindNameOfUID(sopClassUID.c_str(), sopClassUID.c_str());
+    OFString xferName = xfer.getXferName();
+    DCMNET_ERROR("No presentation context found for sending C-STORE with SOP Class / Transfer Syntax: "
+      << sopClassName << " / " << xferName);
+    return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+  }
+
+  /* Send request */
+  DCMNET_INFO("Sending C-STORE Request (MsgID " << req->MessageID << ", "
+    << dcmSOPClassUIDToModality(sopClassUID.c_str(), "OT") << ")");
+
+  cond = sendDIMSEMessage(pcid, &msg, dataset);
+  delete fileformat;
+  fileformat = NULL;
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending C-STORE request: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  /* Receive response */
+  T_DIMSE_Message rsp;
+  cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  if (rsp.CommandField == DIMSE_C_STORE_RSP)
+  {
+    DCMNET_INFO("Received C-STORE Response (" << DU_cstoreStatusString(rsp.msg.CStoreRSP.DimseStatus) << ")");
+  } else {
+    DCMNET_ERROR("Expected C-STORE response but received DIMSE command 0x"
+      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+      << OFstatic_cast(unsigned int, rsp.CommandField));
+    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
+    delete statusDetail;
+    return DIMSE_BADCOMMANDTYPE;
+  }
+  T_DIMSE_C_StoreRSP storeRsp = rsp.msg.CStoreRSP;
+  rspStatusCode = storeRsp.DimseStatus;
+  if (statusDetail != NULL)
+  {
+    DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
+    delete statusDetail;
+  }
+
+  return cond;
+}
+
+
+/* ************************************************************************* */
+/*                            C-MOVE functionality                           */
+/* ************************************************************************* */
+
+// Sends a C-MOVE Request on given presentation context
+OFCondition DcmSCU::sendMOVERequest(const T_ASC_PresentationContextID presID,
+                                    const OFString &moveDestinationAETitle,
+                                    DcmDataset *dataset,
+                                    OFList<RetrieveResponse*> *responses)
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (dataset == NULL)
+    return DIMSE_NULLKEY;
+
+  /* Prepare DIMSE data structures for issuing request */
+  OFCondition cond;
+  OFString tempStr;
+  T_ASC_PresentationContextID pcid = presID;
+  T_DIMSE_Message msg;
+  DcmDataset* statusDetail = NULL;
+  T_DIMSE_C_MoveRQ* req = &(msg.msg.CMoveRQ);
+  // Set type of message
+  msg.CommandField = DIMSE_C_MOVE_RQ;
+  // Set message ID
+  req->MessageID = nextMessageID();
+  // Announce dataset
+  req->DataSetType = DIMSE_DATASET_PRESENT;
+  // Set target for embedded C-Store's
+  OFStandard::strlcpy(req->MoveDestination, moveDestinationAETitle.c_str(), sizeof(req->MoveDestination));
+  // Set priority (mandatory)
+  req->Priority = DIMSE_PRIORITY_LOW;
+
+  /* Determine SOP Class from presentation context */
+  OFString abstractSyntax, transferSyntax;
+  findPresentationContext(pcid, abstractSyntax, transferSyntax);
+  if (abstractSyntax.empty() || transferSyntax.empty())
+    return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+  OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
+
+  /* Send request */
+  DCMNET_INFO("Sending C-MOVE Request (MsgID " << req->MessageID << ")");
+
+  cond = sendDIMSEMessage(pcid, &msg, dataset);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending C-MOVE request: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  /* Receive and handle C-MOVE response messages */
+  OFBool waitForNextResponse = OFTrue;
+  while (waitForNextResponse)
+  {
+    T_DIMSE_Message rsp;
+    statusDetail = NULL;
+
+    // Receive command set
+    cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
+    if (cond.bad())
+    {
+      DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
+      delete statusDetail;
+      break;
+    }
+
+    if (rsp.CommandField == DIMSE_C_MOVE_RSP)
+    {
+      DCMNET_INFO("Received C-MOVE Response (" << DU_cmoveStatusString(rsp.msg.CMoveRSP.DimseStatus) << ")");
+
+    } else {
+      DCMNET_ERROR("Expected C-MOVE response but received DIMSE command 0x"
+        << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+        << OFstatic_cast(unsigned int, rsp.CommandField));
+      DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
+      delete statusDetail;
+      cond = DIMSE_BADCOMMANDTYPE;
+      break;
+    }
+
+    // Prepare response package for response handler
+    RetrieveResponse *moveRSP = new RetrieveResponse();
+    moveRSP->m_affectedSOPClassUID = rsp.msg.CMoveRSP.AffectedSOPClassUID;
+    moveRSP->m_messageIDRespondedTo = rsp.msg.CMoveRSP.MessageIDBeingRespondedTo;
+    moveRSP->m_status = rsp.msg.CMoveRSP.DimseStatus;
+    moveRSP->m_numberOfRemainingSubops = rsp.msg.CMoveRSP.NumberOfRemainingSubOperations;
+    moveRSP->m_numberOfCompletedSubops = rsp.msg.CMoveRSP.NumberOfCompletedSubOperations;
+    moveRSP->m_numberOfFailedSubops = rsp.msg.CMoveRSP.NumberOfFailedSubOperations;
+    moveRSP->m_numberOfWarningSubops = rsp.msg.CMoveRSP.NumberOfWarningSubOperations;
+    moveRSP->m_statusDetail = statusDetail;
+    //DCMNET_DEBUG("C-MOVE response has status 0x"
+    //  << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+    //  << moveRSP->m_status);
+    if (statusDetail != NULL)
+    {
+      DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
+    }
+
+    // Receive dataset if there is one (status PENDING)
+    DcmDataset *rspDataset = NULL;
+    // Check if dataset is announced correctly
+    if (rsp.msg.CMoveRSP.DataSetType != DIMSE_DATASET_NULL) // Some of the sub operations have failed, thus a dataset with a list of them is attached
+    {
+      // Receive dataset
+      cond = receiveDIMSEDataset(&pcid, &rspDataset);
+      if (cond.bad())
+      {
+        DCMNET_ERROR("Unable to receive C-MOVE dataset on presentation context "
+          << OFstatic_cast(unsigned int, pcid) << ": " << DimseCondition::dump(tempStr, cond));
+        delete moveRSP; // includes statusDetail
+        break;
+      }
+      moveRSP->m_dataset = rspDataset;
+    }
+
+    // Handle C-MOVE response (has to handle all possible status flags)
+    cond = handleMOVEResponse(pcid, moveRSP, waitForNextResponse);
+    if (cond.bad())
+    {
+      DCMNET_WARN("Unable to handle C-MOVE response correctly: " << cond.text() << " (ignored)");
+      delete moveRSP; // includes statusDetail
+      // don't return here but trust the "waitForNextResponse" variable
+    }
+    // if response could be handled successfully, add it to response list
+    else
+    {
+      if (responses != NULL) // only add if desired by caller
+        responses->push_back(moveRSP);
+      else
+        delete moveRSP; // includes statusDetail
+    }
+  }
+  /* All responses received or break signal occured */
+  return cond;
+}
+
+
+// Standard handler for C-MOVE message responses
+OFCondition DcmSCU::handleMOVEResponse( const T_ASC_PresentationContextID /* presID */,
+                                        RetrieveResponse *response,
+                                        OFBool &waitForNextResponse )
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (response == NULL)
+    return DIMSE_NULLKEY;
+
+  DCMNET_DEBUG("Handling C-MOVE Response");
+  switch (response->m_status) {
+  case STATUS_MOVE_Failed_IdentifierDoesNotMatchSOPClass:
+    waitForNextResponse = OFFalse;
+    DCMNET_ERROR("Identifier does not match SOP class in C-MOVE response");
+    break;
+  case STATUS_MOVE_Failed_MoveDestinationUnknown:
+    waitForNextResponse = OFFalse;
+    DCMNET_ERROR("Move destination unknown");
+    break;
+  case STATUS_MOVE_Failed_UnableToProcess:
+    waitForNextResponse = OFFalse;
+    DCMNET_ERROR("Unable to process C-Move response");
+    break;
+  case STATUS_MOVE_Cancel_SubOperationsTerminatedDueToCancelIndication:
+    waitForNextResponse = OFFalse;
+    DCMNET_DEBUG("Suboperations canceled by server due to CANCEL indication");
+    break;
+  case STATUS_MOVE_Warning_SubOperationsCompleteOneOrMoreFailures:
+    waitForNextResponse = OFFalse;
+    DCMNET_WARN("Suboperations of C-MOVE completed with one or more failures");
+    break;
+  case STATUS_Pending:
+    /* in this case the current C-MOVE-RSP indicates that */
+    /* there will be some more results */
+    waitForNextResponse = OFTrue;
+    DCMNET_DEBUG("One or more pending C-MOVE responses");
+    break;
+  case STATUS_Success:
+    /* in this case, we received the last C-MOVE-RSP so there */
+    /* will be no other responses we have to wait for. */
+    waitForNextResponse = OFFalse;
+    DCMNET_DEBUG("Received final C-MOVE response, no more C-MOVE responses expected");
+    break;
+  default:
+    /* in all other cases, don't expect further responses to come */
+    waitForNextResponse = OFFalse;
+    DCMNET_WARN("Status is 0x"
+      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+      << response->m_status << " (unknown)");
+    DCMNET_WARN("Will not wait for further C-MOVE responses");
+    break;
+  } //switch
+
+  return EC_Normal;
+}
+
+
+/* ************************************************************************* */
+/*               C-GET and acommpanying C-STORE functionality                */
+/* ************************************************************************* */
+
+// Sends a C-GET Request on given presentation context
+OFCondition DcmSCU::sendCGETRequest(const T_ASC_PresentationContextID presID,
+                                    DcmDataset *dataset,
+                                    OFList<RetrieveResponse*> *responses)
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (dataset == NULL)
+    return DIMSE_NULLKEY;
+
+  /* Prepare DIMSE data structures for issuing request */
+  OFCondition cond;
+  OFString tempStr;
+  T_ASC_PresentationContextID pcid = presID;
+  T_DIMSE_Message msg;
+  T_DIMSE_C_GetRQ* req = &(msg.msg.CGetRQ);
+  // Set type of message
+  msg.CommandField = DIMSE_C_GET_RQ;
+  // Set message ID
+  req->MessageID = nextMessageID();
+  // Announce dataset
+  req->DataSetType = DIMSE_DATASET_PRESENT;
+  // Specify priority
+  req->Priority = DIMSE_PRIORITY_LOW;
+
+  // Determine SOP Class from presentation context
+  OFString abstractSyntax, transferSyntax;
+  findPresentationContext(pcid, abstractSyntax, transferSyntax);
+  if (abstractSyntax.empty() || transferSyntax.empty())
+    return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+  OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
+
+  /* Send request */
+  DCMNET_INFO("Sending C-GET Request (MsgID " << req->MessageID << ")");
+
+  cond = sendDIMSEMessage(pcid, &msg, dataset);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending C-GET request: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  cond = handleCGETSession(pcid, dataset, responses);
+  return cond;
+}
+
+
+// Does the logic for switching between C-GET Response and C-STORE Requests
+OFCondition DcmSCU::handleCGETSession(const T_ASC_PresentationContextID /* presID */,
+                                      DcmDataset * /* dataset */,
+                                      OFList<RetrieveResponse*> *responses)
+{
+  OFCondition result;
+  OFBool continueSession = OFTrue;
+  OFString tempStr;
+
+  // As long we want to continue (usually, as long as we receive more objects,
+  // i.e. the final C-GET reponse has not arrived yet)
+  while (continueSession)
+  {
+    T_DIMSE_Message rsp;
+    DcmDataset *statusDetail = NULL;
+    T_ASC_PresentationContextID pcid = 0;
+
+    // Receive command set
+    result = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
+    if (result.bad())
+    {
+      DCMNET_ERROR("Failed receiving DIMSE command: " << DimseCondition::dump(tempStr, result));
+      delete statusDetail;
+      break;
+    }
+    // Handle C-GET Response
+    if (rsp.CommandField == DIMSE_C_GET_RSP)
+    {
+      DCMNET_INFO("Received C-GET Response (" << DU_cgetStatusString(rsp.msg.CGetRSP.DimseStatus) << ")");
+
+      // Prepare response package for response handler
+      RetrieveResponse *getRSP = new RetrieveResponse();
+      getRSP->m_affectedSOPClassUID = rsp.msg.CGetRSP.AffectedSOPClassUID;
+      getRSP->m_messageIDRespondedTo = rsp.msg.CGetRSP.MessageIDBeingRespondedTo;
+      getRSP->m_status = rsp.msg.CGetRSP.DimseStatus;
+      getRSP->m_numberOfRemainingSubops = rsp.msg.CGetRSP.NumberOfRemainingSubOperations;
+      getRSP->m_numberOfCompletedSubops = rsp.msg.CGetRSP.NumberOfCompletedSubOperations;
+      getRSP->m_numberOfFailedSubops = rsp.msg.CGetRSP.NumberOfFailedSubOperations;
+      getRSP->m_numberOfWarningSubops = rsp.msg.CGetRSP.NumberOfWarningSubOperations;
+      getRSP->m_statusDetail = statusDetail;
+      if (statusDetail != NULL)
+      {
+        DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
+        statusDetail = NULL; // forget reference to status detail, will be deleted with getRSP
+      }
+      result = handleCGETResponse(pcid, getRSP, continueSession);
+      if (result.bad())
+      {
+        DCMNET_WARN("Unable to handle C-GET response correctly: " << result.text() << " (ignored)");
+        delete getRSP; // includes statusDetail
+        // don't return here but trust the "continueSession" variable
+      }
+      // if response could be handled successfully, add it to response list
+      else {
+        if (responses != NULL) // only add if desired by caller
+          responses->push_back(getRSP);
+        else
+          delete getRSP; // includes statusDetail
+      }
+    }
+
+    // Handle C-STORE Request
+    else if (rsp.CommandField == DIMSE_C_STORE_RQ)
+    {
+      DCMNET_INFO("Received C-STORE Request (MsgID " << rsp.msg.CStoreRQ.MessageID << ")");
+
+      // Receive dataset if there is one (status PENDING)
+      DcmDataset *rspDataset = NULL;
+      // Check if dataset is announced correctly
+      if (rsp.msg.CStoreRQ.DataSetType == DIMSE_DATASET_NULL)
+      {
+        DCMNET_WARN("Incoming C-STORE with no dataset, trying to receive one anyway");
+      }
+      Uint16 desiredCStoreReturnStatus = 0;
+      // handle normal storage mode, i.e. receive in memory and store to disk
+      if (m_storageMode == DCMSCU_STORAGE_DISK)
+      {
+        // Receive dataset
+        result = receiveDIMSEDataset(&pcid, &rspDataset);
+        if (result.bad())
+        {
+          result = DIMSE_NULLKEY;
+          desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand;
+        } else {
+          result = handleSTORERequest(pcid, rspDataset, continueSession, desiredCStoreReturnStatus);
+        }
+      }
+      // handle bit preserving storage mode, i.e. receive directly to disk
+      else if (m_storageMode == DCMSCU_STORAGE_BIT_PRESERVING)
+      {
+        OFString storageFilename;
+        OFStandard::combineDirAndFilename(storageFilename, m_storageDir, rsp.msg.CStoreRQ.AffectedSOPInstanceUID, OFTrue);
+        result = handleSTORERequestFile(&pcid, storageFilename, &(rsp.msg.CStoreRQ));
+        if (result.good())
+        {
+          notifyInstanceStored(storageFilename, rsp.msg.CStoreRQ.AffectedSOPClassUID, rsp.msg.CStoreRQ.AffectedSOPInstanceUID);
+        }
+      }
+      // handle ignore storage mode, i.e. ignore received dataset and do not store at all
+      else
+      {
+        result = ignoreSTORERequest(pcid, rsp.msg.CStoreRQ);
+      }
+
+      // Evaluate result from C-STORE request handling and send response
+      if (result.bad())
+      {
+        desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand;
+        continueSession = OFFalse;
+      }
+      result = sendSTOREResponse(pcid, desiredCStoreReturnStatus, rsp.msg.CStoreRQ);
+      if (result.bad())
+      {
+        continueSession = OFFalse;
+      }
+      delete rspDataset; // should be NULL if not existing
+    }
+
+    // Handle other DIMSE command (error since other command than GET/STORE not expected)
+    else
+    {
+      DCMNET_ERROR("Expected C-GET response or C-STORE request but received DIMSE command 0x"
+        << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+        << OFstatic_cast(unsigned int, rsp.CommandField));
+      DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
+      result = DIMSE_BADCOMMANDTYPE;
+      continueSession = OFFalse;
+    }
+
+    delete statusDetail; // should be NULL if not existing or added to response list
+    statusDetail = NULL;
+  }
+  /* All responses received or break signal occured */
+
+  return result;
+
+}
+
+
+// Handles single C-GET Response
+OFCondition DcmSCU::handleCGETResponse(const T_ASC_PresentationContextID /* presID */,
+                                       RetrieveResponse* response,
+                                       OFBool& continueCGETSession)
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (response == NULL)
+    return DIMSE_NULLKEY;
+
+  DCMNET_DEBUG("Handling C-GET Response");
+  
+  /* First, perform separate check for 0xCxxx error codes */
+  Uint16 highNibble = response->m_status & 0xf000;
+  if (highNibble == STATUS_GET_Failed_UnableToProcess)
+  {
+    continueCGETSession = OFFalse;
+    DCMNET_ERROR("Unable to Process");
+    DCMNET_WARN("Full status is 0x"
+      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+      << response->m_status);
+    return EC_Normal;
+  }
+
+  /* Check for other error codes */
+  switch (response->m_status) {
+  case STATUS_GET_Refused_OutOfResourcesNumberOfMatches:
+    continueCGETSession = OFFalse;
+    DCMNET_ERROR("Out of Resouces - Unable to calculate number of matches");
+    break;
+  case STATUS_GET_Refused_OutOfResourcesSubOperations:
+    continueCGETSession = OFFalse;
+    DCMNET_ERROR("Out of Resouces - Unable to perform sub-operations");
+    break;
+  case STATUS_GET_Failed_IdentifierDoesNotMatchSOPClass:
+    continueCGETSession = OFFalse;
+    DCMNET_ERROR("Identifier does not match SOP class");
+    break;
+  case STATUS_GET_Cancel_SubOperationsTerminatedDueToCancelIndication:
+    continueCGETSession = OFFalse;
+    DCMNET_DEBUG("Suboperations canceled by server due to CANCEL indication");
+    break;
+  case STATUS_GET_Warning_SubOperationsCompleteOneOrMoreFailures:
+    continueCGETSession = OFFalse;
+    DCMNET_WARN("Suboperations of C-GET completed with one or more failures");
+    break;
+  case STATUS_Pending:
+    /* in this case the current C-MOVE-RSP indicates that */
+    /* there will be some more results */
+    continueCGETSession = OFTrue;
+    DCMNET_DEBUG("One or more pending C-GET responses");
+    break;
+  case STATUS_Success:
+    /* in this case, we received the last C-MOVE-RSP so there */
+    /* will be no other responses we have to wait for. */
+    continueCGETSession = OFFalse;
+    DCMNET_DEBUG("Received final C-GET response, no more C-GET responses expected");
+    break;
+  default:
+    /* in all other cases, don't expect further responses to come */
+    continueCGETSession = OFFalse;
+    DCMNET_WARN("Status is 0x"
+      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+      << response->m_status << " (unknown)");
+    DCMNET_WARN("Will not wait for further C-GET responses");
+    break;
+  } //switch
+
+  return EC_Normal;
+}
+
+
+// Handles single C-STORE Request received during C-GET session
+OFCondition DcmSCU::handleSTORERequest(const T_ASC_PresentationContextID /* presID */,
+                                       DcmDataset *incomingObject,
+                                       OFBool& /* continueCGETSession */,
+                                       Uint16& cStoreReturnStatus)
+{
+  if (incomingObject == NULL)
+    return DIMSE_NULLKEY;
+
+  OFString sopClassUID;
+  OFString sopInstanceUID;
+  OFCondition result = incomingObject->findAndGetOFString(DCM_SOPClassUID, sopClassUID);
+  if (result.good())
+    result = incomingObject->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID);
+  if (result.bad())
+  {
+    DCMNET_ERROR("Cannot store received object: either SOP Instance or SOP Class UID not present");
+    cStoreReturnStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
+    return EC_TagNotFound;
+  }
+
+  OFString filename = createStorageFilename(incomingObject);
+  result = incomingObject->saveFile(filename.c_str());
+  if (result.good())
+  {
+    E_TransferSyntax xferSyntax;
+    getDatasetInfo(incomingObject, sopClassUID, sopInstanceUID, xferSyntax);
+    notifyInstanceStored(filename, sopClassUID, sopInstanceUID);
+    cStoreReturnStatus = STATUS_Success;
+  }
+  else
+  {
+    cStoreReturnStatus = STATUS_STORE_Refused_OutOfResources;
+  }
+
+  return result;
+}
+
+OFCondition DcmSCU::handleSTORERequestFile(T_ASC_PresentationContextID *presID,
+                                           const OFString& filename,
+                                           T_DIMSE_C_StoreRQ* request)
+{
+  if (filename.empty())
+    return EC_IllegalParameter;
+
+  /* in the following, we want to receive data over the network and write it to a file */
+  /* exactly the way it was received over the network. Hence, a filestream will be created and the data */
+  /* set will be received and written to the file through the call to DIMSE_receiveDataSetInFile(...).*/
+  /* create filestream */
+  DcmOutputFileStream *filestream = NULL;
+  OFCondition cond = DIMSE_createFilestream(filename.c_str(), request, m_assoc, *presID, OFTrue, &filestream);
+  if (cond.good())
+  {
+    if (m_progressNotificationMode)
+    {
+      cond = DIMSE_receiveDataSetInFile(m_assoc, m_blockMode, m_dimseTimeout, presID, filestream,
+                                        callbackRECEIVEProgress, this /*callbackData*/);
+    } else {
+      cond = DIMSE_receiveDataSetInFile(m_assoc, m_blockMode, m_dimseTimeout, presID, filestream,
+                                        NULL /*callback*/, NULL /*callbackData*/);
+    }
+    delete filestream;
+    if (cond != EC_Normal)
+    {
+      unlink(filename.c_str());
+    }
+    DCMNET_DEBUG("Received dataset on presentation context " << OFstatic_cast(unsigned int, *presID));
+  }
+  else
+  {
+    OFString tempStr;
+    DCMNET_ERROR("Unable to receive and store dataset on presentation context "
+      << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
+  }
+  return cond;
+}
+
+
+OFCondition DcmSCU::sendSTOREResponse(T_ASC_PresentationContextID presID,
+                                      Uint16 status,
+                                      const T_DIMSE_C_StoreRQ& request)
+{
+  // Send back response
+  T_DIMSE_Message response;
+  T_DIMSE_C_StoreRSP &storeRsp = response.msg.CStoreRSP;
+  response.CommandField = DIMSE_C_STORE_RSP;
+  storeRsp.MessageIDBeingRespondedTo = request.MessageID;
+  storeRsp.DimseStatus = status;
+  storeRsp.DataSetType = DIMSE_DATASET_NULL;
+  storeRsp.opts = 0;
+  /* Following information is optional and normally not sent by the underlying
+   * dcmnet routines. However, maybe this could be changed later, so insert it.
+   */
+  OFStandard::strlcpy(storeRsp.AffectedSOPClassUID, request.AffectedSOPClassUID, sizeof(storeRsp.AffectedSOPClassUID));
+  OFStandard::strlcpy(storeRsp.AffectedSOPInstanceUID, request.AffectedSOPInstanceUID, sizeof(storeRsp.AffectedSOPInstanceUID));
+
+  OFString tempStr;
+  DCMNET_INFO("Sending C-STORE Response (" << DU_cstoreStatusString(status) << ")");
+
+  OFCondition cond = sendDIMSEMessage(presID, &response, NULL /*dataObject*/);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending C-STORE response: " << DimseCondition::dump(tempStr, cond));
+  }
+  return cond;
+}
+
+
+OFString DcmSCU::createStorageFilename(DcmDataset *dataset)
+{
+  OFString sopClassUID, sopInstanceUID;
+  E_TransferSyntax dummy;
+  getDatasetInfo(dataset, sopClassUID, sopInstanceUID, dummy);
+  // Create unique filename
+  if (sopClassUID.empty() || sopInstanceUID.empty())
+    return "";
+  OFString name = dcmSOPClassUIDToModality(sopClassUID.c_str(), "UNKNOWN");
+  name += ".";
+  name += sopInstanceUID;
+  OFString returnStr;
+  OFStandard::combineDirAndFilename(returnStr, m_storageDir, name, OFTrue);
+  return returnStr;
+}
+
+
+OFCondition DcmSCU::ignoreSTORERequest(T_ASC_PresentationContextID presID,
+                                       const T_DIMSE_C_StoreRQ& request)
+{
+
+  /* We cannot create the filestream, so ignore the incoming dataset and return an out-of-resources error to the SCU */
+  DIC_UL bytesRead = 0;
+  DIC_UL pdvCount=0;
+  DCMNET_DEBUG("Ignoring incoming C-STORE dataset on presentation context "
+    << OFstatic_cast(unsigned int, presID)
+    << " with Affected SOP Instance UID: " << request.AffectedSOPInstanceUID );
+  OFCondition result = DIMSE_ignoreDataSet(m_assoc, m_blockMode, m_dimseTimeout, &bytesRead, &pdvCount);
+  if (result.good())
+  {
+    DCMNET_TRACE("Successfully skipped " << bytesRead << " bytes in " << pdvCount << " PDVs");
+  }
+  return result;
+}
+
+
+void DcmSCU::notifyInstanceStored(const OFString& filename,
+                                  const OFString& sopClassUID,
+                                  const OFString& sopInstanceUID) const
+{
+  DCMNET_DEBUG("Stored instance to disk:");
+  DCMNET_DEBUG("  Filename: " << filename);
+  DCMNET_DEBUG("  SOP Class UID: " << sopClassUID);
+  DCMNET_DEBUG("  SOP Instance UID: " << sopInstanceUID);
+}
+
+
+/* ************************************************************************* */
+/*                            C-FIND functionality                           */
+/* ************************************************************************* */
+
+// Sends a C-FIND Request on given presentation context
+OFCondition DcmSCU::sendFINDRequest(const T_ASC_PresentationContextID presID,
+                                    DcmDataset *queryKeys,
+                                    OFList<QRResponse*> *responses)
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (queryKeys == NULL)
+    return DIMSE_NULLKEY;
+
+  /* Prepare DIMSE data structures for issuing request */
+  OFCondition cond;
+  OFString tempStr;
+  T_ASC_PresentationContextID pcid = presID;
+  T_DIMSE_Message msg;
+  DcmDataset* statusDetail = NULL;
+  T_DIMSE_C_FindRQ* req = &(msg.msg.CFindRQ);
+  // Set type of message
+  msg.CommandField = DIMSE_C_FIND_RQ;
+  // Set message ID
+  req->MessageID = nextMessageID();
+  // Announce dataset
+  req->DataSetType = DIMSE_DATASET_PRESENT;
+  // Specify priority
+  req->Priority = DIMSE_PRIORITY_LOW;
+
+  // Determine SOP Class from presentation context
+  OFString abstractSyntax, transferSyntax;
+  findPresentationContext(pcid, abstractSyntax, transferSyntax);
+  if (abstractSyntax.empty() || transferSyntax.empty())
+    return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+  OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
+
+  /* Send request */
+  DCMNET_INFO("Sending C-FIND Request (MsgID " << req->MessageID << ")");
+
+  cond = sendDIMSEMessage(pcid, &msg, queryKeys);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending C-FIND request: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  /* Receive and handle response */
+  OFBool waitForNextResponse = OFTrue;
+  while (waitForNextResponse)
+  {
+    T_DIMSE_Message rsp;
+    statusDetail = NULL;
+
+    // Receive command set
+    cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
+    if (cond.bad())
+    {
+      DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
+      return cond;
+    }
+
+    if (rsp.CommandField == DIMSE_C_FIND_RSP)
+    {
+      DCMNET_INFO("Received C-FIND Response (" << DU_cfindStatusString(rsp.msg.CFindRSP.DimseStatus) << ")");
+
+    } else {
+      DCMNET_ERROR("Expected C-FIND response but received DIMSE command 0x"
+        << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+        << OFstatic_cast(unsigned int, rsp.CommandField));
+      DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
+      delete statusDetail;
+      return DIMSE_BADCOMMANDTYPE;
+    }
+
+    // Prepare response package for response handler
+    QRResponse *findRSP = new QRResponse();
+    findRSP->m_affectedSOPClassUID = rsp.msg.CFindRSP.AffectedSOPClassUID;
+    findRSP->m_messageIDRespondedTo = rsp.msg.CFindRSP.MessageIDBeingRespondedTo;
+    findRSP->m_status = rsp.msg.CFindRSP.DimseStatus;
+    findRSP->m_statusDetail = statusDetail;
+
+    // Receive dataset if there is one (status PENDING)
+    DcmDataset *rspDataset = NULL;
+    if (DICOM_PENDING_STATUS(findRSP->m_status))
+    {
+      // Check if dataset is announced correctly
+      if (rsp.msg.CFindRSP.DataSetType == DIMSE_DATASET_NULL)
+      {
+        DCMNET_ERROR("Received C-FIND response with PENDING status but no dataset announced, aborting");
+        delete findRSP; // includes statusDetail
+        return DIMSE_BADMESSAGE;
+      }
+
+      // Receive dataset
+      cond = receiveDIMSEDataset(&pcid, &rspDataset);
+      if (cond.bad())
+      {
+        delete findRSP; // includes statusDetail
+        return DIMSE_BADDATA;
+      }
+      findRSP->m_dataset = rspDataset;
+    }
+
+    // Handle C-FIND response (has to handle all possible status flags)
+    cond = handleFINDResponse(pcid, findRSP, waitForNextResponse);
+    if (cond.bad())
+    {
+      DCMNET_WARN("Unable to handle C-FIND response correctly: " << cond.text() << " (ignored)");
+      delete findRSP; // includes statusDetail and rspDataset
+      // don't return here but trust the "waitForNextResponse" variable
+    }
+    // if response could be handled successfully, add it to response list
+    else
+    {
+      if (responses != NULL) // only add if desired by caller
+        responses->push_back(findRSP);
+      else
+        delete findRSP; // includes statusDetail and rspDataset
+    }
+  }
+  /* All responses received or break signal occured */
+  return EC_Normal;
+}
+
+
+// Standard handler for C-FIND message responses
+OFCondition DcmSCU::handleFINDResponse(const T_ASC_PresentationContextID /* presID */,
+                                       QRResponse *response,
+                                       OFBool &waitForNextResponse)
+{
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (response == NULL)
+    return DIMSE_NULLKEY;
+
+  DCMNET_DEBUG("Handling C-FIND Response");
+  switch (response->m_status) {
+    case STATUS_Pending:
+    case STATUS_FIND_Pending_WarningUnsupportedOptionalKeys:
+      /* in this case the current C-FIND-RSP indicates that */
+      /* there will be some more results */
+      waitForNextResponse = OFTrue;
+      DCMNET_DEBUG("One or more pending C-FIND responses");
+      break;
+    case STATUS_Success:
+      /* in this case the current C-FIND-RSP indicates that */
+      /* there are no more records that match the search mask */
+      waitForNextResponse = OFFalse;
+      DCMNET_DEBUG("Received final C-FIND response, no more C-FIND responses expected");
+      break;
+    default:
+      /* in all other cases, don't expect further responses to come */
+      waitForNextResponse = OFFalse;
+      DCMNET_DEBUG("Status tells not to wait for further C-FIND responses");
+      break;
+  } //switch
+  return EC_Normal;
+}
+
+
+/* ************************************************************************* */
+/*                            C-CANCEL functionality                         */
+/* ************************************************************************* */
+
+// Send C-CANCEL-REQ and, therefore, ends current C-FIND, -MOVE or -GET session
+OFCondition DcmSCU::sendCANCELRequest(const T_ASC_PresentationContextID presID)
+{
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+
+  /* Prepare DIMSE data structures for issuing request */
+  OFCondition cond;
+  OFString tempStr;
+  T_ASC_PresentationContextID pcid = presID;
+  T_DIMSE_Message msg;
+  T_DIMSE_C_CancelRQ* req = &(msg.msg.CCancelRQ);
+  // Set type of message
+  msg.CommandField = DIMSE_C_CANCEL_RQ;
+  /* Set message ID responded to. A new message ID is _not_ needed so
+     we do not increment the message ID here but instead have to give the
+     message ID that was used last.
+     Note that that it is required to actually use the message ID of the last
+     C-FIND/GET/MOVE that was issued on this presentation context channel.
+     However, since we only support synchronous association mode so far,
+     it is enough to take the last message ID used at all.
+     For asynchronous operation, we would have to lookup the message ID
+     of the last C-FIND/GET/MOVE request issued and thus, store this
+     information after sending it.
+   */
+  req->MessageIDBeingRespondedTo = m_assoc->nextMsgID - 1;
+  // Announce dataset
+  req->DataSetType = DIMSE_DATASET_NULL;
+
+  /* We do not care about the transfer syntax since no
+     dataset is transported at all, i.e. we trust that the user provided
+     the correct presentation context ID (could be private one).
+   */
+  DCMNET_INFO("Sending C-CANCEL Request (MsgID " << req->MessageIDBeingRespondedTo
+      << ", PresID " << OFstatic_cast(unsigned int, pcid) <<  ")");
+
+  cond = sendDIMSEMessage(pcid, &msg, NULL /*dataObject*/);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending C-CANCEL request: " << DimseCondition::dump(tempStr, cond));
+  }
+
+  DCMNET_TRACE("There is no C-CANCEL response in DICOM, so none expected");
+  return cond;
+}
+
+
+/* ************************************************************************* */
+/*                            N-ACTION functionality                         */
+/* ************************************************************************* */
+
+// Sends N-ACTION request to another DICOM application
+OFCondition DcmSCU::sendACTIONRequest(const T_ASC_PresentationContextID presID,
+                                      const OFString &sopInstanceUID,
+                                      const Uint16 actionTypeID,
+                                      DcmDataset *reqDataset,
+                                      Uint16 &rspStatusCode)
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (sopInstanceUID.empty() || (reqDataset == NULL))
+    return DIMSE_NULLKEY;
+
+  // Prepare DIMSE data structures for issuing request
+  OFCondition cond;
+  OFString tempStr;
+  T_ASC_PresentationContextID pcid = presID;
+  T_DIMSE_Message request;
+  T_DIMSE_N_ActionRQ &actionReq = request.msg.NActionRQ;
+  DcmDataset *statusDetail = NULL;
+
+  request.CommandField = DIMSE_N_ACTION_RQ;
+  actionReq.MessageID = nextMessageID();
+  actionReq.DataSetType = DIMSE_DATASET_PRESENT;
+  actionReq.ActionTypeID = actionTypeID;
+
+  // Determine SOP Class from presentation context
+  OFString abstractSyntax, transferSyntax;
+  findPresentationContext(pcid, abstractSyntax, transferSyntax);
+  if (abstractSyntax.empty() || transferSyntax.empty())
+    return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+  OFStandard::strlcpy(actionReq.RequestedSOPClassUID, abstractSyntax.c_str(), sizeof(actionReq.RequestedSOPClassUID));
+  OFStandard::strlcpy(actionReq.RequestedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(actionReq.RequestedSOPInstanceUID));
+
+  // Send request
+  DCMNET_INFO("Sending N-ACTION Request (MsgID " << actionReq.MessageID << ")");
+
+  cond = sendDIMSEMessage(pcid, &request, reqDataset);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending N-ACTION request: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  // Receive response
+  T_DIMSE_Message response;
+  cond = receiveDIMSECommand(&pcid, &response, &statusDetail, NULL /* commandSet */);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  // Check command set
+  if (response.CommandField == DIMSE_N_ACTION_RSP)
+  {
+    DCMNET_INFO("Received N-ACTION Response (" << DU_nactionStatusString(response.msg.NActionRSP.DimseStatus) << ")");
+
+  } else {
+    DCMNET_ERROR("Expected N-ACTION response but received DIMSE command 0x"
+        << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+        << OFstatic_cast(unsigned int, response.CommandField));
+    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
+    delete statusDetail;
+    return DIMSE_BADCOMMANDTYPE;
+  }
+  if (statusDetail != NULL)
+  {
+    DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
+    delete statusDetail;
+  }
+
+  // Set return value
+  T_DIMSE_N_ActionRSP &actionRsp = response.msg.NActionRSP;
+  rspStatusCode = actionRsp.DimseStatus;
+
+  // Check whether there is a dataset to be received
+  if (actionRsp.DataSetType == DIMSE_DATASET_PRESENT)
+  {
+    // this should never happen
+    DcmDataset *tempDataset = NULL;
+    T_ASC_PresentationContextID tempID;
+    DCMNET_WARN("Trying to retrieve unexpected dataset in N-ACTION response");
+    cond = receiveDIMSEDataset(&tempID, &tempDataset);
+    if (cond.good())
+    {
+      DCMNET_WARN("Received unexpected dataset after N-ACTION response, ignoring");
+      delete tempDataset;
+    } else {
+      return DIMSE_BADDATA;
+    }
+  }
+  if (actionRsp.MessageIDBeingRespondedTo != actionReq.MessageID)
+  {
+    // since we only support synchronous communication, the message ID in the response
+    // should be identical to the one in the request
+    DCMNET_ERROR("Received response with wrong message ID (" << actionRsp.MessageIDBeingRespondedTo
+        << " instead of " << actionReq.MessageID << ")");
+    return DIMSE_BADMESSAGE;
+  }
+
+  return cond;
+}
+
+
+/* ************************************************************************* */
+/*                         N-EVENT REPORT functionality                      */
+/* ************************************************************************* */
+
+// Sends N-EVENT-REPORT request and receives N-EVENT-REPORT response
+OFCondition DcmSCU::sendEVENTREPORTRequest(const T_ASC_PresentationContextID presID,
+                                           const OFString &sopInstanceUID,
+                                           const Uint16 eventTypeID,
+                                           DcmDataset *reqDataset,
+                                           Uint16 &rspStatusCode)
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (sopInstanceUID.empty() || (reqDataset == NULL))
+    return DIMSE_NULLKEY;
+
+  // Prepare DIMSE data structures for issuing request
+  OFCondition cond;
+  OFString tempStr;
+  T_ASC_PresentationContextID pcid = presID;
+  T_DIMSE_Message request;
+  T_DIMSE_N_EventReportRQ &eventReportReq = request.msg.NEventReportRQ;
+  DcmDataset *statusDetail = NULL;
+
+  request.CommandField = DIMSE_N_EVENT_REPORT_RQ;
+
+  // Generate a new message ID
+  eventReportReq.MessageID = nextMessageID();
+  eventReportReq.DataSetType = DIMSE_DATASET_PRESENT;
+  eventReportReq.EventTypeID = eventTypeID;
+
+  // Determine SOP Class from presentation context
+  OFString abstractSyntax, transferSyntax;
+  findPresentationContext(pcid, abstractSyntax, transferSyntax);
+  if (abstractSyntax.empty() || transferSyntax.empty())
+    return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+  OFStandard::strlcpy(eventReportReq.AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(eventReportReq.AffectedSOPClassUID));
+  OFStandard::strlcpy(eventReportReq.AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(eventReportReq.AffectedSOPInstanceUID));
+
+  // Send request
+  DCMNET_INFO("Sending N-EVENT-REPORT Request (MsgID " << eventReportReq.MessageID << ")");
+
+  cond = sendDIMSEMessage(pcid, &request, reqDataset);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending N-EVENT-REPORT request: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+  // Receive response
+  T_DIMSE_Message response;
+  cond = receiveDIMSECommand(&pcid, &response, &statusDetail, NULL /* commandSet */);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  // Check command set
+  if (response.CommandField == DIMSE_N_EVENT_REPORT_RSP)
+  {
+    DCMNET_INFO("Received N-EVENT-REPORT Response (" << DU_neventReportStatusString(response.msg.NEventReportRSP.DimseStatus) << ")");
+
+  } else {
+    DCMNET_ERROR("Expected N-EVENT-REPORT response but received DIMSE command 0x"
+        << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+        << OFstatic_cast(unsigned int, response.CommandField));
+    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
+    delete statusDetail;
+    return DIMSE_BADCOMMANDTYPE;
+  }
+  if (statusDetail != NULL)
+  {
+    DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
+    delete statusDetail;
+  }
+
+  // Set return value
+  T_DIMSE_N_EventReportRSP &eventReportRsp = response.msg.NEventReportRSP;
+  rspStatusCode = eventReportRsp.DimseStatus;
+
+  // Check whether there is a dataset to be received
+  if (eventReportRsp.DataSetType == DIMSE_DATASET_PRESENT)
+  {
+    // this should never happen
+    DcmDataset *tempDataset = NULL;
+    T_ASC_PresentationContextID tempID;
+    cond = receiveDIMSEDataset(&tempID, &tempDataset);
+    if (cond.good())
+    {
+      DCMNET_WARN("Received unexpected dataset after N-EVENT-REPORT response, ignoring");
+      delete tempDataset;
+    } else {
+      DCMNET_ERROR("Failed receiving unexpected dataset after N-EVENT-REPORT response: "
+        << DimseCondition::dump(tempStr, cond));
+      return DIMSE_BADDATA;
+    }
+  }
+
+  // Check whether the message ID being responded to is equal to the message ID of the request
+  if (eventReportRsp.MessageIDBeingRespondedTo != eventReportReq.MessageID)
+  {
+    DCMNET_ERROR("Received response with wrong message ID (" << eventReportRsp.MessageIDBeingRespondedTo
+        << " instead of " << eventReportReq.MessageID << ")");
+    return DIMSE_BADMESSAGE;
+  }
+  return cond;
+}
+
+// Receives N-EVENT-REPORT request
+OFCondition DcmSCU::handleEVENTREPORTRequest(DcmDataset *&reqDataset,
+                                             Uint16 &eventTypeID,
+                                             const int timeout)
+{
+  // Do some basic validity checks
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+
+  OFCondition cond;
+  OFString tempStr;
+  T_ASC_PresentationContextID presID;
+  T_ASC_PresentationContextID presIDdset;
+  T_DIMSE_Message request;
+  T_DIMSE_N_EventReportRQ &eventReportReq = request.msg.NEventReportRQ;
+  DcmDataset *dataset = NULL;
+  DcmDataset *statusDetail = NULL;
+  Uint16 statusCode = 0;
+
+  if (timeout > 0)
+    DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting up to " << timeout << " seconds (only for N-EVENT-REPORT message)");
+  else if ((m_dimseTimeout > 0) && (m_blockMode == DIMSE_NONBLOCKING))
+    DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting up to " << m_dimseTimeout << " seconds (default for all DIMSE messages)");
+  else
+    DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting an unlimited period of time");
+
+  // Receive request, use specific timeout (if defined)
+  cond = receiveDIMSECommand(&presID, &request, &statusDetail, NULL /* commandSet */, timeout);
+  if (cond.bad())
+  {
+    if (cond != DIMSE_NODATAAVAILABLE)
+      DCMNET_ERROR("Failed receiving DIMSE request: " << DimseCondition::dump(tempStr, cond));
+    return cond;
+  }
+
+  // Check command set
+  if (request.CommandField == DIMSE_N_EVENT_REPORT_RQ)
+  {
+      DCMNET_INFO("Received N-EVENT-REPORT Request (MsgID " << eventReportReq.MessageID << ")");
+  } else {
+    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
+    DCMNET_ERROR("Expected N-EVENT-REPORT request but received DIMSE command 0x"
+      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
+      << OFstatic_cast(unsigned int, request.CommandField));
+    delete statusDetail;
+    return DIMSE_BADCOMMANDTYPE;
+  }
+  if (statusDetail != NULL)
+  {
+    DCMNET_DEBUG("Request has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
+    delete statusDetail;
+  }
+
+  // Check if dataset is announced correctly
+  if (eventReportReq.DataSetType == DIMSE_DATASET_NULL)
+  {
+    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
+    DCMNET_ERROR("Received N-EVENT-REPORT request but no dataset announced, aborting");
+    return DIMSE_BADMESSAGE;
+  }
+
+  // Receive dataset
+  cond = receiveDIMSEDataset(&presIDdset, &dataset);
+  if (cond.bad())
+  {
+    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
+    return DIMSE_BADDATA;
+  }
+
+  // Output dataset only if trace level is enabled
+    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
+
+  // Compare presentation context ID of command and data set
+  if (presIDdset != presID)
+  {
+    DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID)
+      << ") and data set (" << OFstatic_cast(unsigned int, presIDdset) << ") differ");
+    delete dataset;
+    return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID, OF_error,
+      "DIMSE: Presentation Contexts of Command and Data Set differ");
+  }
+
+  // Check the request dataset and return the DIMSE status code to be used
+  statusCode = checkEVENTREPORTRequest(eventReportReq, dataset);
+
+  // Send back response
+  T_DIMSE_Message response;
+  T_DIMSE_N_EventReportRSP &eventReportRsp = response.msg.NEventReportRSP;
+  response.CommandField = DIMSE_N_EVENT_REPORT_RSP;
+  eventReportRsp.MessageIDBeingRespondedTo = eventReportReq.MessageID;
+  eventReportRsp.DimseStatus = statusCode;
+  eventReportRsp.DataSetType = DIMSE_DATASET_NULL;
+  eventReportRsp.opts = 0;
+  eventReportRsp.AffectedSOPClassUID[0] = 0;
+  eventReportRsp.AffectedSOPInstanceUID[0] = 0;
+
+  DCMNET_INFO("Sending N-EVENT-REPORT Response (" << DU_neventReportStatusString(statusCode) << ")");
+
+  cond = sendDIMSEMessage(presID, &response, NULL /*dataObject*/);
+  if (cond.bad())
+  {
+    DCMNET_ERROR("Failed sending N-EVENT-REPORT response: " << DimseCondition::dump(tempStr, cond));
+    delete dataset;
+    return cond;
+  }
+
+  // Set return values
+  reqDataset = dataset;
+  eventTypeID = eventReportReq.EventTypeID;
+
+  return cond;
+}
+
+
+Uint16 DcmSCU::checkEVENTREPORTRequest(T_DIMSE_N_EventReportRQ & /*eventReportReq*/,
+                                       DcmDataset * /*reqDataset*/)
+{
+  // we default to success
+  return STATUS_Success;
+}
+
+
+/* ************************************************************************* */
+/*                         General message handling                          */
+/* ************************************************************************* */
+
+void DcmSCU::notifySENDProgress(const unsigned long byteCount)
+{
+  DCMNET_TRACE("Bytes sent: " << byteCount);
+}
+
+
+void DcmSCU::notifyRECEIVEProgress(const unsigned long byteCount)
+{
+  DCMNET_TRACE("Bytes received: " << byteCount);
+}
+
+
+/* ************************************************************************* */
+/*                            Various helpers                                */
+/* ************************************************************************* */
+
+// Sends a DIMSE command and possibly also instance data to the configured peer DICOM application
+OFCondition DcmSCU::sendDIMSEMessage(const T_ASC_PresentationContextID presID,
+                                     T_DIMSE_Message *msg,
+                                     DcmDataset *dataObject,
+                                     DcmDataset **commandSet)
+{
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+  if (msg == NULL)
+    return DIMSE_NULLKEY;
+
+  OFCondition cond;
+  /* call the corresponding DIMSE function to send the message */
+  if (m_progressNotificationMode)
+  {
+    cond = DIMSE_sendMessageUsingMemoryData(m_assoc, presID, msg, NULL /*statusDetail*/, dataObject,
+                                            callbackSENDProgress, this /*callbackData*/, commandSet);
+  } else {
+    cond = DIMSE_sendMessageUsingMemoryData(m_assoc, presID, msg, NULL /*statusDetail*/, dataObject,
+                                            NULL /*callback*/, NULL /*callbackData*/, commandSet);
+  }
+
+#if 0
+  // currently disabled because it is not (yet) needed
+  if (cond.good())
+  {
+    /* create a copy of the current DIMSE command message */
+    delete m_openDIMSERequest;
+    m_openDIMSERequest = new T_DIMSE_Message;
+    memcpy((char*)m_openDIMSERequest, msg, sizeof(*m_openDIMSERequest));
+  }
+#endif
+
+  return cond;
+}
+
+
+// Receive DIMSE command (excluding dataset!) over the currently open association
+OFCondition DcmSCU::receiveDIMSECommand(T_ASC_PresentationContextID *presID,
+                                        T_DIMSE_Message *msg,
+                                        DcmDataset **statusDetail,
+                                        DcmDataset **commandSet,
+                                        const Uint32 timeout)
+{
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+
+  OFCondition cond;
+  if (timeout > 0)
+  {
+    /* call the corresponding DIMSE function to receive the command (use specified timeout)*/
+    cond = DIMSE_receiveCommand(m_assoc, DIMSE_NONBLOCKING, timeout, presID,
+                                msg, statusDetail, commandSet);
+  } else {
+    /* call the corresponding DIMSE function to receive the command (use default timeout) */
+    cond = DIMSE_receiveCommand(m_assoc, m_blockMode, m_dimseTimeout, presID,
+                                msg, statusDetail, commandSet);
+  }
+  return cond;
+}
+
+// Receives one dataset (of instance data) via network from another DICOM application
+OFCondition DcmSCU::receiveDIMSEDataset(T_ASC_PresentationContextID *presID,
+                                        DcmDataset **dataObject)
+{
+  if (!isConnected())
+    return DIMSE_ILLEGALASSOCIATION;
+
+  OFCondition cond;
+  /* call the corresponding DIMSE function to receive the dataset */
+  if (m_progressNotificationMode)
+  {
+    cond = DIMSE_receiveDataSetInMemory(m_assoc, m_blockMode, m_dimseTimeout, presID, dataObject,
+                                        callbackRECEIVEProgress, this /*callbackData*/);
+  } else {
+    cond = DIMSE_receiveDataSetInMemory(m_assoc, m_blockMode, m_dimseTimeout, presID, dataObject,
+                                        NULL /*callback*/, NULL /*callbackData*/);
+  }
+
+  if (cond.good())
+  {
+    DCMNET_DEBUG("Received dataset on presentation context " << OFstatic_cast(unsigned int, *presID));
+  }
+  else
+  {
+    OFString tempStr;
+    DCMNET_ERROR("Unable to receive dataset on presentation context "
+      << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
+  }
+  return cond;
+}
+
+
+void DcmSCU::setMaxReceivePDULength(const unsigned long maxRecPDU)
+{
+  m_maxReceivePDULength = maxRecPDU;
+}
+
+
+void DcmSCU::setDIMSEBlockingMode(const T_DIMSE_BlockingMode blockingMode)
+{
+  m_blockMode = blockingMode;
+}
+
+
+void DcmSCU::setAETitle(const OFString &myAETtitle)
+{
+  m_ourAETitle = myAETtitle;
+}
+
+
+void DcmSCU::setPeerHostName(const OFString &peerHostName)
+{
+  m_peer = peerHostName;
+}
+
+
+void DcmSCU::setPeerAETitle(const OFString &peerAETitle)
+{
+  m_peerAETitle = peerAETitle;
+}
+
+
+void DcmSCU::setPeerPort(const Uint16 peerPort)
+{
+  m_peerPort = peerPort;
+}
+
+
+void DcmSCU::setDIMSETimeout(const Uint32 dimseTimeout)
+{
+  m_dimseTimeout = dimseTimeout;
+}
+
+
+void DcmSCU::setACSETimeout(const Uint32 acseTimeout)
+{
+  m_acseTimeout = acseTimeout;
+}
+
+
+void DcmSCU::setAssocConfigFileAndProfile(const OFString &filename,
+                                          const OFString &profile)
+{
+  m_assocConfigFilename = filename;
+  m_assocConfigProfile = profile;
+}
+
+
+void DcmSCU::setStorageDir(const OFString& storeDir)
+{
+  m_storageDir = storeDir;
+}
+
+
+void DcmSCU::setStorageMode(const DcmStorageMode storageMode)
+{
+  m_storageMode = storageMode;
+}
+
+
+void DcmSCU::setVerbosePCMode(const OFBool mode)
+{
+  m_verbosePCMode = mode;
+}
+
+
+void DcmSCU::setDatasetConversionMode(const OFBool mode)
+{
+  m_datasetConversionMode = mode;
+}
+
+
+void DcmSCU::setProgressNotificationMode(const OFBool mode)
+{
+  m_progressNotificationMode = mode;
+}
+
+
+/* Get methods */
+
+OFBool DcmSCU::isConnected() const
+{
+  return (m_assoc != NULL) && (m_assoc->DULassociation != NULL);
+}
+
+Uint32 DcmSCU::getMaxReceivePDULength() const
+{
+  return m_maxReceivePDULength;
+}
+
+
+OFBool DcmSCU::getTLSEnabled() const
+{
+  return OFFalse;
+}
+
+
+T_DIMSE_BlockingMode DcmSCU::getDIMSEBlockingMode() const
+{
+  return m_blockMode;
+}
+
+
+const OFString &DcmSCU::getAETitle() const
+{
+  return m_ourAETitle;
+}
+
+
+const OFString &DcmSCU::getPeerHostName() const
+{
+  return m_peer;
+}
+
+
+const OFString &DcmSCU::getPeerAETitle() const
+{
+  return m_peerAETitle;
+}
+
+
+Uint16 DcmSCU::getPeerPort() const
+{
+  return m_peerPort;
+}
+
+
+Uint32 DcmSCU::getDIMSETimeout() const
+{
+  return m_dimseTimeout;
+}
+
+
+Uint32 DcmSCU::getACSETimeout() const
+{
+  return m_acseTimeout;
+}
+
+
+OFString DcmSCU::getStorageDir() const
+{
+  return m_storageDir;
+}
+
+
+DcmStorageMode DcmSCU::getStorageMode() const
+{
+  return m_storageMode;
+}
+
+
+OFBool DcmSCU::getVerbosePCMode() const
+{
+  return m_verbosePCMode;
+}
+
+
+OFBool DcmSCU::getDatasetConversionMode() const
+{
+  return m_datasetConversionMode;
+}
+
+
+OFBool DcmSCU::getProgressNotificationMode() const
+{
+  return m_progressNotificationMode;
+}
+
+
+OFCondition DcmSCU::getDatasetInfo(DcmDataset *dataset,
+                                   OFString &sopClassUID,
+                                   OFString &sopInstanceUID,
+                                   E_TransferSyntax &transferSyntax)
+{
+  OFCondition status = EC_IllegalParameter;
+  sopClassUID.clear();
+  sopInstanceUID.clear();
+  transferSyntax = EXS_Unknown;
+  if (dataset != NULL)
+  {
+    // ignore returned condition codes (e.g. EC_TagNotFound)
+    dataset->findAndGetOFString(DCM_SOPClassUID, sopClassUID);
+    dataset->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID);
+    transferSyntax = dataset->getOriginalXfer();
+    // check return values for validity
+    if (sopClassUID.empty())
+      status = NET_EC_InvalidSOPClassUID;
+    else if (sopInstanceUID.empty())
+      status = NET_EC_InvalidSOPInstanceUID;
+    else if (transferSyntax == EXS_Unknown)
+      status = NET_EC_UnknownTransferSyntax;
+    else
+      status = EC_Normal;
+  }
+  return status;
+}
+
+
+/* ************************************************************************* */
+/*                            Callback functions                             */
+/* ************************************************************************* */
+
+void DcmSCU::callbackSENDProgress(void *callbackContext,
+                                  const unsigned long byteCount)
+{
+  if (callbackContext != NULL)
+    OFreinterpret_cast(DcmSCU *, callbackContext)->notifySENDProgress(byteCount);
+}
+
+
+void DcmSCU::callbackRECEIVEProgress(void *callbackContext,
+                                     const unsigned long byteCount)
+{
+  if (callbackContext != NULL)
+    OFreinterpret_cast(DcmSCU *, callbackContext)->notifyRECEIVEProgress(byteCount);
+}
+
+
+/* ************************************************************************* */
+/*                          class RetrieveResponse                           */
+/* ************************************************************************* */
+
+void RetrieveResponse::print()
+{
+  DCMNET_INFO("  Number of Remaining Suboperations : " << m_numberOfRemainingSubops);
+  DCMNET_INFO("  Number of Completed Suboperations : " << m_numberOfCompletedSubops);
+  DCMNET_INFO("  Number of Failed Suboperations    : " << m_numberOfFailedSubops);
+  DCMNET_INFO("  Number of Warning Suboperations   : " << m_numberOfWarningSubops);
+}
+
+
+/*
+** CVS Log
+** $Log: scu.cc,v $
+** Revision 1.60  2012-05-14 10:42:54  onken
+** Enhanced handling of error codes for C-GET (added explicit support for
+** missing code Ax702 and enhanced recognition of customized 0xCxxx codes.)
+**
+** Revision 1.59  2012-02-21 08:48:54  joergr
+** Added support for progress notifications while sending and receiving DICOM
+** datasets.
+**
+** Revision 1.58  2011-10-10 14:01:29  uli
+** Moved SCU-specific error condition to the correct place.
+**
+** Revision 1.57  2011-10-04 08:58:16  joergr
+** Added flag that allows for specifying whether to convert a dataset to be
+** transferred to the network transfer syntax. Also removed unused parameters
+** "rspCommandSet" and "rspStatusDetail" from method sendSTORERequest().
+**
+** Revision 1.56  2011-09-29 17:12:03  joergr
+** Fixed memory leak in sendSTORERequest(), a DICOM dataset was not deleted.
+**
+** Revision 1.55  2011-09-29 13:12:01  joergr
+** Introduced new network-related error codes, e.g. in case that none of the
+** proposed presentation contexts were accepted by the association acceptor.
+**
+** Revision 1.54  2011-09-29 13:04:09  joergr
+** Added check whether the presentation context specified by the caller of the
+** method was really accepted before sending a C-STORE request.
+**
+** Revision 1.53  2011-09-29 12:56:21  joergr
+** Enhanced implementation of the function that retrieves the abstract syntax
+** and transfer syntax of a particular presentation context (using the ID).
+**
+** Revision 1.52  2011-09-29 09:04:26  joergr
+** Output message ID of request and DIMSE status of response messages to the
+** INFO logger (if DEBUG level is not enabled). All tools and classes in the
+** "dcmnet" module now use (more or less) the same output in verbose mode.
+**
+** Revision 1.51  2011-09-28 16:28:18  joergr
+** Added general support for transfer syntax conversions to sendSTORERequest().
+**
+** Revision 1.50  2011-09-28 15:25:36  joergr
+** Return a more appropriate error code in case the dataset to be sent is
+** invalid. This also required to introduce a return value for getDatasetInfo().
+**
+** Revision 1.49  2011-09-28 14:37:01  joergr
+** Output the DIMSE status in verbose mode (if debug mode is not enabled).
+**
+** Revision 1.48  2011-09-28 13:31:54  joergr
+** Added method that allows for clearing the list of presentation contexts.
+**
+** Revision 1.47  2011-09-23 15:27:02  joergr
+** Removed needless deletion of the "statusDetail" variable.
+**
+** Revision 1.46  2011-09-16 09:38:40  joergr
+** Fixed some typos and other small inconsistencies.
+**
+** Revision 1.45  2011-09-06 16:12:53  ogazzar
+** Fixed typos in a log commit message.
+**
+** Revision 1.44  2011-09-06 14:15:10  ogazzar
+** Fixed wrong logger name which caused compiler error.
+**
+** Revision 1.43  2011-09-06 12:58:35  ogazzar
+** Added a function to send N-EVENT-REPORT request and to receive a response.
+**
+** Revision 1.42  2011-08-25 15:46:20  joergr
+** Further cleanup of minor inconsistencies regarding documentation, parameter
+** names, log output and handling of status details information.
+**
+** Revision 1.41  2011-08-25 15:05:09  joergr
+** Changed data structure for Q/R responses from OFVector to OFList. Also fixed
+** some possible memory leaks and made the FIND/MOVE/GET code more consistent.
+**
+** Revision 1.40  2011-08-25 13:49:31  joergr
+** Fixed minor issues in the documentation, parameter and method names. Output
+** retrieve responses to main dcmnet logger instead of response logger.
+**
+** Revision 1.39  2011-08-25 09:31:35  onken
+** Added C-GET functionality to DcmSCU class and accompanying getscu
+** commandline application.
+**
+** Revision 1.38  2011-08-24 11:50:48  joergr
+** Uncommented name of unused method parameter that caused a compiler warning.
+**
+** Revision 1.37  2011-07-06 11:08:48  uli
+** Fixed various compiler warnings.
+**
+** Revision 1.36  2011-06-29 16:33:45  joergr
+** Fixed various issues that are reported when compiled with "gcc -Weffc++".
+**
+** Revision 1.35  2011-06-01 15:04:29  onken
+** Removed unused status variable from C-ECHO code.
+**
+** Revision 1.34  2011-05-30 20:10:44  onken
+** Added dump of query/move keys in debug mode when sending C-FIND or C-MOVE.
+**
+** Revision 1.33  2011-05-27 10:12:18  joergr
+** Fixed typos and source code formatting.
+**
+** Revision 1.32  2011-05-25 09:56:52  ogazzar
+** Renamed a function name.
+**
+** Revision 1.31  2011-05-25 09:31:53  ogazzar
+** Added a function to look for a presentation context ID that best matches the
+** abstract syntax UID and the transfer syntax UID.
+**
+** Revision 1.30  2011-05-24 08:38:39  ogazzar
+** Added role selection negotiation while adding a presenation context.
+**
+** Revision 1.29  2011-05-19 17:19:52  onken
+** Fixed some documentation. Added some extra checks for NULL when handling MOVE
+** and FIND responses. Simplified destructors for FIND and MOVEResponses.
+**
+** Revision 1.28  2011-05-19 10:51:20  onken
+** Simplified C string copy by using OFStandard::strlcpy and removed debugging
+** code introduced with last comit.
+**
+** Revision 1.27  2011-05-19 10:37:44  onken
+** Removed unused variable that caused compiler warning. Fixed typo.
+**
+** Revision 1.26  2011-05-19 09:57:24  onken
+** Fixed message ID field in C-CANCEL request (should be the one of last
+** request). In case of error status codes in C-MOVE responses, the default
+** behaviour is now to not wait for further responses. Fixed log output level
+** to better fit the messages while receiveing C-MOVE responses. Minor
+** code and comment cleanups. Renamed function parameter in sendMOVEREquest
+** to better reflect the standard.
+**
+** Revision 1.25  2011-05-19 08:08:30  onken
+** Fixed wrong usage of strlcpy in new C-CANCEL function.
+**
+** Revision 1.24  2011-05-17 14:26:19  onken
+** Implemented C-CANCEL message. Fixed some minor formatting issues.
+** Changed C-ECHO implementation to rely on sendDIMSEMesage as the other
+** DIMSE functions do. Changed some public function arguments to const to be
+** more correct. Fixed CVS log at the end of the scu.cc file.
+**
+** Revision 1.23  2011-04-28 17:50:05  onken
+** Re-sorted header list to make scu.h come first (after osconfig.h).
+** Protected public networking functions for creating an association
+** from being called twice. Enhanced protection of DIMSE messaging
+** functions from being called without being connected. Introduced
+** status detail into C-FIND responses (and C-MOVE responses). Was
+**  not accessible to the caller before. Minor code cleanups. Added
+** C-MOVE code for retrieving DICOM objects. So far only retrieving
+** on a separate connection is supported. Added function for cleaning
+** up internal memory from destructor. This function also fixes a
+** memory leak in case users call initNetwork more than one time.
+** Added error code returned by functions if SCU is already connected.
+**
+** Revision 1.22  2011-04-18 07:01:03  uli
+** Use global variables for the logger objects. This removes the thread-unsafe
+** static local variables which were used before.
+**
+** Revision 1.21  2011-04-05 11:16:13  joergr
+** Output DIMSE status code in hexadecimal format to the logger. Removed unused
+** code (local half-implemented function). Added more comments.
+**
+** Revision 1.20  2011-03-09 11:13:28  onken
+** Enhanced error message for missing data in store request.
+**
+** Revision 1.19  2011-02-23 08:11:51  joergr
+** Fixed issue with undefined priority field in C-STORE and C-FIND request.
+**
+** Revision 1.18  2011-02-16 08:55:17  joergr
+** Fixed issue in sendSTORERequest() when sending a dataset that was created
+** in memory (and which has, therefore, an original transfer of EXS_Unknown).
+**
+** Revision 1.17  2011-02-04 12:57:40  uli
+** Made sure all members are initialized in the constructor (-Weffc++).
+**
+** Revision 1.16  2010-12-21 09:37:36  onken
+** Fixed wrong response assignment in DcmSCU's C-STORE code. Thanks to
+** forum user "takeos" for the hint and fix.
+**
+** Revision 1.15  2010-10-20 07:41:36  uli
+** Made sure isalpha() & friends are only called with valid arguments.
+**
+** Revision 1.14  2010-10-14 13:14:29  joergr
+** Updated copyright header. Added reference to COPYRIGHT file.
+**
+** Revision 1.13  2010-10-01 12:25:29  uli
+** Fixed most compiler warnings in remaining modules.
+**
+** Revision 1.12  2010-08-10 11:59:32  uli
+** Fixed some cases where dcmFindNameOfUID() returning NULL could cause crashes.
+**
+** Revision 1.11  2010-06-24 09:26:57  joergr
+** Added check on whether the presentation context ID of command and data set are
+** identical. Made sure that received dataset is deleted when an error occurs.
+** Used more appropriate error conditions / return codes. Further code cleanup.
+**
+** Revision 1.10  2010-06-22 15:48:53  joergr
+** Introduced new enumeration type to be used for closeAssociation().
+** Further code cleanup. Renamed some methods, variables, types and so on.
+**
+** Revision 1.9  2010-06-18 14:58:01  joergr
+** Changed some error conditions / return codes to more appropriate values.
+** Further revised logging output. Use DimseCondition::dump() where appropriate.
+**
+** Revision 1.8  2010-06-17 17:13:06  joergr
+** Added preliminary support for N-EVENT-REPORT to DcmSCU. Some further code
+** cleanups and enhancements. Renamed some methods. Revised documentation.
+**
+** Revision 1.7  2010-06-09 16:33:34  joergr
+** Added preliminary support for N-ACTION to DcmSCU. Some further code cleanups
+** and enhancements.
+**
+** Revision 1.6  2010-06-08 17:54:14  onken
+** Added C-FIND functionality to DcmSCU. Some code cleanups. Fixed
+** memory leak sometimes occuring during association configuration.
+**
+** Revision 1.5  2010-06-02 16:01:49  joergr
+** Slightly modified some log messages and levels for reasons of consistency.
+** Use type cast macros (e.g. OFstatic_cast) where appropriate.
+**
+** Revision 1.4  2010-04-29 16:13:25  onken
+** Made SCU class independent from dcmtls, i.e. outsourced TLS API. Added
+** direct API support for sending C-STORE requests. Further API changes and
+** some bugs fixed.
+**
+** Revision 1.3  2009-12-21 15:33:58  onken
+** Added documentation and refactored / enhanced some code.
+**
+** Revision 1.2  2009-12-17 09:12:27  onken
+** Fixed other scu and scp base class compile issues.
+**
+** Revision 1.1  2009-12-16 17:05:35  onken
+** Added base classes for SCU and SCP implementation.
+**
+** Revision 1.5  2009-12-02 14:26:05  uli
+** Stop including dcdebug.h which was removed.
+**
+** Revision 1.4  2009-11-18 12:37:28  uli
+** Fix compiler errors due to removal of DUL_Debug() and DIMSE_Debug().
+**
+** Revision 1.3  2009-01-08 18:25:34  joergr
+** Replaced further OFListIterator() by OFListConstIterator() in order to
+** compile when STL list classes are used.
+**
+** Revision 1.2  2009-01-08 13:33:31  joergr
+** Replaced OFListIterator() by OFListConstIterator() in order to compile when
+** STL list classes are used.
+**
+** Revision 1.1  2008-09-29 13:51:55  onken
+** Initial checkin of module dcmppscu implementing an MPPS commandline client.
+**
+*/
diff --git a/tests/tools/scu.h b/tests/tools/scu.h
new file mode 100644
index 0000000..fb3963d
--- /dev/null
+++ b/tests/tools/scu.h
@@ -0,0 +1,1197 @@
+/*
+ *
+ *  Copyright (C) 2008-2012, OFFIS e.V.
+ *  All rights reserved.  See COPYRIGHT file for details.
+ *
+ *  This software and supporting documentation were developed by
+ *
+ *    OFFIS e.V.
+ *    R&D Division Health
+ *    Escherweg 2
+ *    D-26121 Oldenburg, Germany
+ *
+ *
+ *  Module:  dcmnet
+ *
+ *  Author:  Michael Onken
+ *
+ *  Purpose: Base class for Service Class Users (SCUs)
+ *
+ *  Last Update:      $Author: joergr $
+ *  Update Date:      $Date: 2012-02-21 08:48:51 $
+ *  CVS/RCS Revision: $Revision: 1.41 $
+ *  Status:           $State: Exp $
+ *
+ *  CVS/RCS Log at end of file
+ *
+ */
+
+#ifndef SCU_H
+#define SCU_H
+
+#include "dcmtk/config/osconfig.h"  /* make sure OS specific configuration is included first */
+
+#include "dcmtk/dcmdata/dctk.h"     /* Covers most common dcmdata classes */
+#include "dcmtk/dcmnet/dcompat.h"
+#include "dcmtk/dcmnet/dimse.h"     /* DIMSE network layer */
+#include "dcmtk/dcmnet/dcasccff.h"  /* For reading a association config file */
+#include "dcmtk/dcmnet/dcasccfg.h"  /* For holding association config file infos */
+#include "dcmtk/ofstd/oflist.h"
+// MODIFICATION: Use local header file
+#include "dndefine.h"
+// END Modification
+
+
+/** Different types of closing an association
+ */
+enum DcmCloseAssociationType
+{
+  /// Release the current association
+  DCMSCU_RELEASE_ASSOCIATION,
+  /// Abort the current association
+  DCMSCU_ABORT_ASSOCIATION,
+  /// Peer requested release (Aborting)
+  DCMSCU_PEER_REQUESTED_RELEASE,
+  /// Peer aborted the association
+  DCMSCU_PEER_ABORTED_ASSOCIATION
+};
+
+/** Storage mode used for DICOM objects received via C-STORE
+ */
+enum DcmStorageMode
+{
+  /// Ignore any objects received via C-STORE
+  DCMSCU_STORAGE_IGNORE,
+  /// Try to store the objects to disk
+  DCMSCU_STORAGE_DISK,
+  /// Try to store to disk in bit-preserving mode. This is especially useful
+  /// for huge files that cannot fully be received in memory since the
+  /// data is directly streamed to disk. Originally, this was introduced for
+  /// DICOM signatures which can be kept valid this way.
+  DCMSCU_STORAGE_BIT_PRESERVING
+};
+
+
+/** Base class for C-FIND, C-MOVE and C-GET responses
+ */
+class DCMTK_DCMNET_EXPORT QRResponse
+{
+  public:
+
+  /** Standard constructor.
+   */
+  QRResponse() :
+    m_messageIDRespondedTo(0),
+    m_affectedSOPClassUID(),
+    m_dataset(NULL),
+    m_status(0),
+    m_statusDetail(NULL) {}
+
+  /** Destructor, cleans up internal memory (dataset if present).
+   */
+  virtual ~QRResponse() { delete m_dataset; delete m_statusDetail; }
+
+  /// The message ID responded to (mandatory response field,
+  /// equals message ID from request)
+  Uint16 m_messageIDRespondedTo;
+
+  /// Optional response field according to part 7 of the standard
+  /// If present, equals SOP Class UID from request.
+  OFString m_affectedSOPClassUID;
+
+  /// Conditional response field (NULL if absent). From the standard (2009,
+  /// part 4, C.4.2.1.4.2), for C-MOVE: In Q/R if no C-STORE sub-operation
+  /// failed, Failed SOP Instance UID List (0008,0058) is absent and
+  /// therefore no Data Set shall be sent in the C-MOVE response. Further
+  /// rules: Statuses of Canceled, Failure, Refused, or Warning shall
+  /// contain the Failed SOP Instance UID List Attribute; status of
+  /// Pending shall not.
+  DcmDataset *m_dataset;
+
+  /// The returned DIMSE status (mandatory Response Field)
+  Uint16 m_status;
+
+  /// Status detail (NULL if absent). For some DIMSE return status codes,
+  /// an additional dataset is sent which gives further information (i.e.
+  /// in case of warnings or errors).
+  DcmDataset *m_statusDetail;
+
+private:
+
+  /** Private undefined copy constructor.
+   * @param other The find response to copy from
+   */
+  QRResponse(const QRResponse& other);
+
+  /** Private undefined assignment operator.
+   *  @param other The find response that should be assigned from
+   */
+  QRResponse& operator=(const QRResponse& other);
+};
+
+
+/// Base class representing for single C-GET or C-MOVE response
+class DCMTK_DCMNET_EXPORT RetrieveResponse : public QRResponse
+{
+public:
+  /** Standard constructor
+   */
+  RetrieveResponse() :
+        m_numberOfRemainingSubops(0),
+        m_numberOfCompletedSubops(0),
+        m_numberOfFailedSubops(0),
+        m_numberOfWarningSubops(0) {}
+
+  /** Destructor, cleans up internal memory
+   */
+  virtual ~RetrieveResponse() {}
+
+  /** Prints response to INFO log level.
+   */
+  void print();
+
+  /// Number of remaining sub operations (in Q/R: C-STORE calls).
+  /// For Q/R MOVE and GET, for status of pending this field shall be filled.
+  /// For others, the field may be filled.
+  Uint16 m_numberOfRemainingSubops;
+
+  /// Number of successfully completed sub operations (in Q/R: C-STORE calls).
+  /// For Q/R MOVE and GET, for status of pending this field shall be filled.
+  /// For others, the field may be filled.
+  Uint16 m_numberOfCompletedSubops;
+
+  /// Number of failed sub operations (in Q/R: C-STORE calls).
+  /// For Q/R MOVE and GET, for status of pending this field shall be filled.
+  /// For others, the field may be filled.
+  Uint16 m_numberOfFailedSubops;
+
+  /// Number generated warnings generated by sub operations (in Q/R: C-STORE calls).
+  /// For Q/R MOVE and GET, for status of pending this field shall be filled.
+  /// For others, the field may be filled.
+  Uint16 m_numberOfWarningSubops;
+
+private:
+
+  /** Private undefined copy constructor
+   *  @param other Response to copy from
+   */
+  RetrieveResponse(const RetrieveResponse& other);
+
+  /** Private undefined assignment operator
+   *  @param other Response that should be assigned from
+   */
+  RetrieveResponse& operator=(const RetrieveResponse& other);
+};
+
+
+/** Base class for implementing DICOM Service Class User functionality. The class offers
+ *  support for negotiating associations and sending and receiving arbitrary DIMSE messages
+ *  on that connection. DcmSCU has built-in C-ECHO support so derived classes do not have to
+ *  implement that capability on their own.
+ *  @warning This class is EXPERIMENTAL. Be careful to use it in production environment.
+ */
+class DCMTK_DCMNET_EXPORT DcmSCU
+{
+
+public:
+
+  /** Constructor, just initializes internal class members
+   */
+  DcmSCU();
+
+  /** Virtual destructor
+   */
+  virtual ~DcmSCU();
+
+  /** Add presentation context to be used for association negotiation
+   *  @param abstractSyntax [in] Abstract syntax name in UID format
+   *  @param xferSyntaxes   [in] List of transfer syntaxes to be added for the given abstract
+   *                             syntax
+   *  @param role           [in] The role to be negotiated
+   *  @return EC_Normal if adding was successful, otherwise error code
+   */
+  OFCondition addPresentationContext(const OFString &abstractSyntax,
+                                     const OFList<OFString> &xferSyntaxes,
+                                     const T_ASC_SC_ROLE role = ASC_SC_ROLE_DEFAULT);
+
+  /** Initialize network, i.e.\ prepare for association negotiation. If the SCU is already
+   *  connected, the call will not be successful and the old connection keeps open.
+   *  @return EC_Normal if initialization was successful, otherwise error code.
+   *          NET_EC_AlreadyConnected if SCU is already connected.
+   */
+  virtual OFCondition initNetwork();
+
+  /** Negotiate association by using presentation contexts and parameters as defined by
+   *  earlier function calls. If negotiation fails, there is no need to close the association
+   *  or to do anything else with this class.
+   *  @return EC_Normal if negotiation was successful, otherwise error code.
+   *          NET_EC_AlreadyConnected if SCU is already connected.
+   */
+  virtual OFCondition negotiateAssociation();
+
+  /** After negotiation association, this call returns the first usable presentation context
+   *  given the desired abstract syntax and transfer syntax
+   *  @param abstractSyntax [in] The abstract syntax (UID) to look for
+   *  @param transferSyntax [in] The transfer syntax (UID) to look for. If empty, the transfer
+   *                             syntax is not checked.
+   *  @return Adequate Presentation context ID that can be used. 0 if none found.
+   */
+  T_ASC_PresentationContextID findPresentationContextID(const OFString &abstractSyntax,
+                                                        const OFString &transferSyntax);
+
+  /** After a successful association negotiation, this function is called to return the
+   *  presentation context ID that best matches the desired abstract syntax and transfer
+   *  syntax (TS). The function tries to do the following:
+   *  - If possible finds a presentation context with matching TS
+   *  - Else then tries to find an explicit VR uncompressed TS presentation context
+   *  - Else then tries to find an implicit VR uncompressed TS presentation context
+   *  - Else finally accepts each matching presentation ctx independent of TS.
+   *  @param abstractSyntax [in] The abstract syntax (UID) to look for
+   *  @param transferSyntax [in] The transfer syntax (UID) to look for. If empty, the transfer
+   *                             syntax is not checked.
+   *  @return Adequate Presentation context ID that can be used. 0 if no appropriate
+   *  presentation context could be found at all.
+   */
+   T_ASC_PresentationContextID findAnyPresentationContextID(const OFString &abstractSyntax,
+                                                            const OFString &transferSyntax);
+
+  /** This function sends a C-ECHO command via network to another DICOM application
+   *  @param presID [in] Presentation context ID to use. A value of 0 lets SCP class tries
+   *                     to choose one on its own.
+   *  @return EC_Normal if echo was successful, an error code otherwise
+   *
+   */
+  virtual OFCondition sendECHORequest(const T_ASC_PresentationContextID presID);
+
+  /** This function sends a C-STORE request on the currently opened association and receives
+   *  the corresponding response then. If required and supported, the dataset of the SOP
+   *  instance can be converted automatically to the network transfer syntax that was
+   *  negotiated (and is specified by the parameter 'presID'). However, this feature is
+   *  disabled by default. See setDatasetConversionMode() on how to enable it.
+   *  @param presID        [in]  Contains in the end the ID of the presentation context which
+   *                             was specified in the DIMSE command. If 0 is given, the
+   *                             function tries to find an approriate presentation context
+   *                             itself (based on SOP class and original transfer syntax of
+   *                             the 'dicomFile' or 'dataset').
+   *  @param dicomFile     [in]  The filename of the DICOM file to be sent. Alternatively, a
+   *                             dataset can be given in the next parameter. If both are given
+   *                             the dataset from the file name is used.
+   *  @param dataset       [in]  The dataset to be sent. Alternatively, a filename can be
+   *                             specified in the previous parameter. If both are given the
+   *                             dataset from the filename is used.
+   *  @param rspStatusCode [out] The response status code received. 0 means success, others
+   *                             can be found in the DICOM standard.
+   *  @return EC_Normal if request could be issued and response was received successfully,
+   *          error code otherwise. That means that if the receiver sends a response denoting
+   *          failure of the storage request, EC_Normal will be returned.
+   */
+  virtual OFCondition sendSTORERequest(const T_ASC_PresentationContextID presID,
+                                       const OFString &dicomFile,
+                                       DcmDataset *dataset,
+                                       Uint16 &rspStatusCode);
+
+  /** Sends a C-MOVE Request on given presentation context and receives list of responses.
+   *  The function receives the first response and then calls the function handleMOVEResponse()
+   *  which gets the relevant presentation context together with the response dataset and
+   *  status information. Then it waits again for the next response, if there are more to
+   *  come (i.e. response status is PENDING). In the end, after receiving all responses, the
+   *  full list of responses is returned to the caller. If he is not interested, he just sets
+   *  responses=NULL when calling the function.
+   *  This function can be overwritten by actual SCU implementations but just should work fine
+   *  for most people.
+   *  @param presID                 [in]  The presentation context ID that should be used.
+   *                                      Must be an odd number.
+   *  @param moveDestinationAETitle [in]  The move destination's AE title, i.e.\ the one that
+   *                                      is used for connection to the storage server.
+   *  @param dataset                [in]  The dataset containing the information about the
+   *                                      object(s) to be retrieved.
+   *  @param responses              [out] The incoming C-MOVE responses for this request.
+   *                                      The caller is responsible for providing a non-NULL
+   *                                      pointer for this case. After receiving the results,
+   *                                      the caller is responsible for freeing the memory of
+   *                                      this variable. If NULL is specified, the responses
+   *                                      will not bereturned to the caller.
+   *  @return EC_Normal if everything went fine, i.e.\ if request could be send and responses
+   *          (with whatever status) could be received.
+   */
+  virtual OFCondition sendMOVERequest(const T_ASC_PresentationContextID presID,
+                                      const OFString &moveDestinationAETitle,
+                                      DcmDataset *dataset,
+                                      OFList<RetrieveResponse*> *responses);
+
+  /** This is the standard handler for C-MOVE message responses: It just adds up all responses
+   *  it receives and prints a DEBUG message. Therefore, it is called for each response
+   *  received in sendMOVERequest(). The idea is of course to overwrite this function in a
+   *  derived, actual SCU implementation if required. Thus, after each response, the caller of
+   *  sendMOVERequest() can decide on its own whether he wants to cancel the C-MOVE session,
+   *  terminate the association, do something useful or whatever. Thus this function is a more
+   *  object-oriented kind of callback.
+   *  @param presID              [in]  The presentation context ID where the response was
+   *                                   received on.
+   *  @param response            [in]  The C-MOVE response received.
+   *  @param waitForNextResponse [out] Denotes whether SCU should try to receive another
+   *                                   response. If set to OFTrue, then sendMOVERequest() will
+   *                                   continue waiting for responses. The current
+   *                                   implementation does that for all responses do not have
+   *                                   status Failed, Warning, Success or unknown. If set to
+   *                                   OFFalse, sendMOVERequest() will return control to the
+   *                                   caller.
+   *  @return EC_Normal, if response could be handled. Error code otherwise.
+   *          The current implementation always returns EC_Normal.
+   */
+  virtual OFCondition handleMOVEResponse(const T_ASC_PresentationContextID presID,
+                                         RetrieveResponse *response,
+                                         OFBool &waitForNextResponse);
+
+  /** Sends a C-GET Request on given presentation context and receives list of responses. It
+   *  then switches control to the function handleCGETSession().
+   *  The full list of responses is returned to the caller. If he is not interested, he can
+   *  set responses=NULL when calling the function.
+   *  This function can be overwritten by actual SCU implementations but just should work fine
+   *  for most people.
+   *  @param presID    [in]  The presentation context ID that should be used. Must be an odd
+   *                         number.
+   *  @param dataset   [in]  The dataset containing the information about the
+   *                         object(s) to be retrieved
+   *  @param responses [out] The incoming C-GET responses for this request. If the caller
+   *                         specifies NULL, no responses will be returned; otherwise there
+   *                         should be at least one final C-GET response (mandatory). C-GET
+   *                         responses after each DICOM object received are optional and may
+   *                         have been ommitted by the server.
+   *  @return EC_Normal if everything went fine, i.e.\ if request could be sent and expected
+   *          responses (with whatever status) could be received.
+   */
+  virtual OFCondition sendCGETRequest(const T_ASC_PresentationContextID presID,
+                                      DcmDataset *dataset,
+                                      OFList<RetrieveResponse*> *responses);
+
+  /** Does the logic for switching between C-GET Response and C-STORE Requests. Sends a C-GET
+   *  Request on given presentation context and receives list of responses. Ihe full list of
+   *  responses is returned to the caller. If he is not interested, he can set responses=NULL
+   *  when calling the function. After sending a C-GET Request, there might be two different
+   *  responses coming in: C-GET-RSP (optional after each received object and mandatory after
+   *  the last object) or a mandatory C-STORE for each incoming object that is received due to
+   *  the request. This function therefore either calls handleCGETResponse() or
+   *  handleSTORERequest() in order to deal with the incoming message. All other messages lead
+   *  to an error within this handler.
+   *  This function can be overwritten by actual SCU implementations but just should work fine
+   *  for most people.
+   *  @param presID    [in]  The presentation context ID that should be used. Must be an odd
+   *                         number.
+   *  @param dataset   [in]  The dataset containing the information about the object(s) to be
+   *                         retrieved
+   *  @param responses [out] The incoming C-GET responses for this request. If the caller
+   *                         specifies NULL, no responses will be returned; otherwise there
+   *                         should be at least one final C-GET response (mandatory). C-GET
+   *                         responses after each DICOM object received are optional and may
+   *                         have been ommitted by the server.
+   *  @return EC_Normal if everything went fine, i.e.\ if request could be send
+   *          and expected responses (with whatever status) could be received.
+   */
+  virtual OFCondition handleCGETSession(const T_ASC_PresentationContextID presID,
+                                        DcmDataset *dataset,
+                                        OFList<RetrieveResponse*> *responses);
+
+  /** Function handling a single C-GET Response. This standard handler reads the status of the
+   *  response and decides whether to receive any further messages related to the original
+   *  C-GET Request or whether the last response was received or an error occured.
+   *  @param presID              [in]  The presentation context the C-GET Response was
+   *                                   received on.
+   *  @param response            [in]  The response received
+   *  @param continueCGETSession [out] Defines whether it is decided to wait for further C-GET
+   *                                   Responses/C-STORE Requests within this C-GET session
+   *  @return If no errors occur (dataset response NULL, SCU not connected), this method will
+   *          return EC_Normal, otherwise error code.
+   */
+  virtual OFCondition handleCGETResponse(const T_ASC_PresentationContextID presID,
+                                         RetrieveResponse* response,
+                                         OFBool& continueCGETSession);
+
+  /** Function handling a single C-STORE Request. If storage mode is set to disk (default),
+   *  this function is called and the incoming object stored to disk.
+   *  @param presID              [in]  The presentation context the C-STORE Response was
+   *                                   received on.
+   *  @param incomingObject      [in]  The dataset (the object) received
+   *  @param continueCGETSession [out] Defines whether it is decided to wait for further
+   *                                   C-GET Responses/C-STORE requests within this C-GET
+   *                                   session.
+   *  @param cStoreReturnStatus  [out] Denotes the desired C-STORE return status.
+   *  @return If errors occur (incomingObject NULL or SCU not connected or file could not be
+   *          stored), this method will return an error code otherwise EC_Normal.
+   */
+  virtual OFCondition handleSTORERequest(const T_ASC_PresentationContextID presID,
+                                         DcmDataset *incomingObject,
+                                         OFBool& continueCGETSession,
+                                         Uint16& cStoreReturnStatus);
+
+  /** Function handling a single C-STORE Request. If storage mode is set to bit preserving,
+   *  this function is called and the incoming object stored directly to disk, i.e. not stored
+   *  fully in memory.
+   *  @param presID   [in] The presentation context the C-STORE Response was received on.
+   *  @param filename [in] The filename to store to
+   *  @param request  [in] The incoming C-STORE request command set
+   *  @return If errors occur (incomingObject NULL or SCU not connected filename not
+   *          specified), this method will return an error code otherwise EC_Normal.
+   */
+  virtual OFCondition handleSTORERequestFile(T_ASC_PresentationContextID *presID,
+                                             const OFString& filename,
+                                             T_DIMSE_C_StoreRQ* request);
+
+  /** This function is called if an object was received due to a C-GET request and can be
+   *  overwritten by a user in order to be informed about such an event. The default
+   *  implementation just prints a DEBUG message. Note that this function is not called if
+   *  the SCU is in storage mode DCMSCU_STORAGE_IGNORE.
+   *  @param filename       [in] The filename written
+   *  @param sopClassUID    [in] The SOP Class UID of the object written
+   *  @param sopInstanceUID [in] The SOP Instance UID of the object written
+   */
+  virtual void notifyInstanceStored(const OFString& filename,
+                                    const OFString& sopClassUID,
+                                    const OFString& sopInstanceUID) const;
+
+  /** Sends a C-FIND Request on given presentation context and receives list of responses.
+   *  The function receives the first response and then calls the function handleFINDResponse
+   *  which gets the relevant presentation context together with the response dataset and
+   *  status information. Then it waits again for the next response, if there are more to
+   *  come (i.e. response status is PENDING). In the end, after receiving all responses, the
+   *  full list of responses is returned to the caller. If he is not interested, he just sets
+   *  responses=NULL when calling the function.
+   *  This function can be overwritten by actual SCU implementations but just should work fine
+   *  for most people.
+   *  @param presID    [in]  The presentation context ID that should be used. Must be an odd
+   *                         number.
+   *  @param queryKeys [in]  The dataset containing the query keys to be searched for on the
+   *                         server (SCP).
+   *  @param responses [out] The incoming C-FIND responses for this request. The caller is
+   *                         responsible for providing a non-NULL pointer for this case.
+   *                         After receiving the results, the caller is responsible for
+   *                         freeing the memory of this variable. If NULL is specified, the
+   *                         responses will be not returned to the caller.
+   *  @return EC_Normal if everything went fine, i.e.\ if request could be send and responses
+   *          (with whatever status) could be received.
+   */
+  virtual OFCondition sendFINDRequest(const T_ASC_PresentationContextID presID,
+                                      DcmDataset *queryKeys,
+                                      OFList<QRResponse*> *responses);
+
+  /** This is the standard handler for C-FIND message responses: It just adds up all responses
+   *  it receives and prints a DEBUG message. Therefore, it is called for each response
+   *  received in sendFINDRequest(). The idea is of course to overwrite this function in a
+   *  derived, actual SCU implementation if required. Thus, after each response, the caller of
+   *  sendFINDRequest() can decide on its own whether he wants to cancel the C-FIND session,
+   *  terminate the association, do something useful or whatever. That way this is a more
+   *  object-oriented kind of callback.
+   *  @param presID              [in]  The presentation context ID where the response was
+   *                                   received on.
+   *  @param response            [in]  The C-FIND response received.
+   *  @param waitForNextResponse [out] Denotes whether SCU should try to receive another
+   *                                   response. If set to OFTrue, then sendFINDRequest()
+   *                                   will continue waiting for responses. The current
+   *                                   implementation does that for all responses do not have
+   *                                   status SUCESSS. If set to OFFalse, sendFINDRequest()
+   *                                   will return control to the caller.
+   *  @return EC_Normal, if response could be handled. Error code otherwise.
+   *          The current implementation always returns EC_Normal.
+   */
+  virtual OFCondition handleFINDResponse(const T_ASC_PresentationContextID  presID,
+                                         QRResponse *response,
+                                         OFBool &waitForNextResponse);
+
+  /** Send C-CANCEL and, therefore, ends the C-FIND -GET or -MOVE session, i.e.\ no further
+   *  responses will be handled. A call to this function only makes sense if an association
+   *  is open, the given presentation context represents a valid C-FIND/GET/MOVE-enabled SOP
+   *  class and usually only, if the last command send on that presentation context was a
+   *  C-FIND message.
+   *  @param presID [in] The presentation context ID where the C-CANCEL should be sent on.
+   *  @return The current implementation always returns EC_Normal.
+   */
+  virtual OFCondition sendCANCELRequest(const T_ASC_PresentationContextID presID);
+
+  /** This function sends a N-ACTION request on the currently opened association and receives
+   *  the corresponding response then
+   *  @param presID         [in]  The ID of the presentation context to be used for sending
+   *                              the request message. Should not be 0.
+   *  @param sopInstanceUID [in]  The requested SOP Instance UID
+   *  @param actionTypeID   [in]  The action type ID to be used
+   *  @param reqDataset     [in]  The dataset to be sent
+   *  @param rspStatusCode  [out] The response status code received. 0 means success,
+   *                              others can be found in the DICOM standard.
+   *  @return EC_Normal if request could be issued and response was received successfully,
+   *          an error code otherwise
+   */
+  virtual OFCondition sendACTIONRequest(const T_ASC_PresentationContextID presID,
+                                        const OFString &sopInstanceUID,
+                                        const Uint16 actionTypeID,
+                                        DcmDataset *reqDataset,
+                                        Uint16 &rspStatusCode);
+
+  /** This function sends N-EVENT-REPORT request and receives the corresponding response
+   *  @param presID         [in]  The ID of the presentation context to be used for sending
+   *                              the request message. Should not be 0.
+   *  @param sopInstanceUID [in]  The requested SOP Instance UID
+   *  @param eventTypeID    [in]  The event type ID to be used
+   *  @param reqDataset     [in]  The request dataset to be sent
+   *  @param rspStatusCode  [out] The response status code received. 0 means success,
+   *                              others can be found in the DICOM standard.
+   *  @return EC_Normal if request could be issued and response was received successfully,
+   *          an error code otherwise
+   */
+  virtual OFCondition sendEVENTREPORTRequest(const T_ASC_PresentationContextID presID,
+                                             const OFString &sopInstanceUID,
+                                             const Uint16 eventTypeID,
+                                             DcmDataset *reqDataset,
+                                             Uint16 &rspStatusCode);
+
+  /** Receives N-EVENT-REPORT request on the currently opened association and sends a
+   *  corresponding response. Calls checkEVENTREPORTRequest() in order to determine the
+   *  DIMSE status code to be used for the N-EVENT-REPORT response.
+   *  @param reqDataset  [out] Pointer to the dataset received
+   *  @param eventTypeID [out] Event Type ID from the command set received
+   *  @param timeout     [in]  Optional timeout in seconds for receiving data. This value
+   *                           (if not 0) overwrites the standard DIMSE timeout and also
+   *                           enables the non-blocking mode for receiving the request.
+   *  @return status, EC_Normal if successful, an error code otherwise
+   */
+  virtual OFCondition handleEVENTREPORTRequest(DcmDataset *&reqDataset,
+                                               Uint16 &eventTypeID,
+                                               const int timeout = 0);
+
+  /** This function is called while sending DIMSE messages, i.e.\ on each PDV of a dataset.
+   *  The default implementation just prints a TRACE message on the number of bytes sent so
+   *  far. By overwriting this method, the progress of the send process can be shown to the
+   *  user in a more appropriate way. The progress notification can also be disabled
+   *  completely by calling setProgressNotificationMode().
+   *  @param byteCount [in] Number of bytes sent so far
+   */
+  virtual void notifySENDProgress(const unsigned long byteCount);
+
+  /** This function is called while receiving DIMSE messages, i.e.\ on each PDV of a dataset.
+   *  The default implementation just prints a TRACE message on the number of bytes received
+   *  so far. By overwriting this method, the progress of the receive process can be shown to
+   *  the user in a more appropriate way. The progress notification can also be disabled
+   *  completely by calling setProgressNotificationMode().
+   *  @param byteCount [in] Number of bytes received so far
+   */
+  virtual void notifyRECEIVEProgress(const unsigned long byteCount);
+
+  /** Closes the association created by this SCU. Also resets the current association.
+   *  @param closeType [in] Define whether to release or abort the association
+   */
+  virtual void closeAssociation(const DcmCloseAssociationType closeType);
+
+  /* Set methods */
+
+  /** Set maximum PDU length (to be received by SCU)
+   *  @param maxRecPDU [in] The maximum PDU size to use in bytes
+   */
+  void setMaxReceivePDULength(const unsigned long maxRecPDU);
+
+  /** Set whether to send in DIMSE blocking or non-blocking mode
+   *  @param blockingMode [in] Either blocking or non-blocking mode
+   */
+  void setDIMSEBlockingMode(const T_DIMSE_BlockingMode blockingMode);
+
+  /** Set SCU's AETitle to be used in association negotiation
+   *  @param myAETtitle [in] The SCU's AETitle to be used
+   */
+  void setAETitle(const OFString &myAETtitle);
+
+  /** Set SCP's host (hostname or IP address) to talk to in association negotiation
+   *  @param peerHostName [in] The SCP's hostname or IP address to be used
+   */
+  void setPeerHostName(const OFString &peerHostName);
+
+  /** Set SCP's AETitle to talk to in association negotiation
+   *  @param peerAETitle [in] The SCP's AETitle to be used
+   */
+  void setPeerAETitle(const OFString &peerAETitle);
+
+  /** Set SCP's port number to connect to for association negotiation
+   *  @param peerPort [in] The SCP's port number
+   */
+  void setPeerPort(const Uint16 peerPort);
+
+  /** Set timeout for receiving DIMSE messages
+   *  @param dimseTimeout [in] DIMSE Timeout in seconds for receiving data. If the blocking
+   *                           mode is DIMSE_NONBLOCKING and we are trying to read data from
+   *                           the incoming socket stream and no data has been received.
+   */
+  void setDIMSETimeout(const Uint32 dimseTimeout);
+
+  /** Set timeout for receiving ACSE messages
+   *  @param acseTimeout [in] ACSE Timeout in seconds used by timer for message timeouts
+   *                          during association negotiation
+   */
+  void setACSETimeout(const Uint32 acseTimeout);
+
+  /** Set an association configuration file and profile to be used
+   *  @param filename [in] File name of the association configuration file
+   *  @param profile  [in] Profile inside the association negotiation file
+   */
+  void setAssocConfigFileAndProfile(const OFString &filename,
+                                    const OFString &profile);
+
+  /** Set the directory that should be used by the standard C-GET handler to store objects
+   *  that come in with the corresponding C-STORE rqeuests
+   *  @param storeDir [in] The directory to store to. It is checked in handleSTORERequest()
+   *                       whether the directory is writeable and readable. Per default, the
+   *                       received objects are stored in the current working directory.
+   */
+  void setStorageDir(const OFString& storeDir);
+
+  /** Set the storage mode to be used. Default is DCMSCU_STORAGE_DISK.
+   *  @param storageMode The storage mode to be used.
+   */
+  void setStorageMode(const DcmStorageMode storageMode);
+
+  /** Set whether to show presentation contexts in verbose or debug mode
+   *  @param mode [in] Show presentation contexts in verbose mode if OFTrue. By default, the
+   *                   presentation contexts are shown in debug mode.
+   */
+  void setVerbosePCMode(const OFBool mode);
+
+  /** Set the mode that specifies whether the transfer syntax of the dataset can be changed
+   *  for network transmission. This mainly covers the compression/decompression of datasets,
+   *  which is disabled by default.
+   *  @param mode [in] Allow dataset conversion if OFTrue
+   */
+  void setDatasetConversionMode(const OFBool mode);
+
+  /** Set the mode that specifies whether the progress of sending and receiving DIMSE
+   *  messages is notified by calling notifySENDProgress() and notifyRECEIVEProgress(),
+   *  respectively. The progress notification is enabled by default.
+   *  @param mode [in] Disable progress notification if OFFalse
+   */
+  void setProgressNotificationMode(const OFBool mode);
+
+  /* Get methods */
+
+  /** Get current connection status
+   *  @return OFTrue if SCU is currently connected, OFFalse otherwise
+   */
+  OFBool isConnected() const;
+
+  /** Returns maximum PDU length configured to be received by SCU
+   *  @return Maximum PDU length in bytes
+   */
+  Uint32 getMaxReceivePDULength() const;
+
+  /** Returns whether DIMSE messaging is configured to be blocking or unblocking
+   *  @return The blocking mode configured
+   */
+  T_DIMSE_BlockingMode getDIMSEBlockingMode() const;
+
+  /** Returns the SCU's own configured AETitle
+   *  @return The AETitle configured for this SCU
+   */
+  const OFString &getAETitle() const;
+
+  /** Returns the SCP's (peer's) host name configured
+   *  @return The hostname (or IP) configured to be talked to
+   */
+  const OFString &getPeerHostName() const;
+
+  /** Returns the SCP's (peer's) AETitle configured
+   *  @return The AETitle configured to be talked to
+   */
+  const OFString &getPeerAETitle() const;
+
+  /** Returns the SCP's (peer's) TCP/IP port configured
+   *  @return The port configured to talked to
+   */
+  Uint16 getPeerPort() const;
+
+  /** Returns the DIMSE timeout configured defining how long SCU will wait for DIMSE responses
+   *  @return The DIMSE timeout configured
+   */
+  Uint32 getDIMSETimeout() const;
+
+  /** Returns the timeout configured defining how long SCU will wait for messages during ACSE
+   *  messaging (association negotiation)
+   *  @return The ACSE timeout configured
+   */
+  Uint32 getACSETimeout() const;
+
+  /** Returns the storage directory used for storing objects received with C-STORE requests
+   *  in the context of C-GET sessions. Default is empty string which refers to the current
+   *  working directory.
+   *  @return The storage directory
+   */
+  OFString getStorageDir() const;
+
+  /** Returns the storage mode enabled
+   *  @return The storage mode enabled
+   */
+  DcmStorageMode getStorageMode() const;
+
+  /** Returns the verbose presentation context mode configured specifying whether details
+   *  on the presentation contexts (negotiated during association setup) should be shown in
+   *  verbose or debug mode. The latter is the default.
+   *  @return The current verbose presentation context mode. Show details on the
+   *          presentation contexts on INFO log level (verbose) if OFTrue and on DEBUG
+   *          level if OFFalse.
+   */
+  OFBool getVerbosePCMode() const;
+
+  /** Returns the mode that specifies whether the transfer syntax of the dataset can be
+   *  changed for network transmission. This mainly covers the compression/decompression
+   *  of datasets, which is disabled by default.
+   *  @return The current dataset conversion mode, enabled if OFTrue
+   */
+  OFBool getDatasetConversionMode() const;
+
+  /** Returns the mode that specifies whether the progress of sending and receiving DIMSE
+   *  messages is notified by calling notifySENDProgress() and notifyRECEIVEProgress(),
+   *  respectively. The progress notification is enabled by default.
+   *  @return The current progress notification mode, enabled if OFTrue
+   */
+  OFBool getProgressNotificationMode() const;
+
+  /** Returns whether SCU is configured to create a TLS connection with the SCP
+   *  @return OFFalse for this class but may be overriden by derived classes
+   */
+  OFBool getTLSEnabled() const;
+
+  /** Deletes internal networking structures from memory */
+  void freeNetwork();
+
+protected:
+
+  /** Sends a DIMSE command and possibly also a dataset from a data object via network to
+   *  another DICOM application
+   *  @param presID     [in]  Presentation context ID to be used for message
+   *  @param msg        [in]  Structure that represents a certain DIMSE command which
+   *                          shall be sent
+   *  @param dataObject [in]  The instance data which shall be sent to the other DICOM
+   *                          application; NULL, if there is none
+   *  @param commandSet [out] If this parameter is not NULL it will return a copy of the
+   *                          DIMSE command which is sent to the other DICOM application
+   *  @return EC_Normal if sending request was successful, an error code otherwise
+   */
+  OFCondition sendDIMSEMessage(const T_ASC_PresentationContextID presID,
+                               T_DIMSE_Message *msg,
+                               DcmDataset *dataObject,
+                               DcmDataset **commandSet = NULL);
+
+  /** Returns SOP Class UID, SOP Instance UID and original transfer syntax for a given dataset.
+   *  If the dataset is NULL, all returned values will be undefined (i.e. empty or EXS_Unknown).
+   *  @param dataset        [in]  The dataset to read from
+   *  @param sopClassUID    [out] The value of attribute SOP Class UID if present
+   *  @param sopInstanceUID [out] The value of attribute SOP Instance UID if present
+   *  @param transferSyntax [out] The value of transfer syntax that originally was read from
+   *                              disk. Will be unknown if the dataset was created in memory.
+   *  @return EC_Normal if all information could be retrieved and is valid, an error code
+   *    otherwise
+   */
+  OFCondition getDatasetInfo(DcmDataset *dataset,
+                             OFString &sopClassUID,
+                             OFString &sopInstanceUID,
+                             E_TransferSyntax &transferSyntax);
+
+  /** Tells DcmSCU to use a secure TLS connection described by the given TLS layer
+   *  @param tlayer [in] The TLS transport layer including all TLS parameters
+   *  @return EC_Normal if given transport layer is ok, an error code otherwise
+   */
+  OFCondition useSecureConnection(DcmTransportLayer *tlayer);
+
+  /** Receive DIMSE command (excluding dataset!) over the currently open association
+   *  @param presID       [out] Contains in the end the ID of the presentation context
+   *                            which was specified in the DIMSE command received
+   *  @param msg          [out] The message received
+   *  @param statusDetail [out] If a non-NULL value is passed this variable will in the end
+   *                            contain detailed information with regard to the status
+   *                            information which is captured in the status element
+   *                            (0000,0900). Note that the value for element (0000,0900) is
+   *                            not contained in this return value but in internal msg. For
+   *                            details on the structure of this object, see DICOM standard
+   *                            part 7, annex C).
+   *  @param commandSet   [out] If this parameter is not NULL, it will return a copy of the
+   *                            DIMSE command which was received from the other DICOM
+   *                            application. The caller is responsible to de-allocate the
+   *                            returned object!
+   *  @param timeout      [in]  If this parameter is not 0, it specifies the timeout (in
+   *                            seconds) to be used for receiving the DIMSE command.
+   *                            Otherwise, the default timeout value is used (see
+   *                            setDIMSETimeout()).
+   *  @return EC_Normal if command could be received successfully, an error code otherwise
+   */
+  OFCondition receiveDIMSECommand(T_ASC_PresentationContextID *presID,
+                                  T_DIMSE_Message *msg,
+                                  DcmDataset **statusDetail,
+                                  DcmDataset **commandSet = NULL,
+                                  const Uint32 timeout = 0);
+
+  /** Receives one dataset (of instance data) via network from another DICOM application
+   *  @param presID     [out] Contains in the end the ID of the presentation context which
+   *                          was used in the PDVs that were received on the network. If the
+   *                          PDVs show different presentation context IDs, this function
+   *                          will return an error.
+   *  @param dataObject [out] Contains in the end the information which was received over
+   *                          the network
+   *  @return EC_Normal if dataset could be received successfully, an error code otherwise
+   */
+  OFCondition receiveDIMSEDataset(T_ASC_PresentationContextID *presID,
+                                  DcmDataset **dataObject);
+
+  /** clear list of presentation contexts. In addition, any currently selected association
+   *  configuration file is disabled.
+   */
+  void clearPresentationContexts();
+
+   /** After negotiation association, this call returns the presentation context belonging
+    *  to the given presentation context ID
+    *  @param presID         [in]  The presentation context ID to look for
+    *  @param abstractSyntax [out] The abstract syntax (UID) for that ID.
+    *                              Empty, if such a presentation context does not exist.
+    *  @param transferSyntax [out] The transfer syntax (UID) for that ID.
+    *                              Empty, if such a presentation context does not exist.
+    */
+  void findPresentationContext(const T_ASC_PresentationContextID presID,
+                               OFString &abstractSyntax,
+                               OFString &transferSyntax);
+
+  /** Check given N-EVENT-REPORT request and dataset for validity. This method is called by
+   *  handleEVENTREPORTRequest() before sending the response in order to determine the
+   *  DIMSE status code to be used for the response message.
+   *  @param request    [in] The N-EVENT-REPORT request message data structure
+   *  @param reqDataset [in] The N-EVENT-REPORT request dataset received. Might be NULL.
+   *  @return DIMSE status code to be used for the N-EVENT-REPORT response.
+   *          Always returns STATUS_Success (0). Derived classes should, therefore,
+   *          overwrite this method and return a more appropriate value based on the
+   *          result of the checks performed.
+   */
+  virtual Uint16 checkEVENTREPORTRequest(T_DIMSE_N_EventReportRQ &request,
+                                         DcmDataset *reqDataset);
+
+  /** Sends back a C-STORE response on the given presentation context, with the designated
+   *  status, fitting the corresponding C-STORE request.
+   *  @param presID  [in] The presentation context ID to be used.
+   *  @param status  [in] The storage DIMSE status to be used.
+   *  @param request [in] The C-STORE request that should be responded to.
+   *  @result EC_Normal if the response could be sent, error otherwise.
+   */
+  virtual OFCondition sendSTOREResponse(T_ASC_PresentationContextID presID,
+                                        Uint16 status,
+                                        const T_DIMSE_C_StoreRQ& request);
+
+  /** Helper function that generates a storage filename by extracting SOP Class and SOP
+   *  Instance UID from a dataset and combining that with the configured storage directory.
+   *  The SOP class is used to create an initial two letter abbreviation for the
+   *  corresponding modality, e.g. CT. An example for a storage filename created by this
+   *  function is /storage_dir/CT.1.2.3.4.5 for a CT with SOP Instance UID 1.2.3.4.
+   *  This function might be overwritten to change the filename behaviour completely. This
+   *  function is only called if the SCU is in DCMSCU_STORAGE_DISK mode.
+   *  @param dataset [in] The dataset that should be stored to disk
+   *  @result Non-empty string if successful, otherwise empty string.
+   */
+  virtual OFString createStorageFilename(DcmDataset *dataset);
+
+  /** Receives a DICOM dataset on a given presentation context ID but does not store it in
+   *  memory or disk, thus ignoring it.
+   *  @param presID  [in] The presentation context to be used
+   *  @param request [in] The corresponding C-STORE request
+   *  @return EC_Normal if ignoring worked, error code otherwise.
+   */
+  virtual OFCondition ignoreSTORERequest(T_ASC_PresentationContextID presID,
+                                         const T_DIMSE_C_StoreRQ& request);
+
+  /* Callback functions */
+
+  /** Callback function used for sending DIMSE messages.
+   *  @param callbackContext [in] The desired user callback data
+   *  @param byteCount       [in] Progress bytes count
+   */
+  static void callbackSENDProgress(void *callbackContext,
+                                   unsigned long byteCount);
+
+  /** Callback function used for receiving DIMSE messages.
+   *  @param callbackContext [in] The desired user callback data
+   *  @param byteCount       [in] Progress bytes count
+   */
+  static void callbackRECEIVEProgress(void *callbackContext,
+                                      unsigned long byteCount);
+
+private:
+
+  /** Private undefined copy-constructor. Shall never be called.
+   *  @param src Source object
+   */
+  DcmSCU(const DcmSCU &src);
+
+  /** Private undefined operator=. Shall never be called.
+   *  @param src Source object
+   *  @return Reference to this
+   */
+  DcmSCU &operator=(const DcmSCU &src);
+
+  /// Associaton of this SCU. This class only handles 1 association at a time.
+  T_ASC_Association *m_assoc;
+
+  /// The DICOM network the association is based on
+  T_ASC_Network *m_net;
+
+  /// Association parameters
+  T_ASC_Parameters *m_params;
+
+  /// Configuration file for presentation contexts (optional)
+  OFString m_assocConfigFilename;
+
+  /// Profile in configuration file that should be used (optional)
+  OFString m_assocConfigProfile;
+
+  /// Defines presentation context, consisting of one abstract syntax name
+  /// and a list of transfer syntaxes for this abstract syntax
+  struct DCMTK_DCMNET_EXPORT DcmSCUPresContext {
+    /** Default constructor
+     */
+    DcmSCUPresContext()
+    : abstractSyntaxName()
+    , transferSyntaxes()
+    , roleSelect(ASC_SC_ROLE_DEFAULT)
+    {
+    }
+    /// Abstract Syntax Name of Presentation Context
+    OFString abstractSyntaxName;
+    /// List of Transfer Syntaxes for Presentation Context
+    OFList<OFString> transferSyntaxes;
+    /// Role Selection
+    T_ASC_SC_ROLE roleSelect;
+  };
+
+  /// List of presentation contexts that should be negotiated
+  OFList<DcmSCUPresContext> m_presContexts;
+
+  /// Configuration file containing association parameters
+  OFString m_assocConfigFile;
+
+  /// The last DIMSE successfully sent, unresponded DIMSE request
+  T_DIMSE_Message *m_openDIMSERequest;
+
+  /// Maximum PDU size
+  Uint32 m_maxReceivePDULength;
+
+  /// DIMSE blocking mode
+  T_DIMSE_BlockingMode m_blockMode;
+
+  /// AEtitle of this application
+  OFString m_ourAETitle;
+
+  /// Peer hostname
+  OFString m_peer;
+
+  /// AEtitle of remote application
+  OFString m_peerAETitle;
+
+  /// Port of remote application entity
+  Uint16 m_peerPort;
+
+  /// DIMSE timeout
+  Uint32 m_dimseTimeout;
+
+  /// ACSE timeout
+  Uint32 m_acseTimeout;
+
+  /// Storage directory for objects received with C-STORE due to a
+  /// running C-GET session. Per default, the received objects
+  /// are stored in the current working directory.
+  OFString m_storageDir;
+
+  /// Set whether bit preserving storage should be enabled, i.e.\ any objects
+  /// retrieved via C-STORE should be written directly to disk without
+  /// any data correction/re-computation (e.g.\ group length calculations,
+  /// padding, etc.). This is especially interesting for retaining valid
+  /// signatures, and also to receive huge files which cannot be fully received
+  /// in memory. Default is OFFalse (no bit preserving) storage.
+  DcmStorageMode m_storageMode;
+
+  /// Verbose PC mode
+  OFBool m_verbosePCMode;
+
+  /// Dataset conversion mode
+  OFBool m_datasetConversionMode;
+
+  /// Progress notification mode
+  OFBool m_progressNotificationMode;
+
+  /** Returns next available message ID free to be used by SCU
+   *  @return Next free message ID
+   */
+  Uint16 nextMessageID();
+};
+
+#endif // SCU_H
+
+/*
+** CVS Log
+** $Log: scu.h,v $
+** Revision 1.41  2012-02-21 08:48:51  joergr
+** Added support for progress notifications while sending and receiving DICOM
+** datasets.
+**
+** Revision 1.40  2011-12-14 11:45:15  uli
+** Make it possible to perfectly build dcmnet and dcmtls a DLLs.
+**
+** Revision 1.39  2011-10-10 14:01:29  uli
+** Moved SCU-specific error condition to the correct place.
+**
+** Revision 1.38  2011-10-04 08:58:14  joergr
+** Added flag that allows for specifying whether to convert a dataset to be
+** transferred to the network transfer syntax. Also removed unused parameters
+** "rspCommandSet" and "rspStatusDetail" from method sendSTORERequest().
+**
+** Revision 1.37  2011-09-28 16:28:20  joergr
+** Added general support for transfer syntax conversions to sendSTORERequest().
+**
+** Revision 1.36  2011-09-28 15:25:34  joergr
+** Return a more appropriate error code in case the dataset to be sent is
+** invalid. This also required to introduce a return value for getDatasetInfo().
+**
+** Revision 1.35  2011-09-28 13:31:56  joergr
+** Added method that allows for clearing the list of presentation contexts.
+**
+** Revision 1.34  2011-09-22 13:49:03  joergr
+** Fixed incorrect comment on return code of some sendXXXRequest() methods.
+**
+** Revision 1.33  2011-09-06 16:11:06  ogazzar
+** Fixed typos in a log commit message.
+**
+** Revision 1.32  2011-09-06 12:57:36  ogazzar
+** Added a function to send N-EVENT-REPORT request and receive a reponse.
+**
+** Revision 1.31  2011-08-25 15:46:18  joergr
+** Further cleanup of minor inconsistencies regarding documentation, parameter
+** names, log output and handling of status details information.
+**
+** Revision 1.30  2011-08-25 15:05:06  joergr
+** Changed data structure for Q/R responses from OFVector to OFList. Also fixed
+** some possible memory leaks and made the FIND/MOVE/GET code more consistent.
+**
+** Revision 1.29  2011-08-25 13:46:28  joergr
+** Fixed minor issues in the API documentation, parameter and method names.
+**
+** Revision 1.28  2011-08-25 09:31:33  onken
+** Added C-GET functionality to DcmSCU class and accompanying getscu
+** commandline application.
+**
+** Revision 1.27  2011-05-25 09:57:54  ogazzar
+** Renamed a function name.
+**
+** Revision 1.26  2011-05-25 09:30:12  ogazzar
+** Added a function to look for a presentation context ID that best matches
+** the abstract syntax and the transfer syntax.
+**
+** Revision 1.25  2011-05-24 13:17:58  onken
+** Added missing default initializiation of role flag in presentation context
+** constructor.
+**
+** Revision 1.24  2011-05-24 08:28:08  ogazzar
+** Added support for role selection negotiation.
+**
+** Revision 1.23  2011-05-19 17:20:09  onken
+** Fixed some documentation.
+**
+** Revision 1.22  2011-05-19 09:57:26  onken
+** Fixed message ID field in C-CANCEL request (should be the one of last
+** request). In case of error status codes in C-MOVE responses, the default
+** behaviour is now to not wait for further responses. Fixed log output level
+** to better fit the messages while receiveing C-MOVE responses. Minor
+** code and comment cleanups. Renamed function parameter in sendMOVEREquest
+** to better reflect the standard.
+**
+** Revision 1.21  2011-05-17 14:43:55  onken
+** Removed some windows line endings.
+**
+** Revision 1.20  2011-05-17 14:34:32  onken
+** Completed doxygen documentation of DcmSCU class. Fixed some minor formatting
+** issues. Fixed CVS log at end of the file. Implemented C-CANCEL message.
+** Fixed some minor formatting issues. Changed C-ECHO implementation to rely
+** on sendDIMSEMesage as the other DIMSE functions do. Changed some public
+** function arguments to const to be more correct. Fixed CVS log at the end
+** of the file. Re-formatted CHANGES log entry from 2011-04-28.
+**
+** Revision 1.19 2011-05-17 14:26:19  onken
+** Implemented C-CANCEL message. Fixed some minor formatting issues.
+** Changed C-ECHO implementation to rely on sendDIMSEMesage as the other
+** DIMSE functions do. Changed some public function arguments to const to be
+** more correct. Fixed CVS log at the end of the scu.cc file.
+**
+** Revision 1.18 2011-04-28 17:50:05  onken
+** Protected public networking functions for creating an association
+** from being called twice. Enhanced protection of DIMSE messaging
+** functions from being called without being connected. Introduced
+** status detail into C-FIND responses (and C-MOVE responses). Was
+**  not accessible to the caller before. Minor code cleanups. Added
+** C-MOVE code for retrieving DICOM objects. So far only retrieving
+** on a separate connection is supported. Added function for cleaning
+** up internal memory from destructor. This function also fixes a
+** memory leak in case users call initNetwork more than one time.
+** Added error code returned by functions if SCU is already connected.
+**
+** Revision 1.17  2011-02-16 08:55:13  joergr
+** Fixed issue in sendSTORERequest() when sending a dataset that was created
+** in memory (and which has, therefore, an original transfer of EXS_Unknown).
+**
+** Revision 1.16  2011-02-04 12:57:40  uli
+** Made sure all members are initialized in the constructor (-Weffc++).
+**
+** Revision 1.15  2011-02-04 11:24:40  uli
+** Added private undefined functions where gcc's -Weffc++ warns otherwise.
+**
+** Revision 1.14  2011-02-01 09:01:19  joergr
+** Added "const" specifier to parameter in order to be consistent with the
+** source file (caused warnings/errors with certain compilers).
+**
+** Revision 1.13  2010-10-14 13:17:22  joergr
+** Updated copyright header. Added reference to COPYRIGHT file.
+**
+** Revision 1.12  2010-10-07 12:54:07  joergr
+** Fixed minor Doxygen API documentation issues (added backslash in order to
+** avoid that the short description ends at the first period).
+**
+** Revision 1.11  2010-06-24 09:21:54  joergr
+** Revised comment to make clear that the parameter "presID" shall never be 0
+** for the sendACTIONRequest() method.
+**
+** Revision 1.10  2010-06-22 15:45:27  joergr
+** Introduced new enumeration type to be used for closeAssociation().
+** Further code cleanup. Renamed some methods, variables, types and so on.
+**
+** Revision 1.9  2010-06-17 17:11:27  joergr
+** Added preliminary support for N-EVENT-REPORT to DcmSCU. Some further code
+** cleanups and enhancements. Renamed some methods. Revised documentation.
+**
+** Revision 1.8  2010-06-09 16:09:01  joergr
+** Added preliminary support for N-ACTION to DcmSCU. Some further code cleanups
+** and enhancements.
+**
+** Revision 1.7  2010-06-08 17:54:12  onken
+** Added C-FIND functionality to DcmSCU. Some code cleanups. Fixed
+** memory leak sometimes occuring during association configuration.
+**
+** Revision 1.6  2010-04-29 16:13:28  onken
+** Made SCU class independent from dcmtls, i.e. outsourced TLS API. Added
+** direct API support for sending C-STORE requests. Further API changes and
+** some bugs fixed.
+**
+** Revision 1.5  2009-12-21 17:00:32  onken
+** Fixed API documentation to keep doxygen quiet.
+**
+** Revision 1.4  2009-12-21 15:33:55  onken
+** Added documentation and refactored / enhanced some code.
+**
+** Revision 1.3  2009-12-17 09:12:10  onken
+** Fixed other scu and scp base class compile issues.
+**
+** Revision 1.2  2009-12-17 09:05:15  onken
+** Fixed typo resulting in build failure.
+**
+** Revision 1.1  2009-12-17 09:02:44  onken
+** Added base classes for SCU and SCP implementations.
+**
+** Revision 1.2  2009-12-02 14:26:05  uli
+** Stop including dcdebug.h which was removed.
+**
+** Revision 1.1  2008-09-29 13:51:52  onken
+** Initial checkin of module dcmppscu implementing an MPPS commandline client.
+**
+*/

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/dcmtkpp.git



More information about the debian-med-commit mailing list