Passed
Branch master (6083f5)
by Olivier
03:32
created

MessageChunk.from_header_and_body()   B

Complexity

Conditions 5

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 5.0018

Importance

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