exabgp.reactor.api.transcoder   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 124
dl 0
loc 176
rs 10
c 0
b 0
f 0
wmc 28

5 Methods

Rating   Name   Duplication   Size   Complexity  
A Transcoder.__init__() 0 9 3
F Transcoder._from_json() 0 115 21
A _FakeNeighbor.__init__() 0 5 1
A Transcoder._open() 0 7 2
A Transcoder._state() 0 4 1
1
from __future__ import print_function
2
3
import sys
4
import json
5
import string
6
7
from exabgp.util import hexstring
8
9
from exabgp.bgp.message import Message
10
from exabgp.bgp.message import Open
11
from exabgp.bgp.message import Notification
12
from exabgp.bgp.message.open.asn import ASN
13
from exabgp.bgp.message.open.capability import Negotiated
14
15
from exabgp.version import json as json_version
16
from exabgp.reactor.api.response import Response
17
18
from exabgp.protocol.ip import IPv4
19
20
21
class _FakeNeighbor(object):
22
    def __init__(self, local, remote, asn, peer):
23
        self.local_address = IPv4(local)
24
        self.peer_address = IPv4(remote)
25
        self.peer_as = ASN(asn)
26
        self.local_as = ASN(peer)
27
28
29
class Transcoder(object):
30
    seen_open = {
31
        'send': None,
32
        'receive': None,
33
    }
34
    negotiated = None
35
36
    json = Response.JSON(json_version)
37
38
    def __init__(self, src='json', dst='json'):
39
        if src != 'json':
40
            raise RuntimeError('left as an exercise to the reader')
41
42
        if dst != 'json':
43
            raise RuntimeError('left as an exercise to the reader')
44
45
        self.convert = self._from_json
46
        self.encoder = self.json
47
48
    def _state(self):
49
        self.seen_open['send'] = None
50
        self.seen_open['receive'] = None
51
        self.negotiated = None
52
53
    def _open(self, direction, message):
54
        self.seen_open[direction] = message
55
56
        if all(self.seen_open.values()):
57
            self.negotiated = Negotiated(None)
58
            self.negotiated.sent(self.seen_open['send'])
59
            self.negotiated.received(self.seen_open['receive'])
60
61
    def _from_json(self, json_string):
62
        try:
63
            parsed = json.loads(json_string)
64
        except ValueError:
65
            print('invalid JSON message', file=sys.stderr)
66
            sys.exit(1)
67
68
        if parsed.get('exabgp', '0.0.0') != json_version:
69
            print('invalid json version', json_string, file=sys.stderr)
70
            sys.exit(1)
71
72
        content = parsed.get('type', '')
73
74
        if not content:
75
            print('invalid json content', json_string, file=sys.stderr)
76
            sys.exit(1)
77
78
        neighbor = _FakeNeighbor(
79
            parsed['neighbor']['address']['local'],
80
            parsed['neighbor']['address']['peer'],
81
            parsed['neighbor']['asn']['local'],
82
            parsed['neighbor']['asn']['peer'],
83
        )
84
85
        if content == 'state':
86
            self._state()
87
            return json_string
88
89
        direction = parsed['neighbor']['direction']
90
        category = parsed['neighbor']['message']['category']
91
        header = parsed['neighbor']['message']['header']
92
        body = parsed['neighbor']['message']['body']
93
        data = b''.join(bytes([int(body[_ : _ + 2], 16)]) for _ in range(0, len(body), 2))
94
95
        if content == 'open':
96
            message = Open.unpack_message(data)
97
            self._open(direction, message)
98
            return self.encoder.open(neighbor, direction, message, None, header, body)
99
100
        if content == 'keepalive':
101
            return self.encoder.keepalive(neighbor, direction, None, header, body)
102
103
        if content == 'notification':
104
            # XXX: Use the code of the Notifcation class here ..
105
            message = Notification.unpack_message(data)
106
107
            if (message.code, message.subcode) != (6, 2):
108
                message.data = data if not len([_ for _ in data if _ not in string.printable]) else hexstring(data)
109
                return self.encoder.notification(neighbor, direction, message, None, header, body)
110
111
            if len(data) == 0:
112
                # shutdown without shutdown communication (the old fashioned way)
113
                message.data = ''
114
                return self.encoder.notification(neighbor, direction, message, None, header, body)
115
116
            # draft-ietf-idr-shutdown or the peer was using 6,2 with data
117
118
            shutdown_length = data[0]
119
            data = data[1:]
120
121
            if shutdown_length == 0:
122
                message.data = "empty Shutdown Communication."
123
                # move offset past length field
124
                return self.encoder.notification(neighbor, direction, message, None, header, body)
125
126
            if len(data) < shutdown_length:
127
                message.data = "invalid Shutdown Communication (buffer underrun) length : %i [%s]" % (
128
                    shutdown_length,
129
                    hexstring(data),
130
                )
131
                return self.encoder.notification(neighbor, direction, message, None, header, body)
132
133
            if shutdown_length > 128:
134
                message.data = "invalid Shutdown Communication (too large) length : %i [%s]" % (
135
                    shutdown_length,
136
                    hexstring(data),
137
                )
138
                return self.encoder.notification(neighbor, direction, message, None, header, body)
139
140
            try:
141
                message.data = 'Shutdown Communication: "%s"' % data[:shutdown_length].decode('utf-8').replace(
142
                    '\r', ' '
143
                ).replace('\n', ' ')
144
            except UnicodeDecodeError:
145
                message.data = "invalid Shutdown Communication (invalid UTF-8) length : %i [%s]" % (
146
                    shutdown_length,
147
                    hexstring(data),
148
                )
149
                return self.encoder.notification(neighbor, direction, message, None, header, body)
150
151
            trailer = data[shutdown_length:]
152
            if trailer:
153
                message.data += ", trailing data: " + hexstring(trailer)
154
155
            return self.encoder.notification(neighbor, direction, message, None, header, body)
156
157
        if not self.negotiated:
158
            print('invalid message sequence, open not exchange not complete', json_string, file=sys.stderr)
159
            sys.exit(1)
160
161
        message = Message.unpack(category, data, self.negotiated)
162
163
        if content == 'update':
164
            return self.encoder.update(neighbor, direction, message, None, header, body)
165
166
        if content == 'eor':  # XXX: Should not be required
167
            return self.encoder.update(neighbor, direction, message, None, header, body)
168
169
        if content == 'refresh':
170
            return self.json.refresh(neighbor, direction, message, None, header, body)
171
172
        if content == 'operational':
173
            return self.json.refresh(neighbor, direction, message, None, header, body)
174
175
        raise RuntimeError('the programer is a monkey and forgot a JSON message type')
176