Completed
Pull Request — master (#494)
by Olivier
03:37
created

Hello   A

Complexity

Total Complexity 3

Size/Duplication

Total Lines 31
Duplicated Lines 0 %

Test Coverage

Coverage 14.81%

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
ccs 4
cts 27
cp 0.1481
rs 10
wmc 3

3 Methods

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