示例#1
0
 def test_length_check(self):
     """Test validate_ae_title with no length check."""
     assert _config.ALLOW_LONG_DIMSE_AET is False
     aet = b"12345678901234567890"
     assert 16 == len(validate_ae_title(aet))
     _config.ALLOW_LONG_DIMSE_AET = True
     assert 20 == len(validate_ae_title(aet))
     _config.ALLOW_LONG_DIMSE_AET = False
示例#2
0
    def test_conversion_rq(self):
        """ Check conversion to a -RQ PDU produces the correct output """
        primitive = C_MOVE()
        primitive.MessageID = 7
        primitive.AffectedSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'
        primitive.Priority = 0x02
        primitive.MoveDestination = validate_ae_title("MOVE_SCP")

        ref_identifier = Dataset()
        ref_identifier.PatientID = '*'
        ref_identifier.QueryRetrieveLevel = "PATIENT"

        primitive.Identifier = BytesIO(encode(ref_identifier, True, True))

        dimse_msg = C_MOVE_RQ()
        dimse_msg.primitive_to_message(primitive)

        pdvs = []
        for fragment in dimse_msg.encode_msg(1, 16382):
            pdvs.append(fragment)

        cs_pdv = pdvs[0].presentation_data_value_list[0][1]
        ds_pdv = pdvs[1].presentation_data_value_list[0][1]
        assert cs_pdv == c_move_rq_cmd
        assert ds_pdv == c_move_rq_ds
    def associate(self, addr, port, ae_title='ANY-SCP', 
                                max_pdu=16382, ext_neg=None):
        """Attempts to associate with a remote application entity

        When requesting an association the local AE is acting as an SCU

        Parameters
        ----------
        addr : str
            The peer AE's TCP/IP address (IPv4)
        port : int
            The peer AE's listen port number
        ae_title : str, optional
            The peer AE's title
        max_pdu : int, optional
            The maximum PDV receive size in bytes to use when negotiating the 
            association
        ext_neg : List of UserInformation objects, optional
            Used if extended association negotiation is required

        Returns
        -------
        assoc : pynetdicom.association.Association
            The Association thread
        """
        if not isinstance(addr, str):
            raise ValueError("ip_address must be a valid IPv4 string")

        if not isinstance(port, int):
            raise ValueError("port must be a valid port number")

        peer_ae = {'AET' : validate_ae_title(ae_title), 
                   'Address' : addr, 
                   'Port' : port}

        # Associate
        assoc = Association(local_ae=self,
                            peer_ae=peer_ae,
                            acse_timeout=self.acse_timeout,
                            dimse_timeout=self.dimse_timeout,
                            max_pdu=max_pdu,
                            ext_neg=ext_neg)

        # Endlessly loops while the Association negotiation is taking place
        while (not assoc.is_established and 
                not assoc.is_refused and 
                not assoc.is_aborted and
                not assoc.dul.kill):
            time.sleep(0.1)

        # If the Association was established
        if assoc.is_established:
            self.active_associations.append(assoc)

        return assoc
 def MoveOriginatorApplicationEntityTitle(self, value):
     """
     Set the Move Originator AE Title
     
     Parameters
     ----------
     value : str or bytes
         The Move Originator AE Title as a string or bytes object. Cannot be
         an empty string and will be truncated to 16 characters long
     """
     if isinstance(value, str):
         value = bytes(value, 'utf-8')
     
     if value is not None:
         self._move_originator_application_entity_title = validate_ae_title(value)
     else:
         self._move_originator_application_entity_title = None
 def MoveDestination(self, value):
     """
     Set the Move Destination AE Title
     
     Parameters
     ----------
     value : str or bytes
         The Move Destination AE Title as a string or bytes object. Cannot be
         an empty string and will be truncated to 16 characters long
     """
     if isinstance(value, str):
         value = bytes(value, 'utf-8')
     
     if value is not None:
         self._move_destination = validate_ae_title(value)
     else:
         self._move_destination = None
示例#6
0
    def test_conversion_rsp(self):
        """ Check conversion to a -RSP PDU produces the correct output """
        primitive = C_FIND()
        primitive.MessageIDBeingRespondedTo = 5
        primitive.AffectedSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'
        primitive.Status = 0xFF00

        ref_identifier = Dataset()
        ref_identifier.QueryRetrieveLevel = "PATIENT"
        ref_identifier.RetrieveAETitle = validate_ae_title("FINDSCP")
        ref_identifier.PatientName = "ANON^A^B^C^D"

        primitive.Identifier = BytesIO(encode(ref_identifier, True, True))

        dimse_msg = C_FIND_RSP()
        dimse_msg.primitive_to_message(primitive)

        pdvs = []
        for fragment in dimse_msg.encode_msg(1, 16382):
            pdvs.append(fragment)
        cs_pdv = pdvs[0].presentation_data_value_list[0][1]
        ds_pdv = pdvs[1].presentation_data_value_list[0][1]
        assert cs_pdv == c_find_rsp_cmd
        assert ds_pdv == c_find_rsp_ds
示例#7
0
def main(args=None):
    """Run the application."""
    if args is not None:
        sys.argv = args

    args = _setup_argparser()

    if args.version:
        print(f"qrscp.py v{__version__}")
        sys.exit()

    APP_LOGGER = setup_logging(args, "qrscp")
    APP_LOGGER.debug(f"qrscp.py v{__version__}")
    APP_LOGGER.debug("")

    APP_LOGGER.debug("Using configuration from:")
    APP_LOGGER.debug(f"  {args.config}")
    APP_LOGGER.debug("")
    config = ConfigParser()
    config.read(args.config)

    if args.ae_title:
        config["DEFAULT"]["ae_title"] = args.ae_title
    if args.port:
        config["DEFAULT"]["port"] = args.port
    if args.max_pdu:
        config["DEFAULT"]["max_pdu"] = args.max_pdu
    if args.acse_timeout:
        config["DEFAULT"]["acse_timeout"] = args.acse_timeout
    if args.dimse_timeout:
        config["DEFAULT"]["dimse_timeout"] = args.dimse_timeout
    if args.network_timeout:
        config["DEFAULT"]["network_timeout"] = args.network_timeout
    if args.bind_address:
        config["DEFAULT"]["bind_address"] = args.bind_address
    if args.database_location:
        config["DEFAULT"]["database_location"] = args.database_location
    if args.instance_location:
        config["DEFAULT"]["instance_location"] = args.instance_location

    # Log configuration settings
    _log_config(config, APP_LOGGER)
    app_config = config["DEFAULT"]

    dests = {}
    for ae_title in config.sections():
        dest = config[ae_title]
        # Convert to bytes and validate the AE title
        ae_title = validate_ae_title(ae_title.encode("ascii"), use_short=True)
        dests[ae_title] = (dest["address"], dest.getint("port"))

    # Use default or specified configuration file
    current_dir = os.path.abspath(os.path.dirname(__file__))
    instance_dir = os.path.join(current_dir, app_config["instance_location"])
    db_path = os.path.join(current_dir, app_config["database_location"])

    # The path to the database
    db_path = f"sqlite:///{db_path}"
    db.create(db_path)

    # Clean up the database and storage directory
    if args.clean:
        response = input(
            "This will delete all instances from both the storage directory "
            "and the database. Are you sure you wish to continue? [yes/no]: ")
        if response != "yes":
            sys.exit()

        if clean(db_path, APP_LOGGER):
            sys.exit()
        else:
            sys.exit(1)

    # Try to create the instance storage directory
    os.makedirs(instance_dir, exist_ok=True)

    ae = AE(app_config["ae_title"])
    ae.maximum_pdu_size = app_config.getint("max_pdu")
    ae.acse_timeout = app_config.getfloat("acse_timeout")
    ae.dimse_timeout = app_config.getfloat("dimse_timeout")
    ae.network_timeout = app_config.getfloat("network_timeout")

    ## Add supported presentation contexts
    # Verification SCP
    ae.add_supported_context(VerificationSOPClass, ALL_TRANSFER_SYNTAXES)

    # Storage SCP - support all transfer syntaxes
    for cx in AllStoragePresentationContexts:
        ae.add_supported_context(cx.abstract_syntax,
                                 ALL_TRANSFER_SYNTAXES,
                                 scp_role=True,
                                 scu_role=False)

    # Query/Retrieve SCP
    ae.add_supported_context(PatientRootQueryRetrieveInformationModelFind)
    ae.add_supported_context(PatientRootQueryRetrieveInformationModelMove)
    ae.add_supported_context(PatientRootQueryRetrieveInformationModelGet)
    ae.add_supported_context(StudyRootQueryRetrieveInformationModelFind)
    ae.add_supported_context(StudyRootQueryRetrieveInformationModelMove)
    ae.add_supported_context(StudyRootQueryRetrieveInformationModelGet)

    # Set our handler bindings
    handlers = [
        (evt.EVT_C_ECHO, handle_echo, [args, APP_LOGGER]),
        (evt.EVT_C_FIND, handle_find, [db_path, args, APP_LOGGER]),
        (evt.EVT_C_GET, handle_get, [db_path, args, APP_LOGGER]),
        (evt.EVT_C_MOVE, handle_move, [dests, db_path, args, APP_LOGGER]),
        (evt.EVT_C_STORE, handle_store,
         [instance_dir, db_path, args, APP_LOGGER]),
    ]

    # Listen for incoming association requests
    ae.start_server((app_config["bind_address"], app_config.getint("port")),
                    evt_handlers=handlers)
示例#8
0
 def test_bad_ae_bytes(self, aet):
     """Test validate_ae_title using bad bytes input."""
     with pytest.raises((TypeError, ValueError)):
         validate_ae_title(aet)
示例#9
0
 def test_good_ae_bytes(self, aet, output):
     """Test validate_ae_title using bytes input."""
     assert validate_ae_title(aet) == output
     assert isinstance(validate_ae_title(aet), bytes)
 def ae_title(self, value):
     try:
         self._ae_title = validate_ae_title(value)
     except:
         raise