Completed
Pull Request — master (#206)
by Olivier
03:46
created

SecureConnection.open()   A

Complexity

Conditions 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0416

Importance

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