#include "dcm_store.h" //---------------------------------------------------------------------------------------------------- // dcm_cstore_callback dcm_cstore_callback::dcm_cstore_callback() { } dcm_cstore_callback::~dcm_cstore_callback() { } ////dcm_cstore_callback::dcm_cstore_callback(const dcm_cstore_callback& other) ////{ ////} //// ////dcm_cstore_callback& dcm_cstore_callback::operator=(const dcm_cstore_callback& other) ////{ ////} //--------------------------------------------------------------------------------------------------------- // static function static void progressCallback(void *callbackData, T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *request) { dcm_cstore_callback *callback = reinterpret_cast(callbackData); if (callback) callback->callback(progress, request); } //--------------------------------------------------------------------------------------------------------- // dcm-cstore dcm_cstore::dcm_cstore(const char *peerIp, unsigned long peerPort, const char *peerTitle, const char *ourTitle) : abortAssociation(false) , maxReceivedPDULength(ASC_DEFAULTMAXPDU) , maxSendPDULength(ASC_DEFAULTMAXPDU) , networkTransferSyntax(EXS_Unknown) , readMode(ERM_autoDetect) , scanDir(true) , recurse(true) , scanPattern("") , haltOnUnsuccessfulStore(true) , unsuccessfulStoreEncountered(false) , lastStatusCode(STATUS_Success) , proposeOnlyRequiredPresentationContexts(false) , combineProposedTransferSyntaxes(false) , repeatCount(1) , inventPatientCount(25) , inventStudyCount(50) , inventSeriesCount(100) , inventSOPInstanceInformation(false) , correctUIDPadding(false) , patientNamePrefix("OFFIS^TEST_PN_") , patientIDPrefix("PID_") , studyIDPrefix("SID_") , accessionNumberPrefix("") , configFile(NULL) , profileName(NULL) , blockMode(DIMSE_BLOCKING) , dimse_tiemout(0) , acse_tiemout(30) , socket_tiemout(60) , identMode(ASC_USER_IDENTITY_NONE) , user("") , password("") , identFile_("") , identResponse(false) , cstore_callback(nullptr) , patientCounter(0) , studyCounter(0) , seriesCounter(0) , imageCounter(0) { peerIp_ = peerIp; peerPort_ = peerPort; peerTitle_ = peerTitle; ourTitle_ = ourTitle; } dcm_cstore::~dcm_cstore() { } void dcm_cstore::set_dimse_tiemout(int timeout) { dimse_tiemout = timeout; } void dcm_cstore::set_acse_tiemout(int timeout) { acse_tiemout = timeout; } void dcm_cstore::set_ma_received_pdu_length(unsigned long length) { maxReceivedPDULength = length; } void dcm_cstore::set_max_send_pdu_length(unsigned long length) { maxSendPDULength = length; } void dcm_cstore::set_store_callback(dcm_cstore_callback *callback) { cstore_callback = callback; } int dcm_cstore::secondsSince1970() { time_t t = time(NULL); return static_cast(t); } std::string dcm_cstore::intToString(int i) { char numbuf[32]; sprintf(numbuf, "%d", i); return numbuf; } std::string dcm_cstore::makeUID(std::string basePrefix, int counter) { std::string prefix = basePrefix + "." + intToString(counter); char uidbuf[65]; std::string uid = dcmGenerateUniqueIdentifier(uidbuf, prefix.c_str()); return uid; } bool dcm_cstore::updateStringAttributeValue(DcmItem *dataset, const DcmTagKey &key, std::string &value) { DcmStack stack; DcmTag tag(key); OFCondition cond = EC_Normal; cond = dataset->search(key, stack, ESM_fromHere, false); if (cond != EC_Normal) { // log >> "updateStringAttributeValue : cannot find return false; } DcmElement *elem = static_cast(stack.top()); DcmVR vr(elem->ident()); if (elem->getLength() > vr.getMaxValueLength()) { // log >> undateStringAttributeValue : INTERNAL ERROR return false; } cond = elem->putOFStringArray(value); if (cond != EC_Normal) { // log >> updateStringAttributeValue : cannot put string in attribute return false; } return true; } void dcm_cstore::replaceSOPInstanceInformation(DcmDataset *dataset) { std::string seriesInstanceUID; std::string seriesNumber; std::string studyInstanceUID; std::string studyID; std::string accessionNumber; std::string patientID; std::string patientName; if (seriesInstanceUID.empty()) seriesInstanceUID = makeUID(SITE_SERIES_UID_ROOT, static_cast(seriesCounter)); if (seriesNumber.empty()) seriesNumber = intToString(static_cast(seriesCounter)); if (studyInstanceUID.empty()) studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, static_cast(studyCounter)); if (studyID.empty()) studyID = studyIDPrefix + intToString(static_cast(secondsSince1970())) + intToString(static_cast(studyCounter)); if (accessionNumber.empty()) accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(static_cast(studyCounter)); if (patientID.empty()) patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(static_cast(patientCounter)); if (patientName.empty()) patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(static_cast(patientCounter)); if (imageCounter >= inventSeriesCount) { imageCounter = 0; seriesCounter++; seriesInstanceUID = makeUID(SITE_SERIES_UID_ROOT, static_cast(seriesCounter)); seriesNumber = intToString(static_cast(seriesCounter)); } if (seriesCounter >= inventStudyCount) { seriesCounter = 0; studyCounter++; studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, static_cast(studyCounter)); studyID = studyIDPrefix + intToString(secondsSince1970()) + intToString(static_cast(studyCounter)); accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(static_cast(studyCounter)); } if (studyCounter >= inventPatientCount) { studyCounter = 0; patientCounter++; patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(static_cast(patientCounter)); patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(static_cast(patientCounter)); } std::string sopInstanceUID = makeUID(SITE_INSTANCE_UID_ROOT, static_cast(imageCounter)); std::string imageNumber = intToString(static_cast(imageCounter)); updateStringAttributeValue(dataset, DCM_PatientName, patientName); updateStringAttributeValue(dataset, DCM_PatientID, patientID); updateStringAttributeValue(dataset, DCM_StudyInstanceUID, studyInstanceUID); updateStringAttributeValue(dataset, DCM_StudyID, studyID); updateStringAttributeValue(dataset, DCM_SeriesInstanceUID, seriesInstanceUID); updateStringAttributeValue(dataset, DCM_SeriesNumber, seriesNumber); updateStringAttributeValue(dataset, DCM_SOPInstanceUID, sopInstanceUID); updateStringAttributeValue(dataset, DCM_InstanceNumber, imageNumber); imageCounter++; } OFCondition dcm_cstore::addStoragePresentationContexts(T_ASC_Parameters *params, std::list &sopClasses) { std::string preferredTransferSyntax; if (networkTransferSyntax == EXS_Unknown) { if (gLocalByteOrder == E_ByteOrder::EBO_LittleEndian) { preferredTransferSyntax = UID_LittleEndianExplicitTransferSyntax; } else { preferredTransferSyntax = UID_BigEndianExplicitTransferSyntax; } } else { DcmXfer xfer(networkTransferSyntax); preferredTransferSyntax = xfer.getXferID(); } std::list::iterator s_cur; std::list::iterator s_end; std::list fallbackSyntaxes; if ((networkTransferSyntax != EXS_LittleEndianImplicit) && (networkTransferSyntax != EXS_MPEG2MainProfileAtMainLevel) && (networkTransferSyntax != EXS_MPEG2MainProfileAtHighLevel) && (networkTransferSyntax != EXS_MPEG4HighProfileLevel4_1) && (networkTransferSyntax != EXS_MPEG4BDcompatibleHighProfileLevel4_1) && (networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For2DVideo) && (networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For3DVideo) && (networkTransferSyntax != EXS_MPEG4StereoHighProfileLevel4_2) && (networkTransferSyntax != EXS_HEVCMainProfileLevel5_1) && (networkTransferSyntax != EXS_HEVCMain10ProfileLevel5_1)) { fallbackSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); fallbackSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); fallbackSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); fallbackSyntaxes.remove(preferredTransferSyntax); } std::list combinedSyntaxes; s_cur = fallbackSyntaxes.begin(); s_end = fallbackSyntaxes.end(); combinedSyntaxes.push_back(preferredTransferSyntax); while (s_cur != s_end) { if (!isaListMember(combinedSyntaxes, *s_cur)) { combinedSyntaxes.push_back(*s_cur); } ++s_cur; } if (!proposeOnlyRequiredPresentationContexts) { for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++) { sopClasses.push_back(dcmShortSCUStorageSOPClassUIDs[i]); } } std::list sops; s_cur = sopClasses.begin(); s_end = sopClasses.end(); while (s_cur != s_end) { if (!isaListMember(sops, *s_cur)) { sops.push_back(*s_cur); } ++s_cur; } OFCondition cond = EC_Normal; int pid = 1; s_cur = sops.begin(); s_end = sops.end(); while (s_cur != s_end && cond.good()) { if (pid > 255) { // log >> Too many presentation contexts return ASC_BADPRESENTATIONCONTEXTID; } if (combineProposedTransferSyntaxes) { addPresentationContext(params, pid, *s_cur, combinedSyntaxes); pid += 2; } else { cond = addPresentationContext(params, pid, *s_cur, preferredTransferSyntax); pid += 2; if (fallbackSyntaxes.size() > 0) { if (pid > 255) { // log >> Too many presentation contexes return ASC_BADPRESENTATIONCONTEXTID; } cond = addPresentationContext(params, pid, *s_cur, fallbackSyntaxes); pid += 2; } } ++s_cur; } return cond; } bool dcm_cstore::findSOPClassAndInstanceInFile(const char *fname, char *sopClass, size_t sopClassSize, char *sopInstance, size_t sopInstanceSize) { DcmFileFormat ff; if (!ff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, readMode).good()) { return false; } bool found = DU_findSOPClassAndInstanceInDataSet(ff.getMetaInfo(), sopClass, sopClassSize, sopInstance, sopInstanceSize, correctUIDPadding); if (!found) found = DU_findSOPClassAndInstanceInDataSet(ff.getDataset(), sopClass, sopClassSize, sopInstance, sopInstanceSize, correctUIDPadding); return found; } OFCondition dcm_cstore::configureUserIdentityRequest(T_ASC_Parameters *params) { OFCondition cond = EC_Normal; switch (identMode) { case ASC_USER_IDENTITY_USER: { cond = ASC_setIdentRQUserOnly(params, user, identResponse); break; } case ASC_USER_IDENTITY_USER_PASSWORD: { cond = ASC_setIdentRQUserPassword(params, user, password, identResponse); break; } case ASC_USER_IDENTITY_KERBEROS: case ASC_USER_IDENTITY_SAML: case ASC_USER_IDENTITY_JWT: { OFFile identFile; if (!identFile.fopen(identFile_.c_str(), "rb")) { // log >> Unable to open Kerberbose, SAML or JWT file return EC_IllegalCall; } offile_off_t result = identFile.fseek(0, SEEK_END); if (result != 0) return EC_IllegalParameter; offile_off_t filesize = identFile.ftell(); if (filesize > 65535) { // log >> "Kerberos, SAML or JWT file is larger than 65535 bytes, bytes after that position are ignored filesize = 65535; } char *buf = new char[static_cast(filesize)]; size_t bytesRead = identFile.fread(buf, 1, static_cast(filesize)); identFile.fclose(); if (bytesRead == 0) { // log >> Unable to read Kerberos, SAML or JWT info file delete[] buf; return EC_IllegalCall; } if (identMode == ASC_USER_IDENTITY_KERBEROS) cond = ASC_setIdentRQKerberos(params, buf, static_cast(bytesRead), identResponse); else if (identMode == ASC_USER_IDENTITY_SAML) cond = ASC_setIdentRQSaml(params, buf, static_cast(bytesRead), identResponse); else cond = ASC_setIdentRQJwt(params, buf, static_cast(bytesRead), identResponse); delete[] buf; break; } default: { cond = EC_IllegalCall; break; } } if (cond.bad()) { // log >> Failed } return cond; } OFCondition dcm_cstore::checkUserIdentityResponse(T_ASC_Parameters *params) { if (params == NULL) return ASC_NULLKEY; if (identMode == ASC_USER_IDENTITY_NONE || !identResponse) return EC_Normal; if (identMode == ASC_USER_IDENTITY_USER || identMode == ASC_USER_IDENTITY_USER_PASSWORD) { UserIdentityNegotiationSubItemAC *rsp = params->DULparams.ackUserIdentNeg; if (rsp == NULL) { // log >> User Identity Negotitation failed: Positive response requested but none received return ASC_USERIDENTIFICATIONFAILED; } } return EC_Normal; } bool dcm_cstore::isaListMember(std::list &lst, std::string &s) { std::list::iterator cur = lst.begin(); std::list::iterator end = lst.end(); bool found = false; while (cur != end && !found) { found = (s == *cur); ++cur; } return found; } OFCondition dcm_cstore::addPresentationContext(T_ASC_Parameters *params, int presentationContextId, const std::string &abstractSyntax, const std::string &transferSyntax, T_ASC_SC_ROLE proposedRole) { const char *c_p = transferSyntax.c_str(); OFCondition cond = ASC_addPresentationContext(params, presentationContextId, abstractSyntax.c_str(), &c_p, 1, proposedRole); return cond; } OFCondition dcm_cstore::addPresentationContext(T_ASC_Parameters *params, int presentationContextId, const std::string &abstracySyntax, const std::list &transferSyntaxList, T_ASC_SC_ROLE proposedRole) { const char **transferSyntaxes = new const char*[transferSyntaxList.size()]; int transferSyntaxCount = 0; std::list::const_iterator s_cur = transferSyntaxList.begin(); std::list::const_iterator s_end = transferSyntaxList.end(); while (s_cur != s_end) { transferSyntaxes[transferSyntaxCount++] = (*s_cur).c_str(); ++s_cur; } OFCondition cond = ASC_addPresentationContext(params, presentationContextId, abstracySyntax.c_str(), transferSyntaxes, transferSyntaxCount, proposedRole); delete[] transferSyntaxes; return cond; } OFCondition dcm_cstore::storeSCU(T_ASC_Association *assoc, const char *fname) { DIC_US msgId = assoc->nextMsgID++; T_ASC_PresentationContextID presID; T_DIMSE_C_StoreRQ req; T_DIMSE_C_StoreRSP rsp; DIC_UI sopClass; DIC_UI sopInstance; DcmDataset *statusDetail = NULL; unsuccessfulStoreEncountered = true; DcmFileFormat dcmff; OFCondition cond = dcmff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, readMode); if (cond.bad()) { // log >> Bad DICOM file return cond; } if (inventSOPInstanceInformation) { replaceSOPInstanceInformation(dcmff.getDataset()); } if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), correctUIDPadding)) { // log >> No SOP Class or Instance UID in file return DIMSE_BADDATA; } DcmXfer filexfer(dcmff.getDataset()->getOriginalXfer()); if (filexfer.isNotEncapsulated() && networkTransferSyntax == EXS_DeflatedLittleEndianExplicit) { filexfer = EXS_DeflatedLittleEndianExplicit; } if (filexfer.getXfer() != EXS_Unknown) { presID = ASC_findAcceptedPresentationContextID(assoc, sopClass, filexfer.getXferID()); } else { presID = ASC_findAcceptedPresentationContextID(assoc, sopClass); } if (presID == 0) { const char *modalityName = dcmSOPClassUIDToModality(sopClass); if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); if (!modalityName) modalityName = "unknown SOP class"; // log >> No presentation context for : return DIMSE_NOVALIDPRESENTATIONCONTEXTID; } T_ASC_PresentationContext pc; ASC_findAcceptedPresentationContext(assoc->params, presID, &pc); DcmXfer netTransfer(pc.acceptedTransferSyntax); bzero(reinterpret_cast(&req), sizeof(req)); req.MessageID = msgId; OFStandard::strlcpy(req.AffectedSOPClassUID, sopClass, sizeof(req.AffectedSOPClassUID)); OFStandard::strlcpy(req.AffectedSOPInstanceUID, sopInstance, sizeof(req.AffectedSOPInstanceUID)); req.DataSetType = DIMSE_DATASET_PRESENT; req.Priority = DIMSE_PRIORITY_MEDIUM; cond = DIMSE_storeUser(assoc, presID, &req, NULL, dcmff.getDataset(), progressCallback, cstore_callback, blockMode, dimse_tiemout, &rsp, &statusDetail, NULL, static_cast(OFStandard::getFileSize(fname))); if (cond == EC_Normal && (rsp.DimseStatus == STATUS_Success || DICOM_WARNING_STATUS(rsp.DimseStatus))) { unsuccessfulStoreEncountered = false; } lastStatusCode = rsp.DimseStatus; if (cond == EC_Normal) { // log >> Recevied Store Response } else { // log >> Store Failed, file } return cond; } OFCondition dcm_cstore::cstore(T_ASC_Association *assoc, const std::string &fname) { OFCondition cond = EC_Normal; int n = static_cast(repeatCount); while (cond.good() && n-- && !(haltOnUnsuccessfulStore && unsuccessfulStoreEncountered)) { cond = storeSCU(assoc, fname.c_str()); } if (!haltOnUnsuccessfulStore) { cond = EC_Normal; } return cond; } int dcm_cstore::docstore(std::string dir) { std::list fileNameList; std::list sopClassUIDList; std::list sopInstanceUIDList; T_ASC_Network * net; T_ASC_Parameters * params; DIC_NODENAME peerHost; T_ASC_Association * assoc; DcmAssociationConfiguration asccfg; OFStandard::initializeNetwork(); if (!dcmDataDict.isDictionaryLoaded()) { // log >> "no data dictionary loaded, check environment variable } std::list inputFiles; OFStandard::searchDirectoryRecursively(dir.c_str(), inputFiles, scanPattern, "", recurse); if (inputFiles.empty()) { return e_no_input_file; } DcmFileFormat dfile; char sopClassUID[128]; char sopInstanceUID[128]; bool ignoreName; const char * currentFileName = NULL; std::list::iterator if_iter = inputFiles.begin(); std::list::iterator if_last = inputFiles.end(); while (if_iter != if_last) { ignoreName = false; currentFileName = (*if_iter).c_str(); if (OFStandard::fileExists(currentFileName)) { if (proposeOnlyRequiredPresentationContexts) { if (!findSOPClassAndInstanceInFile(currentFileName, sopClassUID, sizeof(sopClassUID), sopInstanceUID, sizeof(sopInstanceUID))) { ignoreName = true; if (haltOnUnsuccessfulStore) { // missing SOP class (or instance) in file return e_no_presentation_context; } } else if (!dcmIsaStorageSOPClassUID(sopClassUID, ESSC_All)) { ignoreName = true; if (haltOnUnsuccessfulStore) { // unknown storage SOP class in file return e_unknown_storage_sopclass; } } else { sopClassUIDList.push_back(sopClassUID); sopInstanceUIDList.push_back(sopInstanceUID); } } if (!ignoreName) { fileNameList.push_back(currentFileName); } } else { if (haltOnUnsuccessfulStore) { // cannot access file return e_cannot_access_file; } } ++if_iter; } OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, acse_tiemout, &net); if (cond.bad()) { // log >> ASC_initializeNetwork Failed return cond.code(); } cond = ASC_createAssociationParameters(¶ms, maxReceivedPDULength); if (cond.bad()) { // log >> ASC_createAssociationParameters Failed return cond.code(); } ASC_setAPTitles(params, ourTitle_, peerTitle_, NULL); sprintf(peerHost, "%s:%d", peerIp_, static_cast(peerPort_)); ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), peerHost); if (identMode != ASC_USER_IDENTITY_NONE) { cond = configureUserIdentityRequest(params); if (cond.bad()) return cond.code(); } if (profileName) { std::string sprofile; const unsigned char * c = reinterpret_cast(profileName); while (*c) { if (!isspace(*c)) sprofile += static_cast(toupper(*c)); ++c; } cond = asccfg.setAssociationParameters(sprofile.c_str(), *params); } else { cond = addStoragePresentationContexts(params, sopClassUIDList); } if (cond.bad()) { // log >> addStoragePresentationContexts List Failed return cond.code(); } cond = ASC_requestAssociation(net, params, &assoc); if (cond.bad()) { if (cond == DUL_ASSOCIATIONREJECTED) { // Association Rejected return cond.code(); } else { // C-Store Association Request Failed return cond.code(); } } if (ASC_countAcceptedPresentationContexts(params) == 0) { // No Acceptable Presentation Contexts return e_no_acceptable_presentation_context; } cond = checkUserIdentityResponse(params); if (cond.bad()) { // checkUserIdentityResponse Failed return cond.code(); } // Association Accepted cond = EC_Normal; std::list::iterator iter = fileNameList.begin(); std::list::iterator enditer = fileNameList.end(); while (iter != enditer && cond.good()) { cond = cstore(assoc, *iter); ++iter; } if (cond == EC_Normal) { if (abortAssociation) { // Aborting Association cond = ASC_abortAssociation(assoc); if (cond.bad()) { // Association Abort Failed return cond.code(); } else { // Releasing Association cond = ASC_releaseAssociation(assoc); if (cond.bad()) { // Association Release Failed return cond.code(); } } } } else if (cond == DUL_PEERREQUESTEDRELEASE) { // Protocol Error: Peer request release cond = ASC_abortAssociation(assoc); if (cond.bad()) { // Association Abort Failed return cond.code(); } } else if (cond == DUL_PEERABORTEDASSOCIATION) { // Peer Aborted Association } else { // Store SCU Failed cond = ASC_abortAssociation(assoc); if (cond.bad()) { // Association Abort Failed return cond.code(); } } cond = ASC_destroyAssociation(&assoc); if (cond.bad()) { // ASC_destroyAssociation Failed return cond.code(); } cond = ASC_dropNetwork(&net); if (cond.bad()) { // ASC_dropNetwork Failed return cond.code(); } OFStandard::shutdownNetwork(); return e_ok; }