Completed
Push — master ( eb5e78...c75f71 )
by Olivier
04:30
created

SecureConnection.__init__()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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