dcmtk::log4cplus::threadCleanup() should be called by the user code in
order to clean-up oflog's thread local storage.
+DCMTK_MAX_SEQUENCE_NESTING
+ Affected: dcmdata
+ Type of modification: Compile-time tunable
+ Explanation: Defines the default maximum permitted sequence nesting depth
+ during DICOM parsing. Deeply nested sequences can cause a stack overflow
+ by exhausting the call stack through unbounded recursion. When this macro
+ is not defined, the default limit of 64 nested sequence levels is used.
+ Real-world DICOM data rarely exceeds 5-10 nesting levels. The limit can
+ be changed at runtime per parse operation via
+ DcmInputStream::setMaxNestingDepth(), DcmItem::setMaxNestingDepth(), or
+ DcmFileFormat::setMaxNestingDepth(). Setting the runtime value to -1
+ disables the check entirely.
+ Minimum value: 1. Values less than 1 cause a compile-time error.
+ Maximum value: 2147483647 (aligned to 32-bit signed integer runtime API).
+
DCMTK_MERGE_STDERR_TO_STDOUT
Affected: dcmdata
Type of modification: Activates feature
/*
*
- * Copyright (C) 1994-2025, OFFIS e.V.
+ * Copyright (C) 1994-2026, OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were developed by
extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_UnsupportedURIType;
/// Execution of command line failed
extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_CommandLineFailed;
+/// Maximum sequence nesting depth exceeded (stack overflow protection)
+extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_NestingDepthLimitExceeded;
///@}
/// implementation version name to write in the meta-header
OFString ImplementationVersionName;
+
+ /// maximum sequence nesting depth for parsing
+ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited)
+ Sint32 MaxNestingDepth;
+
+ public:
+
+ /** set the maximum permitted sequence nesting depth for parsing.
+ * Applied to the input stream in loadFile() and read(), and also
+ * forwarded to the contained dataset.
+ * - Value 0 (default): apply the compile-time default
+ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64)
+ * - Value -1: disable the check (allow unlimited nesting)
+ * - Value > 0: use this value as the maximum permitted nesting depth
+ * @param maxDepth maximum nesting depth setting
+ */
+ void setMaxNestingDepth(Sint32 maxDepth)
+ {
+ MaxNestingDepth = maxDepth;
+ DcmDataset* dset = getDataset();
+ if (dset)
+ dset->setMaxNestingDepth(maxDepth);
+ }
+
+ /** return the maximum permitted sequence nesting depth for parsing.
+ * @return maximum nesting depth setting
+ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited)
+ */
+ Sint32 getMaxNestingDepth() const { return MaxNestingDepth; }
};
/*
*
- * Copyright (C) 1994-2018, OFFIS e.V.
+ * Copyright (C) 1994-2026, OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were developed by
*/
virtual DcmInputStreamFactory *newFactory() const = 0;
+ /** returns the current sequence nesting depth.
+ * This counter is used to prevent stack overflow from deeply nested
+ * DICOM sequences in malicious input files.
+ * @return current nesting depth
+ */
+ Uint32 nestingDepth() const;
+
+ /** increments the sequence nesting depth counter.
+ * @return the new nesting depth after incrementing
+ */
+ Uint32 incrementNestingDepth();
+
+ /** decrements the sequence nesting depth counter.
+ * Does nothing if the counter is already zero.
+ */
+ void decrementNestingDepth();
+
+ /** returns the maximum permitted sequence nesting depth for this stream.
+ * A value of 0 means the compile-time default (DCMTK_MAX_SEQUENCE_NESTING) applies.
+ * A value of -1 means the check is disabled (unlimited nesting).
+ * @return maximum nesting depth setting
+ */
+ Sint32 maxNestingDepth() const;
+
+ /** sets the maximum permitted sequence nesting depth for this stream.
+ * Must be called before parsing begins.
+ * - Value 0 (default): apply the compile-time default
+ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64)
+ * - Value -1: disable the check (allow unlimited nesting)
+ * - Value > 0: use this value as the maximum permitted nesting depth
+ * @param maxDepth maximum nesting depth setting
+ */
+ void setMaxNestingDepth(Sint32 maxDepth);
+
/** marks the current stream position for a later putback operation,
* overwriting a possibly existing prior putback mark.
* The DcmObject read methods rely on the possibility to putback
/// putback marker
offile_off_t mark_;
+
+ /// current sequence nesting depth (for stack overflow protection)
+ Uint32 nestingDepth_;
+
+ /// maximum permitted sequence nesting depth (0 = default 64, -1 = unlimited)
+ Sint32 maxNestingDepth_;
};
/*
*
- * Copyright (C) 1994-2025, OFFIS e.V.
+ * Copyright (C) 1994-2026, OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were developed by
/// cache for private creator tags and identifiers
DcmPrivateTagCache privateCreatorCache;
+
+ /// maximum sequence nesting depth for parsing
+ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited)
+ Sint32 maxNestingDepth;
+
+ public:
+
+ /** set the maximum permitted sequence nesting depth for parsing.
+ * This limit is applied to the input stream before parsing in
+ * loadFile() and read().
+ * - Value 0 (default): apply the compile-time default
+ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64)
+ * - Value -1: disable the check (allow unlimited nesting)
+ * - Value > 0: use this value as the maximum permitted nesting depth
+ * @param maxDepth maximum nesting depth setting
+ */
+ void setMaxNestingDepth(Sint32 maxDepth) { maxNestingDepth = maxDepth; }
+
+ /** return the maximum permitted sequence nesting depth for parsing.
+ * @return maximum nesting depth setting
+ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited)
+ */
+ Sint32 getMaxNestingDepth() const { return maxNestingDepth; }
};
/** Checks whether left hand side item is smaller than right hand side
/*
*
- * Copyright (C) 1994-2024, OFFIS e.V.
+ * Copyright (C) 1994-2026, OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were developed by
// Undefined Length Identifier now defined in dctypes.h
+// Default maximum sequence nesting depth (can be overridden at compile time).
+// Must be in the range [1, 2147483647].
+#ifndef DCMTK_MAX_SEQUENCE_NESTING
+#define DCMTK_MAX_SEQUENCE_NESTING 64
+#endif
+#if DCMTK_MAX_SEQUENCE_NESTING < 1
+#error "DCMTK_MAX_SEQUENCE_NESTING must be >= 1"
+#endif
+#if DCMTK_MAX_SEQUENCE_NESTING > 2147483647
+#error "DCMTK_MAX_SEQUENCE_NESTING must be <= 2147483647"
+#endif
+
// Maximum number of read bytes for a Value Element
const Uint32 DCM_MaxReadLength = 4096;
{
/* use stdin stream */
DcmStdinStream inStream;
+ /* apply configured nesting depth limit */
+ if (getMaxNestingDepth() > 0)
+ inStream.setMaxNestingDepth(getMaxNestingDepth());
/* clear this object */
l_error = clear();
} else {
/* open file for input */
DcmInputFileStream fileStream(fileName);
+ /* apply configured nesting depth limit */
+ if (getMaxNestingDepth() > 0)
+ fileStream.setMaxNestingDepth(getMaxNestingDepth());
/* check stream status */
l_error = fileStream.status();
makeOFConditionConst(EC_BulkDataURINotSupported, OFM_dcmdata, 66, OF_error, "BulkDataURI not yet supported" );
makeOFConditionConst(EC_UnsupportedURIType, OFM_dcmdata, 67, OF_error, "Unsupported URI type" );
makeOFConditionConst(EC_CommandLineFailed, OFM_dcmdata, 68, OF_error, "Execution of command line failed" );
+makeOFConditionConst(EC_NestingDepthLimitExceeded, OFM_dcmdata, 69, OF_error, "Maximum sequence nesting depth exceeded" );
const unsigned short EC_CODE_CannotSelectCharacterSet = 35;
const unsigned short EC_CODE_CannotConvertCharacterSet = 36;
: DcmSequenceOfItems(DCM_InternalUseTag),
FileReadMode(ERM_autoDetect),
ImplementationClassUID(OFFIS_IMPLEMENTATION_CLASS_UID),
- ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME)
+ ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME),
+ MaxNestingDepth(0)
{
DcmMetaInfo *MetaInfo = new DcmMetaInfo();
DcmSequenceOfItems::itemList->insert(MetaInfo);
: DcmSequenceOfItems(DCM_InternalUseTag),
FileReadMode(ERM_autoDetect),
ImplementationClassUID(OFFIS_IMPLEMENTATION_CLASS_UID),
- ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME)
+ ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME),
+ MaxNestingDepth(0)
{
DcmMetaInfo *MetaInfo = new DcmMetaInfo();
DcmSequenceOfItems::itemList->insert(MetaInfo);
: DcmSequenceOfItems(old),
FileReadMode(old.FileReadMode),
ImplementationClassUID(old.ImplementationClassUID),
- ImplementationVersionName(old.ImplementationVersionName)
+ ImplementationVersionName(old.ImplementationVersionName),
+ MaxNestingDepth(old.MaxNestingDepth)
{
}
FileReadMode = obj.FileReadMode;
ImplementationClassUID = obj.ImplementationClassUID;
ImplementationVersionName = obj.ImplementationVersionName;
+ MaxNestingDepth = obj.MaxNestingDepth;
}
return *this;
{
/* use stdin stream */
DcmStdinStream inStream;
+ /* apply configured nesting depth limit */
+ if (MaxNestingDepth != 0)
+ inStream.setMaxNestingDepth(MaxNestingDepth);
/* clear this object */
l_error = clear();
} else {
/* open file for output */
DcmInputFileStream fileStream(fileName);
+ /* apply configured nesting depth limit */
+ if (MaxNestingDepth > 0)
+ fileStream.setMaxNestingDepth(MaxNestingDepth);
/* check stream status */
l_error = fileStream.status();
/*
*
- * Copyright (C) 1994-2010, OFFIS e.V.
+ * Copyright (C) 1994-2026, OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were developed by
, compressionFilter_(NULL)
, tell_(0)
, mark_(0)
+, nestingDepth_(0)
+, maxNestingDepth_(0)
{
}
return current_;
}
+Uint32 DcmInputStream::nestingDepth() const
+{
+ return nestingDepth_;
+}
+
+Uint32 DcmInputStream::incrementNestingDepth()
+{
+ return ++nestingDepth_;
+}
+
+void DcmInputStream::decrementNestingDepth()
+{
+ if (nestingDepth_ > 0)
+ --nestingDepth_;
+}
+
+Sint32 DcmInputStream::maxNestingDepth() const
+{
+ return maxNestingDepth_;
+}
+
+void DcmInputStream::setMaxNestingDepth(Sint32 maxDepth)
+{
+ maxNestingDepth_ = maxDepth;
+}
+
OFCondition DcmInputStream::installCompressionFilter(E_StreamCompression filterType)
{
OFCondition result = EC_Normal;
elementList(NULL),
lastElementComplete(OFTrue),
fStartPosition(0),
- privateCreatorCache()
+ privateCreatorCache(),
+ maxNestingDepth(0)
{
elementList = new DcmList;
}
elementList(NULL),
lastElementComplete(OFTrue),
fStartPosition(0),
- privateCreatorCache()
+ privateCreatorCache(),
+ maxNestingDepth(0)
{
elementList = new DcmList;
}
elementList(new DcmList),
lastElementComplete(old.lastElementComplete),
fStartPosition(old.fStartPosition),
- privateCreatorCache()
+ privateCreatorCache(),
+ maxNestingDepth(old.maxNestingDepth)
{
if (!old.elementList->empty())
{
// copy DcmItem's member variables
lastElementComplete = obj.lastElementComplete;
fStartPosition = obj.fStartPosition;
+ maxNestingDepth = obj.maxNestingDepth;
if (!obj.elementList->empty())
{
elementList->seek(ELP_first);
return errorFlag;
}
+ /* apply configured nesting depth limit to the input stream (0 = use default, skip override) */
+ if (getMaxNestingDepth() != 0)
+ inStream.setMaxNestingDepth(getMaxNestingDepth());
+
/* figure out if the stream reported an error */
errorFlag = inStream.status();
/* if the stream reported an error or if it is the end of the */
/*
*
- * Copyright (C) 1994-2023, OFFIS e.V.
+ * Copyright (C) 1994-2026, OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were developed by
errorFlag = EC_IllegalCall;
else
{
+ const Uint32 depth = inStream.incrementNestingDepth();
+ const Sint32 maxDepthSetting = inStream.maxNestingDepth();
+ /* -1 = unlimited; 0 = use built-in default (DCMTK_MAX_SEQUENCE_NESTING); > 0 = custom limit */
+ const Uint32 effectiveMax = (maxDepthSetting == 0) ? DCMTK_MAX_SEQUENCE_NESTING
+ : (maxDepthSetting < 0) ? 0 /* unlimited */
+ : OFstatic_cast(Uint32, maxDepthSetting);
+ if (effectiveMax > 0 && depth > effectiveMax)
+ {
+ DCMDATA_ERROR("DcmSequenceOfItems: Maximum nesting depth (" << effectiveMax
+ << ") exceeded while parsing sequence " << getTagName() << " " << getTag());
+ inStream.decrementNestingDepth();
+ errorFlag = EC_NestingDepthLimitExceeded;
+ // dump information if required
+ DCMDATA_TRACE("DcmSequenceOfItems::read() returns error = " << errorFlag.text());
+ return errorFlag;
+ }
+
errorFlag = inStream.status();
if (errorFlag.good() && inStream.eos())
errorFlag = EC_Normal;
if (errorFlag.good())
setTransferState(ERW_ready); // sequence is complete
+ inStream.decrementNestingDepth();
}
// dump information if required
DCMDATA_TRACE("DcmSequenceOfItems::read() returns error = " << errorFlag.text());
ti2dbmp.cc
titem.cc
tmatch.cc
+ tnesting.cc
tnewdcme.cc
tparent.cc
tparser.cc
../include/dcmtk/dcmdata/dcvr.h \
../../ofstd/include/dcmtk/ofstd/ofglobal.h \
../include/dcmtk/dcmdata/dcmatch.h
+tnesting.o: tnesting.cc ../../config/include/dcmtk/config/osconfig.h \
+ ../../ofstd/include/dcmtk/ofstd/oftest.h \
+ ../../ofstd/include/dcmtk/ofstd/ofconapp.h \
+ ../../ofstd/include/dcmtk/ofstd/oftypes.h \
+ ../../ofstd/include/dcmtk/ofstd/ofdefine.h \
+ ../../ofstd/include/dcmtk/ofstd/ofcast.h \
+ ../../ofstd/include/dcmtk/ofstd/ofexport.h \
+ ../../ofstd/include/dcmtk/ofstd/ofstdinc.h \
+ ../../ofstd/include/dcmtk/ofstd/ofcmdln.h \
+ ../../ofstd/include/dcmtk/ofstd/ofexbl.h \
+ ../../ofstd/include/dcmtk/ofstd/oftraits.h \
+ ../../ofstd/include/dcmtk/ofstd/oflist.h \
+ ../../ofstd/include/dcmtk/ofstd/ofstring.h \
+ ../../ofstd/include/dcmtk/ofstd/ofstream.h \
+ ../../ofstd/include/dcmtk/ofstd/ofconsol.h \
+ ../../ofstd/include/dcmtk/ofstd/ofthread.h \
+ ../../ofstd/include/dcmtk/ofstd/offile.h \
+ ../../ofstd/include/dcmtk/ofstd/ofstd.h \
+ ../../ofstd/include/dcmtk/ofstd/ofcond.h \
+ ../../ofstd/include/dcmtk/ofstd/ofdiag.h \
+ ../../ofstd/include/dcmtk/ofstd/diag/push.def \
+ ../../ofstd/include/dcmtk/ofstd/diag/useafree.def \
+ ../../ofstd/include/dcmtk/ofstd/diag/pop.def \
+ ../../ofstd/include/dcmtk/ofstd/oflimits.h \
+ ../../ofstd/include/dcmtk/ofstd/oferror.h \
+ ../../ofstd/include/dcmtk/ofstd/ofexit.h \
+ ../include/dcmtk/dcmdata/dcuid.h ../include/dcmtk/dcmdata/dcdefine.h \
+ ../../oflog/include/dcmtk/oflog/oflog.h \
+ ../../oflog/include/dcmtk/oflog/logger.h \
+ ../../oflog/include/dcmtk/oflog/config.h \
+ ../../oflog/include/dcmtk/oflog/config/defines.h \
+ ../../oflog/include/dcmtk/oflog/helpers/threadcf.h \
+ ../../oflog/include/dcmtk/oflog/loglevel.h \
+ ../../ofstd/include/dcmtk/ofstd/ofvector.h \
+ ../../oflog/include/dcmtk/oflog/tstring.h \
+ ../../oflog/include/dcmtk/oflog/tchar.h \
+ ../../oflog/include/dcmtk/oflog/spi/apndatch.h \
+ ../../oflog/include/dcmtk/oflog/appender.h \
+ ../../ofstd/include/dcmtk/ofstd/ofmem.h \
+ ../../ofstd/include/dcmtk/ofstd/ofutil.h \
+ ../../ofstd/include/dcmtk/ofstd/variadic/tuplefwd.h \
+ ../../oflog/include/dcmtk/oflog/layout.h \
+ ../../oflog/include/dcmtk/oflog/streams.h \
+ ../../oflog/include/dcmtk/oflog/helpers/pointer.h \
+ ../../oflog/include/dcmtk/oflog/thread/syncprim.h \
+ ../../oflog/include/dcmtk/oflog/spi/filter.h \
+ ../../oflog/include/dcmtk/oflog/helpers/lockfile.h \
+ ../../oflog/include/dcmtk/oflog/spi/logfact.h \
+ ../../oflog/include/dcmtk/oflog/logmacro.h \
+ ../../oflog/include/dcmtk/oflog/helpers/snprintf.h \
+ ../../oflog/include/dcmtk/oflog/tracelog.h \
+ ../../ofstd/include/dcmtk/ofstd/oftempf.h \
+ ../include/dcmtk/dcmdata/dcdatset.h ../include/dcmtk/dcmdata/dcitem.h \
+ ../include/dcmtk/dcmdata/dctypes.h ../include/dcmtk/dcmdata/dcobject.h \
+ ../../ofstd/include/dcmtk/ofstd/ofglobal.h \
+ ../include/dcmtk/dcmdata/dcerror.h ../include/dcmtk/dcmdata/dcxfer.h \
+ ../include/dcmtk/dcmdata/dcvr.h \
+ ../../ofstd/include/dcmtk/ofstd/ofdeprec.h \
+ ../include/dcmtk/dcmdata/dctag.h ../include/dcmtk/dcmdata/dctagkey.h \
+ ../../ofstd/include/dcmtk/ofstd/diag/ignrattr.def \
+ ../include/dcmtk/dcmdata/dcstack.h ../include/dcmtk/dcmdata/dclist.h \
+ ../include/dcmtk/dcmdata/dcpcache.h ../include/dcmtk/dcmdata/dcfilefo.h \
+ ../include/dcmtk/dcmdata/dcsequen.h ../include/dcmtk/dcmdata/dcelem.h \
+ ../include/dcmtk/dcmdata/dcistrmb.h ../include/dcmtk/dcmdata/dcistrma.h \
+ ../include/dcmtk/dcmdata/dcostrmb.h ../include/dcmtk/dcmdata/dcostrma.h \
+ ../include/dcmtk/dcmdata/dcdeftag.h ../include/dcmtk/dcmdata/dcwcache.h \
+ ../include/dcmtk/dcmdata/dcfcache.h
tnewdcme.o: tnewdcme.cc ../../config/include/dcmtk/config/osconfig.h \
../../ofstd/include/dcmtk/ofstd/oftest.h \
../../ofstd/include/dcmtk/ofstd/ofconapp.h \
objs = tests.o tpread.o ti2dbmp.o tchval.o tpath.o tvrdatim.o telemlen.o tparser.o \
tdict.o tvrds.o tvrfd.o tvrui.o tvrol.o tvrov.o tvrsv.o tvruv.o tstrval.o \
- tspchrs.o tvrpn.o tparent.o tfilter.o tvrcomp.o tmatch.o tnewdcme.o \
+ tspchrs.o tvrpn.o tparent.o tfilter.o tvrcomp.o tmatch.o tnesting.o tnewdcme.o \
tgenuid.o tsequen.o titem.o ttag.o txfer.o tbytestr.o tfrmsiz.o
progs = tests
/*
*
- * Copyright (C) 2011-2025 OFFIS e.V.
+ * Copyright (C) 2011-2026 OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were developed by
OFTEST_REGISTER(dcmdata_xferLookup_4);
OFTEST_REGISTER(dcmdata_putOFStringAtPos);
OFTEST_REGISTER(dcmdata_uncompressedFrameSize);
+OFTEST_REGISTER(dcmdata_nestingDepthLimit_exceeded);
+OFTEST_REGISTER(dcmdata_nestingDepthLimit_atLimit);
+OFTEST_REGISTER(dcmdata_nestingDepthLimit_wellBelow);
+OFTEST_REGISTER(dcmdata_nestingDepthLimit_customLimit);
+OFTEST_REGISTER(dcmdata_nestingDepthLimit_disabled);
+OFTEST_REGISTER(dcmdata_nestingDepthLimit_datasetAPI);
+OFTEST_REGISTER(dcmdata_nestingDepthLimit_fileFormatAPI);
OFTEST_MAIN("dcmdata")
--- /dev/null
+/*
+ *
+ * Copyright (C) 2026, 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: dcmdata
+ *
+ * Author: Michael Onken
+ *
+ * Purpose: test program for sequence nesting depth limit
+ *
+ */
+
+
+#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
+
+#include "dcmtk/ofstd/oftest.h"
+#include "dcmtk/ofstd/oftempf.h"
+#include "dcmtk/dcmdata/dcdatset.h"
+#include "dcmtk/dcmdata/dcfilefo.h"
+#include "dcmtk/dcmdata/dcsequen.h"
+#include "dcmtk/dcmdata/dcistrmb.h"
+#include "dcmtk/dcmdata/dcostrmb.h"
+#include "dcmtk/dcmdata/dcerror.h"
+#include "dcmtk/dcmdata/dcdeftag.h"
+#include "dcmtk/dcmdata/dcwcache.h"
+
+
+/** Helper: build a DcmDataset containing a chain of nested sequences to the
+ * specified depth using the high-level dcmdata API. Each nesting level
+ * consists of a DcmSequenceOfItems with one DcmItem, and the innermost
+ * item is empty.
+ * @param depth the number of nested sequence levels to generate
+ * @param dset output dataset (cleared before use)
+ */
+static void buildNestedDataset(Uint32 depth, DcmDataset& dset)
+{
+ dset.clear();
+ /* start with the dataset as the outermost item */
+ DcmItem* currentItem = &dset;
+ for (Uint32 i = 0; i < depth; ++i)
+ {
+ DcmSequenceOfItems* seq = new DcmSequenceOfItems(DCM_ReferencedSeriesSequence);
+ currentItem->insert(seq);
+ DcmItem* item = new DcmItem();
+ seq->insert(item);
+ currentItem = item;
+ }
+}
+
+
+/** Helper: serialize a dataset to a byte buffer and parse it back using a
+ * fresh DcmInputStream, with an optional custom nesting depth limit.
+ * @param srcDset the dataset to serialize
+ * @param dstDset output dataset to receive the parsed result
+ * @param maxNestingDepth nesting depth setting passed to setMaxNestingDepth():
+ * 0 (default) = compile-time default (DCMTK_MAX_SEQUENCE_NESTING), -1 = unlimited, > 0 = custom limit
+ * @return the OFCondition returned by DcmDataset::read()
+ */
+static OFCondition serializeAndParse(DcmDataset& srcDset, DcmDataset& dstDset,
+ Sint32 maxNestingDepth = 0)
+{
+ /* write the dataset to a memory buffer */
+ const size_t bufLen = 1024 * 1024;
+ Uint8* buf = new Uint8[bufLen];
+
+ DcmOutputBufferStream outStream(buf, bufLen);
+ srcDset.transferInit();
+ DcmWriteCache wcache;
+ OFCondition cond = srcDset.write(outStream, EXS_LittleEndianExplicit, EET_UndefinedLength, &wcache);
+ srcDset.transferEnd();
+ if (cond.bad())
+ {
+ delete[] buf;
+ return cond;
+ }
+ offile_off_t bytesWritten = 0;
+ void* writtenBuf = NULL;
+ outStream.flushBuffer(writtenBuf, bytesWritten);
+
+ /* parse it back with nesting depth limit */
+ DcmInputBufferStream inStream;
+ inStream.setBuffer(buf, bytesWritten);
+ inStream.setEos();
+ inStream.setMaxNestingDepth(maxNestingDepth);
+
+ dstDset.clear();
+ dstDset.transferInit();
+ cond = dstDset.read(inStream, EXS_LittleEndianExplicit);
+ dstDset.transferEnd();
+
+ delete[] buf;
+ return cond;
+}
+
+
+OFTEST(dcmdata_nestingDepthLimit_exceeded)
+{
+ /* One level beyond the default limit (DCMTK_MAX_SEQUENCE_NESTING) must be rejected. */
+ DcmDataset srcDset, dstDset;
+ buildNestedDataset(DCMTK_MAX_SEQUENCE_NESTING + 1, srcDset);
+
+ OFCondition cond = serializeAndParse(srcDset, dstDset);
+ if (cond != EC_NestingDepthLimitExceeded)
+ {
+ OFCHECK_FAIL("Expected EC_NestingDepthLimitExceeded for " << (DCMTK_MAX_SEQUENCE_NESTING + 1)
+ << " levels of nesting, but got: " << cond.text());
+ }
+}
+
+
+OFTEST(dcmdata_nestingDepthLimit_atLimit)
+{
+ /* Exactly DCMTK_MAX_SEQUENCE_NESTING levels must still pass. */
+ DcmDataset srcDset, dstDset;
+ buildNestedDataset(DCMTK_MAX_SEQUENCE_NESTING, srcDset);
+
+ OFCondition cond = serializeAndParse(srcDset, dstDset);
+ if (cond.bad())
+ {
+ OFCHECK_FAIL("Parsing " << DCMTK_MAX_SEQUENCE_NESTING
+ << " levels of nesting should succeed, but got: " << cond.text());
+ }
+}
+
+
+OFTEST(dcmdata_nestingDepthLimit_wellBelow)
+{
+ /* 5 levels of nesting (typical real-world depth) must parse fine. */
+ DcmDataset srcDset, dstDset;
+ buildNestedDataset(5, srcDset);
+
+ OFCondition cond = serializeAndParse(srcDset, dstDset);
+ if (cond.bad())
+ {
+ OFCHECK_FAIL("Parsing 5 levels of nesting should succeed, but got: " << cond.text());
+ }
+}
+
+
+OFTEST(dcmdata_nestingDepthLimit_customLimit)
+{
+ /* Test setMaxNestingDepth(): set the limit to 10.
+ * 10 levels must succeed, 11 levels must fail. */
+ DcmDataset srcDset, dstDset;
+ OFCondition cond;
+
+ buildNestedDataset(10, srcDset);
+ cond = serializeAndParse(srcDset, dstDset, 10);
+ if (cond.bad())
+ {
+ OFCHECK_FAIL("Parsing 10 levels with maxNestingDepth=10 should succeed, but got: " << cond.text());
+ }
+
+ buildNestedDataset(11, srcDset);
+ cond = serializeAndParse(srcDset, dstDset, 10);
+ if (cond != EC_NestingDepthLimitExceeded)
+ {
+ OFCHECK_FAIL("Expected EC_NestingDepthLimitExceeded for 11 levels with maxNestingDepth=10, but got: " << cond.text());
+ }
+}
+
+
+OFTEST(dcmdata_nestingDepthLimit_disabled)
+{
+ /* Test that setMaxNestingDepth(-1) disables the limit entirely.
+ * Even 200 levels of nesting must succeed. */
+ DcmDataset srcDset, dstDset;
+ buildNestedDataset(200, srcDset);
+
+ OFCondition cond = serializeAndParse(srcDset, dstDset, -1);
+ if (cond.bad())
+ {
+ OFCHECK_FAIL("Parsing 200 levels with maxNestingDepth=-1 (unlimited) should succeed, but got: " << cond.text());
+ }
+}
+
+
+/** Helper: create a temporary file path.
+ * @param tmpFile receives the temporary file path
+ * @return EC_Normal if successful
+ */
+static OFCondition makeTempFile(OFString& tmpFile)
+{
+ return OFTempFile::createFile(tmpFile, NULL /* fd_out */, O_RDWR,
+ "" /* dir */, "" /* prefix */, ".dcm" /* postfix */);
+}
+
+
+OFTEST(dcmdata_nestingDepthLimit_datasetAPI)
+{
+ /* Test DcmDataset::setMaxNestingDepth() with loadFile(). */
+ DcmDataset srcDset;
+ OFString tmpFile;
+ OFCondition cond;
+
+ /* save as raw dataset (no meta header) */
+ buildNestedDataset(11, srcDset);
+ cond = makeTempFile(tmpFile);
+ OFCHECK(cond.good());
+ cond = srcDset.saveFile(tmpFile, EXS_LittleEndianExplicit);
+ OFCHECK(cond.good());
+
+ /* default limit (DCMTK_MAX_SEQUENCE_NESTING): 11 levels should succeed */
+ DcmDataset dset1;
+ cond = dset1.loadFile(tmpFile, EXS_LittleEndianExplicit);
+ if (cond.bad())
+ {
+ OFCHECK_FAIL("DcmDataset::loadFile() with 11 levels should succeed with default limit, but got: " << cond.text());
+ }
+
+ /* custom limit 10: 11 levels should fail */
+ DcmDataset dset2;
+ dset2.setMaxNestingDepth(10);
+ cond = dset2.loadFile(tmpFile, EXS_LittleEndianExplicit);
+ if (cond != EC_NestingDepthLimitExceeded)
+ {
+ OFCHECK_FAIL("DcmDataset::loadFile() with 11 levels and maxNestingDepth=10 should fail, but got: " << cond.text());
+ }
+
+ OFStandard::deleteFile(tmpFile);
+}
+
+
+OFTEST(dcmdata_nestingDepthLimit_fileFormatAPI)
+{
+ /* Test DcmFileFormat::setMaxNestingDepth() with loadFile(). */
+ DcmDataset srcDset;
+ OFString tmpFile;
+ OFCondition cond;
+
+ /* save as DcmFileFormat (with meta header) */
+ buildNestedDataset(11, srcDset);
+ cond = makeTempFile(tmpFile);
+ OFCHECK(cond.good());
+ DcmFileFormat srcFF(&srcDset);
+ cond = srcFF.saveFile(tmpFile, EXS_LittleEndianExplicit);
+ OFCHECK(cond.good());
+
+ /* default limit (DCMTK_MAX_SEQUENCE_NESTING): 11 levels should succeed */
+ DcmFileFormat ff1;
+ cond = ff1.loadFile(tmpFile);
+ if (cond.bad())
+ {
+ OFCHECK_FAIL("DcmFileFormat::loadFile() with 11 levels should succeed with default limit, but got: " << cond.text());
+ }
+
+ /* custom limit 10: 11 levels should fail */
+ DcmFileFormat ff2;
+ ff2.setMaxNestingDepth(10);
+ cond = ff2.loadFile(tmpFile);
+ if (cond != EC_NestingDepthLimitExceeded)
+ {
+ OFCHECK_FAIL("DcmFileFormat::loadFile() with 11 levels and maxNestingDepth=10 should fail, but got: " << cond.text());
+ }
+
+ OFStandard::deleteFile(tmpFile);
+}
*/
void setAlwaysAcceptDefaultRole(const OFBool enabled);
+ /** Set the maximum permitted sequence nesting depth for parsing
+ * datasets received over the network. This limit is applied to
+ * each dataset received via receiveDIMSEDataset().
+ * - Value 0 (default): apply the compile-time default
+ * (DCMTK_MAX_SEQUENCE_NESTING)
+ * - Value -1: disable the check (allow unlimited nesting)
+ * - Value > 0: use this value as the maximum nesting depth
+ * @param maxDepth maximum nesting depth setting
+ */
+ void setMaxNestingDepth(const Sint32 maxDepth);
+
/* Get methods for SCP settings */
/** Returns TCP/IP port number SCP listens for new connection requests
*/
OFBool getProgressNotificationMode() const;
+ /** Return the maximum permitted sequence nesting depth for
+ * parsing datasets received over the network.
+ * @return maximum nesting depth setting
+ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING,
+ * -1 = unlimited)
+ */
+ Sint32 getMaxNestingDepth() const;
+
/** Get access to the configuration of the SCP. Note that the functionality
* on the configuration object is shadowed by other API functions of DcmSCP.
* The existing functions are provided in order to not break users of this
/// it, e.g. in the context of the DcmSCPPool class.
DcmSharedSCPConfig m_cfg;
+ /// Maximum sequence nesting depth for parsing received datasets
+ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING,
+ /// -1 = unlimited)
+ Sint32 m_maxNestingDepth;
+
/** Drops association and clears internal structures to free memory
*/
void dropAndDestroyAssociation();
*/
void setProgressNotificationMode(const OFBool mode);
+ /** Set the maximum permitted sequence nesting depth for parsing
+ * datasets received over the network. This limit is applied to
+ * each dataset received via receiveDIMSEDataset().
+ * - Value 0 (default): apply the compile-time default
+ * (DCMTK_MAX_SEQUENCE_NESTING)
+ * - Value -1: disable the check (allow unlimited nesting)
+ * - Value > 0: use this value as the maximum nesting depth
+ * @param maxDepth maximum nesting depth setting
+ */
+ void setMaxNestingDepth(const Sint32 maxDepth);
+
/* Get methods */
/** Get current connection status
*/
OFBool getProgressNotificationMode() const;
+ /** Return the maximum permitted sequence nesting depth for
+ * parsing datasets received over the network.
+ * @return maximum nesting depth setting
+ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING,
+ * -1 = unlimited)
+ */
+ Sint32 getMaxNestingDepth() const;
+
/** Returns whether SCU is configured to create a TLS connection with the SCP
* @return OFTrue if TLS mode has been enabled, OFFalse otherwise
*/
/// Progress notification mode (default: enabled)
OFBool m_progressNotificationMode;
+ /// Maximum sequence nesting depth for parsing received datasets
+ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING,
+ /// -1 = unlimited)
+ Sint32 m_maxNestingDepth;
+
/// Flag indicating whether secure mode has been enabled (default: disabled)
OFBool m_secureConnectionEnabled;
/*
*
- * Copyright (C) 1994-2025, OFFIS e.V.
+ * Copyright (C) 1994-2026, OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were partly developed by
dset = *dataObject;
}
- /* check if there is still no DcmDataset object which can be used to store the data set. */
+ /* check if there is still no DcmDataset object (i.e. if allocation fails) */
+ /* which can be used to store the data set. */
if (dset == NULL)
{
/* if this is the case, just go ahead an receive data, but do not store it anywhere */
: m_network(NULL)
, m_assoc(NULL)
, m_cfg()
+, m_maxNestingDepth(0)
{
OFStandard::initializeNetwork();
}
if (m_assoc == NULL)
return DIMSE_ILLEGALASSOCIATION;
+ /* if a custom nesting depth limit is configured, pre-allocate
+ * the dataset and apply the setting before parsing starts.
+ * DIMSE_receiveDataSetInMemory() will use this dataset instead
+ * of creating a new one internally. */
+ OFBool weAllocated = OFFalse;
+ if (dataObject != NULL && *dataObject == NULL
+ && m_maxNestingDepth != 0)
+ {
+ *dataObject = new DcmDataset();
+ (*dataObject)->setMaxNestingDepth(m_maxNestingDepth);
+ weAllocated = OFTrue;
+ }
+
OFCondition cond;
/* call the corresponding DIMSE function to receive the dataset */
if (m_cfg->getProgressNotificationMode())
}
else
{
+ /* if we pre-allocated the dataset, clean up on error since
+ * DIMSE_receiveDataSetInMemory() won't delete a dataset
+ * that was passed in by the caller */
+ if (weAllocated && dataObject != NULL)
+ {
+ delete *dataObject;
+ *dataObject = NULL;
+ }
OFString tempStr;
DCMNET_ERROR("Unable to receive dataset on presentation context "
<< OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
// ----------------------------------------------------------------------------
+void DcmSCP::setMaxNestingDepth(const Sint32 maxDepth)
+{
+ m_maxNestingDepth = maxDepth;
+}
+
+// ----------------------------------------------------------------------------
+
/* Get methods for SCP settings and current association information */
OFBool DcmSCP::getRefuseAssociation() const
// ----------------------------------------------------------------------------
+Sint32 DcmSCP::getMaxNestingDepth() const
+{
+ return m_maxNestingDepth;
+}
+
+// ----------------------------------------------------------------------------
+
OFBool DcmSCP::isConnected() const
{
return (m_assoc != NULL) && (m_assoc->DULassociation != NULL);
, m_verbosePCMode(OFFalse)
, m_datasetConversionMode(OFFalse)
, m_progressNotificationMode(OFTrue)
+ , m_maxNestingDepth(0)
, m_secureConnectionEnabled(OFFalse)
, m_protocolVersion(ASC_AF_Default)
{
if (!isConnected())
return DIMSE_ILLEGALASSOCIATION;
+ /* if a custom nesting depth limit is configured, pre-allocate
+ * the dataset and apply the setting before parsing starts.
+ * DIMSE_receiveDataSetInMemory() will use this dataset instead
+ * of creating a new one internally. */
+ OFBool weAllocated = OFFalse;
+ if (dataObject != NULL && *dataObject == NULL
+ && m_maxNestingDepth != 0)
+ {
+ *dataObject = new DcmDataset();
+ (*dataObject)->setMaxNestingDepth(m_maxNestingDepth);
+ weAllocated = OFTrue;
+ }
+
OFCondition cond;
/* call the corresponding DIMSE function to receive the dataset */
if (m_progressNotificationMode)
}
else
{
+ /* if we pre-allocated the dataset, clean up on error since
+ * DIMSE_receiveDataSetInMemory() won't delete a dataset
+ * that was passed in by the caller */
+ if (weAllocated && dataObject != NULL)
+ {
+ delete *dataObject;
+ *dataObject = NULL;
+ }
OFString tempStr;
DCMNET_ERROR("Unable to receive dataset on presentation context "
<< OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
m_progressNotificationMode = mode;
}
+void DcmSCU::setMaxNestingDepth(const Sint32 maxDepth)
+{
+ m_maxNestingDepth = maxDepth;
+}
+
void DcmSCU::setProtocolVersion(T_ASC_ProtocolFamily protocolVersion)
{
m_protocolVersion = protocolVersion;
return m_progressNotificationMode;
}
+Sint32 DcmSCU::getMaxNestingDepth() const
+{
+ return m_maxNestingDepth;
+}
+
OFCondition DcmSCU::getDatasetInfo(DcmDataset* dataset,
OFString& sopClassUID,
OFString& sopInstanceUID,
OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_succeeds_and_modifies_instance_when_scp_has_instance);
OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_succeeds_and_sets_responsestatuscode_from_scp_when_scp_sets_error_status);
+OFTEST_REGISTER(dcmnet_scp_maxNestingDepth_getset);
+OFTEST_REGISTER(dcmnet_scu_maxNestingDepth_getset);
+OFTEST_REGISTER(dcmnet_scp_maxNestingDepth_rejects_deep);
+OFTEST_REGISTER(dcmnet_scp_maxNestingDepth_accepts_shallow);
+
#endif // WITH_THREADS
OFTEST_MAIN("dcmnet")
}
+// Test that setMaxNestingDepth/getMaxNestingDepth work on DcmSCP
+OFTEST(dcmnet_scp_maxNestingDepth_getset)
+{
+ DcmSCP scp;
+ // Default must be 0 (use compile-time default)
+ OFCHECK_EQUAL(scp.getMaxNestingDepth(), 0);
+ scp.setMaxNestingDepth(10);
+ OFCHECK_EQUAL(scp.getMaxNestingDepth(), 10);
+ scp.setMaxNestingDepth(-1);
+ OFCHECK_EQUAL(scp.getMaxNestingDepth(), -1);
+ scp.setMaxNestingDepth(0);
+ OFCHECK_EQUAL(scp.getMaxNestingDepth(), 0);
+}
+
+
+// Test that setMaxNestingDepth/getMaxNestingDepth work on DcmSCU
+OFTEST(dcmnet_scu_maxNestingDepth_getset)
+{
+ DcmSCU scu;
+ // Default must be 0 (use compile-time default)
+ OFCHECK_EQUAL(scu.getMaxNestingDepth(), 0);
+ scu.setMaxNestingDepth(5);
+ OFCHECK_EQUAL(scu.getMaxNestingDepth(), 5);
+ scu.setMaxNestingDepth(-1);
+ OFCHECK_EQUAL(scu.getMaxNestingDepth(), -1);
+ scu.setMaxNestingDepth(0);
+ OFCHECK_EQUAL(scu.getMaxNestingDepth(), 0);
+}
+
+
+/** Helper: build a DcmDataset with nested sequences to a given depth.
+ * @param depth number of nesting levels
+ * @param dset output dataset (cleared before use)
+ */
+static void buildNestedDataset(Uint32 depth, DcmDataset& dset)
+{
+ dset.clear();
+ DcmItem* currentItem = &dset;
+ for (Uint32 i = 0; i < depth; ++i)
+ {
+ DcmSequenceOfItems* sq =
+ new DcmSequenceOfItems(DCM_ReferencedSeriesSequence);
+ currentItem->insert(sq);
+ DcmItem* item = new DcmItem();
+ sq->insert(item);
+ currentItem = item;
+ }
+}
+
+
+/** SCP that applies a nesting depth limit and records whether
+ * receiving the dataset succeeded or failed.
+ */
+struct NestingTestSCP : TestSCP
+{
+ NestingTestSCP(Sint32 maxNesting)
+ : TestSCP()
+ , m_receiveResult(EC_NotYetImplemented)
+ {
+ setMaxNestingDepth(maxNesting);
+ DcmSCPConfig& config = getConfig();
+ config.setAETitle("NESTING_SCP");
+ config.setConnectionBlockingMode(DUL_NOBLOCK);
+ config.setConnectionTimeout(10);
+ config.setHostLookupEnabled(OFFalse);
+ config.setPort(0);
+ OFList<OFString> xfers;
+ xfers.push_back(UID_LittleEndianImplicitTransferSyntax);
+ OFCHECK(config.addPresentationContext(
+ UID_ModalityPerformedProcedureStepSOPClass, xfers).good());
+ OFCHECK(openListenPort().good());
+ m_portNum = config.getPort();
+ }
+
+ OFCondition handleIncomingCommand(
+ T_DIMSE_Message* incomingMsg,
+ const DcmPresentationContextInfo& presInfo)
+ {
+ if (incomingMsg->CommandField == DIMSE_N_CREATE_RQ)
+ {
+ T_DIMSE_N_CreateRQ& req = incomingMsg->msg.NCreateRQ;
+ if (req.DataSetType != DIMSE_DATASET_NULL)
+ {
+ DcmDataset* dataset = OFnullptr;
+ T_ASC_PresentationContextID presIDdset;
+ m_receiveResult =
+ receiveDIMSEDataset(&presIDdset, &dataset);
+ /* send a response regardless of receive outcome */
+ T_DIMSE_Message rsp;
+ memset(&rsp, 0, sizeof(rsp));
+ rsp.CommandField = DIMSE_N_CREATE_RSP;
+ T_DIMSE_N_CreateRSP& createRsp = rsp.msg.NCreateRSP;
+ createRsp.MessageIDBeingRespondedTo = req.MessageID;
+ createRsp.DimseStatus = m_receiveResult.good()
+ ? STATUS_N_Success
+ : STATUS_N_ProcessingFailure;
+ createRsp.DataSetType = DIMSE_DATASET_NULL;
+ OFStandard::strlcpy(
+ createRsp.AffectedSOPClassUID,
+ req.AffectedSOPClassUID,
+ sizeof(createRsp.AffectedSOPClassUID));
+ createRsp.opts = O_NCREATE_AFFECTEDSOPCLASSUID;
+ sendDIMSEMessage(
+ presInfo.presentationContextID, &rsp, NULL);
+ delete dataset;
+ }
+ return EC_Normal;
+ }
+ return DcmSCP::handleIncomingCommand(incomingMsg, presInfo);
+ }
+
+ /// Result of receiveDIMSEDataset() for the last request
+ OFCondition m_receiveResult;
+ /// Port the SCP is listening on
+ Uint16 m_portNum;
+};
+
+
+// Test that SCP's nesting depth limit rejects deeply nested data
+// received over the network
+OFTEST_FLAGS(dcmnet_scp_maxNestingDepth_rejects_deep, EF_Slow)
+{
+ NestingTestSCP scp(3);
+ scp.m_set_stop_after_assoc = OFTrue;
+ scp.start();
+ OFStandard::forceSleep(1);
+
+ // SCU sends a dataset with 4 levels of nesting (exceeds limit 3)
+ DcmSCU scu;
+ scu.setAETitle("NESTING_SCU");
+ scu.setPeerAETitle("NESTING_SCP");
+ scu.setPeerHostName("localhost");
+ scu.setPeerPort(scp.m_portNum);
+ OFList<OFString> xfers;
+ xfers.push_back(UID_LittleEndianImplicitTransferSyntax);
+ OFCHECK(scu.addPresentationContext(
+ UID_ModalityPerformedProcedureStepSOPClass, xfers).good());
+ OFCHECK(scu.initNetwork().good());
+ OFCHECK(scu.negotiateAssociation().good());
+
+ DcmDataset reqDataset;
+ buildNestedDataset(4, reqDataset);
+ T_ASC_PresentationContextID presID = scu.findPresentationContextID(
+ UID_ModalityPerformedProcedureStepSOPClass,
+ UID_LittleEndianImplicitTransferSyntax);
+ OFCHECK(presID != 0);
+ Uint16 rspStatus = 0;
+ DcmDataset* rspDataset = NULL;
+ scu.sendNCREATERequest(presID, "1.2.3.4.5",
+ &reqDataset, rspDataset, rspStatus);
+ delete rspDataset;
+ if (scu.isConnected())
+ scu.releaseAssociation();
+ OFStandard::forceSleep(2);
+ scp.join();
+
+ // Verify SCP saw the nesting depth error
+ OFCHECK(scp.m_receiveResult == DIMSE_RECEIVEFAILED);
+}
+
+
+// Test that SCP's nesting depth limit accepts data within the limit
+OFTEST_FLAGS(dcmnet_scp_maxNestingDepth_accepts_shallow, EF_Slow)
+{
+ NestingTestSCP scp(3);
+ scp.m_set_stop_after_assoc = OFTrue;
+ scp.start();
+ OFStandard::forceSleep(1);
+
+ // SCU sends a dataset with 3 levels of nesting (at the limit)
+ DcmSCU scu;
+ scu.setAETitle("NESTING_SCU");
+ scu.setPeerAETitle("NESTING_SCP");
+ scu.setPeerHostName("localhost");
+ scu.setPeerPort(scp.m_portNum);
+ OFList<OFString> xfers;
+ xfers.push_back(UID_LittleEndianImplicitTransferSyntax);
+ OFCHECK(scu.addPresentationContext(
+ UID_ModalityPerformedProcedureStepSOPClass, xfers).good());
+ OFCHECK(scu.initNetwork().good());
+ OFCHECK(scu.negotiateAssociation().good());
+
+ DcmDataset reqDataset;
+ buildNestedDataset(3, reqDataset);
+ T_ASC_PresentationContextID presID = scu.findPresentationContextID(
+ UID_ModalityPerformedProcedureStepSOPClass,
+ UID_LittleEndianImplicitTransferSyntax);
+ OFCHECK(presID != 0);
+ Uint16 rspStatus = 0;
+ DcmDataset* rspDataset = NULL;
+ OFCondition result = scu.sendNCREATERequest(presID, "1.2.3.4.5",
+ &reqDataset, rspDataset, rspStatus);
+ OFCHECK_MSG(result.good(), result.text());
+ OFCHECK(rspStatus == STATUS_N_Success);
+ delete rspDataset;
+ if (scu.isConnected())
+ OFCHECK(scu.releaseAssociation().good());
+ OFStandard::forceSleep(2);
+ scp.join();
+
+ // Verify SCP successfully received the dataset
+ OFCHECK(scp.m_receiveResult.good());
+}
+
+
#endif // WITH_THREADS