Exemple #1
0
    def receive_primitive(self, primitive):
        """Process a P-DATA primitive received from the remote.

        .. versionadded:: 1.2

        A DIMSE message is split into one or more P-DATA primitives, which
        must be sent in sequential order. While waiting for all the P-DATA
        primitives associated with a message the encoded data is stored in
        :attr:`~DIMSEServiceProvider.message`, which is decoded only when
        complete and converted into a DIMSE Message primitive which is added
        to the :attr:`~DIMSEServiceProvider.msg_queue`.

        This makes it possible to process incoming P-DATA primitives into
        DIMSE messages while a service class implementation is running.

        Parameters
        ----------
        primitive : pdu_primitives.P_DATA
            A P-DATA primitive received from the peer to be processed.
        """
        if self.message is None:
            self.message = DIMSEMessage()

        if self.message.decode_msg(primitive):
            # Trigger event
            evt.trigger(
                self.assoc, evt.EVT_DIMSE_RECV, {'message' : self.message}
            )

            context_id = self.message.context_id
            try:
                primitive = self.message.message_to_primitive()
            except Exception as exc:
                LOGGER.error("Received an invalid DIMSE message")
                LOGGER.exception(exc)
                self.dul.event_queue.put('Evt19')
                return

            # Keep C-CANCEL requests separate from other messages
            # Only allow up to 10 C-CANCEL requests
            if isinstance(primitive, C_CANCEL) and len(self.cancel_req) < 10:
                msg_id = primitive.MessageIDBeingRespondedTo
                self.cancel_req[msg_id] = primitive
            elif (isinstance(primitive, N_EVENT_REPORT) and
                  primitive.is_valid_request):
                # N-EVENT-REPORT service requests are handled immediately
                # Ugly hack, but would block the DUL otherwise
                t = threading.Thread(
                    target=self.assoc._serve_request,
                    args=(primitive, context_id)
                )
                t.start()
            else:
                self.msg_queue.put((context_id, primitive))

            # Fix for memory leak, Issue #41
            #   Reset the DIMSE message, ready for the next one
            self.message.encoded_command_set = BytesIO()
            self.message.data_set = BytesIO()
            self.message = None
Exemple #2
0
class DIMSEServiceProvider(object):
    """The DIMSE service provider.

    .. versionchanged:: 1.2

        Added `msg_queue` attribute

    **Messages**

    +----------------+-----------------------+------------------------+
    | Primitive      | Type                  | Message Class          |
    +----------------+-----------------------+------------------------+
    | C-CANCEL       | Request/indication    | C_CANCEL_RQ            |
    +----------------+-----------------------+------------------------+
    | C-ECHO         | Request/indication    | C_ECHO_RQ              |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | C_ECHO_RSP             |
    +----------------+-----------------------+------------------------+
    | C-FIND         | Request/indication    | C_FIND_RQ              |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | C_FIND_RSP             |
    +----------------+-----------------------+------------------------+
    | C-GET          | Request/indication    | C_GET_RQ               |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | C_GET_RSP              |
    +----------------+-----------------------+------------------------+
    | C-MOVE         | Request/indication    | C_MOVE_RQ              |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | C_MOVE_RSP             |
    +----------------+-----------------------+------------------------+
    | C-STORE        | Request/indication    | C_STORE_RQ             |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | C_STORE_RSP            |
    +----------------+-----------------------+------------------------+
    | N-ACTION       | Request/indication    | N_ACTION_RQ            |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | N_ACTION_RSP           |
    +----------------+-----------------------+------------------------+
    | N-CREATE       | Request/indication    | N_CREATE_RQ            |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | N_CREATE_RSP           |
    +----------------+-----------------------+------------------------+
    | N-DELETE       | Request/indication    | N_DELETE_RQ            |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | N_DELETE_RSP           |
    +----------------+-----------------------+------------------------+
    | N-EVENT-REPORT | Request/indication    | N_EVENT_REPORT_RQ      |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | N_EVENT_REPORT_RSP     |
    +----------------+-----------------------+------------------------+
    | N-GET          | Request/indication    | N_GET_RQ               |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | N_GET_RSP              |
    +----------------+-----------------------+------------------------+
    | N-SET          | Request/indication    | N_SET_RQ               |
    |                +-----------------------+------------------------+
    |                | Response/confirmation | N_SET_RSP              |
    +----------------+-----------------------+------------------------+

    Attributes
    ----------
    cancel_rq : dict
        A dict of ``{MessageIDBeingRespondedTo : C_CANCEL}`` messages received.
        The dict is cleared out at the start and end of Service Class
        operations and is limited to a maximum of 10 messages.
    message : dimse_messages.DIMSEMessage or None
        The DIMSE message currently being received.
    msg_queue: queue.queue of dimse_messages.DIMSEMessage
        A queue holding decoded DIMSE Message primitives received from the
        peer, except for C-CANCEL requests.

    References
    ----------

    * DICOM Standard, :dcm:`Part 7<part07/PS3.7.html>`
    """
    def __init__(self, assoc):
        """Initialise the DIMSE service provider.

        .. versionchanged:: 1.3

            Class initialisation now only takes `assoc` parameter.

        Parameters
        ----------
        assoc : association.Association
            The association to provide DIMSE services for.
        """
        self._assoc = assoc

        self.cancel_req = {}
        self.message = None
        self.msg_queue = queue.Queue()

    @property
    def assoc(self):
        """Return the parent :class:`~pynetdicom.association.Association`.

        .. versionadded:: 1.3
        """
        return self._assoc

    @property
    def dimse_timeout(self):
        """Return the DIMSE timeout as numeric or ``None``."""
        return self.assoc.dimse_timeout

    @property
    def dul(self):
        """Return the :class:`~pynetdicom.dul.DULServiceProvider`."""
        return self.assoc.dul

    def get_msg(self, block=False):
        """Get the next available DIMSE message.

        .. versionadded:: 1.2

        .. versionchanged:: 1.5

            Changed to also return ``(None, None)`` if the peer aborts the
            association or the connection is closed.

        Parameters
        ----------
        block : bool
            If ``True`` then the function will block until either a message is
            available or :attr:`~DIMSEServiceProvider.dimse_timeout` expires,
            otherwise non-blocking.

        Returns
        -------
        (int, dimse_messages.DIMSEMessage) or (None, None)
            The next available (*Context ID*, *DIMSE Message*), which is taken
            off the queue, or ``(None, None)`` if the peer has aborted the
            association, the connection is closed or if no messages are
            available within the :attr:`~DIMSEServiceProvider.dimse_timeout`
            period.
        """
        try:
            return self.msg_queue.get(block=block, timeout=self.dimse_timeout)
        except queue.Empty:
            return None, None

    @property
    def maximum_pdu_size(self):
        """Return the peer's maximum PDU length as :class:`int`."""
        if self.assoc.is_requestor:
            return self.assoc.acceptor.maximum_length

        return self.assoc.requestor.maximum_length

    def peek_msg(self):
        """Return the first message in the message queue or ``None``.

        .. versionadded:: 1.2

        Returns
        -------
        (int, dimse_messages.DIMSEMessage) or (None, None)
            The first (*Context ID*, *Message*) in the queue if one is
            available, otherwise ``(None, None)``. No messages are taken out
            of the queue.
        """
        try:
            return self.msg_queue.queue[0]
        except (queue.Empty, IndexError):
            return None, None

    def receive_primitive(self, primitive):
        """Process a P-DATA primitive received from the remote.

        .. versionadded:: 1.2

        A DIMSE message is split into one or more P-DATA primitives, which
        must be sent in sequential order. While waiting for all the P-DATA
        primitives associated with a message the encoded data is stored in
        :attr:`~DIMSEServiceProvider.message`, which is decoded only when
        complete and converted into a DIMSE Message primitive which is added
        to the :attr:`~DIMSEServiceProvider.msg_queue`.

        This makes it possible to process incoming P-DATA primitives into
        DIMSE messages while a service class implementation is running.

        Parameters
        ----------
        primitive : pdu_primitives.P_DATA
            A P-DATA primitive received from the peer to be processed.
        """
        if self.message is None:
            self.message = DIMSEMessage()

        if self.message.decode_msg(primitive):
            # Trigger event
            evt.trigger(
                self.assoc, evt.EVT_DIMSE_RECV, {'message' : self.message}
            )

            context_id = self.message.context_id
            try:
                primitive = self.message.message_to_primitive()
            except Exception as exc:
                LOGGER.error("Received an invalid DIMSE message")
                LOGGER.exception(exc)
                self.dul.event_queue.put('Evt19')
                return

            # Keep C-CANCEL requests separate from other messages
            # Only allow up to 10 C-CANCEL requests
            if isinstance(primitive, C_CANCEL) and len(self.cancel_req) < 10:
                msg_id = primitive.MessageIDBeingRespondedTo
                self.cancel_req[msg_id] = primitive
            elif (isinstance(primitive, N_EVENT_REPORT) and
                  primitive.is_valid_request):
                # N-EVENT-REPORT service requests are handled immediately
                # Ugly hack, but would block the DUL otherwise
                t = threading.Thread(
                    target=self.assoc._serve_request,
                    args=(primitive, context_id)
                )
                t.start()
            else:
                self.msg_queue.put((context_id, primitive))

            # Fix for memory leak, Issue #41
            #   Reset the DIMSE message, ready for the next one
            self.message.encoded_command_set = BytesIO()
            self.message.data_set = BytesIO()
            self.message = None

    def send_msg(self, primitive, context_id):
        """Send a DIMSE-C or DIMSE-N message to the peer AE.

        Parameters
        ----------
        primitive : dimse_primitives DIMSE Primitive class
            The DIMSE message primitive to send to the peer.
        context_id : int
            The ID of the presentation context that the message is to be
            sent under.
        """
        if primitive.MessageIDBeingRespondedTo is None:
            dimse_msg = _RQ_TO_MESSAGE[primitive.__class__]()
        else:
            dimse_msg = _RSP_TO_MESSAGE[primitive.__class__]()

        # Convert DIMSE primitive to DIMSE Message
        dimse_msg.primitive_to_message(primitive)
        dimse_msg.context_id = context_id

        # Trigger event
        evt.trigger(
            self.assoc, evt.EVT_DIMSE_SENT, {'message' : dimse_msg}
        )

        # Split the full messages into P-DATA chunks,
        #   each below the max_pdu size
        for pdata in dimse_msg.encode_msg(context_id, self.maximum_pdu_size):
            self.dul.send_pdu(pdata)
Exemple #3
0
 def test_id_deprecation(self):
     """Test deprecation warning for DIMSEMessage"""
     msg = DIMSEMessage()
     msg.ID = 1
     with pytest.deprecated_call():
         assert msg.ID == 1
Exemple #4
0
 def time_decode(self):
     """Benchmark for standard decode."""
     for ii in range(100):
         msg = DIMSEMessage()
         for fragment in self.fragments:
             msg.decode_msg(fragment)