Completed
Push — master ( 271a32...7dfda5 )
by Olivier
16:15
created

opcua.ua.MessageChunk.message_to_chunks()   B

Complexity

Conditions 6

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 6.0164
Metric Value
cc 6
dl 0
loc 36
ccs 24
cts 26
cp 0.9231
crap 6.0164
rs 7.5385
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 1
    def __init__(self):
275 1
        pass
276
277 1
    def plain_block_size(self):
278
        """
279
        Size of plain text block for block cipher.
280
        """
281 1
        return 1
282
283 1
    def encrypted_block_size(self):
284
        """
285
        Size of encrypted text block for block cipher.
286
        """
287 1
        return 1
288
289 1
    def padding(self, size):
290
        """
291
        Create padding for a block of given size.
292
        plain_size = size + len(padding) + signature_size()
293
        plain_size = N * plain_block_size()
294
        """
295 1
        return b''
296
297 1
    def min_padding_size(self):
298 1
        return 0
299
300 1
    def signature_size(self):
301 1
        return 0
302
303 1
    def signature(self, data):
304 1
        return b''
305
306 1
    def encrypt(self, data):
307 1
        return data
308
309 1
    def decrypt(self, data):
310 1
        return data
311
312 1
    def vsignature_size(self):
313 1
        return 0
314
315 1
    def verify(self, data, signature):
316
        """
317
        Verify signature and raise exception if signature is invalid
318
        """
319 1
        pass
320
321 1
    def remove_padding(self, data):
322 1
        return data
323
324
325 1
class SecurityPolicy(object):
326
    """
327
    Base class for security policy
328
    """
329 1
    URI = "http://opcfoundation.org/UA/SecurityPolicy#None"
330 1
    signature_key_size = 0
331 1
    symmetric_key_size = 0
332
333 1
    def __init__(self):
334 1
        self.asymmetric_cryptography = CryptographyNone()
335 1
        self.symmetric_cryptography = CryptographyNone()
336 1
        self.Mode = auto.MessageSecurityMode.None_
337 1
        self.server_certificate = b""
338 1
        self.client_certificate = b""
339
340 1
    def make_symmetric_key(self, a, b):
341 1
        pass
342
343
344 1
class SecurityPolicyFactory(object):
345
    """
346
    Helper class for creating server-side SecurityPolicy.
347
    Server has one certificate and private key, but needs a separate
348
    SecurityPolicy for every client and client's certificate
349
    """
350 1
    def __init__(self, cls=SecurityPolicy, mode=auto.MessageSecurityMode.None_,
351
            certificate=None, private_key=None):
352 1
        self.cls = cls
353 1
        self.mode = mode
354 1
        self.certificate = certificate
355 1
        self.private_key = private_key
356
357 1
    def matches(self, uri, mode=None):
358 1
        return self.cls.URI == uri and (mode is None or self.mode == mode)
359
360 1
    def create(self, peer_certificate):
361 1
        if self.cls is SecurityPolicy:
362 1
            return self.cls()
363
        else:
364
            return self.cls(peer_certificate,
365
                            self.certificate, self.private_key,
366
                            self.mode)
367
368
369 1
class MessageChunk(uatypes.FrozenClass):
370
    """
371
    Message Chunk, as described in OPC UA specs Part 6, 6.7.2.
372
    """
373 1
    def __init__(self, security_policy, body=b'', msg_type=MessageType.SecureMessage, chunk_type=ChunkType.Single):
374 1
        self.MessageHeader = Header(msg_type, chunk_type)
375 1
        if msg_type in (MessageType.SecureMessage, MessageType.SecureClose):
376 1
            self.SecurityHeader = SymmetricAlgorithmHeader()
377 1
        elif msg_type == MessageType.SecureOpen:
378 1
            self.SecurityHeader = AsymmetricAlgorithmHeader()
379
        else:
380
            raise Exception("Unsupported message type: {}".format(msg_type))
381 1
        self.SequenceHeader = SequenceHeader()
382 1
        self.Body = body
383 1
        self._security_policy = security_policy
384
385 1
    @staticmethod
386
    def from_binary(security_policy, data):
387 1
        h = Header.from_string(data)
388 1
        return MessageChunk.from_header_and_body(security_policy, h, data)
389
390 1
    @staticmethod
391
    def from_header_and_body(security_policy, header, buf):
392 1
        assert len(buf) >= header.body_size, 'Full body expected here'
393 1
        data = buf.copy(header.body_size)
394 1
        buf.skip(header.body_size)
395 1
        if header.MessageType in (MessageType.SecureMessage, MessageType.SecureClose):
396 1
            security_header = SymmetricAlgorithmHeader.from_binary(data)
397 1
            crypto = security_policy.symmetric_cryptography
398 1
        elif header.MessageType == MessageType.SecureOpen:
399 1
            security_header = AsymmetricAlgorithmHeader.from_binary(data)
400 1
            crypto = security_policy.asymmetric_cryptography
401
        else:
402
            raise Exception("Unsupported message type: {}".format(header.MessageType))
403 1
        obj = MessageChunk(crypto)
404 1
        obj.MessageHeader = header
405 1
        obj.SecurityHeader = security_header
406 1
        decrypted = crypto.decrypt(data.read(len(data)))
407 1
        signature_size = crypto.vsignature_size()
408 1
        if signature_size > 0:
409
            signature = decrypted[-signature_size:]
410
            decrypted = decrypted[:-signature_size]
411
            crypto.verify(obj.MessageHeader.to_binary() + obj.SecurityHeader.to_binary() + decrypted, signature)
412 1
        data = utils.Buffer(crypto.remove_padding(decrypted))
413 1
        obj.SequenceHeader = SequenceHeader.from_binary(data)
414 1
        obj.Body = data.read(len(data))
415 1
        return obj
416
417 1
    def encrypted_size(self, plain_size):
418 1
        size = plain_size + self._security_policy.signature_size()
419 1
        pbs = self._security_policy.plain_block_size()
420 1
        assert(size % pbs == 0)
421 1
        return size // pbs * self._security_policy.encrypted_block_size()
422
423 1
    def to_binary(self):
424 1
        security = self.SecurityHeader.to_binary()
425 1
        encrypted_part = self.SequenceHeader.to_binary() + self.Body
426 1
        encrypted_part += self._security_policy.padding(len(encrypted_part))
427 1
        self.MessageHeader.body_size = len(security) + self.encrypted_size(len(encrypted_part))
428 1
        header = self.MessageHeader.to_binary()
429 1
        encrypted_part += self._security_policy.signature(header + security + encrypted_part)
430 1
        return header + security + self._security_policy.encrypt(encrypted_part)
431
432 1
    @staticmethod
433
    def max_body_size(crypto, max_chunk_size):
434 1
        max_encrypted_size = max_chunk_size - Header.max_size() - SymmetricAlgorithmHeader.max_size()
435 1
        max_plain_size = (max_encrypted_size // crypto.encrypted_block_size()) * crypto.plain_block_size()
436 1
        return max_plain_size - SequenceHeader.max_size() - crypto.signature_size() - crypto.min_padding_size()
437
438 1
    @staticmethod
439 1
    def message_to_chunks(security_policy, body, max_chunk_size, message_type=MessageType.SecureMessage, channel_id=1, request_id=1, token_id=1):
440
        """
441
        Pack message body (as binary string) into one or more chunks.
442
        Size of each chunk will not exceed max_chunk_size.
443
        Returns a list of MessageChunks. SequenceNumber is not initialized here,
444
        it must be set by Secure Channel driver.
445
        """
446 1
        if message_type == MessageType.SecureOpen:
447
            # SecureOpen message must be in a single chunk (specs, Part 6, 6.7.2)
448 1
            chunk = MessageChunk(security_policy.asymmetric_cryptography, body, message_type, ChunkType.Single)
449 1
            chunk.SecurityHeader.SecurityPolicyURI = security_policy.URI
450 1
            if security_policy.client_certificate:
451
                chunk.SecurityHeader.SenderCertificate = security_policy.client_certificate
452 1
            if security_policy.server_certificate:
453
                chunk.SecurityHeader.ReceiverCertificateThumbPrint = hashlib.sha1(security_policy.server_certificate).digest()
454 1
            chunk.MessageHeader.ChannelId = channel_id
455 1
            chunk.SequenceHeader.RequestId = request_id
456 1
            return [chunk]
457
458 1
        crypto = security_policy.symmetric_cryptography
459 1
        max_size = MessageChunk.max_body_size(crypto, max_chunk_size)
460
461 1
        chunks = []
462 1
        for i in range(0, len(body), max_size):
463 1
            part = body[i:i+max_size]
464 1
            if i+max_size >= len(body):
465 1
                chunk_type = ChunkType.Single
466
            else:
467 1
                chunk_type = ChunkType.Intermediate
468 1
            chunk = MessageChunk(crypto, part, message_type, chunk_type)
469 1
            chunk.SecurityHeader.TokenId = token_id
470 1
            chunk.MessageHeader.ChannelId = channel_id
471 1
            chunk.SequenceHeader.RequestId = request_id
472 1
            chunks.append(chunk)
473 1
        return chunks
474
475 1
    def __str__(self):
476
        return "{}({}, {}, {}, {} bytes)".format(self.__class__.__name__,
477
                self.MessageHeader, self.SequenceHeader, self.SecurityHeader, len(self.Body))
478 1
    __repr__ = __str__
479
480
481 1
class Message(object):
482 1
    def __init__(self, chunks):
483 1
        self._chunks = chunks
484
485 1
    def request_id(self):
486 1
        return self._chunks[0].SequenceHeader.RequestId
487
488 1
    def SequenceHeader(self):
489 1
        return self._chunks[0].SequenceHeader
490
491 1
    def SecurityHeader(self):
492 1
        return self._chunks[0].SecurityHeader
493
494 1
    def body(self):
495 1
        body = b"".join([c.Body for c in self._chunks])
496 1
        return utils.Buffer(body)
497
498
499 1
class SecureConnection(object):
500
    """
501
    Common logic for client and server
502
    """
503 1
    def __init__(self, security_policy):
504 1
        self._sequence_number = 0
505 1
        self._peer_sequence_number = None
506 1
        self._incoming_parts = []
507 1
        self._security_policy = security_policy
508 1
        self._policies = []
509 1
        self._security_token = auto.ChannelSecurityToken()
510 1
        self._max_chunk_size = 65536
511
512 1
    def set_policy_factories(self, policies):
513
        """
514
        Set a list of available security policies.
515
        Use this in servers with multiple endpoints with different security
516
        """
517 1
        self._policies = policies
518
519 1
    @staticmethod
520 1
    def _policy_matches(policy, uri, mode=None):
521
        return policy.URI == uri and (mode is None or policy.Mode == mode)
522
523 1
    def select_policy(self, uri, peer_certificate, mode=None):
524 1
        for policy in self._policies:
525 1
            if policy.matches(uri, mode):
526 1
                self._security_policy = policy.create(peer_certificate)
527 1
                return
528 1
        if self._security_policy.URI != uri or (mode is not None and
529
                self._security_policy.Mode != mode):
530
            raise Exception("No matching policy: {}, {}".format(uri, mode))
531
532 1
    def set_security_token(self, tok):
533 1
        self._security_token = tok
534
535 1
    def tcp_to_binary(self, message_type, message):
536
        """
537
        Convert OPC UA TCP message (see OPC UA specs Part 6, 7.1) to binary.
538
        The only supported types are Hello, Acknowledge and ErrorMessage
539
        """
540 1
        header = Header(message_type, ChunkType.Single)
541 1
        binmsg = message.to_binary()
542 1
        header.body_size = len(binmsg)
543 1
        return header.to_binary() + binmsg
544
545 1
    def message_to_binary(self, message,
546
                message_type=MessageType.SecureMessage, request_id=0):
547
        """
548
        Convert OPC UA secure message to binary.
549
        The only supported types are SecureOpen, SecureMessage, SecureClose
550
        """
551 1
        chunks = MessageChunk.message_to_chunks(
552
                self._security_policy, message, self._max_chunk_size,
553
                message_type=message_type,
554
                channel_id=self._security_token.ChannelId,
555
                request_id=request_id,
556
                token_id=self._security_token.TokenId)
557 1
        for chunk in chunks:
558 1
            self._sequence_number += 1
559 1
            if self._sequence_number >= (1 << 32):
560
                logger.debug("Wrapping sequence number: %d -> 1",
561
                                      self._sequence_number)
562
                self._sequence_number = 1
563 1
            chunk.SequenceHeader.SequenceNumber = self._sequence_number
564 1
        return b"".join([chunk.to_binary() for chunk in chunks])
565
566 1
    def _check_incoming_chunk(self, chunk):
567 1
        assert isinstance(chunk, MessageChunk), "Expected chunk, got: {}".format(chunk)
568 1
        if chunk.MessageHeader.MessageType != MessageType.SecureOpen:
569 1
            if chunk.MessageHeader.ChannelId != self._security_token.ChannelId:
570
                raise Exception("Wrong channel id {}, expected {}".format(
571
                    chunk.MessageHeader.ChannelId,
572
                    self._security_token.ChannelId))
573 1
            if chunk.SecurityHeader.TokenId != self._security_token.TokenId:
574
                raise Exception("Wrong token id {}, expected {}".format(
575
                    chunk.SecurityHeader.TokenId,
576
                    self._security_token.TokenId))
577 1
        if self._incoming_parts:
578
            if self._incoming_parts[0].SequenceHeader.RequestId != chunk.SequenceHeader.RequestId:
579
                raise Exception("Wrong request id {}, expected {}".format(
580
                    chunk.SequenceHeader.RequestId,
581
                    self._incoming_parts[0].SequenceHeader.RequestId))
582
583
        # sequence number must be incremented or wrapped
584 1
        num = chunk.SequenceHeader.SequenceNumber
585 1
        if self._peer_sequence_number is not None:
586 1
            if num != self._peer_sequence_number + 1:
587
                wrap = (1 << 32) - 1024
588
                if num < 1024 and self._peer_sequence_number >= wrap:
589
                    # specs Part 6, 6.7.2
590
                    logger.debug("Sequence number wrapped: %d -> %d",
591
                                      self._peer_sequence_number, num)
592
                else:
593
                    raise Exception(
594
                        "Wrong sequence {} -> {} (server bug or replay attack)"
595
                        .format(self._peer_sequence_number, num))
596 1
        self._peer_sequence_number = num
597
598 1
    def receive_from_header_and_body(self, header, body):
599
        """
600
        Convert MessageHeader and binary body to OPC UA TCP message (see OPC UA
601
        specs Part 6, 7.1: Hello, Acknowledge or ErrorMessage), or a Message
602
        object, or None (if intermediate chunk is received)
603
        """
604 1
        if header.MessageType == MessageType.SecureOpen:
605 1
            data = body.copy(header.body_size)
606 1
            security_header = AsymmetricAlgorithmHeader.from_binary(data)
607 1
            self.select_policy(security_header.SecurityPolicyURI, security_header.SenderCertificate)
608
609 1
        if header.MessageType in (MessageType.SecureMessage,
610
                                  MessageType.SecureOpen,
611
                                  MessageType.SecureClose):
612 1
            chunk = MessageChunk.from_header_and_body(self._security_policy,
613
                    header, body)
614 1
            return self._receive(chunk)
615 1
        elif header.MessageType == MessageType.Hello:
616 1
            msg = Hello.from_binary(body)
617 1
            self._max_chunk_size = msg.ReceiveBufferSize
618 1
            return msg
619 1
        elif header.MessageType == MessageType.Acknowledge:
620 1
            msg = Acknowledge.from_binary(body)
621 1
            self._max_chunk_size = msg.SendBufferSize
622 1
            return msg
623
        elif header.MessageType == MessageType.Error:
624
            msg = ErrorMessage.from_binary(body)
625
            logger.warning("Received an error: {}".format(msg))
626
            return msg
627
        else:
628
            raise Exception("Unsupported message type {}".format(header.MessageType))
629
630 1
    def receive_from_socket(self, socket):
631
        """
632
        Convert binary stream to OPC UA TCP message (see OPC UA
633
        specs Part 6, 7.1: Hello, Acknowledge or ErrorMessage), or a Message
634
        object, or None (if intermediate chunk is received)
635
        """
636 1
        logger.debug("Waiting for header")
637 1
        header = Header.from_string(socket)
638 1
        logger.info("received header: %s", header)
639 1
        body = socket.read(header.body_size)
640 1
        if len(body) != header.body_size:
641
            raise Exception("{} bytes expected, {} available".format(header.body_size, len(body)))
642 1
        return self.receive_from_header_and_body(header, utils.Buffer(body))
643
644 1
    def _receive(self, msg):
645 1
        self._check_incoming_chunk(msg)
646 1
        self._incoming_parts.append(msg)
647 1
        if msg.MessageHeader.ChunkType == ChunkType.Intermediate:
648
            return None
649 1
        if msg.MessageHeader.ChunkType == ChunkType.Abort:
650
            err = ErrorMessage.from_binary(utils.Buffer(msg.Body))
651
            logger.warning("Message {} aborted: {}".format(msg, err))
652
            # specs Part 6, 6.7.3 say that aborted message shall be ignored
653
            # and SecureChannel should not be closed
654
            self._incoming_parts = []
655
            return None
656 1
        elif msg.MessageHeader.ChunkType == ChunkType.Single:
657 1
            message = Message(self._incoming_parts)
658 1
            self._incoming_parts = []
659 1
            return message
660
        else:
661
            raise Exception("Unsupported chunk type: {}".format(msg))
662
663
664
# FIXES for missing switchfield in NodeAttributes classes
665 1
ana = auto.NodeAttributesMask
666
667
668 1
class ObjectAttributes(auto.ObjectAttributes):
669
670 1
    def __init__(self):
671 1
        auto.ObjectAttributes.__init__(self)
672 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.EventNotifier
673
674
675 1
class ObjectTypeAttributes(auto.ObjectTypeAttributes):
676
677 1
    def __init__(self):
678 1
        auto.ObjectTypeAttributes.__init__(self)
679 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract
680
681
682 1
class VariableAttributes(auto.VariableAttributes):
683
684 1
    def __init__(self):
685 1
        auto.VariableAttributes.__init__(self)
686 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
687 1
        self.Historizing = False
688
689
690 1
class VariableTypeAttributes(auto.VariableTypeAttributes):
691
692 1
    def __init__(self):
693 1
        auto.VariableTypeAttributes.__init__(self)
694 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.Value | ana.DataType | ana.ValueRank | ana.ArrayDimensions | ana.IsAbstract
695
696
697 1
class MethodAttributes(auto.MethodAttributes):
698
699 1
    def __init__(self):
700 1
        auto.MethodAttributes.__init__(self)
701 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.Executable | ana.UserExecutable
702
703
704 1
class ReferenceTypeAttributes(auto.ReferenceTypeAttributes):
705
706 1
    def __init__(self):
707 1
        auto.ReferenceTypeAttributes.__init__(self)
708 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract | ana.Symmetric | ana.InverseName
709
710
711 1
class DataTypeAttributes(auto.DataTypeAttributes):
712
713 1
    def __init__(self):
714 1
        auto.DataTypeAttributes.__init__(self)
715 1
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract
716
717
718 1
class ViewAttributes(auto.ViewAttributes):
719
720 1
    def __init__(self):
721
        auto.ViewAttributes.__init__(self)
722
        self.SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.ContainsNoLoops | ana.EventNotifier
723
724
725 1
class Argument(auto.Argument):
726
727 1
    def __init__(self):
728 1
        auto.Argument.__init__(self)
729 1
        self.ValueRank = -2
730
731
732
#AttributeIdsInv = {v: k for k, v in AttributeIds.__dict__.items()}
733