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