def AE_7(dul): """Association establishment action AE-7. On receiving association request acceptance, issue A-ASSOCIATE-AC State-event triggers: Sta3 + Evt7 References ---------- 1. DICOM Standard 2015b, PS3.8, Table 9-7, "Associate Establishment Related Actions" Parameters ---------- dul : pynetdicom.dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str Sta6, the next state of the state machine """ # Send A-ASSOCIATE-AC PDU dul.pdu = A_ASSOCIATE_AC() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta6'
def AR_3(dul: "DULServiceProvider") -> str: """Association release AR-3. On receiving an association release response, send release confirmation, close connection and go back to Idle state State-event triggers: Sta7 + Evt13, Sta11 + Evt13 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta1'``, the next state of the state machine """ # A-RELEASE-RP PDU received from peer pdu = dul._recv_pdu.get(False) # Issue A-RELEASE confirmation primitive and close transport connection dul.to_user_queue.put(pdu.to_primitive()) sock = cast("AssociationSocket", dul.socket) sock.close() assoc = dul.assoc remote = assoc.acceptor if assoc.is_requestor else assoc.requestor address = (remote.address, remote.port) evt.trigger(dul.assoc, evt.EVT_CONN_CLOSE, {"address": address}) dul.kill_dul() return "Sta1"
def AA_4(dul: "DULServiceProvider") -> str: """Association abort AA-4. If connection closed, issue A-P-ABORT and return to Idle State-event triggers: Sta3/Sta4/Sta5/Sta6/Sta7/Sta8/Sta9/Sta10/Sta11/Sta12 + Evt17 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta1'``, the next state of the state machine """ assoc = dul.assoc assoc.dimse.msg_queue.put((None, None)) remote = assoc.acceptor if assoc.is_requestor else assoc.requestor address = (remote.address, remote.port) evt.trigger(dul.assoc, evt.EVT_CONN_CLOSE, {"address": address}) # Issue A-P-ABORT indication primitive. primitive = A_P_ABORT() primitive.provider_reason = 0x00 dul.to_user_queue.put(primitive) dul.kill_dul() return "Sta1"
def AA_7(dul): """Association abort AA-7. If receive a association request or invalid PDU while waiting for connection to close, send A-ABORT PDU State-event triggers: Sta13 + Evt6/Evt19 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta13'``, the next state of the state machine """ primitive = A_P_ABORT() primitive.provider_reason = 0x02 # Send A-ABORT PDU. pdu = A_ABORT_RQ() pdu.from_primitive(primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta13'
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)
def AR_5(dul): """Association release AR-5. On receiving transport connection closed, stop the ARTIM timer and go back to Idle state State-event triggers: Sta13 + Evt17 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta1'``, the next state of the state machine """ assoc = dul.assoc remote = assoc.acceptor if assoc.is_requestor else assoc.requestor address = (remote.address, remote.port) evt.trigger(dul.assoc, evt.EVT_CONN_CLOSE, {'address': address}) # Stop ARTIM timer dul.artim_timer.stop() dul.kill_dul() return 'Sta1'
def AR_9(dul): """Association release AR-9. On receiving A-RELEASE primitive, send release response State-event triggers: Sta9 + Evt14 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta11'``, the next state of the state machine """ # Send A-RELEASE-RP PDU dul.pdu = A_RELEASE_RP() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta11'
def _decode_pdu(self, bytestream): """Decode a received PDU. Parameters ---------- bytestream : bytearray The received PDU. Returns ------- pdu.PDU subclass, str The PDU subclass corresponding to the PDU and the event string corresponding to receiving that PDU type. """ # Trigger before data is decoded in case of exception in decoding bytestream = bytes(bytestream) evt.trigger(self.assoc, evt.EVT_DATA_RECV, {'data': bytestream}) pdu, event = _PDU_TYPES[bytestream[0:1]] pdu = pdu() pdu.decode(bytestream) evt.trigger(self.assoc, evt.EVT_PDU_RECV, {'pdu': pdu}) return pdu, event
def send_pdu(self, primitive): """Place a primitive in the provider queue to be sent to the peer. Primitives are converted to the corresponding PDU and encoded before sending. Parameters ---------- primitive : pdu_primitives.PDU sub-class A service primitive, one of: .. currentmodule:: pynetdicom.pdu_primitives * :class:`A_ASSOCIATE` * :class:`A_RELEASE` * :class:`A_ABORT` * :class:`A_P_ABORT` * :class:`P_DATA` """ # Event handler - ACSE sent primitive to the DUL service acse_primitives = (A_ASSOCIATE, A_RELEASE, A_ABORT, A_P_ABORT) if isinstance(primitive, acse_primitives): evt.trigger(self.assoc, evt.EVT_ACSE_SENT, {'primitive': primitive}) self.to_provider_queue.put(primitive)
def AA_5(dul): """Association abort AA-5. If connection closed during association request, stop ARTIM timer and return to Idle State-event triggers: Sta2 + Evt17 References ---------- 1. DICOM Standard 2015b, PS3.8, Table 9-7, "Associate Establishment Related Actions" Parameters ---------- dul : pynetdicom.dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str Sta1, the next state of the state machine """ assoc = dul.assoc remote = assoc.acceptor if assoc.is_requestor else assoc.requestor address = (remote.address, remote.port) evt.trigger(dul.assoc, evt.EVT_CONN_CLOSE, {'address': address}) # Stop ARTIM timer. dul.artim_timer.stop() dul.kill_dul() return 'Sta1'
def _decode_pdu(self, bytestream: bytearray) -> Tuple[_PDUType, str]: """Decode a received PDU. Parameters ---------- bytestream : bytearray The received PDU. Returns ------- pdu.PDU subclass, str The PDU subclass corresponding to the PDU and the event string corresponding to receiving that PDU type. """ # Trigger before data is decoded in case of exception in decoding b = bytes(bytestream) evt.trigger(self.assoc, evt.EVT_DATA_RECV, {"data": b}) pdu_cls, event = _PDU_TYPES[b[0:1]] pdu = pdu_cls() pdu.decode(b) evt.trigger(self.assoc, evt.EVT_PDU_RECV, {"pdu": pdu}) return pdu, event
def AR_9(dul): """Association release AR-9. On receiving A-RELEASE primitive, send release response State-event triggers: Sta9 + Evt14 References ---------- 1. DICOM Standard 2015b, PS3.8, Table 9-7, "Associate Establishment Related Actions" Parameters ---------- dul : pynetdicom.dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str Sta11, the next state of the state machine """ # Send A-RELEASE-RP PDU dul.pdu = A_RELEASE_RP() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta11'
def AR_7(dul): """Association release AR-7. On receiving P-DATA request during attempted association release request send P-DATA-TF State-event triggers: Sta8 + Evt9 References ---------- 1. DICOM Standard 2015b, PS3.8, Table 9-7, "Associate Establishment Related Actions" Parameters ---------- dul : pynetdicom.dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str Sta8, the next state of the state machine """ # Issue P-DATA-TF PDU dul.pdu = P_DATA_TF() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta8'
def DT_1(dul): """Data transfer DT-1. On receiving a P-DATA request, send P-DATA-TF State-event triggers: Sta6 + Evt9 References ---------- 1. DICOM Standard 2015b, PS3.8, Table 9-7, "Associate Establishment Related Actions" Parameters ---------- dul : pynetdicom.dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str Sta6, the next state of the state machine """ # Send P-DATA-TF PDU dul.pdu = P_DATA_TF() dul.pdu.from_primitive(dul.primitive) dul.primitive = None # Why this? dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta6'
def AR_3(dul): """Association release AR-3. On receiving an association release response, send release confirmation, close connection and go back to Idle state State-event triggers: Sta7 + Evt13, Sta11 + Evt13 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta1'``, the next state of the state machine """ # Issue A-RELEASE confirmation primitive and close transport connection dul.to_user_queue.put(dul.primitive) dul.socket.close() assoc = dul.assoc remote = assoc.acceptor if assoc.is_requestor else assoc.requestor address = (remote.address, remote.port) evt.trigger(dul.assoc, evt.EVT_CONN_CLOSE, {'address': address}) dul.kill_dul() return 'Sta1'
def _decode_pdu(self, bytestream): """Decode a received PDU. Parameters ---------- bytestream : bytearray The received PDU. Returns ------- pdu.PDU subclass, str The PDU subclass corresponding to the PDU and the event string corresponding to receiving that PDU type. """ # Trigger before data is decoded in case of exception in decoding bytestream = bytes(bytestream) evt.trigger(self.assoc, evt.EVT_DATA_RECV, {'data': bytestream}) pdu_types = { b'\x01': (A_ASSOCIATE_RQ, 'Evt6'), b'\x02': (A_ASSOCIATE_AC, 'Evt3'), b'\x03': (A_ASSOCIATE_RJ, 'Evt4'), b'\x04': (P_DATA_TF, 'Evt10'), b'\x05': (A_RELEASE_RQ, 'Evt12'), b'\x06': (A_RELEASE_RP, 'Evt13'), b'\x07': (A_ABORT_RQ, 'Evt16') } pdu, event = pdu_types[bytestream[0:1]] pdu = pdu() pdu.decode(bytestream) evt.trigger(self.assoc, evt.EVT_PDU_RECV, {'pdu': pdu}) return pdu, event
def AR_4(dul): """Association release AR-4. On receiving an association release response, send release response State-event triggers: Sta8 + Evt14, Sta12 + Evt14 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta13'``, the next state of the state machine """ # Issue A-RELEASE-RP PDU and start ARTIM timer dul.pdu = A_RELEASE_RP() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) dul.artim_timer.start() return 'Sta13'
def AE_2(dul): """Association establishment action AE-2. On receiving connection confirmation, send A-ASSOCIATE-RQ to the peer AE This send a byte stream with the format given by Table 9-11 State-event triggers: Sta4 + Evt2 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta5'``, the next state of the state machine. """ # Send A-ASSOCIATE-RQ PDU dul.pdu = A_ASSOCIATE_RQ() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta5'
def AR_7(dul): """Association release AR-7. On receiving P-DATA request during attempted association release request send P-DATA-TF State-event triggers: Sta8 + Evt9 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta8'``, the next state of the state machine """ # Issue P-DATA-TF PDU dul.pdu = P_DATA_TF() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta8'
def AE_4(dul): """Association establishment action AE-4. On receiving A-ASSOCIATE-RJ, issue rejection confirmation and close connection State-event triggers: Sta5 + Evt4 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta1'``, the next state of the state machine """ # Issue A-ASSOCIATE confirmation (reject) primitive and close transport # connection dul.to_user_queue.put(dul.primitive) dul.socket.close() assoc = dul.assoc remote = assoc.acceptor if assoc.is_requestor else assoc.requestor address = (remote.address, remote.port) evt.trigger(dul.assoc, evt.EVT_CONN_CLOSE, {'address': address}) dul.kill_dul() return 'Sta1'
def AA_1(dul): """Association abort AA-1. If on sending A-ASSOCIATE-RQ we receive an invalid reply, or an abort request then abort State-event triggers: Sta2 + Evt3/Evt4/Evt10/Evt12/Evt13/Evt19, Sta3/Sta5/Sta6/Sta7/Sta8/Sta9/Sta10/Sta11/Sta12 + Evt15 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta13'``, the next state of the state machine """ # Send A-ABORT PDU (service-user source) and start (or restart # if already started) ARTIM timer. dul.pdu = A_ABORT_RQ() dul.pdu.source = 0x00 # Reason not specified dul.pdu.reason_diagnostic = 0x00 dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) dul.artim_timer.restart() return 'Sta13'
def AE_7(dul): """Association establishment action AE-7. On receiving association request acceptance, issue A-ASSOCIATE-AC State-event triggers: Sta3 + Evt7 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta6'``, the next state of the state machine """ # Send A-ASSOCIATE-AC PDU dul.pdu = A_ASSOCIATE_AC() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta6'
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 AE_8(dul): """Association establishment action AE-8. On receiving association request rejection, issue A-ASSOCIATE-RJ State-event triggers: Sta3 + Evt8 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta13'``, the next state of the state machine """ # Send A-ASSOCIATE-RJ PDU and start ARTIM timer dul.pdu = A_ASSOCIATE_RJ() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) dul.artim_timer.start() return 'Sta13'
def send(self, bytestream: bytes) -> None: """Try and send the data in `bytestream` to the remote. *Events Emitted* - None - Evt17: Transport connected closed. Parameters ---------- bytestream : bytes The data to send to the remote. """ self.socket = cast(socket.socket, self.socket) total_sent = 0 length_data = len(bytestream) try: while total_sent < length_data: # Returns the number of bytes sent nr_sent = self.socket.send(bytestream[total_sent:]) total_sent += nr_sent evt.trigger(self.assoc, evt.EVT_DATA_SENT, {"data": bytestream}) except (socket.error, socket.timeout): # Evt17: Transport connection closed self.event_queue.put("Evt17")
def DT_1(dul): """Data transfer DT-1. On receiving a P-DATA request, send P-DATA-TF State-event triggers: Sta6 + Evt9 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta6'``, the next state of the state machine """ # Send P-DATA-TF PDU dul.pdu = P_DATA_TF() dul.pdu.from_primitive(dul.primitive) dul.primitive = None # Why this? dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta6'
def AA_2(dul: "DULServiceProvider") -> str: """Association abort AA-2. On receiving an A-ABORT or if the ARTIM timer expires, close connection and return to Idle State-event triggers: Sta2 + Evt16/Evt18, Sta4 + Evt15, Sta13 + Evt16/Evt18 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta1'``, the next state of the state machine """ # Stop ARTIM timer if running. Close transport connection. dul.artim_timer.stop() sock = cast("AssociationSocket", dul.socket) sock.close() assoc = dul.assoc assoc.dimse.msg_queue.put((None, None)) remote = assoc.acceptor if assoc.is_requestor else assoc.requestor address = (remote.address, remote.port) evt.trigger(dul.assoc, evt.EVT_CONN_CLOSE, {"address": address}) dul.kill_dul() return "Sta1"
def AR_1(dul): """Association release AR-1. Send Association release request State-event triggers: Sta6 + Evt11 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta7'``, the next state of the state machine """ # Send A-RELEASE-RQ PDU dul.pdu = A_RELEASE_RQ() dul.pdu.from_primitive(dul.primitive) dul.socket.send(dul.pdu.encode()) evt.trigger(dul.assoc, evt.EVT_PDU_SENT, {'pdu': dul.pdu}) return 'Sta7'
def AA_5(dul: "DULServiceProvider") -> str: """Association abort AA-5. If connection closed during association request, stop ARTIM timer and return to Idle State-event triggers: Sta2 + Evt17 Parameters ---------- dul : dul.DULServiceProvider The DICOM Upper Layer Service instance for the local AE Returns ------- str ``'Sta1'``, the next state of the state machine """ assoc = dul.assoc remote = assoc.acceptor if assoc.is_requestor else assoc.requestor address = (remote.address, remote.port) evt.trigger(dul.assoc, evt.EVT_CONN_CLOSE, {"address": address}) # Stop ARTIM timer. dul.artim_timer.stop() dul.kill_dul() return "Sta1"
def test_send_assoc_ac_async(self, caplog): """Test ACSE.debug_send_associate_ac with async ops.""" with caplog.at_level(logging.DEBUG, logger='pynetdicom'): self.ae = ae = AE() ae.add_supported_context(VerificationSOPClass) ae.add_requested_context(VerificationSOPClass) ae.add_requested_context(VerificationSOPClass) ae.add_requested_context(VerificationSOPClass) ae.add_requested_context(VerificationSOPClass) ae.add_requested_context(VerificationSOPClass) scp = ae.start_server(('', 11112), block=False) assoc = ae.associate('localhost', 11112) self.add_async_ops(self.associate_ac) pdu = A_ASSOCIATE_AC() pdu.from_primitive(self.associate_ac) evt.trigger(assoc, evt.EVT_PDU_SENT, {'pdu': pdu}) messages = [ "Accepted Extended Negotiation: None", "Accepted Asynchronous Operations Window Negotiation:", "Maximum Invoked Operations: 2", "Maximum Performed Operations: 3", "User Identity Negotiation Response: None", ] for msg in messages: assert msg in caplog.text assoc.release() scp.shutdown()