/* * * Copyright (C) 1998-2018, 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: dcmpstat * * Author: Marco Eichelberg * * Purpose: * classes: DVPSPrintMessageHandler * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/ofstd/ofstring.h" #include "dcmtk/ofstd/ofstd.h" #include "dcmtk/dcmpstat/dvpsdef.h" #include "dcmtk/dcmpstat/dvpspr.h" /* --------------- class DVPSPrintMessageHandler --------------- */ DVPSPrintMessageHandler::DVPSPrintMessageHandler() : assoc(NULL) , net(NULL) , eventHandler(NULL) , blockMode(DIMSE_BLOCKING) , timeout(0) { } DVPSPrintMessageHandler::~DVPSPrintMessageHandler() { abortAssociation(); // won't do any harm if there is no association in place } void DVPSPrintMessageHandler::dumpNMessage(T_DIMSE_Message &msg, DcmItem *dataset, OFBool outgoing) { OFString str; if (outgoing) { DIMSE_dumpMessage(str, msg, DIMSE_OUTGOING, dataset); } else { DIMSE_dumpMessage(str, msg, DIMSE_INCOMING, dataset); } DCMPSTAT_DUMP(str); } OFCondition DVPSPrintMessageHandler::sendNRequest( T_ASC_PresentationContextID presId, T_DIMSE_Message &request, DcmDataset *rqDataSet, T_DIMSE_Message &response, DcmDataset* &statusDetail, DcmDataset* &rspDataset) { OFCondition cond = EC_Normal; T_DIMSE_Command expectedResponse; DIC_US expectedMessageID=0; if (assoc == NULL) { return DIMSE_ILLEGALASSOCIATION; } T_DIMSE_DataSetType datasetType = DIMSE_DATASET_NULL; if (rqDataSet && (rqDataSet->card() > 0)) datasetType = DIMSE_DATASET_PRESENT; switch(request.CommandField) { case DIMSE_N_GET_RQ: request.msg.NGetRQ.DataSetType = datasetType; expectedResponse = DIMSE_N_GET_RSP; expectedMessageID = request.msg.NGetRQ.MessageID; break; case DIMSE_N_SET_RQ: request.msg.NSetRQ.DataSetType = datasetType; expectedResponse = DIMSE_N_SET_RSP; expectedMessageID = request.msg.NSetRQ.MessageID; break; case DIMSE_N_ACTION_RQ: request.msg.NActionRQ.DataSetType = datasetType; expectedResponse = DIMSE_N_ACTION_RSP; expectedMessageID = request.msg.NActionRQ.MessageID; break; case DIMSE_N_CREATE_RQ: request.msg.NCreateRQ.DataSetType = datasetType; expectedResponse = DIMSE_N_CREATE_RSP; expectedMessageID = request.msg.NCreateRQ.MessageID; break; case DIMSE_N_DELETE_RQ: request.msg.NDeleteRQ.DataSetType = datasetType; expectedResponse = DIMSE_N_DELETE_RSP; expectedMessageID = request.msg.NDeleteRQ.MessageID; break; default: return DIMSE_BADCOMMANDTYPE; /* break; */ } dumpNMessage(request, rqDataSet, OFTrue); cond = DIMSE_sendMessageUsingMemoryData(assoc, presId, &request, NULL, rqDataSet, NULL, NULL); if (cond.bad()) return cond; T_ASC_PresentationContextID thisPresId; T_DIMSE_Message eventReportRsp; DIC_US eventReportStatus; do { thisPresId = presId; statusDetail = NULL; cond = DIMSE_receiveCommand(assoc, blockMode, timeout, &thisPresId, &response, &statusDetail); if (cond.bad()) return cond; if (response.CommandField == DIMSE_N_EVENT_REPORT_RQ) { /* handle N-EVENT-REPORT-RQ */ rspDataset = NULL; if (response.msg.NEventReportRQ.DataSetType == DIMSE_DATASET_PRESENT) { cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &thisPresId, &rspDataset, NULL, NULL); if (cond.bad()) return cond; } dumpNMessage(response, rspDataset, OFFalse); // call event handler if registered eventReportStatus = STATUS_Success; if (eventHandler) eventReportStatus = eventHandler->handleEvent(response.msg.NEventReportRQ, rspDataset, statusDetail); if (rspDataset) delete rspDataset; rspDataset = NULL; if (statusDetail) delete statusDetail; statusDetail = NULL; // send back N-EVENT-REPORT-RSP */ eventReportRsp.CommandField = DIMSE_N_EVENT_REPORT_RSP; eventReportRsp.msg.NEventReportRSP.MessageIDBeingRespondedTo = response.msg.NEventReportRQ.MessageID; eventReportRsp.msg.NEventReportRSP.EventTypeID = response.msg.NEventReportRQ.EventTypeID; eventReportRsp.msg.NEventReportRSP.DimseStatus = eventReportStatus; eventReportRsp.msg.NEventReportRSP.DataSetType = DIMSE_DATASET_NULL; eventReportRsp.msg.NEventReportRSP.opts = O_NEVENTREPORT_EVENTTYPEID; eventReportRsp.msg.NEventReportRSP.AffectedSOPClassUID[0] = 0; eventReportRsp.msg.NEventReportRSP.AffectedSOPInstanceUID[0] = 0; dumpNMessage(eventReportRsp, NULL, OFTrue); cond = DIMSE_sendMessageUsingMemoryData(assoc, thisPresId, &eventReportRsp, NULL, NULL, NULL, NULL); if (cond.bad()) return cond; } else { /* No N-EVENT-REPORT-RQ. Check if this message is what we expected */ if (response.CommandField != expectedResponse) { char buf1[256]; sprintf(buf1, "DIMSE: Unexpected Response Command Field: 0x%x", (unsigned)response.CommandField); return makeDcmnetCondition(DIMSEC_UNEXPECTEDRESPONSE, OF_error, buf1); } T_DIMSE_DataSetType responseDataset = DIMSE_DATASET_NULL; DIC_US responseMessageID = 0; /** change request to response */ switch(expectedResponse) { case DIMSE_N_GET_RSP: responseDataset = response.msg.NGetRSP.DataSetType; responseMessageID = response.msg.NGetRSP.MessageIDBeingRespondedTo; break; case DIMSE_N_SET_RSP: responseDataset = response.msg.NSetRSP.DataSetType; responseMessageID = response.msg.NSetRSP.MessageIDBeingRespondedTo; break; case DIMSE_N_ACTION_RSP: responseDataset = response.msg.NActionRSP.DataSetType; responseMessageID = response.msg.NActionRSP.MessageIDBeingRespondedTo; break; case DIMSE_N_CREATE_RSP: responseDataset = response.msg.NCreateRSP.DataSetType; responseMessageID = response.msg.NCreateRSP.MessageIDBeingRespondedTo; break; case DIMSE_N_DELETE_RSP: responseDataset = response.msg.NDeleteRSP.DataSetType; responseMessageID = response.msg.NDeleteRSP.MessageIDBeingRespondedTo; break; default: { char buf1[256]; sprintf(buf1, "DIMSE: Unexpected Response Command Field: 0x%x", (unsigned)response.CommandField); return makeDcmnetCondition(DIMSEC_UNEXPECTEDRESPONSE, OF_error, buf1); } /* break; */ } if (responseMessageID != expectedMessageID) { char buf1[256]; sprintf(buf1, "DIMSE: Unexpected Response Command Field: 0x%x", (unsigned)response.CommandField); return makeDcmnetCondition(DIMSEC_UNEXPECTEDRESPONSE, OF_error, buf1); } rspDataset = NULL; if (responseDataset == DIMSE_DATASET_PRESENT) { cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &thisPresId, &rspDataset, NULL, NULL); if (cond.bad()) return cond; } dumpNMessage(response, rspDataset, OFFalse); } } while (response.CommandField == DIMSE_N_EVENT_REPORT_RQ); return EC_Normal; } OFCondition DVPSPrintMessageHandler::createRQ( const char *sopclassUID, OFString& sopinstanceUID, DcmDataset *attributeListIn, Uint16& status, DcmDataset* &attributeListOut) { if (assoc == NULL) { return DIMSE_ILLEGALASSOCIATION; } if (sopclassUID==NULL) { return DIMSE_NULLKEY; } T_ASC_PresentationContextID presCtx = findAcceptedPC(sopclassUID); if (presCtx == 0) { return DIMSE_NOVALIDPRESENTATIONCONTEXTID; } T_DIMSE_Message request; T_DIMSE_Message response; DcmDataset *statusDetail = NULL; // construct N-CREATE-RQ request.CommandField = DIMSE_N_CREATE_RQ; request.msg.NCreateRQ.MessageID = assoc->nextMsgID++; OFStandard::strlcpy(request.msg.NCreateRQ.AffectedSOPClassUID, sopclassUID, sizeof(request.msg.NCreateRQ.AffectedSOPClassUID)); if (sopinstanceUID.size() > 0) { OFStandard::strlcpy(request.msg.NCreateRQ.AffectedSOPInstanceUID, sopinstanceUID.c_str(), sizeof(request.msg.NCreateRQ.AffectedSOPInstanceUID)); request.msg.NCreateRQ.opts = O_NCREATE_AFFECTEDSOPINSTANCEUID; } else { request.msg.NCreateRQ.AffectedSOPInstanceUID[0] = 0; request.msg.NCreateRQ.opts = 0; } OFCondition cond = sendNRequest(presCtx, request, attributeListIn, response, statusDetail, attributeListOut); if (cond.good()) { status = response.msg.NCreateRSP.DimseStatus; // if response contains SOP Instance UID, copy it. if (response.msg.NCreateRSP.opts & O_NCREATE_AFFECTEDSOPINSTANCEUID) { sopinstanceUID = response.msg.NCreateRSP.AffectedSOPInstanceUID; } } if (statusDetail) delete statusDetail; return cond; } OFCondition DVPSPrintMessageHandler::setRQ( const char *sopclassUID, const char *sopinstanceUID, DcmDataset *modificationList, Uint16& status, DcmDataset* &attributeListOut) { if (assoc == NULL) { return DIMSE_ILLEGALASSOCIATION; } if ((sopclassUID==NULL)||(sopinstanceUID==NULL)||(modificationList==NULL)) { return DIMSE_NULLKEY; } T_ASC_PresentationContextID presCtx = findAcceptedPC(sopclassUID); if (presCtx == 0) { return DIMSE_NOVALIDPRESENTATIONCONTEXTID; } T_DIMSE_Message request; T_DIMSE_Message response; DcmDataset *statusDetail = NULL; // construct N-SET-RQ request.CommandField = DIMSE_N_SET_RQ; request.msg.NSetRQ.MessageID = assoc->nextMsgID++; OFStandard::strlcpy(request.msg.NSetRQ.RequestedSOPClassUID, sopclassUID, sizeof(request.msg.NSetRQ.RequestedSOPClassUID)); OFStandard::strlcpy(request.msg.NSetRQ.RequestedSOPInstanceUID, sopinstanceUID, sizeof(request.msg.NSetRQ.RequestedSOPInstanceUID)); OFCondition cond = sendNRequest(presCtx, request, modificationList, response, statusDetail, attributeListOut); if (cond.good()) status = response.msg.NSetRSP.DimseStatus; if (statusDetail) delete statusDetail; return cond; } OFCondition DVPSPrintMessageHandler::getRQ( const char *sopclassUID, const char *sopinstanceUID, const Uint16 *attributeIdentifierList, size_t numShorts, Uint16& status, DcmDataset* &attributeListOut) { if (assoc == NULL) { return DIMSE_ILLEGALASSOCIATION; } if ((sopclassUID==NULL)||(sopinstanceUID==NULL)) { return DIMSE_NULLKEY; } T_ASC_PresentationContextID presCtx = findAcceptedPC(sopclassUID); if (presCtx == 0) { return DIMSE_NOVALIDPRESENTATIONCONTEXTID; } T_DIMSE_Message request; T_DIMSE_Message response; DcmDataset *statusDetail = NULL; // construct N-GET-RQ request.CommandField = DIMSE_N_GET_RQ; request.msg.NGetRQ.MessageID = assoc->nextMsgID++; OFStandard::strlcpy(request.msg.NGetRQ.RequestedSOPClassUID, sopclassUID, sizeof(request.msg.NGetRQ.RequestedSOPClassUID)); OFStandard::strlcpy(request.msg.NGetRQ.RequestedSOPInstanceUID, sopinstanceUID, sizeof(request.msg.NGetRQ.RequestedSOPInstanceUID)); request.msg.NGetRQ.ListCount = 0; if (attributeIdentifierList) request.msg.NGetRQ.ListCount = (int)numShorts; request.msg.NGetRQ.AttributeIdentifierList = (DIC_US *)attributeIdentifierList; OFCondition cond = sendNRequest(presCtx, request, NULL, response, statusDetail, attributeListOut); if (cond.good()) status = response.msg.NGetRSP.DimseStatus; if (statusDetail) delete statusDetail; return cond; } OFCondition DVPSPrintMessageHandler::actionRQ( const char *sopclassUID, const char *sopinstanceUID, Uint16 actionTypeID, DcmDataset *actionInformation, Uint16& status, DcmDataset* &actionReply) { if (assoc == NULL) { return DIMSE_ILLEGALASSOCIATION; } if ((sopclassUID==NULL)||(sopinstanceUID==NULL)) { return DIMSE_NULLKEY; } T_ASC_PresentationContextID presCtx = findAcceptedPC(sopclassUID); if (presCtx == 0) { return DIMSE_NOVALIDPRESENTATIONCONTEXTID; } T_DIMSE_Message request; T_DIMSE_Message response; DcmDataset *statusDetail = NULL; // construct N-ACTION-RQ request.CommandField = DIMSE_N_ACTION_RQ; request.msg.NActionRQ.MessageID = assoc->nextMsgID++; OFStandard::strlcpy(request.msg.NActionRQ.RequestedSOPClassUID, sopclassUID, sizeof(request.msg.NActionRQ.RequestedSOPClassUID)); OFStandard::strlcpy(request.msg.NActionRQ.RequestedSOPInstanceUID, sopinstanceUID, sizeof(request.msg.NActionRQ.RequestedSOPInstanceUID)); request.msg.NActionRQ.ActionTypeID = (DIC_US)actionTypeID; OFCondition cond = sendNRequest(presCtx, request, actionInformation, response, statusDetail, actionReply); if (cond.good()) status = response.msg.NActionRSP.DimseStatus; if (statusDetail) delete statusDetail; return cond; } OFCondition DVPSPrintMessageHandler::deleteRQ( const char *sopclassUID, const char *sopinstanceUID, Uint16& status) { if (assoc == NULL) { return DIMSE_ILLEGALASSOCIATION; } if ((sopclassUID==NULL)||(sopinstanceUID==NULL)) { return DIMSE_NULLKEY; } T_ASC_PresentationContextID presCtx = findAcceptedPC(sopclassUID); if (presCtx == 0) { return DIMSE_NOVALIDPRESENTATIONCONTEXTID; } T_DIMSE_Message request; T_DIMSE_Message response; DcmDataset *statusDetail = NULL; DcmDataset *attributeListOut = NULL; // construct N-DELETE-RQ request.CommandField = DIMSE_N_DELETE_RQ; request.msg.NDeleteRQ.MessageID = assoc->nextMsgID++; OFStandard::strlcpy(request.msg.NDeleteRQ.RequestedSOPClassUID, sopclassUID, sizeof(request.msg.NDeleteRQ.RequestedSOPClassUID)); OFStandard::strlcpy(request.msg.NDeleteRQ.RequestedSOPInstanceUID, sopinstanceUID, sizeof(request.msg.NDeleteRQ.RequestedSOPInstanceUID)); OFCondition cond = sendNRequest(presCtx, request, NULL, response, statusDetail, attributeListOut); if (cond.good()) status = response.msg.NDeleteRSP.DimseStatus; if (statusDetail) delete statusDetail; if (attributeListOut) delete attributeListOut; // should never happen return cond; } OFCondition DVPSPrintMessageHandler::releaseAssociation() { OFCondition result = EC_Normal; if (assoc) { result = ASC_releaseAssociation(assoc); ASC_destroyAssociation(&assoc); ASC_dropNetwork(&net); assoc = NULL; net = NULL; } return result; } OFCondition DVPSPrintMessageHandler::abortAssociation() { OFCondition result = EC_Normal; if (assoc) { result = ASC_abortAssociation(assoc); ASC_destroyAssociation(&assoc); ASC_dropNetwork(&net); assoc = NULL; net = NULL; } return result; } T_ASC_PresentationContextID DVPSPrintMessageHandler::findAcceptedPC(const char *sopclassuid) { if ((assoc==NULL)||(sopclassuid==NULL)) return 0; // if the SOP class is one of the Basic Grayscale Print Management Meta SOP Classes, // look for a presentation context for Basic Grayscale Print. OFString sopclass(sopclassuid); if ((sopclass == UID_BasicFilmSessionSOPClass) || (sopclass == UID_BasicFilmBoxSOPClass) || (sopclass == UID_BasicGrayscaleImageBoxSOPClass) || (sopclass == UID_PrinterSOPClass)) sopclassuid = UID_BasicGrayscalePrintManagementMetaSOPClass; return ASC_findAcceptedPresentationContextID(assoc, sopclassuid); } OFCondition DVPSPrintMessageHandler::negotiateAssociation( DcmTransportLayer *tlayer, const char *myAEtitle, const char *peerAEtitle, const char *peerHost, int peerPort, long peerMaxPDU, OFBool negotiatePresentationLUT, OFBool negotiateAnnotationBox, OFBool implicitOnly) { if (assoc) { return makeDcmnetCondition(DIMSEC_ILLEGALASSOCIATION, OF_error, "association already in place"); } if ((myAEtitle==NULL)||(peerAEtitle==NULL)||(peerHost==NULL)) { return DIMSE_NULLKEY; } T_ASC_Parameters *params=NULL; DIC_NODENAME dnpeerHost; OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, 30, &net); if (cond.good()) cond = ASC_createAssociationParameters(¶ms, peerMaxPDU); if (tlayer && cond.good()) { cond = ASC_setTransportLayer(net, tlayer, 0); if (cond.good()) cond = ASC_setTransportLayerType(params, OFTrue /* use TLS */); } if (cond.bad()) return cond; ASC_setAPTitles(params, myAEtitle, peerAEtitle, NULL); sprintf(dnpeerHost, "%s:%d", peerHost, peerPort); ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), dnpeerHost); /* presentation contexts */ const char* transferSyntaxes[3]; int transferSyntaxCount = 0; if (implicitOnly) { transferSyntaxes[0] = UID_LittleEndianImplicitTransferSyntax; transferSyntaxCount = 1; } else { /* gLocalByteOrder is defined in dcxfer.h */ if (gLocalByteOrder == EBO_LittleEndian) { /* we are on a little endian machine */ transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax; transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax; } else { /* we are on a big endian machine */ transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax; transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; } transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax; transferSyntaxCount = 3; } /* we always propose basic grayscale, presentation LUT and annotation box*/ if (cond.good()) cond = ASC_addPresentationContext(params, 1, UID_BasicGrayscalePrintManagementMetaSOPClass, transferSyntaxes, transferSyntaxCount); if (negotiatePresentationLUT) { if (cond.good()) cond = ASC_addPresentationContext(params, 3, UID_PresentationLUTSOPClass, transferSyntaxes, transferSyntaxCount); } if (negotiateAnnotationBox) { if (cond.good()) cond = ASC_addPresentationContext(params, 5, UID_BasicAnnotationBoxSOPClass, transferSyntaxes, transferSyntaxCount); } /* create association */ DCMPSTAT_INFO("Requesting Association"); if (cond.good()) { cond = ASC_requestAssociation(net, params, &assoc); if (cond == DUL_ASSOCIATIONREJECTED) { OFString temp_str; T_ASC_RejectParameters rej; ASC_getRejectParameters(params, &rej); DCMPSTAT_WARN("Association Rejected" << OFendl << ASC_printRejectParameters(temp_str, &rej)); } else { if (cond.bad()) { // if assoc is non-NULL, then params has already been moved into the // assoc structure. Make sure we only delete once! if (assoc) ASC_destroyAssociation(&assoc); else if (params) ASC_destroyAssociationParameters(¶ms); if (net) ASC_dropNetwork(&net); assoc = NULL; net = NULL; return cond; } } } if ((cond.good()) && (0 == ASC_findAcceptedPresentationContextID(assoc, UID_BasicGrayscalePrintManagementMetaSOPClass))) { DCMPSTAT_WARN("Peer does not support Basic Grayscale Print Management, aborting association."); abortAssociation(); cond = DIMSE_NOVALIDPRESENTATIONCONTEXTID; } if (cond.good()) { DCMPSTAT_INFO("Association accepted (Max Send PDV: " << assoc->sendPDVLength << ")"); } else { // params is now an alias to assoc->params. Don't call ASC_destroyAssociationParameters. if (assoc) ASC_destroyAssociation(&assoc); if (net) ASC_dropNetwork(&net); assoc = NULL; net = NULL; } return cond; } OFBool DVPSPrintMessageHandler::printerSupportsPresentationLUT() { if ((assoc)&&(0 != ASC_findAcceptedPresentationContextID(assoc, UID_PresentationLUTSOPClass))) return OFTrue; return OFFalse; } OFBool DVPSPrintMessageHandler::printerSupportsAnnotationBox() { if ((assoc)&&(0 != ASC_findAcceptedPresentationContextID(assoc, UID_BasicAnnotationBoxSOPClass))) return OFTrue; return OFFalse; }