blob: 2db1baf305bcd7fb062bce51b7037f3d511075cf [file] [log] [blame]
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05001#
2# Copyright 2016 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16import structlog
17from hexdump import hexdump
18from twisted.internet import protocol
19from twisted.internet.defer import inlineCallbacks, returnValue
20from common.utils.message_queue import MessageQueue
21
22log = structlog.get_logger()
23
24from netconf import MAXSSHBUF
25
26
27class NetconfConnection(protocol.Protocol):
28 def __init__(self, data=None, avatar=None, max_chunk=MAXSSHBUF):
29 self.avatar = avatar
30 self.nc_server = self.avatar.get_nc_server()
31 self.rx = MessageQueue()
32 self.max_chunk = max_chunk
33 self.connected = True
34 self.proto_handler = None
35 self.exiting = False
36
37 def connectionLost(self, reason):
38 log.info('connection-lost')
39 self.connected = False
40 if not self.exiting:
41 self.proto_handler.stop('Connection-Lost')
42
43 def connectionMade(self):
44 log.info('connection-made')
45 self.nc_server.client_connected(self)
46
47 def dataReceived(self, data):
48 log.debug('data-received', len=len(data),
49 received=hexdump(data, result='return'))
50 assert len(data)
51 self.rx.put(data)
52
53 def processEnded(self, reason=None):
54 log.info('process-ended', reason=reason)
55 self.connected = False
56
57 def chunkit(self, msg, maxsend):
58 sz = len(msg)
59 left = 0
60 for unused in range(0, sz // maxsend):
61 right = left + maxsend
62 chunk = msg[left:right]
63 left = right
64 yield chunk
65 msg = msg[left:]
66 yield msg
67
68 def send_msg(self, msg, new_framing):
69 assert self.connected
70 # Apparently ssh has a bug that requires minimum of 64 bytes?
71 # This may not be sufficient to fix this.
72 if new_framing:
73 msg = "\n#{}\n{}\n##\n".format(len(msg), msg)
74 else:
75 msg += "]]>]]>"
76 for chunk in self.chunkit(msg, self.max_chunk - 64):
77 log.debug('sending', chunk=chunk,
78 framing="1.1" if new_framing else "1.0")
79 # out = hexdump(chunk, result='return')
80 self.transport.write('{}\r\n'.format(chunk))
81
82 @inlineCallbacks
83 def receive_msg_any(self, new_framing):
84 assert self.connected
85 msg = yield self.recv(lambda _: True)
86 if new_framing:
87 returnValue(self._receive_11(msg))
88 else:
89 returnValue(self._receive_10(msg))
90
91 def _receive_10(self, msg):
92 # search for message end indicator
93 searchfrom = 0
94 eomidx = msg.find(b"]]>]]>", searchfrom)
95 if eomidx != -1:
96 log.info('received-msg', msg=msg[:eomidx])
97 return msg[:eomidx]
98 else:
99 log.error('no-message-end-indicators', msg=msg)
100 return msg
101
102 def _receive_11(self, msg):
103 # Message is received in the format "\n#{len}\n{msg}\n##\n"
104 if msg:
105 msg = msg.split('\n')
106 if len(msg) > 2:
107 log.info('received-msg', msg=msg[2])
108 return msg[2]
109 return None
110
111 def close_connection(self):
112 log.info('closing-connection')
113 self.exiting = True
114 self.transport.loseConnection()
115
116 def recv(self, predicate):
117 assert self.connected
118 return self.rx.get(predicate)
119
120 def recv_any(self, new_framing):
121 return self.recv(lambda _: True)
122
123 def recv_xid(self, xid):
124 return self.recv(lambda msg: msg.xid == xid)