blob: b0272a05cb5c651a321ee5893025b5c0b9bc0bef [file] [log] [blame]
Khen Nursimulua7b842a2016-12-03 23:28:42 -05001#!/usr/bin/env python
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05002#
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -05003# Copyright 2017 the original author or authors.
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17import structlog
18from hexdump import hexdump
19from twisted.internet import protocol
20from twisted.internet.defer import inlineCallbacks, returnValue
21from common.utils.message_queue import MessageQueue
Khen Nursimulua7b842a2016-12-03 23:28:42 -050022from netconf.constants import Constants as C
Khen Nursimulub21bd642017-02-20 21:32:27 -050023import re
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050024
25log = structlog.get_logger()
26
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050027MAXSSHBUF = C.MAXSSHBUF
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050028
Khen Nursimulub21bd642017-02-20 21:32:27 -050029
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050030class NetconfConnection(protocol.Protocol):
31 def __init__(self, data=None, avatar=None, max_chunk=MAXSSHBUF):
32 self.avatar = avatar
33 self.nc_server = self.avatar.get_nc_server()
34 self.rx = MessageQueue()
35 self.max_chunk = max_chunk
36 self.connected = True
37 self.proto_handler = None
38 self.exiting = False
39
40 def connectionLost(self, reason):
41 log.info('connection-lost')
42 self.connected = False
43 if not self.exiting:
44 self.proto_handler.stop('Connection-Lost')
45
46 def connectionMade(self):
47 log.info('connection-made')
48 self.nc_server.client_connected(self)
49
50 def dataReceived(self, data):
51 log.debug('data-received', len=len(data),
Khen Nursimulub21bd642017-02-20 21:32:27 -050052 received=hexdump(data, result='return'))
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050053 assert len(data)
54 self.rx.put(data)
55
56 def processEnded(self, reason=None):
57 log.info('process-ended', reason=reason)
58 self.connected = False
59
60 def chunkit(self, msg, maxsend):
61 sz = len(msg)
62 left = 0
63 for unused in range(0, sz // maxsend):
64 right = left + maxsend
65 chunk = msg[left:right]
66 left = right
67 yield chunk
68 msg = msg[left:]
69 yield msg
70
71 def send_msg(self, msg, new_framing):
72 assert self.connected
73 # Apparently ssh has a bug that requires minimum of 64 bytes?
74 # This may not be sufficient to fix this.
75 if new_framing:
Khen Nursimulua7b842a2016-12-03 23:28:42 -050076 msg = "#{}\n{}\n##\n".format(len(msg), msg)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050077 else:
Khen Nursimulua7b842a2016-12-03 23:28:42 -050078 msg += C.DELIMITER
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050079 for chunk in self.chunkit(msg, self.max_chunk - 64):
Khen Nursimulua7b842a2016-12-03 23:28:42 -050080 log.info('sending', chunk=chunk,
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050081 framing="1.1" if new_framing else "1.0")
82 # out = hexdump(chunk, result='return')
Khen Nursimulua7b842a2016-12-03 23:28:42 -050083 self.transport.write('{}\n'.format(chunk))
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050084
85 @inlineCallbacks
86 def receive_msg_any(self, new_framing):
87 assert self.connected
88 msg = yield self.recv(lambda _: True)
89 if new_framing:
Khen Nursimulub21bd642017-02-20 21:32:27 -050090 response = yield self._receive_11(msg)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050091 else:
Khen Nursimulub21bd642017-02-20 21:32:27 -050092 response = yield self._receive_10(msg)
93 returnValue(response)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050094
Khen Nursimulub21bd642017-02-20 21:32:27 -050095 @inlineCallbacks
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050096 def _receive_10(self, msg):
97 # search for message end indicator
98 searchfrom = 0
Khen Nursimulub21bd642017-02-20 21:32:27 -050099 partial_msgs = []
100 while 1:
101 eomidx = msg.find(C.DELIMITER, searchfrom)
102 if eomidx != -1:
103 partial_msgs.append(msg[:eomidx])
104 full_msg = ''.join(partial_msgs)
105 log.info('full-msg-received', msg=full_msg)
106 returnValue(full_msg)
107 else:
108 partial_msgs.append(msg)
109 log.debug('partial-msg-received', msg=msg)
110 msg = yield self.recv(lambda _: True)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500111
Khen Nursimulub21bd642017-02-20 21:32:27 -0500112 @inlineCallbacks
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500113 def _receive_11(self, msg):
Khen Nursimulub21bd642017-02-20 21:32:27 -0500114 # A message can be received in chunks where each chunk is formatted as:
115 # /\n#[len]\n
116 # \n##\n
117 # msg\n
118 # msg
119 # \n
120 # \nmsg\n
121 partial_msgs = []
122 while 1:
123 log.info('received-msg', msg=msg)
124 # Remove all reference to length if any, i.e any '#len'
125 msg = re.sub(r'#[0-9]+', "", msg)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500126 msg = msg.split('\n')
Khen Nursimulub21bd642017-02-20 21:32:27 -0500127 if C.DELIMITER_1_1 in msg: # The '##' is the second last ref
128 partial_msgs.append(''.join(msg[0:(len(msg) - 2)]))
129 full_msg = ''.join(partial_msgs)
130 log.debug('full-msg-received', msg=full_msg)
131 returnValue(full_msg)
132 else:
133 partial_msgs.append(''.join(msg))
134 log.debug('partial-msg-received', msg=msg)
135 msg = yield self.recv(lambda _: True)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500136
137 def close_connection(self):
138 log.info('closing-connection')
139 self.exiting = True
140 self.transport.loseConnection()
141
142 def recv(self, predicate):
143 assert self.connected
144 return self.rx.get(predicate)
145
146 def recv_any(self, new_framing):
147 return self.recv(lambda _: True)
148
149 def recv_xid(self, xid):
150 return self.recv(lambda msg: msg.xid == xid)