def __init__(self, server, nodeid): self.server = server self.nodeid = None if isinstance(nodeid, Node): self.nodeid = nodeid.nodeid elif isinstance(nodeid, ua.NodeId): self.nodeid = nodeid elif type(nodeid) in (str, bytes): self.nodeid = ua.NodeId.from_string(nodeid) elif isinstance(nodeid, int): self.nodeid = ua.NodeId(nodeid, 0) else: raise ua.UaError( "argument to node must be a NodeId object or a string defining a nodeid found {0} of type {1}" .format(nodeid, type(nodeid))) self.basenodeid = None
def __init__(self, security_policy, body=b'', msg_type=ua.MessageType.SecureMessage, chunk_type=ua.ChunkType.Single): self.MessageHeader = ua.Header(msg_type, chunk_type) if msg_type in (ua.MessageType.SecureMessage, ua.MessageType.SecureClose): self.SecurityHeader = ua.SymmetricAlgorithmHeader() elif msg_type == ua.MessageType.SecureOpen: self.SecurityHeader = ua.AsymmetricAlgorithmHeader() else: raise ua.UaError("Unsupported message type: {0}".format(msg_type)) self.SequenceHeader = ua.SequenceHeader() self.Body = body self.security_policy = security_policy
def historize_event(self, source, period=timedelta(days=7)): """ subscribe to the source nodes' events and store the data in the active storage; custom event properties included """ if not self._sub: self._sub = self._create_subscription(SubHandler(self.storage)) if source in self._handlers: raise ua.UaError("Events from {} are already historized".format(source)) # get the event types the source node generates and a list of all possible event fields event_types, ev_fields = self._get_source_event_data(source) self.storage.new_historized_event(source.nodeid, ev_fields, period) handler = self._sub.subscribe_events(source) # FIXME supply list of event types when master is fixed self._handlers[source] = handler
def create_session(self): """ send a CreateSessionRequest to server with reasonable parameters. If you want o modify settings look at code of this methods and make your own """ desc = ua.ApplicationDescription() desc.ApplicationUri = self.application_uri desc.ProductUri = self.product_uri desc.ApplicationName = ua.LocalizedText(self.name) desc.ApplicationType = ua.ApplicationType.Client params = ua.CreateSessionParameters() # at least 32 random bytes for server to prove possession of private key (specs part 4, 5.6.2.2) nonce = utils.create_nonce(32) params.ClientNonce = nonce params.ClientCertificate = self.security_policy.client_certificate params.ClientDescription = desc params.EndpointUrl = self.server_url.geturl() params.SessionName = self.description + " Session" + str( self._session_counter) params.RequestedSessionTimeout = 3600000 params.MaxResponseMessageSize = 0 # means no max size response = self.uaclient.create_session(params) if self.security_policy.client_certificate is None: data = nonce else: data = self.security_policy.client_certificate + nonce self.security_policy.asymmetric_cryptography.verify( data, response.ServerSignature.Signature) self._server_nonce = response.ServerNonce if not self.security_policy.server_certificate: self.security_policy.server_certificate = response.ServerCertificate elif self.security_policy.server_certificate != response.ServerCertificate: raise ua.UaError("Server certificate mismatch") # remember PolicyId's: we will use them in activate_session() ep = Client.find_endpoint(response.ServerEndpoints, self.security_policy.Mode, self.security_policy.URI) self._policy_ids = ep.UserIdentityTokens self.session_timeout = response.RevisedSessionTimeout self.keepalive = KeepAlive( self, min(self.session_timeout, self.secure_channel_timeout) * 0.7) # 0.7 is from spec self.keepalive.start() return response
def get_base_data_type(datatype): """ Looks up the base datatype of the provided datatype Node The base datatype is either: A primitive type (ns=0, i<=21) or a complex one (ns=0 i>21 and i<=30) like Enum and Struct. Args: datatype: NodeId of a datype of a variable Returns: NodeId of datatype base or None in case base datype can not be determined """ base = datatype while base: if base.nodeid.NamespaceIndex == 0 and isinstance(base.nodeid.Identifier, int) and base.nodeid.Identifier <= 30: return base base = get_node_supertype(base) raise ua.UaError("Datatype must be a subtype of builtin types {0!s}".format(datatype))
def _receive(self, msg): self._check_incoming_chunk(msg) self._incoming_parts.append(msg) if msg.MessageHeader.ChunkType == ua.ChunkType.Intermediate: return None if msg.MessageHeader.ChunkType == ua.ChunkType.Abort: err = struct_from_binary(ua.ErrorMessage, ua.utils.Buffer(msg.Body)) logger.warning("Message %s aborted: %s", msg, err) # specs Part 6, 6.7.3 say that aborted message shall be ignored # and SecureChannel should not be closed self._incoming_parts = [] return None elif msg.MessageHeader.ChunkType == ua.ChunkType.Single: message = ua.Message(self._incoming_parts) self._incoming_parts = [] return message else: raise ua.UaError("Unsupported chunk type: {0}".format(msg))
def set_security_string(self, string): """ Set SecureConnection mode. String format: Policy,Mode,certificate,private_key[,server_private_key] where Policy is Basic128Rsa15, Basic256 or Basic256Sha256, Mode is Sign or SignAndEncrypt certificate, private_key and server_private_key are paths to .pem or .der files Call this before connect() """ if not string: return parts = string.split(',') if len(parts) < 4: raise ua.UaError('Wrong format: `{0}`, expected at least 4 comma-separated values'.format(string)) policy_class = getattr(security_policies, 'SecurityPolicy' + parts[0]) mode = getattr(ua.MessageSecurityMode, parts[1]) return self.set_security(policy_class, parts[2], parts[3], parts[4] if len(parts) >= 5 else None, mode)
def data_type_to_variant_type(dtype_node): """ Given a Node datatype, find out the variant type to encode data. This is not exactly straightforward... """ base = get_base_data_type(dtype_node) if base.nodeid.Identifier != 29: return ua.VariantType(base.nodeid.Identifier) else: # we have an enumeration, we need to look at child to find type descs = dtype_node.get_children_descriptions() bnames = [d.BrowseName.Name for d in descs] if "EnumStrings" in bnames: return ua.VariantType.LocalizedText elif "EnumValues" in bnames: return ua.VariantType.ExtensionObject else: raise ua.UaError( "Enumeration must have a child node describing its type and values" )
def receive_from_header_and_body(self, header, body): """ Convert MessageHeader and binary body to OPC UA TCP message (see OPC UA specs Part 6, 7.1: Hello, Acknowledge or ErrorMessage), or a Message object, or None (if intermediate chunk is received) """ if header.MessageType == ua.MessageType.SecureOpen: data = body.copy(header.body_size) security_header = struct_from_binary(ua.AsymmetricAlgorithmHeader, data) self.select_policy(security_header.SecurityPolicyURI, security_header.SenderCertificate) elif header.MessageType in (ua.MessageType.SecureMessage, ua.MessageType.SecureClose): data = body.copy(header.body_size) security_header = struct_from_binary(ua.SymmetricAlgorithmHeader, data) self._check_sym_header(security_header) if header.MessageType in (ua.MessageType.SecureMessage, ua.MessageType.SecureOpen, ua.MessageType.SecureClose): chunk = MessageChunk.from_header_and_body(self.security_policy, header, body) return self._receive(chunk) elif header.MessageType == ua.MessageType.Hello: msg = struct_from_binary(ua.Hello, body) self._max_chunk_size = msg.ReceiveBufferSize return msg elif header.MessageType == ua.MessageType.Acknowledge: msg = struct_from_binary(ua.Acknowledge, body) self._max_chunk_size = msg.SendBufferSize return msg elif header.MessageType == ua.MessageType.Error: msg = struct_from_binary(ua.ErrorMessage, body) logger.warning("Received an error: %s", msg) return msg else: raise ua.UaError("Unsupported message type {0}".format( header.MessageType))
def _call_callback(self, request_id, body): with self._lock: future = self._callbackmap.pop(request_id, None) if future is None: raise ua.UaError("No future object found for request: {0}, callbacks in list are {1}".format(request_id, self._callbackmap.keys())) future.set_result(body)