Completed
Pull Request — master (#394)
by
unknown
06:10
created

ObjectAttributes   A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 5
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
c 1
b 0
f 0
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 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 1
        self.ProtocolVersion = 0
22 1
        self.ReceiveBufferSize = 65536
23 1
        self.SendBufferSize = 65536
24 1
        self.MaxMessageSize = 0
25 1
        self.MaxChunkCount = 0
26 1
        self.EndpointUrl = ""
27 1
        self._freeze = True
28
29 1
    def to_binary(self):
30 1
        b = []
31 1
        b.append(uabin.Primitives.UInt32.pack(self.ProtocolVersion))
32 1
        b.append(uabin.Primitives.UInt32.pack(self.ReceiveBufferSize))
33 1
        b.append(uabin.Primitives.UInt32.pack(self.SendBufferSize))
34 1
        b.append(uabin.Primitives.UInt32.pack(self.MaxMessageSize))
35 1
        b.append(uabin.Primitives.UInt32.pack(self.MaxChunkCount))
36 1
        b.append(uabin.Primitives.String.pack(self.EndpointUrl))
37 1
        return b"".join(b)
38
39 1
    @staticmethod
40
    def from_binary(data):
41 1
        hello = Hello()
42 1
        hello.ProtocolVersion = uabin.Primitives.UInt32.unpack(data)
43 1
        hello.ReceiveBufferSize = uabin.Primitives.UInt32.unpack(data)
44 1
        hello.SendBufferSize = uabin.Primitives.UInt32.unpack(data)
45 1
        hello.MaxMessageSize = uabin.Primitives.UInt32.unpack(data)
46 1
        hello.MaxChunkCount = uabin.Primitives.UInt32.unpack(data)
47 1
        hello.EndpointUrl = uabin.Primitives.String.unpack(data)
48 1
        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 1
        self.MessageType = msgType
72 1
        self.ChunkType = chunkType
73 1
        self.ChannelId = channelid
74 1
        self.body_size = 0
75 1
        self.packet_size = 0
76 1
        self._freeze = True
77
78 1
    def add_size(self, size):
79
        self.body_size += size
80
81 1
    def to_binary(self):
82 1
        b = []
83 1
        b.append(struct.pack("<3ss", self.MessageType, self.ChunkType))
84 1
        size = self.body_size + 8
85 1
        if self.MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
86 1
            size += 4
87 1
        b.append(uabin.Primitives.UInt32.pack(size))
88 1
        if self.MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
89 1
            b.append(uabin.Primitives.UInt32.pack(self.ChannelId))
90 1
        return b"".join(b)
91
92 1
    @staticmethod
93
    def from_string(data):
94 1
        hdr = Header()
95 1
        hdr.MessageType, hdr.ChunkType, hdr.packet_size = struct.unpack("<3scI", data.read(8))
96 1
        hdr.body_size = hdr.packet_size - 8
97 1
        if hdr.MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
98 1
            hdr.body_size -= 4
99 1
            hdr.ChannelId = uabin.Primitives.UInt32.unpack(data)
100 1
        return hdr
101
102 1
    @staticmethod
103
    def max_size():
104 1
        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 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
112 1
class ErrorMessage(uatypes.FrozenClass):
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 1
        self.ProtocolVersion = 0
141 1
        self.ReceiveBufferSize = 65536
142 1
        self.SendBufferSize = 65536
143 1
        self.MaxMessageSize = 0  # No limits
144 1
        self.MaxChunkCount = 0  # No limits
145 1
        self._freeze = True
146
147 1
    def to_binary(self):
148 1
        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 1
        ack = Acknowledge()
159 1
        ack.ProtocolVersion, ack.ReceiveBufferSize, ack.SendBufferSize, ack.MaxMessageSize, ack.MaxChunkCount \
160
            = struct.unpack("<5I", data.read(20))
161 1
        return ack
162
163
164 1
class AsymmetricAlgorithmHeader(uatypes.FrozenClass):
165
166 1
    def __init__(self):
167 1
        self.SecurityPolicyURI = "http://opcfoundation.org/UA/SecurityPolicy#None"
168 1
        self.SenderCertificate = None
169 1
        self.ReceiverCertificateThumbPrint = None
170 1
        self._freeze = True
171
172 1
    def to_binary(self):
173 1
        b = []
174 1
        b.append(uabin.Primitives.String.pack(self.SecurityPolicyURI))
175 1
        b.append(uabin.Primitives.String.pack(self.SenderCertificate))
176 1
        b.append(uabin.Primitives.String.pack(self.ReceiverCertificateThumbPrint))
177 1
        return b"".join(b)
178
179 1
    @staticmethod
180
    def from_binary(data):
181 1
        hdr = AsymmetricAlgorithmHeader()
182 1
        hdr.SecurityPolicyURI = uabin.Primitives.String.unpack(data)
183 1
        hdr.SenderCertificate = uabin.Primitives.Bytes.unpack(data)
184 1
        hdr.ReceiverCertificateThumbPrint = uabin.Primitives.Bytes.unpack(data)
185 1
        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 1
        self.TokenId = 0
198 1
        self._freeze = True
199
200 1
    @staticmethod
201
    def from_binary(data):
202 1
        obj = SymmetricAlgorithmHeader()
203 1
        obj.TokenId = uabin.Primitives.UInt32.unpack(data)
204 1
        return obj
205
206 1
    def to_binary(self):
207 1
        return uabin.Primitives.UInt32.pack(self.TokenId)
208
209 1
    @staticmethod
210
    def max_size():
211 1
        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 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
218 1
class SequenceHeader(uatypes.FrozenClass):
219
220 1
    def __init__(self):
221 1
        self.SequenceNumber = None
222 1
        self.RequestId = None
223 1
        self._freeze = True
224
225 1
    @staticmethod
226
    def from_binary(data):
227 1
        obj = SequenceHeader()
228 1
        obj.SequenceNumber = uabin.Primitives.UInt32.unpack(data)
229 1
        obj.RequestId = uabin.Primitives.UInt32.unpack(data)
230 1
        return obj
231
232 1
    def to_binary(self):
233 1
        b = []
234 1
        b.append(uabin.Primitives.UInt32.pack(self.SequenceNumber))
235 1
        b.append(uabin.Primitives.UInt32.pack(self.RequestId))
236 1
        return b"".join(b)
237
238 1
    @staticmethod
239
    def max_size():
240 1
        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 1
        return 1
261
262 1
    def encrypted_block_size(self):
263
        """
264
        Size of encrypted text block for block cipher.
265
        """
266 1
        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 1
        return b''
275
276 1
    def min_padding_size(self):
277 1
        return 0
278
279 1
    def signature_size(self):
280 1
        return 0
281
282 1
    def signature(self, data):
283 1
        return b''
284
285 1
    def encrypt(self, data):
286 1
        return data
287
288 1
    def decrypt(self, data):
289 1
        return data
290
291 1
    def vsignature_size(self):
292 1
        return 0
293
294 1
    def verify(self, data, signature):
295
        """
296
        Verify signature and raise exception if signature is invalid
297
        """
298 1
        pass
299
300 1
    def remove_padding(self, data):
301 1
        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 1
        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 1
        self.cls = cls
333 1
        self.mode = mode
334 1
        self.certificate = certificate
335 1
        self.private_key = private_key
336
337 1
    def matches(self, uri, mode=None):
338 1
        return self.cls.URI == uri and (mode is None or self.mode == mode)
339
340 1
    def create(self, peer_certificate):
341 1
        if self.cls is SecurityPolicy:
342 1
            return self.cls()
343
        else:
344 1
            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 1
        self.MessageHeader = Header(msg_type, chunk_type)
356 1
        if msg_type in (MessageType.SecureMessage, MessageType.SecureClose):
357 1
            self.SecurityHeader = SymmetricAlgorithmHeader()
358 1
        elif msg_type == MessageType.SecureOpen:
359 1
            self.SecurityHeader = AsymmetricAlgorithmHeader()
360
        else:
361
            raise UaError("Unsupported message type: {0}".format(msg_type))
362 1
        self.SequenceHeader = SequenceHeader()
363 1
        self.Body = body
364 1
        self._security_policy = security_policy
365
366 1
    @staticmethod
367
    def from_binary(security_policy, data):
368 1
        h = Header.from_string(data)
369 1
        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 1
        assert len(buf) >= header.body_size, 'Full body expected here'
374 1
        data = buf.copy(header.body_size)
375 1
        buf.skip(header.body_size)
376 1
        if header.MessageType in (MessageType.SecureMessage, MessageType.SecureClose):
377 1
            security_header = SymmetricAlgorithmHeader.from_binary(data)
378 1
            crypto = security_policy.symmetric_cryptography
379 1
        elif header.MessageType == MessageType.SecureOpen:
380 1
            security_header = AsymmetricAlgorithmHeader.from_binary(data)
381 1
            crypto = security_policy.asymmetric_cryptography
382
        else:
383
            raise UaError("Unsupported message type: {0}".format(header.MessageType))
384 1
        obj = MessageChunk(crypto)
385 1
        obj.MessageHeader = header
386 1
        obj.SecurityHeader = security_header
387 1
        decrypted = crypto.decrypt(data.read(len(data)))
388 1
        signature_size = crypto.vsignature_size()
389 1
        if signature_size > 0:
390 1
            signature = decrypted[-signature_size:]
391 1
            decrypted = decrypted[:-signature_size]
392 1
            crypto.verify(obj.MessageHeader.to_binary() + obj.SecurityHeader.to_binary() + decrypted, signature)
393 1
        data = utils.Buffer(crypto.remove_padding(decrypted))
394 1
        obj.SequenceHeader = SequenceHeader.from_binary(data)
395 1
        obj.Body = data.read(len(data))
396 1
        return obj
397
398 1
    def encrypted_size(self, plain_size):
399 1
        size = plain_size + self._security_policy.signature_size()
400 1
        pbs = self._security_policy.plain_block_size()
401 1
        assert(size % pbs == 0)
402 1
        return size // pbs * self._security_policy.encrypted_block_size()
403
404 1
    def to_binary(self):
405 1
        security = self.SecurityHeader.to_binary()
406 1
        encrypted_part = self.SequenceHeader.to_binary() + self.Body
407 1
        encrypted_part += self._security_policy.padding(len(encrypted_part))
408 1
        self.MessageHeader.body_size = len(security) + self.encrypted_size(len(encrypted_part))
409 1
        header = self.MessageHeader.to_binary()
410 1
        encrypted_part += self._security_policy.signature(header + security + encrypted_part)
411 1
        return header + security + self._security_policy.encrypt(encrypted_part)
412
413 1
    @staticmethod
414
    def max_body_size(crypto, max_chunk_size):
415 1
        max_encrypted_size = max_chunk_size - Header.max_size() - SymmetricAlgorithmHeader.max_size()
416 1
        max_plain_size = (max_encrypted_size // crypto.encrypted_block_size()) * crypto.plain_block_size()
417 1
        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 1
        if message_type == MessageType.SecureOpen:
429
            # SecureOpen message must be in a single chunk (specs, Part 6, 6.7.2)
430 1
            chunk = MessageChunk(security_policy.asymmetric_cryptography, body, message_type, ChunkType.Single)
431 1
            chunk.SecurityHeader.SecurityPolicyURI = security_policy.URI
432 1
            if security_policy.client_certificate:
433 1
                chunk.SecurityHeader.SenderCertificate = security_policy.client_certificate
434 1
            if security_policy.server_certificate:
435 1
                chunk.SecurityHeader.ReceiverCertificateThumbPrint =\
436
                    hashlib.sha1(security_policy.server_certificate).digest()
437 1
            chunk.MessageHeader.ChannelId = channel_id
438 1
            chunk.SequenceHeader.RequestId = request_id
439 1
            return [chunk]
440
441 1
        crypto = security_policy.symmetric_cryptography
442 1
        max_size = MessageChunk.max_body_size(crypto, max_chunk_size)
443
444 1
        chunks = []
445 1
        for i in range(0, len(body), max_size):
446 1
            part = body[i:i + max_size]
447 1
            if i + max_size >= len(body):
448 1
                chunk_type = ChunkType.Single
449
            else:
450 1
                chunk_type = ChunkType.Intermediate
451 1
            chunk = MessageChunk(crypto, part, message_type, chunk_type)
452 1
            chunk.SecurityHeader.TokenId = token_id
453 1
            chunk.MessageHeader.ChannelId = channel_id
454 1
            chunk.SequenceHeader.RequestId = request_id
455 1
            chunks.append(chunk)
456 1
        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 1
        self._chunks = chunks
469
470 1
    def request_id(self):
471 1
        return self._chunks[0].SequenceHeader.RequestId
472
473 1
    def SequenceHeader(self):
474 1
        return self._chunks[0].SequenceHeader
475
476 1
    def SecurityHeader(self):
477 1
        return self._chunks[0].SecurityHeader
478
479 1
    def body(self):
480 1
        body = b"".join([c.Body for c in self._chunks])
481 1
        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 1
        self._sequence_number = 0
491 1
        self._peer_sequence_number = None
492 1
        self._incoming_parts = []
493 1
        self._security_policy = security_policy
494 1
        self._policies = []
495 1
        self.channel = auto.OpenSecureChannelResult()
496 1
        self._old_tokens = []
497 1
        self._open = False
498 1
        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 1
        self.channel = channel
505
506 1
    def open(self, params, server):
507
        """
508
        called on server side to open secure channel
509
        """
510 1
        if not self._open or params.RequestType == auto.SecurityTokenRequestType.Issue:
511 1
            self._open = True
512 1
            self.channel = auto.OpenSecureChannelResult()
513 1
            self.channel.SecurityToken.TokenId = 13  # random value
514 1
            self.channel.SecurityToken.ChannelId = server.get_new_channel_id()
515 1
            self.channel.SecurityToken.RevisedLifetime = params.RequestedLifetime
516
        else:
517
            self._old_tokens.append(self.channel.SecurityToken.TokenId)
518 1
        self.channel.SecurityToken.TokenId += 1
519 1
        self.channel.SecurityToken.CreatedAt = datetime.utcnow()
520 1
        self.channel.SecurityToken.RevisedLifetime = params.RequestedLifetime
521 1
        self.channel.ServerNonce = utils.create_nonce(self._security_policy.symmetric_key_size)
522 1
        self._security_policy.make_symmetric_key(self.channel.ServerNonce, params.ClientNonce)
523 1
        return self.channel
524
525 1
    def close(self):
526 1
        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 1
        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 1
        for policy in self._policies:
544 1
            if policy.matches(uri, mode):
545 1
                self._security_policy = policy.create(peer_certificate)
546 1
                return
547 1
        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 1
        header = Header(message_type, ChunkType.Single)
557 1
        binmsg = message.to_binary()
558 1
        header.body_size = len(binmsg)
559 1
        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 1
        if algohdr is None:
568 1
            token_id = self.channel.SecurityToken.TokenId
569
        else:
570 1
            token_id = algohdr.TokenId
571 1
        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 1
        for chunk in chunks:
578 1
            self._sequence_number += 1
579 1
            if self._sequence_number >= (1 << 32):
580
                logger.debug("Wrapping sequence number: %d -> 1", self._sequence_number)
581
                self._sequence_number = 1
582 1
            chunk.SequenceHeader.SequenceNumber = self._sequence_number
583 1
        return b"".join([chunk.to_binary() for chunk in chunks])
584
585 1
    def _check_incoming_chunk(self, chunk):
586 1
        assert isinstance(chunk, MessageChunk), "Expected chunk, got: {0}".format(chunk)
587 1
        if chunk.MessageHeader.MessageType != MessageType.SecureOpen:
588 1
            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 1
            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 1
        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 1
        num = chunk.SequenceHeader.SequenceNumber
614 1
        if self._peer_sequence_number is not None:
615 1
            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 1
        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 1
        if header.MessageType == MessageType.SecureOpen:
634 1
            data = body.copy(header.body_size)
635 1
            security_header = AsymmetricAlgorithmHeader.from_binary(data)
636 1
            self.select_policy(security_header.SecurityPolicyURI, security_header.SenderCertificate)
637
638 1
        if header.MessageType in (MessageType.SecureMessage,
639
                                  MessageType.SecureOpen,
640
                                  MessageType.SecureClose):
641 1
            chunk = MessageChunk.from_header_and_body(self._security_policy,
642
                                                      header, body)
643 1
            return self._receive(chunk)
644 1
        elif header.MessageType == MessageType.Hello:
645 1
            msg = Hello.from_binary(body)
646 1
            self._max_chunk_size = msg.ReceiveBufferSize
647 1
            return msg
648 1
        elif header.MessageType == MessageType.Acknowledge:
649 1
            msg = Acknowledge.from_binary(body)
650 1
            self._max_chunk_size = msg.SendBufferSize
651 1
            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 1
        logger.debug("Waiting for header")
666 1
        header = Header.from_string(socket)
667 1
        logger.info("received header: %s", header)
668 1
        body = socket.read(header.body_size)
669 1
        if len(body) != header.body_size:
670
            raise UaError("{0} bytes expected, {1} available".format(header.body_size, len(body)))
671 1
        return self.receive_from_header_and_body(header, utils.Buffer(body))
672
673 1
    def _receive(self, msg):
674 1
        self._check_incoming_chunk(msg)
675 1
        self._incoming_parts.append(msg)
676 1
        if msg.MessageHeader.ChunkType == ChunkType.Intermediate:
677
            return None
678 1
        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 1
        elif msg.MessageHeader.ChunkType == ChunkType.Single:
686 1
            message = Message(self._incoming_parts)
687 1
            self._incoming_parts = []
688 1
            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 1
        auto.ObjectAttributes.__init__(self)
701 1
        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 1
        auto.ObjectTypeAttributes.__init__(self)
708 1
        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 1
        auto.VariableAttributes.__init__(self)
715 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
716 1
        self.Historizing = False
717 1
        self.AccessLevel = AccessLevel.CurrentRead.mask
718 1
        self.UserAccessLevel = AccessLevel.CurrentRead.mask
719
720
721 1
class VariableTypeAttributes(auto.VariableTypeAttributes):
722
723 1
    def __init__(self):
724 1
        auto.VariableTypeAttributes.__init__(self)
725 1
        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 1
        auto.MethodAttributes.__init__(self)
732 1
        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 1
        auto.ReferenceTypeAttributes.__init__(self)
739 1
        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 1
        auto.DataTypeAttributes.__init__(self)
746 1
        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 1
        auto.Argument.__init__(self)
760 1
        self.ValueRank = -2
761
762
#AttributeIdsInv = {v: k for k, v in AttributeIds.__dict__.items()}
763