Completed
Pull Request — master (#128)
by Alexander
02:55
created

opcua.ua.Argument   A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 5
Duplicated Lines 0 %

Test Coverage

Coverage 100%
Metric Value
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
wmc 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 3 1
1 1
import struct
2 1
import logging
3 1
import hashlib
4 1
from enum import IntEnum
5
6 1
from opcua.ua import uaprotocol_auto as auto
7 1
from opcua.ua import uatypes
8 1
from opcua.ua.uatypes import uatype_UInt32
9 1
from opcua.common import utils
10
11 1
logger = logging.getLogger('opcua.uaprotocol')
12
13 1
OPC_TCP_SCHEME = 'opc.tcp'
14
15
16 1
class ValueRank(IntEnum):
17
    """
18
    Defines dimensions of a variable.
19
    This enum does not support all cases since ValueRank support any n>0
20
    but since it is an IntEnum it can be replace by a normal int
21
    """
22 1
    ScalarOrOneDimension = -3
23 1
    Any = -2
24 1
    Scalar = -1
25 1
    OneOrMoreDimensions = 0
26 1
    OneDimension = 1
27
    # the next names are not in spec but so common we express them here
28 1
    TwoDimensions = 2
29 1
    ThreeDimensions = 3
30 1
    FourDimensions = 3
31
32
33 1
class AccessLevelMask(object):
34
    """
35
    Used by AccessLevel and UserAccessLevel
36
    """
37 1
    CurrentRead = 0
38 1
    CurrentWrite = 1
39 1
    HistoryRead = 2
40 1
    HistoryWrite = 3
41 1
    SemanticChange = 4
42
43
44 1
class Hello(uatypes.FrozenClass):
45
46 1
    def __init__(self):
47 1
        self.ProtocolVersion = 0
48 1
        self.ReceiveBufferSize = 65536
49 1
        self.SendBufferSize = 65536
50 1
        self.MaxMessageSize = 0
51 1
        self.MaxChunkCount = 0
52 1
        self.EndpointUrl = ""
53 1
        self._freeze = True
54
55 1
    def to_binary(self):
56 1
        b = []
57 1
        b.append(uatype_UInt32.pack(self.ProtocolVersion))
58 1
        b.append(uatype_UInt32.pack(self.ReceiveBufferSize))
59 1
        b.append(uatype_UInt32.pack(self.SendBufferSize))
60 1
        b.append(uatype_UInt32.pack(self.MaxMessageSize))
61 1
        b.append(uatype_UInt32.pack(self.MaxChunkCount))
62 1
        b.append(uatypes.pack_string(self.EndpointUrl))
63 1
        return b"".join(b)
64
65 1
    @staticmethod
66
    def from_binary(data):
67 1
        hello = Hello()
68 1
        hello.ProtocolVersion = uatype_UInt32.unpack(data.read(4))[0]
69 1
        hello.ReceiveBufferSize = uatype_UInt32.unpack(data.read(4))[0]
70 1
        hello.SendBufferSize = uatype_UInt32.unpack(data.read(4))[0]
71 1
        hello.MaxMessageSize = uatype_UInt32.unpack(data.read(4))[0]
72 1
        hello.MaxChunkCount = uatype_UInt32.unpack(data.read(4))[0]
73 1
        hello.EndpointUrl = uatypes.unpack_string(data)
74 1
        return hello
75
76
77 1
class MessageType(object):
78 1
    Invalid = b"INV"  # FIXME: check value
79 1
    Hello = b"HEL"
80 1
    Acknowledge = b"ACK"
81 1
    Error = b"ERR"
82 1
    SecureOpen = b"OPN"
83 1
    SecureClose = b"CLO"
84 1
    SecureMessage = b"MSG"
85
86
87 1
class ChunkType(object):
88 1
    Invalid = b"0"  # FIXME check
89 1
    Single = b"F"
90 1
    Intermediate = b"C"
91 1
    Abort = b"A"    # when an error occurred and the Message is aborted (body is ErrorMessage)
92
93
94 1
class Header(uatypes.FrozenClass):
95
96 1
    def __init__(self, msgType=None, chunkType=None, channelid=0):
97 1
        self.MessageType = msgType
98 1
        self.ChunkType = chunkType
99 1
        self.ChannelId = channelid
100 1
        self.body_size = 0
101 1
        self.packet_size = 0
102 1
        self._freeze = True
103
104 1
    def add_size(self, size):
105
        self.body_size += size
106
107 1
    def to_binary(self):
108 1
        b = []
109 1
        b.append(struct.pack("<3ss", self.MessageType, self.ChunkType))
110 1
        size = self.body_size + 8
111 1
        if self.MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
112 1
            size += 4
113 1
        b.append(uatype_UInt32.pack(size))
114 1
        if self.MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
115 1
            b.append(uatype_UInt32.pack(self.ChannelId))
116 1
        return b"".join(b)
117
118 1
    @staticmethod
119
    def from_string(data):
120 1
        hdr = Header()
121 1
        hdr.MessageType, hdr.ChunkType, hdr.packet_size = struct.unpack("<3scI", data.read(8))
122 1
        hdr.body_size = hdr.packet_size - 8
123 1
        if hdr.MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
124 1
            hdr.body_size -= 4
125 1
            hdr.ChannelId = uatype_UInt32.unpack(data.read(4))[0]
126 1
        return hdr
127
128 1
    @staticmethod
129
    def max_size():
130 1
        return struct.calcsize("<3scII")
131
132 1
    def __str__(self):
133
        return "Header(type:{}, chunk_type:{}, body_size:{}, channel:{})".format(self.MessageType, self.ChunkType, self.body_size, self.ChannelId)
134 1
    __repr__ = __str__
135
136
137 1
class ErrorMessage(uatypes.FrozenClass):
138
139 1
    def __init__(self):
140
        self.Error = uatypes.StatusCode()
141
        self.Reason = ""
142
        self._freeze = True
143
144 1
    def to_binary(self):
145
        b = []
146
        b.append(self.Error.to_binary())
147
        b.append(uatypes.pack_string(self.Reason))
148
        return b"".join(b)
149
150 1
    @staticmethod
151
    def from_binary(data):
152
        ack = ErrorMessage()
153
        ack.Error = uatypes.StatusCode.from_binary(data)
154
        ack.Reason = uatypes.unpack_string(data)
155
        return ack
156
157 1
    def __str__(self):
158
        return "MessageAbort(error:{}, reason:{})".format(self.Error, self.Reason)
159 1
    __repr__ = __str__
160
161
162 1
class Acknowledge(uatypes.FrozenClass):
163
164 1
    def __init__(self):
165 1
        self.ProtocolVersion = 0
166 1
        self.ReceiveBufferSize = 65536
167 1
        self.SendBufferSize = 65536
168 1
        self.MaxMessageSize = 0  # No limits
169 1
        self.MaxChunkCount = 0  # No limits
170 1
        self._freeze = True
171
172 1
    def to_binary(self):
173 1
        return struct.pack(
174
            "<5I",
175
            self.ProtocolVersion,
176
            self.ReceiveBufferSize,
177
            self.SendBufferSize,
178
            self.MaxMessageSize,
179
            self.MaxChunkCount)
180
181 1
    @staticmethod
182
    def from_binary(data):
183 1
        ack = Acknowledge()
184 1
        ack.ProtocolVersion, ack.ReceiveBufferSize, ack.SendBufferSize, ack.MaxMessageSize, ack.MaxChunkCount \
185
            = struct.unpack("<5I", data.read(20))
186 1
        return ack
187
188
189 1
class AsymmetricAlgorithmHeader(uatypes.FrozenClass):
190
191 1
    def __init__(self):
192 1
        self.SecurityPolicyURI = "http://opcfoundation.org/UA/SecurityPolicy#None"
193 1
        self.SenderCertificate = b""
194 1
        self.ReceiverCertificateThumbPrint = b""
195 1
        self._freeze = True
196
197 1
    def to_binary(self):
198 1
        b = []
199 1
        b.append(uatypes.pack_string(self.SecurityPolicyURI))
200 1
        b.append(uatypes.pack_string(self.SenderCertificate))
201 1
        b.append(uatypes.pack_string(self.ReceiverCertificateThumbPrint))
202 1
        return b"".join(b)
203
204 1
    @staticmethod
205
    def from_binary(data):
206 1
        hdr = AsymmetricAlgorithmHeader()
207 1
        hdr.SecurityPolicyURI = uatypes.unpack_string(data)
208 1
        hdr.SenderCertificate = uatypes.unpack_bytes(data)
209 1
        hdr.ReceiverCertificateThumbPrint = uatypes.unpack_bytes(data)
210 1
        return hdr
211
212 1
    def __str__(self):
213
        return "{}(SecurityPolicy:{}, certificatesize:{}, receiverCertificatesize:{} )".format(self.__class__.__name__, self.SecurityPolicyURI, len(self.SenderCertificate), len(self.ReceiverCertificateThumbPrint))
214 1
    __repr__ = __str__
215
216
217 1
class SymmetricAlgorithmHeader(uatypes.FrozenClass):
218
219 1
    def __init__(self):
220 1
        self.TokenId = 0
221 1
        self._freeze = True
222
223 1
    @staticmethod
224
    def from_binary(data):
225 1
        obj = SymmetricAlgorithmHeader()
226 1
        obj.TokenId = uatype_UInt32.unpack(data.read(4))[0]
227 1
        return obj
228
229 1
    def to_binary(self):
230 1
        return uatype_UInt32.pack(self.TokenId)
231
232 1
    @staticmethod
233
    def max_size():
234 1
        return struct.calcsize("<I")
235
236 1
    def __str__(self):
237
        return "{}(TokenId:{} )".format(self.__class__.__name__, self.TokenId)
238 1
    __repr__ = __str__
239
240
241 1
class SequenceHeader(uatypes.FrozenClass):
242
243 1
    def __init__(self):
244 1
        self.SequenceNumber = None
245 1
        self.RequestId = None
246 1
        self._freeze = True
247
248 1
    @staticmethod
249
    def from_binary(data):
250 1
        obj = SequenceHeader()
251 1
        obj.SequenceNumber = uatype_UInt32.unpack(data.read(4))[0]
252 1
        obj.RequestId = uatype_UInt32.unpack(data.read(4))[0]
253 1
        return obj
254
255 1
    def to_binary(self):
256 1
        b = []
257 1
        b.append(uatype_UInt32.pack(self.SequenceNumber))
258 1
        b.append(uatype_UInt32.pack(self.RequestId))
259 1
        return b"".join(b)
260
261 1
    @staticmethod
262
    def max_size():
263 1
        return struct.calcsize("<II")
264
265 1
    def __str__(self):
266
        return "{}(SequenceNumber:{}, RequestId:{} )".format(self.__class__.__name__, self.SequenceNumber, self.RequestId)
267 1
    __repr__ = __str__
268
269
270 1
class CryptographyNone:
271
    """
272
    Base class for symmetric/asymmetric cryprography
273
    """
274
275 1
    def __init__(self):
276 1
        pass
277
278 1
    def plain_block_size(self):
279
        """
280
        Size of plain text block for block cipher.
281
        """
282 1
        return 1
283
284 1
    def encrypted_block_size(self):
285
        """
286
        Size of encrypted text block for block cipher.
287
        """
288 1
        return 1
289
290 1
    def padding(self, size):
291
        """
292
        Create padding for a block of given size.
293
        plain_size = size + len(padding) + signature_size()
294
        plain_size = N * plain_block_size()
295
        """
296 1
        return b''
297
298 1
    def min_padding_size(self):
299 1
        return 0
300
301 1
    def signature_size(self):
302 1
        return 0
303
304 1
    def signature(self, data):
305 1
        return b''
306
307 1
    def encrypt(self, data):
308 1
        return data
309
310 1
    def decrypt(self, data):
311 1
        return data
312
313 1
    def vsignature_size(self):
314 1
        return 0
315
316 1
    def verify(self, data, signature):
317
        """
318
        Verify signature and raise exception if signature is invalid
319
        """
320 1
        pass
321
322 1
    def remove_padding(self, data):
323 1
        return data
324
325
326 1
class SecurityPolicy(object):
327
    """
328
    Base class for security policy
329
    """
330 1
    URI = "http://opcfoundation.org/UA/SecurityPolicy#None"
331 1
    signature_key_size = 0
332 1
    symmetric_key_size = 0
333
334 1
    def __init__(self):
335 1
        self.asymmetric_cryptography = CryptographyNone()
336 1
        self.symmetric_cryptography = CryptographyNone()
337 1
        self.Mode = auto.MessageSecurityMode.None_
338 1
        self.server_certificate = b""
339 1
        self.client_certificate = b""
340
341 1
    def make_symmetric_key(self, a, b):
342 1
        pass
343
344
345 1
class SecurityPolicyFactory(object):
346
    """
347
    Helper class for creating server-side SecurityPolicy.
348
    Server has one certificate and private key, but needs a separate
349
    SecurityPolicy for every client and client's certificate
350
    """
351
352 1
    def __init__(self, cls=SecurityPolicy, mode=auto.MessageSecurityMode.None_,
353
                 certificate=None, private_key=None):
354 1
        self.cls = cls
355 1
        self.mode = mode
356 1
        self.certificate = certificate
357 1
        self.private_key = private_key
358
359 1
    def matches(self, uri, mode=None):
360 1
        return self.cls.URI == uri and (mode is None or self.mode == mode)
361
362 1
    def create(self, peer_certificate):
363 1
        if self.cls is SecurityPolicy:
364 1
            return self.cls()
365
        else:
366
            return self.cls(peer_certificate,
367
                            self.certificate, self.private_key,
368
                            self.mode)
369
370
371 1
class MessageChunk(uatypes.FrozenClass):
372
    """
373
    Message Chunk, as described in OPC UA specs Part 6, 6.7.2.
374
    """
375
376 1
    def __init__(self, security_policy, body=b'', msg_type=MessageType.SecureMessage, chunk_type=ChunkType.Single):
377 1
        self.MessageHeader = Header(msg_type, chunk_type)
378 1
        if msg_type in (MessageType.SecureMessage, MessageType.SecureClose):
379 1
            self.SecurityHeader = SymmetricAlgorithmHeader()
380 1
        elif msg_type == MessageType.SecureOpen:
381 1
            self.SecurityHeader = AsymmetricAlgorithmHeader()
382
        else:
383
            raise UaError("Unsupported message type: {}".format(msg_type))
384 1
        self.SequenceHeader = SequenceHeader()
385 1
        self.Body = body
386 1
        self._security_policy = security_policy
387
388 1
    @staticmethod
389
    def from_binary(security_policy, data):
390 1
        h = Header.from_string(data)
391 1
        return MessageChunk.from_header_and_body(security_policy, h, data)
392
393 1
    @staticmethod
394
    def from_header_and_body(security_policy, header, buf):
395 1
        assert len(buf) >= header.body_size, 'Full body expected here'
396 1
        data = buf.copy(header.body_size)
397 1
        buf.skip(header.body_size)
398 1
        if header.MessageType in (MessageType.SecureMessage, MessageType.SecureClose):
399 1
            security_header = SymmetricAlgorithmHeader.from_binary(data)
400 1
            crypto = security_policy.symmetric_cryptography
401 1
        elif header.MessageType == MessageType.SecureOpen:
402 1
            security_header = AsymmetricAlgorithmHeader.from_binary(data)
403 1
            crypto = security_policy.asymmetric_cryptography
404
        else:
405
            raise UaError("Unsupported message type: {}".format(header.MessageType))
406 1
        obj = MessageChunk(crypto)
407 1
        obj.MessageHeader = header
408 1
        obj.SecurityHeader = security_header
409 1
        decrypted = crypto.decrypt(data.read(len(data)))
410 1
        signature_size = crypto.vsignature_size()
411 1
        if signature_size > 0:
412
            signature = decrypted[-signature_size:]
413
            decrypted = decrypted[:-signature_size]
414
            crypto.verify(obj.MessageHeader.to_binary() + obj.SecurityHeader.to_binary() + decrypted, signature)
415 1
        data = utils.Buffer(crypto.remove_padding(decrypted))
416 1
        obj.SequenceHeader = SequenceHeader.from_binary(data)
417 1
        obj.Body = data.read(len(data))
418 1
        return obj
419
420 1
    def encrypted_size(self, plain_size):
421 1
        size = plain_size + self._security_policy.signature_size()
422 1
        pbs = self._security_policy.plain_block_size()
423 1
        assert(size % pbs == 0)
424 1
        return size // pbs * self._security_policy.encrypted_block_size()
425
426 1
    def to_binary(self):
427 1
        security = self.SecurityHeader.to_binary()
428 1
        encrypted_part = self.SequenceHeader.to_binary() + self.Body
429 1
        encrypted_part += self._security_policy.padding(len(encrypted_part))
430 1
        self.MessageHeader.body_size = len(security) + self.encrypted_size(len(encrypted_part))
431 1
        header = self.MessageHeader.to_binary()
432 1
        encrypted_part += self._security_policy.signature(header + security + encrypted_part)
433 1
        return header + security + self._security_policy.encrypt(encrypted_part)
434
435 1
    @staticmethod
436
    def max_body_size(crypto, max_chunk_size):
437 1
        max_encrypted_size = max_chunk_size - Header.max_size() - SymmetricAlgorithmHeader.max_size()
438 1
        max_plain_size = (max_encrypted_size // crypto.encrypted_block_size()) * crypto.plain_block_size()
439 1
        return max_plain_size - SequenceHeader.max_size() - crypto.signature_size() - crypto.min_padding_size()
440
441 1
    @staticmethod
442 1
    def message_to_chunks(security_policy, body, max_chunk_size, message_type=MessageType.SecureMessage, channel_id=1, request_id=1, token_id=1):
443
        """
444
        Pack message body (as binary string) into one or more chunks.
445
        Size of each chunk will not exceed max_chunk_size.
446
        Returns a list of MessageChunks. SequenceNumber is not initialized here,
447
        it must be set by Secure Channel driver.
448
        """
449 1
        if message_type == MessageType.SecureOpen:
450
            # SecureOpen message must be in a single chunk (specs, Part 6, 6.7.2)
451 1
            chunk = MessageChunk(security_policy.asymmetric_cryptography, body, message_type, ChunkType.Single)
452 1
            chunk.SecurityHeader.SecurityPolicyURI = security_policy.URI
453 1
            if security_policy.client_certificate:
454
                chunk.SecurityHeader.SenderCertificate = security_policy.client_certificate
455 1
            if security_policy.server_certificate:
456
                chunk.SecurityHeader.ReceiverCertificateThumbPrint = hashlib.sha1(security_policy.server_certificate).digest()
457 1
            chunk.MessageHeader.ChannelId = channel_id
458 1
            chunk.SequenceHeader.RequestId = request_id
459 1
            return [chunk]
460
461 1
        crypto = security_policy.symmetric_cryptography
462 1
        max_size = MessageChunk.max_body_size(crypto, max_chunk_size)
463
464 1
        chunks = []
465 1
        for i in range(0, len(body), max_size):
466 1
            part = body[i:i + max_size]
467 1
            if i + max_size >= len(body):
468 1
                chunk_type = ChunkType.Single
469
            else:
470 1
                chunk_type = ChunkType.Intermediate
471 1
            chunk = MessageChunk(crypto, part, message_type, chunk_type)
472 1
            chunk.SecurityHeader.TokenId = token_id
473 1
            chunk.MessageHeader.ChannelId = channel_id
474 1
            chunk.SequenceHeader.RequestId = request_id
475 1
            chunks.append(chunk)
476 1
        return chunks
477
478 1
    def __str__(self):
479
        return "{}({}, {}, {}, {} bytes)".format(self.__class__.__name__,
480
                                                 self.MessageHeader, self.SequenceHeader, self.SecurityHeader, len(self.Body))
481 1
    __repr__ = __str__
482
483
484 1
class Message(object):
485
486 1
    def __init__(self, chunks):
487 1
        self._chunks = chunks
488
489 1
    def request_id(self):
490 1
        return self._chunks[0].SequenceHeader.RequestId
491
492 1
    def SequenceHeader(self):
493 1
        return self._chunks[0].SequenceHeader
494
495 1
    def SecurityHeader(self):
496 1
        return self._chunks[0].SecurityHeader
497
498 1
    def body(self):
499 1
        body = b"".join([c.Body for c in self._chunks])
500 1
        return utils.Buffer(body)
501
502
503 1
class SecureConnection(object):
504
    """
505
    Common logic for client and server
506
    """
507
508 1
    def __init__(self, security_policy):
509 1
        self._sequence_number = 0
510 1
        self._peer_sequence_number = None
511 1
        self._incoming_parts = []
512 1
        self._security_policy = security_policy
513 1
        self._policies = []
514 1
        self._security_token = auto.ChannelSecurityToken()
515 1
        self._max_chunk_size = 65536
516
517 1
    def set_policy_factories(self, policies):
518
        """
519
        Set a list of available security policies.
520
        Use this in servers with multiple endpoints with different security
521
        """
522 1
        self._policies = policies
523
524 1
    @staticmethod
525 1
    def _policy_matches(policy, uri, mode=None):
526
        return policy.URI == uri and (mode is None or policy.Mode == mode)
527
528 1
    def select_policy(self, uri, peer_certificate, mode=None):
529 1
        for policy in self._policies:
530 1
            if policy.matches(uri, mode):
531 1
                self._security_policy = policy.create(peer_certificate)
532 1
                return
533 1
        if self._security_policy.URI != uri or (mode is not None and
534
                                                self._security_policy.Mode != mode):
535
            raise UaError("No matching policy: {}, {}".format(uri, mode))
536
537 1
    def set_security_token(self, tok):
538 1
        self._security_token = tok
539
540 1
    def tcp_to_binary(self, message_type, message):
541
        """
542
        Convert OPC UA TCP message (see OPC UA specs Part 6, 7.1) to binary.
543
        The only supported types are Hello, Acknowledge and ErrorMessage
544
        """
545 1
        header = Header(message_type, ChunkType.Single)
546 1
        binmsg = message.to_binary()
547 1
        header.body_size = len(binmsg)
548 1
        return header.to_binary() + binmsg
549
550 1
    def message_to_binary(self, message,
551
                          message_type=MessageType.SecureMessage, request_id=0):
552
        """
553
        Convert OPC UA secure message to binary.
554
        The only supported types are SecureOpen, SecureMessage, SecureClose
555
        """
556 1
        chunks = MessageChunk.message_to_chunks(
557
            self._security_policy, message, self._max_chunk_size,
558
            message_type=message_type,
559
            channel_id=self._security_token.ChannelId,
560
            request_id=request_id,
561
            token_id=self._security_token.TokenId)
562 1
        for chunk in chunks:
563 1
            self._sequence_number += 1
564 1
            if self._sequence_number >= (1 << 32):
565
                logger.debug("Wrapping sequence number: %d -> 1",
566
                             self._sequence_number)
567
                self._sequence_number = 1
568 1
            chunk.SequenceHeader.SequenceNumber = self._sequence_number
569 1
        return b"".join([chunk.to_binary() for chunk in chunks])
570
571 1
    def _check_incoming_chunk(self, chunk):
572 1
        assert isinstance(chunk, MessageChunk), "Expected chunk, got: {}".format(chunk)
573 1
        if chunk.MessageHeader.MessageType != MessageType.SecureOpen:
574 1
            if chunk.MessageHeader.ChannelId != self._security_token.ChannelId:
575
                raise UaError("Wrong channel id {}, expected {}".format(
576
                    chunk.MessageHeader.ChannelId,
577
                    self._security_token.ChannelId))
578 1
            if chunk.SecurityHeader.TokenId != self._security_token.TokenId:
579
                raise UaError("Wrong token id {}, expected {}".format(
580
                    chunk.SecurityHeader.TokenId,
581
                    self._security_token.TokenId))
582 1
        if self._incoming_parts:
583
            if self._incoming_parts[0].SequenceHeader.RequestId != chunk.SequenceHeader.RequestId:
584
                raise UaError("Wrong request id {}, expected {}".format(
585
                    chunk.SequenceHeader.RequestId,
586
                    self._incoming_parts[0].SequenceHeader.RequestId))
587
588
        # sequence number must be incremented or wrapped
589 1
        num = chunk.SequenceHeader.SequenceNumber
590 1
        if self._peer_sequence_number is not None:
591 1
            if num != self._peer_sequence_number + 1:
592
                wrap = (1 << 32) - 1024
593
                if num < 1024 and self._peer_sequence_number >= wrap:
594
                    # specs Part 6, 6.7.2
595
                    logger.debug("Sequence number wrapped: %d -> %d",
596
                                 self._peer_sequence_number, num)
597
                else:
598
                    raise UaError(
599
                        "Wrong sequence {} -> {} (server bug or replay attack)"
600
                        .format(self._peer_sequence_number, num))
601 1
        self._peer_sequence_number = num
602
603 1
    def receive_from_header_and_body(self, header, body):
604
        """
605
        Convert MessageHeader and binary body to OPC UA TCP message (see OPC UA
606
        specs Part 6, 7.1: Hello, Acknowledge or ErrorMessage), or a Message
607
        object, or None (if intermediate chunk is received)
608
        """
609 1
        if header.MessageType == MessageType.SecureOpen:
610 1
            data = body.copy(header.body_size)
611 1
            security_header = AsymmetricAlgorithmHeader.from_binary(data)
612 1
            self.select_policy(security_header.SecurityPolicyURI, security_header.SenderCertificate)
613
614 1
        if header.MessageType in (MessageType.SecureMessage,
615
                                  MessageType.SecureOpen,
616
                                  MessageType.SecureClose):
617 1
            chunk = MessageChunk.from_header_and_body(self._security_policy,
618
                                                      header, body)
619 1
            return self._receive(chunk)
620 1
        elif header.MessageType == MessageType.Hello:
621 1
            msg = Hello.from_binary(body)
622 1
            self._max_chunk_size = msg.ReceiveBufferSize
623 1
            return msg
624 1
        elif header.MessageType == MessageType.Acknowledge:
625 1
            msg = Acknowledge.from_binary(body)
626 1
            self._max_chunk_size = msg.SendBufferSize
627 1
            return msg
628
        elif header.MessageType == MessageType.Error:
629
            msg = ErrorMessage.from_binary(body)
630
            logger.warning("Received an error: {}".format(msg))
631
            return msg
632
        else:
633
            raise UaError("Unsupported message type {}".format(header.MessageType))
634
635 1
    def receive_from_socket(self, socket):
636
        """
637
        Convert binary stream to OPC UA TCP message (see OPC UA
638
        specs Part 6, 7.1: Hello, Acknowledge or ErrorMessage), or a Message
639
        object, or None (if intermediate chunk is received)
640
        """
641 1
        logger.debug("Waiting for header")
642 1
        header = Header.from_string(socket)
643 1
        logger.info("received header: %s", header)
644 1
        body = socket.read(header.body_size)
645 1
        if len(body) != header.body_size:
646
            raise UaError("{} bytes expected, {} available".format(header.body_size, len(body)))
647 1
        return self.receive_from_header_and_body(header, utils.Buffer(body))
648
649 1
    def _receive(self, msg):
650 1
        self._check_incoming_chunk(msg)
651 1
        self._incoming_parts.append(msg)
652 1
        if msg.MessageHeader.ChunkType == ChunkType.Intermediate:
653
            return None
654 1
        if msg.MessageHeader.ChunkType == ChunkType.Abort:
655
            err = ErrorMessage.from_binary(utils.Buffer(msg.Body))
656
            logger.warning("Message {} aborted: {}".format(msg, err))
657
            # specs Part 6, 6.7.3 say that aborted message shall be ignored
658
            # and SecureChannel should not be closed
659
            self._incoming_parts = []
660
            return None
661 1
        elif msg.MessageHeader.ChunkType == ChunkType.Single:
662 1
            message = Message(self._incoming_parts)
663 1
            self._incoming_parts = []
664 1
            return message
665
        else:
666
            raise UaError("Unsupported chunk type: {}".format(msg))
667
668
669
# FIXES for missing switchfield in NodeAttributes classes
670 1
ana = auto.NodeAttributesMask
671
672
673 1
class ObjectAttributes(auto.ObjectAttributes):
674
675 1
    def __init__(self):
676 1
        auto.ObjectAttributes.__init__(self)
677 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.EventNotifier
678
679
680 1
class ObjectTypeAttributes(auto.ObjectTypeAttributes):
681
682 1
    def __init__(self):
683 1
        auto.ObjectTypeAttributes.__init__(self)
684 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract
685
686
687 1
class VariableAttributes(auto.VariableAttributes):
688
689 1
    def __init__(self):
690 1
        auto.VariableAttributes.__init__(self)
691 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.Value | ana.DataType | ana.ValueRank | ana.ArrayDimensions | ana.AccessLevel | ana.UserAccessLevel | ana.MinimumSamplingInterval | ana.Historizing
692 1
        self.Historizing = False
693
694
695 1
class VariableTypeAttributes(auto.VariableTypeAttributes):
696
697 1
    def __init__(self):
698 1
        auto.VariableTypeAttributes.__init__(self)
699 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.Value | ana.DataType | ana.ValueRank | ana.ArrayDimensions | ana.IsAbstract
700
701
702 1
class MethodAttributes(auto.MethodAttributes):
703
704 1
    def __init__(self):
705 1
        auto.MethodAttributes.__init__(self)
706 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.Executable | ana.UserExecutable
707
708
709 1
class ReferenceTypeAttributes(auto.ReferenceTypeAttributes):
710
711 1
    def __init__(self):
712 1
        auto.ReferenceTypeAttributes.__init__(self)
713 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract | ana.Symmetric | ana.InverseName
714
715
716 1
class DataTypeAttributes(auto.DataTypeAttributes):
717
718 1
    def __init__(self):
719 1
        auto.DataTypeAttributes.__init__(self)
720 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract
721
722
723 1
class ViewAttributes(auto.ViewAttributes):
724
725 1
    def __init__(self):
726
        auto.ViewAttributes.__init__(self)
727
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.ContainsNoLoops | ana.EventNotifier
728
729
730 1
class Argument(auto.Argument):
731
732 1
    def __init__(self):
733 1
        auto.Argument.__init__(self)
734 1
        self.ValueRank = -2
735
736
737
#AttributeIdsInv = {v: k for k, v in AttributeIds.__dict__.items()}
738