def _callback_log(level, text): """ Receives log information from the LibSub (not from the S2OPC toolkit). """ dLevel = {libsub.SOPC_TOOLKIT_LOG_LEVEL_ERROR: '# Error: ', libsub.SOPC_TOOLKIT_LOG_LEVEL_WARNING: '# Warning: ', libsub.SOPC_TOOLKIT_LOG_LEVEL_INFO: '# Info: ', libsub.SOPC_TOOLKIT_LOG_LEVEL_DEBUG: '# Debug: '} if level <= LOG_LEVEL: print(dLevel[level] + ffi.string(text).decode(), file=sys.stderr)
def load_configuration(xml_path, address_space_handler=None, user_handler=None, method_handler=None, pki_handler=None): """ Creates a configuration structure for a server from an XML file. This configuration is later used to open an endpoint. There should be only one created configuration. The XML configuration format is specific to S2OPC and follows the s2opc_config.xsd scheme. Optionally configure the callbacks of the server. If handlers are left None, the following default behaviors are used: - address space: no notification of address space events, - user authentications and authorizations: allow all user and all operations, - methods: no callable methods, - pki: the default secure Public Key Infrastructure, which thoroughly checks the validity of certificates based on trusted issuers, untrusted issuers, and issued certificates. This function must be called after `PyS2OPC_Server.initialize`, and before `PyS2OPC_Server.mark_configured`. It must be called at most once. Note: limitation: for now, changes in user authentications and authorizations, methods, and pki, are not supported. Args: xml_path: Path to the configuration in the s2opc_config.xsd format address_space_handler: None (no notification) or an instance of a subclass of `pys2opc.server_callbacks.BaseAddressSpaceHandler` user_handler: None (authenticate all user and authorize all operations) method_handler: None (no method available) pki_handler: None (certificate authentications based on certificate authorities) """ assert PyS2OPC._initialized_srv and not PyS2OPC._configured and PyS2OPC_Server._config is None,\ 'Toolkit is either not initialized, initialized as a Client, or already configured.' assert user_handler is None, 'Custom User Manager not implemented yet' assert method_handler is None, 'Custom Method Manager not implemented yet' assert pki_handler is None, 'Custom PKI Manager not implemented yet' if address_space_handler is not None: assert isinstance(address_space_handler, BaseAddressSpaceHandler) # Note: if part of the configuration fails, this leaves the toolkit in an half-configured configuration. # In this case, a Clear is required before trying to configure it again. # Creates the configuration config = ffi.new('SOPC_S2OPC_Config *') with open(xml_path, 'r') as fd: assert libsub.SOPC_Config_Parse(fd, config) # Finish the configuration by setting the manual fields: server certificate and key, create the pki, # the user auth* managers, and the method call manager # If any of them fails, we must still clear the config! try: # Cryptography serverCfg = config.serverConfig if serverCfg.serverCertPath != NULL or serverCfg.serverKeyPath != NULL: assert serverCfg.serverCertPath != NULL and serverCfg.serverKeyPath != NULL,\ 'The server private key and server certificate work by pair. Either configure them both of them or none of them.' ppCert = ffi.addressof(serverCfg, 'serverCertificate') status = libsub.SOPC_KeyManager_SerializedCertificate_CreateFromFile( serverCfg.serverCertPath, ppCert) assert status == ReturnStatus.OK,\ 'Cannot load server certificate file {} with status {}. Is path correct?'\ .format(ffi.string(serverCfg.serverCertPath), ReturnStatus.get_both_from_id(status)) ppKey = ffi.addressof(serverCfg, 'serverKey') status = libsub.SOPC_KeyManager_SerializedAsymmetricKey_CreateFromFile( serverCfg.serverKeyPath, ppKey) assert status == ReturnStatus.OK,\ 'Cannot load secret key file {} with status {}. Is path correct?'\ .format(ffi.string(serverCfg.serverKeyPath), ReturnStatus.get_both_from_id(status)) # PKI is not required if no CA is configured if (serverCfg.trustedRootIssuersList != NULL and serverCfg.trustedRootIssuersList[0] != NULL) or\ (serverCfg.issuedCertificatesList != NULL and serverCfg.issuedCertificatesList[0] != NULL): ppPki = ffi.addressof(serverCfg, 'pki') status = libsub.SOPC_PKIProviderStack_CreateFromPaths( serverCfg.trustedRootIssuersList, serverCfg.trustedIntermediateIssuersList, serverCfg.untrustedRootIssuersList, serverCfg.untrustedIntermediateIssuersList, serverCfg.issuedCertificatesList, serverCfg.certificateRevocationPathList, ppPki) # Methods serverCfg.mcm # Leave NULL # Endpoints have the user management for i in range(serverCfg.nbEndpoints): endpoint = serverCfg.endpoints[i] # By default, creates user managers that accept all users and allow all operations endpoint.authenticationManager = libsub.SOPC_UserAuthentication_CreateManager_AllowAll( ) endpoint.authorizationManager = libsub.SOPC_UserAuthorization_CreateManager_AllowAll( ) assert endpoint.authenticationManager != NULL and endpoint.authorizationManager != NULL # Register endpoint epConfigIdx = libsub.SOPC_ToolkitServer_AddEndpointConfig( ffi.addressof(endpoint)) assert epConfigIdx,\ 'Cannot add endpoint configuration. There may be no more endpoint left, or the configuration parameters are incorrect.' assert epConfigIdx not in PyS2OPC_Server._dEpIdx,\ 'Internal failure, epConfigIdx already reserved by another configuration.' PyS2OPC_Server._dEpIdx[epConfigIdx] = endpoint except: libsub.SOPC_S2OPC_Config_Clear(config) config = None raise PyS2OPC_Server._config = config # Set address space handler if address_space_handler is not None: PyS2OPC_Server._adds_handler = address_space_handler # Note: SetAddressSpaceNotifCb cannot be called twice, or with NULL assert libsub.SOPC_ToolkitServer_SetAddressSpaceNotifCb( libsub._callback_address_space_event) == ReturnStatus.OK
def get_version(): # TODO: use build infos """Returns complete version string (PyS2OPC, LibSub, S2OPC)""" return 'PyS2OPC v' + VERSION + ' on ' + ffi.string( libsub.SOPC_LibSub_GetVersion()).decode()