/* * * Copyright (C) 1994-2021, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were partly developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * For further copyrights, see the following paragraphs. * */ /* ** Copyright (C) 1993/1994, OFFIS, Oldenburg University and CERIUM ** ** This software and supporting documentation were ** developed by ** ** Institut OFFIS ** Bereich Kommunikationssysteme ** Westerstr. 10-12 ** 26121 Oldenburg, Germany ** ** Fachbereich Informatik ** Abteilung Prozessinformatik ** Carl von Ossietzky Universitaet Oldenburg ** Ammerlaender Heerstr. 114-118 ** 26111 Oldenburg, Germany ** ** CERIUM ** Laboratoire SIM ** Faculte de Medecine ** 2 Avenue du Pr. Leon Bernard ** 35043 Rennes Cedex, France ** ** for CEN/TC251/WG4 as a contribution to the Radiological ** Society of North America (RSNA) 1993 Digital Imaging and ** Communications in Medicine (DICOM) Demonstration. ** ** THIS SOFTWARE IS MADE AVAILABLE, AS IS, AND NEITHER OFFIS, ** OLDENBURG UNIVERSITY NOR CERIUM MAKE ANY WARRANTY REGARDING ** THE SOFTWARE, ITS PERFORMANCE, ITS MERCHANTABILITY OR ** FITNESS FOR ANY PARTICULAR USE, FREEDOM FROM ANY COMPUTER ** DISEASES OR ITS CONFORMITY TO ANY SPECIFICATION. THE ** ENTIRE RISK AS TO QUALITY AND PERFORMANCE OF THE SOFTWARE ** IS WITH THE USER. ** ** Copyright of the software and supporting documentation ** is, unless otherwise stated, jointly owned by OFFIS, ** Oldenburg University and CERIUM and free access is hereby ** granted as a license to use this software, copy this ** software and prepare derivative works based upon this ** software. However, any distribution of this software ** source code or supporting documentation or derivative ** works (source code and supporting documentation) must ** include the three paragraphs of this copyright notice. ** */ /* ** ** Author: Andrew Hewett Created: 03-06-93 ** ** Module: dimstore ** ** Purpose: ** This file contains the routines which help with ** storage services. ** ** Module Prefix: DIMSE_ */ /* ** Include Files */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #ifdef HAVE_FCNTL_H #include #endif #include "dcmtk/dcmnet/diutil.h" #include "dcmtk/dcmnet/dimse.h" /* always include the module header */ #include "dcmtk/dcmnet/cond.h" #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */ #include "dcmtk/ofstd/ofstd.h" /* for OFStandard::getFileSize() */ /* Global flag to enable/disable workaround code for some buggy Store SCUs * in DIMSE_storeProvider(). If enabled, an illegal space-padding in the * Affected SOP Instance UID field of the C-STORE-RQ message is retained * in the corresponding C-STORE-RSP message. * To enable the workaround, this flag must be set to OFTrue and * dcmEnableAutomaticInputDataCorrection must be set to OFFalse. * (see declaration in dcmdata/include/dcobject.h) */ OFGlobal dcmPeerRequiresExactUIDCopy(OFFalse); /* ** */ typedef struct { void *callbackData; T_DIMSE_StoreProgress *progress; T_DIMSE_C_StoreRQ *request; DIMSE_StoreUserCallback callback; } DIMSE_PrivateUserContext; static void privateUserCallback(void *callbackData, unsigned long bytes) { DIMSE_PrivateUserContext *ctx; ctx = (DIMSE_PrivateUserContext*)callbackData; ctx->progress->state = DIMSE_StoreProgressing; ctx->progress->progressBytes = bytes; ctx->progress->callbackCount++; if (ctx->callback) { ctx->callback(ctx->callbackData, ctx->progress, ctx->request); } } OFCondition DIMSE_storeUser( T_ASC_Association *assoc, T_ASC_PresentationContextID presId, T_DIMSE_C_StoreRQ *request, const char *imageFileName, DcmDataset *imageDataSet, DIMSE_StoreUserCallback callback, void *callbackData, T_DIMSE_BlockingMode blockMode, int timeout, T_DIMSE_C_StoreRSP *response, DcmDataset **statusDetail, T_DIMSE_DetectedCancelParameters *checkForCancelParams, long imageFileTotalBytes) /* * This function transmits data from a file or a dataset to an SCP. The transmission is * conducted via network and using DIMSE C-STORE messages. Additionally, this function * evaluates C-STORE-Response messages which were received from the SCP. * * Parameters: * assoc - [in] The association (network connection to SCP). * presId - [in] The ID of the presentation context which shall be used * request - [in] Represents a DIMSE C-Store Request Message. Contains corresponding * information, e.g. message ID, affected SOP class UID, etc. * imageFileName - [in] The name of the file which is currently processed. * imageDataSet - [in] The data set which is currently processed. * callback - [in] Pointer to a function which shall be called to indicate progress. * callbackData - [in] Pointer to data which shall be passed to the progress indicating function * blockMode - [in] The blocking mode for receiving data (either DIMSE_BLOCKING or DIMSE_NONBLOCKING) * timeout - [in] Timeout interval for receiving data. If the blocking mode is DIMSE_NONBLOCKING * response - [out] Represents a DIMSE C-Store Response Message. Contains corresponding * information, e.g. message ID being responded to, affected SOP class UID, etc. * This variable contains in the end the C-STORE-RSP command which was received * as a response to the C-STORE-RQ which was sent. * 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) of the response message. Note that the value for element (0000,0900) * is not contained in this return value but in response. * checkForCancelParams - [out] Indicates, if a C-Cancel (Request) Message was encountered. Contains corresponding * information, e.g. a boolean value if a corresponding message was encountered and the * C-Cancel (Request) Message itself (in case it actually was encountered). * imageFileTotalBytes - [in] The size of the file which is currently processed in bytes. */ { OFCondition cond = EC_Normal; T_DIMSE_Message req, rsp; DIMSE_PrivateUserContext callbackCtx; DIMSE_ProgressCallback privCallback = NULL; T_DIMSE_StoreProgress progress; progress.state = DIMSE_StoreBegin; progress.callbackCount = 0; progress.progressBytes = 0; progress.totalBytes = 0; /* if there is no image file or no data set, no data can be sent */ if (imageFileName == NULL && imageDataSet == NULL) return DIMSE_NULLKEY; /* initialize the variables which represent DIMSE C-STORE request and DIMSE C-STORE response messages */ memset((char*)&req, 0, sizeof(req)); memset((char*)&rsp, 0, sizeof(rsp)); /* set corresponding values in the request message variable */ req.CommandField = DIMSE_C_STORE_RQ; request->DataSetType = DIMSE_DATASET_PRESENT; req.msg.CStoreRQ = *request; /* set up callback routine which is used to indicate progress */ if (callback != NULL) { /* in case the caller indicated that he has his own progress indicating */ /* function set some variables correspondingly so that this particular */ /* function will be called whenever progress shall be indicated. */ privCallback = privateUserCallback; /* function defined above */ callbackCtx.callbackData = callbackData; progress.state = DIMSE_StoreBegin; progress.callbackCount = 1; progress.progressBytes = 0; if (imageFileTotalBytes > 0) progress.totalBytes = imageFileTotalBytes; else { if (imageFileName != NULL) progress.totalBytes = OFstatic_cast(long, OFStandard::getFileSize(imageFileName)); else progress.totalBytes = dcmGuessModalityBytes(request->AffectedSOPClassUID); } callbackCtx.progress = &progress; callbackCtx.request = request; callbackCtx.callback = callback; /* execute initial callback */ callback(callbackData, &progress, request); } else { /* in case the caller does not have his own progress indicating function no */ /* corresponding function will be called when progress shall be indicated. */ privCallback = NULL; } /* send C-STORE-RQ message and instance data using file data or data set */ if (imageFileName != NULL) { cond = DIMSE_sendMessageUsingFileData(assoc, presId, &req, NULL, imageFileName, privCallback, &callbackCtx); } else { cond = DIMSE_sendMessageUsingMemoryData(assoc, presId, &req, NULL, imageDataSet, privCallback, &callbackCtx); } if (cond != EC_Normal) { return cond; } /* execute final callback */ if (callback) { progress.state = DIMSE_StoreEnd; progress.callbackCount++; /* execute final callback */ callback(callbackData, &progress, request); } /* check if a C-CANCEL-RQ message was encountered earlier */ if (checkForCancelParams != NULL) { checkForCancelParams->cancelEncountered = OFFalse; } /* try to receive C-STORE-RSP */ do { /* remember the ID of the presentation context in a local variable */ T_ASC_PresentationContextID thisPresId = presId; /* try to receive a C-STORE-RSP over the network. */ cond = DIMSE_receiveCommand(assoc, blockMode, timeout, &thisPresId, &rsp, statusDetail); if (cond != EC_Normal) return cond; /* if everything was successful so far, the rsp variable contains the command which */ /* was received check if we encountered a C-CANCEL-RQ; if so, set some variables */ if (checkForCancelParams != NULL && rsp.CommandField == DIMSE_C_CANCEL_RQ) { checkForCancelParams->cancelEncountered = OFTrue; checkForCancelParams->req = rsp.msg.CCancelRQ; checkForCancelParams->presId = thisPresId; } else { /* if we did not receive a C-CANCEL-RQ */ /* if we did also not encounter a C-STORE-RSP, something is wrong */ if (rsp.CommandField != DIMSE_C_STORE_RSP) { char buf[256]; sprintf(buf, "DIMSE: Unexpected Response Command Field: 0x%x", (unsigned)rsp.CommandField); return makeDcmnetCondition(DIMSEC_UNEXPECTEDRESPONSE, OF_error, buf); } /* if we get to here, we received a C-STORE-RSP; store this message in the reference parameter */ *response = rsp.msg.CStoreRSP; // BoundsChecker warning !? /* check if the response relates to the request which was sent earlier; if not, return an error */ if (response->MessageIDBeingRespondedTo != request->MessageID) { char buf2[256]; sprintf(buf2, "DIMSE: Unexpected Response MsgId: %d (expected: %d)", response->MessageIDBeingRespondedTo, request->MessageID); return makeDcmnetCondition(DIMSEC_UNEXPECTEDRESPONSE, OF_error, buf2); } } } while (checkForCancelParams != NULL && rsp.CommandField == DIMSE_C_CANCEL_RQ); /* return result value */ return EC_Normal; } OFCondition DIMSE_sendStoreResponse(T_ASC_Association * assoc, T_ASC_PresentationContextID presID, const T_DIMSE_C_StoreRQ *request, T_DIMSE_C_StoreRSP *response, DcmDataset *statusDetail) /* * This function takes care of sending a C-STORE-RSP message over the network to the DICOM * application this application is connected with. * * Parameters: * assoc - [in] The association (network connection to another DICOM application). * presID - [in] The ID of the presentation context which was specified in the PDV * which contained the DIMSE C-STORE-RQ command. * request - [in] The DIMSE C-STORE-RQ command which was received earlier. * response - [inout] The C-STORE-RSP command which shall be sent. Might be modified. * statusDetail - [in] 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 contained in this variable. */ { OFCondition cond = EC_Normal; T_DIMSE_Message rsp; /* create response message */ memset((char*)&rsp, 0, sizeof(rsp)); rsp.CommandField = DIMSE_C_STORE_RSP; response->MessageIDBeingRespondedTo = request->MessageID; OFStandard::strlcpy(response->AffectedSOPClassUID, request->AffectedSOPClassUID, sizeof(response->AffectedSOPClassUID)); OFStandard::strlcpy(response->AffectedSOPInstanceUID, request->AffectedSOPInstanceUID, sizeof(response->AffectedSOPInstanceUID)); response->opts = (O_STORE_AFFECTEDSOPCLASSUID | O_STORE_AFFECTEDSOPINSTANCEUID); response->DataSetType = DIMSE_DATASET_NULL; rsp.msg.CStoreRSP = *response; /* send response message over the network */ cond = DIMSE_sendMessageUsingMemoryData(assoc, presID, &rsp, statusDetail, NULL, NULL, NULL); /* return result value */ return cond; } typedef struct { void *callbackData; T_DIMSE_StoreProgress *progress; T_DIMSE_C_StoreRQ *request; char *imageFileName; DcmDataset **imageDataSet; T_DIMSE_C_StoreRSP *response; DcmDataset **statusDetail; DIMSE_StoreProviderCallback callback; } DIMSE_PrivateProviderContext; static void privateProviderCallback(void *callbackData, unsigned long bytes) { DIMSE_PrivateProviderContext *ctx; ctx = (DIMSE_PrivateProviderContext*)callbackData; ctx->progress->state = DIMSE_StoreProgressing; ctx->progress->progressBytes = bytes; ctx->progress->callbackCount++; if (ctx->callback) { ctx->callback(ctx->callbackData, ctx->progress, ctx->request, ctx->imageFileName, ctx->imageDataSet, ctx->response, ctx->statusDetail); } } OFCondition DIMSE_storeProvider( T_ASC_Association *assoc, T_ASC_PresentationContextID presIdCmd, T_DIMSE_C_StoreRQ *request, const char* imageFileName, int writeMetaheader, DcmDataset **imageDataSet, DIMSE_StoreProviderCallback callback, void *callbackData, T_DIMSE_BlockingMode blockMode, int timeout) /* * This function receives a data set over the network and either stores this data in a file (exactly as it was * received) or it stores this data in memory. Before, during and after the process of receiving data, the callback * function which was provided by the caller (if it was provided) will be called to indicate progress. * * Parameters: * assoc - [in] The association (network connection to another DICOM application). * presIDCmd - [in] The ID of the presentation context which was specified in the PDV which contained * the DIMSE command. * request - [in] The DIMSE C-STORE-RQ message that was received. * imageFileName - [in] If this variable does not equal NULL, the information (which was received over the network) * will be written to a file exactly as it was received over the network. In such a case, this * this variable contains the name of the file the information shall be written to. * writeMetaheader - [in] Specifies if the resulting file shall only contain the dataset which was received * (OFFalse) or if it shall contain both metaheader and dataset information (OFTrue) * (i.e if the file will be written according to the DICOM file format). * imageDataSet - [inout] If this variable does not equal NULL, and at the same time imageFileName equals NULL, * this variable will in the end contain the information which was received over the network. * Note that this function assumes that either imageFileName or imageDataSet does not equal NULL. * callback - [in] Pointer to a function which shall be called to indicate progress. * callbackData - [in] Pointer to data which shall be passed to the progress indicating function * blockMode - [in] The blocking mode for receiving data (either DIMSE_BLOCKING or DIMSE_NONBLOCKING) * timeout - [in] Timeout interval for receiving data (if the blocking mode is DIMSE_NONBLOCKING). */ { OFCondition cond = EC_Normal; DIMSE_PrivateProviderContext callbackCtx; DIMSE_ProgressCallback privCallback = NULL; T_ASC_PresentationContextID presIdData = 0; T_DIMSE_C_StoreRSP response; DcmDataset *statusDetail = NULL; T_DIMSE_StoreProgress progress; /* initialize progress struct */ progress.state = DIMSE_StoreBegin; progress.callbackCount = 1; progress.progressBytes = 0; progress.totalBytes = 0; /* initialize the C-STORE-RSP message variable */ memset((char*)&response, 0, sizeof(response)); response.DimseStatus = STATUS_STORE_Success; /* assume */ response.MessageIDBeingRespondedTo = request->MessageID; response.DataSetType = DIMSE_DATASET_NULL; /* always for C-STORE-RSP */ OFStandard::strlcpy(response.AffectedSOPClassUID, request->AffectedSOPClassUID, sizeof(response.AffectedSOPClassUID)); OFStandard::strlcpy(response.AffectedSOPInstanceUID, request->AffectedSOPInstanceUID, sizeof(response.AffectedSOPInstanceUID)); response.opts = (O_STORE_AFFECTEDSOPCLASSUID | O_STORE_AFFECTEDSOPINSTANCEUID); if (request->opts & O_STORE_RQ_BLANK_PADDING) response.opts |= O_STORE_RSP_BLANK_PADDING; if (dcmPeerRequiresExactUIDCopy.get()) response.opts |= O_STORE_PEER_REQUIRES_EXACT_UID_COPY; /* set up callback routine */ if (callback != NULL) { /* only if caller requires */ privCallback = privateProviderCallback; /* function defined above */ callbackCtx.callbackData = callbackData; progress.totalBytes = dcmGuessModalityBytes(request->AffectedSOPClassUID); callbackCtx.progress = &progress; callbackCtx.request = request; callbackCtx.imageFileName = (char*)imageFileName; callbackCtx.imageDataSet = imageDataSet; callbackCtx.response = &response; callbackCtx.statusDetail = &statusDetail; callbackCtx.callback = callback; /* execute initial callback */ callback(callbackData, &progress, request, (char*)imageFileName, imageDataSet, &response, &statusDetail); } else { privCallback = NULL; } /* in the following, we want to receive data over the network and do something with this data. If the */ /* imageFileName does not equal NULL, the caller required that the data shall be written 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(...).*/ /* If the imageFileName does equal NULL but at the same time imageDataSet does not equal NULL, the */ /* data shall be received and stored in memory. This will be handled through the call to function */ /* DIMSE_receiveDataSetInMemory(...). The case in which both variables are NULL is considered to */ /* be an error and will be handled correspondingly. */ if (imageFileName != NULL) { /* create filestream */ DcmOutputFileStream *filestream = NULL; cond = DIMSE_createFilestream(imageFileName, request, assoc, presIdCmd, writeMetaheader, &filestream); if (cond.bad()) { /* 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; cond = DIMSE_ignoreDataSet(assoc, blockMode, timeout, &bytesRead, &pdvCount); if (cond.good()) { OFString s = "DIMSE_storeProvider: Cannot create file: "; s += imageFileName; cond = makeDcmnetCondition(DIMSEC_OUTOFRESOURCES, OF_error, s.c_str()); } } else { /* if no error occurred, receive data and write it to the file */ cond = DIMSE_receiveDataSetInFile(assoc, blockMode, timeout, &presIdData, filestream, privCallback, &callbackCtx); delete filestream; if (cond != EC_Normal) { if (strcmp(imageFileName, NULL_DEVICE_NAME) != 0) OFStandard::deleteFile(imageFileName); } } } else if (imageDataSet != NULL) { /* receive data and store it in memory */ cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &presIdData, imageDataSet, privCallback, &callbackCtx); } else { /* if both variables are set to NULL, report an error */ return DIMSE_BADDATA; } /* check if presentation context IDs of the command (which was received earlier) and of the data */ /* set (which was received just now) differ from each other. If this is the case, return an error. */ if (cond.good() && (presIdData != presIdCmd)) { cond = makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID, OF_error, "DIMSE: Presentation Contexts of Command and Data Differ"); } /* depending on the error status, set the success indicating flag in the response message */ if (cond == EC_Normal) { response.DimseStatus = STATUS_STORE_Success; } else if (cond == DIMSE_OUTOFRESOURCES) { response.DimseStatus = STATUS_STORE_Refused_OutOfResources; } else { return cond; } /* execute final callback (user does not have to provide callback) */ if (callback) { progress.state = DIMSE_StoreEnd; progress.callbackCount++; /* execute final callback */ callback(callbackData, &progress, request, (char*)imageFileName, imageDataSet, &response, &statusDetail); } /* send a C-STORE-RSP message over the network to the other DICOM application */ OFCondition cond2 = DIMSE_sendStoreResponse(assoc, presIdCmd, request, &response, statusDetail); /* if we already had an error condition, don't overwrite */ if (cond.good()) cond = cond2; /* return result value */ return cond; }