/* * * Copyright (C) 2011-2020, 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: Uli Schlachter * * Purpose: test program for reading DICOM datasets * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/ofstd/oftest.h" #include "dcmtk/ofstd/ofstd.h" #include "dcmtk/ofstd/oftempf.h" #include "dcmtk/dcmdata/dctk.h" #include "dcmtk/dcmdata/dcpxitem.h" #include "dcmtk/dcmdata/dcistrmb.h" #include "dcmtk/dcmdata/dcostrmb.h" #include "dctmacro.h" /* To switch between big and little endian, just change these macros */ #define TRANSFER_SYNTAX EXS_LittleEndianExplicit #define ENDIAN_UINT16(w) LITTLE_ENDIAN_UINT16(w) #define ENDIAN_UINT32(w) LITTLE_ENDIAN_UINT32(w) static OFLogger tparserLogger = OFLog::getLogger("dcmtk.test.tparser"); OFTEST(dcmdata_parser_missingDelimitationItems) { const Uint8 data[] = { TAG_AND_LENGTH(DCM_IconImageSequence, 'S', 'Q', UNDEFINED_LENGTH), ITEM(UNDEFINED_LENGTH), TAG_AND_LENGTH(DCM_PixelData, 'O', 'B', 4), VALUE, VALUE, VALUE, VALUE, /* Delimitation Items missing at the end of the stream */ }; DcmDataset dset; OFCondition cond; // This should assume the above sequence is complete dcmIgnoreParsingErrors.set(OFTrue); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond.bad()) { OFCHECK_FAIL("Parsing should have worked, but got error: " << cond.text()); } // This should complain about the missing item dcmIgnoreParsingErrors.set(OFFalse); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond != EC_SequDelimitationItemMissing) { OFCHECK_FAIL("Parsing should have failed with 'Sequence Delimitation Item missing', but got: " << cond.text()); } } OFTEST(dcmdata_parser_missingSequenceDelimitationItem_1) { const Uint8 data[] = { TAG_AND_LENGTH(DCM_PixelData, 'O', 'B', UNDEFINED_LENGTH), ITEM(4), VALUE, VALUE, VALUE, VALUE /* SequenceDelimitationItem missing at the end of the stream */ }; DcmDataset dset; OFCondition cond; // This should assume the above sequence is complete dcmIgnoreParsingErrors.set(OFTrue); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond.bad()) { OFCHECK_FAIL("Parsing should have worked, but got error: " << cond.text()); } // This should complain about the missing item dcmIgnoreParsingErrors.set(OFFalse); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond != EC_SequDelimitationItemMissing) { OFCHECK_FAIL("Parsing should have failed with 'Sequence Delimitation Item missing', but got: " << cond.text()); } } OFTEST(dcmdata_parser_missingSequenceDelimitationItem_2) { const Uint8 data[] = { TAG_AND_LENGTH(DCM_IconImageSequence, 'S', 'Q', UNDEFINED_LENGTH), ITEM(UNDEFINED_LENGTH), TAG_AND_LENGTH(DCM_PixelData, 'O', 'B', 4), VALUE, VALUE, VALUE, VALUE, ITEM_END, /* SequenceDelimitationItem missing, but there's further data after the sequence */ TAG_AND_LENGTH(DCM_DataSetTrailingPadding, 'O', 'B', 4), VALUE, VALUE, VALUE, VALUE }; DcmDataset dset; OFCondition cond; // This should fail with a specific error code dcmIgnoreParsingErrors.set(OFFalse); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond != EC_SequDelimitationItemMissing) { OFCHECK_FAIL("Parsing should have failed with 'Sequence Delimitation Item missing', but got: " << cond.text()); } } OFTEST(dcmdata_parser_wrongDelimitationItemForSequence) { const Uint8 data[] = { TAG_AND_LENGTH(DCM_IconImageSequence, 'S', 'Q', UNDEFINED_LENGTH), ITEM(UNDEFINED_LENGTH), TAG_AND_LENGTH(DCM_PixelData, 'O', 'B', 4), VALUE, VALUE, VALUE, VALUE, ITEM_END, /* wrong delimitation item */ ITEM_END, TAG_AND_LENGTH(DCM_DataSetTrailingPadding, 'O', 'B', 4), VALUE, VALUE, VALUE, VALUE }; DcmDataset dset; OFCondition cond; // This should complain about the wrong delimitation item dcmIgnoreParsingErrors.set(OFFalse); dcmReplaceWrongDelimitationItem.set(OFFalse); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond != EC_SequDelimitationItemMissing) { OFCHECK_FAIL("Parsing should have failed with 'Sequence Delimitation Item missing', but got: " << cond.text()); } // This should assume the above sequence encoding is correct dcmIgnoreParsingErrors.set(OFTrue); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond.good()) { OFLOG_DEBUG(tparserLogger, DcmObject::PrintHelper(dset)); if (dset.card() != 1) OFCHECK_FAIL("There should be exactly 1 element on the main dataset level, but " << dset.card() << " found"); } else { OFCHECK_FAIL("Parsing should have worked, but got error: " << cond.text()); } // This should replace the wrong delimitation item dcmIgnoreParsingErrors.set(OFFalse); dcmReplaceWrongDelimitationItem.set(OFTrue); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond.good()) { OFLOG_DEBUG(tparserLogger, DcmObject::PrintHelper(dset)); if (dset.card() != 2) OFCHECK_FAIL("There should be exactly 2 elements on the main dataset level, but " << dset.card() << " found"); } else { OFCHECK_FAIL("Parsing should have worked, but got error: " << cond.text()); } } OFTEST(dcmdata_parser_wrongDelimitationItemForItem) { const Uint8 data[] = { TAG_AND_LENGTH(DCM_IconImageSequence, 'S', 'Q', UNDEFINED_LENGTH), ITEM(UNDEFINED_LENGTH), TAG_AND_LENGTH(DCM_PixelData, 'O', 'B', 4), VALUE, VALUE, VALUE, VALUE, /* wrong delimitation item */ SEQUENCE_END, SEQUENCE_END, TAG_AND_LENGTH(DCM_DataSetTrailingPadding, 'O', 'B', 4), VALUE, VALUE, VALUE, VALUE }; DcmDataset dset; OFCondition cond; // This should complain about the wrong delimitation item dcmIgnoreParsingErrors.set(OFFalse); dcmReplaceWrongDelimitationItem.set(OFFalse); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond != EC_ItemDelimitationItemMissing) { OFCHECK_FAIL("Parsing should have failed with 'Item Delimitation Item missing', but got: " << cond.text()); } // This should assume the above item encoding is correct dcmIgnoreParsingErrors.set(OFTrue); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond.good()) { OFLOG_DEBUG(tparserLogger, DcmObject::PrintHelper(dset)); if (dset.card() != 2) OFCHECK_FAIL("There should be exactly 2 elements on the main dataset level, but " << dset.card() << " found"); } else { OFCHECK_FAIL("Parsing should have worked, but got error: " << cond.text()); } // This should replace the wrong delimitation item dcmIgnoreParsingErrors.set(OFFalse); dcmReplaceWrongDelimitationItem.set(OFTrue); cond = readDataset(dset, data, sizeof(data), TRANSFER_SYNTAX); if (cond.good()) { OFLOG_DEBUG(tparserLogger, DcmObject::PrintHelper(dset)); if (dset.card() != 2) OFCHECK_FAIL("There should be exactly 2 elements on the main dataset level, but " << dset.card() << " found"); } else { OFCHECK_FAIL("Parsing should have worked, but got error: " << cond.text()); } } OFTEST(dcmdata_parser_explicitItemLengthTooLarge) { /* Produce some test data: Sequence with single item, containing a single element. */ const Uint8 data[] = { IMPLICIT_TAG_AND_LENGTH(DCM_RequestAttributesSequence, UNDEFINED_LENGTH), ITEM(12), // 4 for tag, 4 for length, and 4 bytes extra that should produce the error IMPLICIT_TAG_AND_LENGTH(DCM_CodeValue, 0), SEQUENCE_END }; DcmDataset dset; OFCondition cond; // This should complain about the item being too large dcmIgnoreParsingErrors.set(OFFalse); cond = readDataset(dset, data, sizeof(data), EXS_LittleEndianImplicit); if (cond != EC_PrematureSequDelimitationItem) { OFCHECK_FAIL("Parsing should have failed with 'Sequence Delimitation Item occurred before Item was completely read', but got: " << cond.text()); } // This should ignore the error during parsing dcmIgnoreParsingErrors.set(OFTrue); cond = readDataset(dset, data, sizeof(data), EXS_LittleEndianImplicit); if (cond.bad()) { OFCHECK_FAIL("Parsing should have worked, but got error: " << cond.text()); } } static void testOddLengthPartialValue(const Uint8* data, size_t length) { const unsigned int bytesToRead = 5; DcmFileFormat dfile; OFCondition cond; DcmElement *elem; Uint8 buf[bytesToRead]; OFTempFile temp; if (temp.getStatus().bad()) { OFCHECK_FAIL("Could not create temporary file: " << temp.getStatus().text()); return; } // Deferred value loading only works with files OFFile f; f.fopen(temp.getFilename(), "wb"); f.fwrite(data, 1, length); f.fclose(); // Everything larger than 1 byte won't be loaded now but only later cond = dfile.loadFile(temp.getFilename(), TRANSFER_SYNTAX, EGL_noChange, 1, ERM_dataset); if (cond.bad()) { OFCHECK_FAIL(cond.text()); return; } cond = dfile.getDataset()->findAndGetElement(DCM_PixelData, elem); if (cond.bad()) { OFCHECK_FAIL(cond.text()); return; } // This is what we are actually testing for. The bug was that // getPartialValue() tried to load one more byte than what was available. // This resulted in an "Invalid stream" error. cond = elem->getPartialValue(buf, 0, bytesToRead); if (cond.bad()) { OFCHECK_FAIL(cond.text()); return; } for (unsigned int i = 0; i < bytesToRead; i++) { OFCHECK_EQUAL(buf[i], VALUE); } } OFTEST(dcmdata_parser_oddLengthPartialValue_lastItem) { const Uint8 data[] = { /* This needs an odd length and OW for the bug that we are testing */ TAG_AND_LENGTH(DCM_PixelData, 'O', 'W', 5), VALUE, VALUE, VALUE, VALUE, VALUE }; testOddLengthPartialValue(data, sizeof(data)); } OFTEST(dcmdata_parser_oddLengthPartialValue_notLastItem) { const Uint8 data[] = { /* This needs an odd length and OW for the bug that we are testing */ TAG_AND_LENGTH(DCM_PixelData, 'O', 'W', 5), VALUE, VALUE, VALUE, VALUE, VALUE, /* This time something else is behind the tested element */ TAG_AND_LENGTH(DCM_DataSetTrailingPadding, 'O', 'B', 4), VALUE, VALUE, VALUE, VALUE }; testOddLengthPartialValue(data, sizeof(data)); } static const DcmTagKey wrongExplicitVRinDataset_unknownTag1(0x0006, 0x0006); static const DcmTagKey wrongExplicitVRinDataset_unknownTag2(0x0006, 0x0008); #define WRONG_EXPLICIT_VR_COMMON \ /* This non-standard tag cannot be corrected (not in data dictionary, short length field) */ \ TAG_AND_LENGTH_SHORT(wrongExplicitVRinDataset_unknownTag1, 'P', 'N', 4), \ 'A', 'B', 'C', 'D', \ /* This non-standard tag cannot be corrected (not in data dictionary, long length field) */ \ TAG_AND_LENGTH(wrongExplicitVRinDataset_unknownTag2, 'U', 'T', 4), \ 'E', 'F', 'G', 'H', \ /* This standard tag has a wrong VR ("ST" instead of "PN") */ \ TAG_AND_LENGTH_SHORT(DCM_PatientName, 'S', 'T', 4), \ 'I', 'J', 'K', 'L', \ /* This standard tag has a correct VR, no modification required */ \ TAG_AND_LENGTH_SHORT(DCM_PatientSex, 'C', 'S', 2), \ 'O', ' ' static const Uint8 wrongExplicitVRinDataset_default_testData[] = { WRONG_EXPLICIT_VR_COMMON, /* This standard tag has a wrong VR ("UN") and does not use the "PN" length field size */ TAG_AND_LENGTH(DCM_PatientBirthName, 'U', 'N', 4), '0', '8', '1', '5', /* This standard tag has a wrong VR ("LO") and does not use the "UT" length field size */ TAG_AND_LENGTH_SHORT(DCM_PixelDataProviderURL, 'L', 'O', 4), '4', '2', '4', '2', }; static const Uint8 wrongExplicitVRinDataset_dictLen_testData[] = { WRONG_EXPLICIT_VR_COMMON, /* This standard tag has a wrong VR ("UN") and uses the "PN" length field size */ TAG_AND_LENGTH_SHORT(DCM_PatientBirthName, 'U', 'N', 4), '0', '8', '1', '5', /* This standard tag has a wrong VR ("LO") and uses the "UT" length field size */ TAG_AND_LENGTH(DCM_PixelDataProviderURL, 'L', 'O', 4), '4', '2', '4', '2', }; #undef WRONG_EXPLICIT_VR_COMMON static void testForExpectedVR(DcmDataset &dset, const DcmTagKey &tag, const DcmEVR vr) { DcmElement *elem; OFCondition cond = dset.findAndGetElement(tag, elem); if (cond.good()) { /* check whether the VR generated by the parser is the expected one */ OFCHECK_EQUAL(elem->getVR(), vr); } else { OFCHECK_FAIL(cond.text()); } } static void testExplicitVRinDataset(OFBool useDictionaryVR, OFBool useDictionaryVRLen) { DcmDataset dset; OFCondition cond; const OFConditionConst *expectedError; const Uint8 *broken, *working; size_t brokenLength, workingLength; if (useDictionaryVRLen) { broken = wrongExplicitVRinDataset_default_testData; brokenLength = sizeof(wrongExplicitVRinDataset_default_testData); working = wrongExplicitVRinDataset_dictLen_testData; workingLength = sizeof(wrongExplicitVRinDataset_dictLen_testData); // This gets the length field wrong and interprets an element's value as // the beginning of the following element; then complains about the length expectedError = NULL; } else { working = wrongExplicitVRinDataset_default_testData; workingLength = sizeof(wrongExplicitVRinDataset_default_testData); broken = wrongExplicitVRinDataset_dictLen_testData; brokenLength = sizeof(wrongExplicitVRinDataset_dictLen_testData); // This gets the length field wrong and tries to allocate a huge element expectedError = &EC_MemoryExhausted; } dcmPreferLengthFieldSizeFromDataDictionary.set(useDictionaryVRLen); dcmPreferVRFromDataDictionary.set(useDictionaryVR); // The broken version shouldn't be parseable cond = readDataset(dset, broken, brokenLength, TRANSFER_SYNTAX); if (cond != EC_InvalidStream && (expectedError == NULL || cond != *expectedError)) { OFCHECK_FAIL("Dataset should fail, but got: " << cond.text()); if (cond.good()) OFLOG_DEBUG(tparserLogger, DcmObject::PrintHelper(dset)); } // The good version should work cond = readDataset(dset, working, workingLength, TRANSFER_SYNTAX); // Reset to the default values dcmPreferLengthFieldSizeFromDataDictionary.set(OFFalse); dcmPreferVRFromDataDictionary.set(OFFalse); if (cond.bad()) { OFCHECK_FAIL(cond.text()); return; } OFLOG_DEBUG(tparserLogger, DcmObject::PrintHelper(dset)); testForExpectedVR(dset, wrongExplicitVRinDataset_unknownTag1, EVR_PN); testForExpectedVR(dset, wrongExplicitVRinDataset_unknownTag2, EVR_UT); testForExpectedVR(dset, DCM_PatientName, useDictionaryVR ? EVR_PN : EVR_ST); testForExpectedVR(dset, DCM_PatientBirthName, useDictionaryVR ? EVR_PN : EVR_UN); testForExpectedVR(dset, DCM_PixelDataProviderURL, useDictionaryVR ? EVR_UR : EVR_LO); } OFTEST(dcmdata_parser_wrongExplicitVRinDataset_default) { // Make sure data dictionary is loaded if (!dcmDataDict.isDictionaryLoaded()) { OFCHECK_FAIL("no data dictionary loaded, check environment variable: " DCM_DICT_ENVIRONMENT_VARIABLE); return; } testExplicitVRinDataset(OFFalse, OFFalse); } OFTEST(dcmdata_parser_wrongExplicitVRinDataset_defaultVR_dictLen) { // Make sure data dictionary is loaded if (!dcmDataDict.isDictionaryLoaded()) { OFCHECK_FAIL("no data dictionary loaded, check environment variable: " DCM_DICT_ENVIRONMENT_VARIABLE); return; } testExplicitVRinDataset(OFFalse, OFTrue); } OFTEST(dcmdata_parser_wrongExplicitVRinDataset_dictVR_defaultLen) { // Make sure data dictionary is loaded if (!dcmDataDict.isDictionaryLoaded()) { OFCHECK_FAIL("no data dictionary loaded, check environment variable: " DCM_DICT_ENVIRONMENT_VARIABLE); return; } testExplicitVRinDataset(OFTrue, OFFalse); } OFTEST(dcmdata_parser_wrongExplicitVRinDataset_preferDataDict) { // Make sure data dictionary is loaded if (!dcmDataDict.isDictionaryLoaded()) { OFCHECK_FAIL("no data dictionary loaded, check environment variable: " DCM_DICT_ENVIRONMENT_VARIABLE); return; } testExplicitVRinDataset(OFTrue, OFTrue); } OFTEST(dcmdata_parser_undefinedLengthUNSequence) { // Make sure data dictionary is loaded if (!dcmDataDict.isDictionaryLoaded()) { OFCHECK_FAIL("no data dictionary loaded, check environment variable: " DCM_DICT_ENVIRONMENT_VARIABLE); return; } const Uint8 data[] = { // Sequence with undefined length and VR UN => gets read as implicit TS TAG_AND_LENGTH(DCM_IconImageSequence, 'U', 'N', UNDEFINED_LENGTH), ITEM(UNDEFINED_LENGTH), IMPLICIT_TAG_AND_LENGTH(DCM_PatientName, 4), 'A', 'B', 'C', 'D', IMPLICIT_TAG_AND_LENGTH(DCM_PixelData, 2), VALUE, VALUE, ITEM_END, SEQUENCE_END }; DcmDataset dset; OFCondition cond; cond = readDatasetTwice(dset, data, sizeof(data), EXS_JPEGProcess14, EXS_JPEGProcess14); if (cond.good()) { OFLOG_DEBUG(tparserLogger, DcmObject::PrintHelper(dset)); OFCHECK_EQUAL(1, dset.card()); DcmElement *elem = NULL; cond = dset.findAndGetElement(DCM_PatientName, elem, OFTrue); if (cond.good()) OFCHECK(elem != NULL); else OFCHECK_FAIL("There should have been a PatientName item, but: " << cond.text()); } else { OFCHECK_FAIL("Parsing should have worked, but got error: " << cond.text()); return; } }