blob: d8a2afe54da6e67b3c5eda60d54848a033b668a3 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2016 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import structlog
from hexdump import hexdump
from twisted.internet import protocol
from twisted.internet.defer import inlineCallbacks, returnValue
from common.utils.message_queue import MessageQueue
from netconf.constants import Constants as C
log = structlog.get_logger()
from netconf import MAXSSHBUF
class NetconfConnection(protocol.Protocol):
def __init__(self, data=None, avatar=None, max_chunk=MAXSSHBUF):
self.avatar = avatar
self.nc_server = self.avatar.get_nc_server()
self.rx = MessageQueue()
self.max_chunk = max_chunk
self.connected = True
self.proto_handler = None
self.exiting = False
def connectionLost(self, reason):
log.info('connection-lost')
self.connected = False
if not self.exiting:
self.proto_handler.stop('Connection-Lost')
def connectionMade(self):
log.info('connection-made')
self.nc_server.client_connected(self)
def dataReceived(self, data):
log.debug('data-received', len=len(data),
received=hexdump(data, result='return'))
assert len(data)
self.rx.put(data)
def processEnded(self, reason=None):
log.info('process-ended', reason=reason)
self.connected = False
def chunkit(self, msg, maxsend):
sz = len(msg)
left = 0
for unused in range(0, sz // maxsend):
right = left + maxsend
chunk = msg[left:right]
left = right
yield chunk
msg = msg[left:]
yield msg
def send_msg(self, msg, new_framing):
assert self.connected
# Apparently ssh has a bug that requires minimum of 64 bytes?
# This may not be sufficient to fix this.
if new_framing:
msg = "#{}\n{}\n##\n".format(len(msg), msg)
else:
msg += C.DELIMITER
for chunk in self.chunkit(msg, self.max_chunk - 64):
log.info('sending', chunk=chunk,
framing="1.1" if new_framing else "1.0")
# out = hexdump(chunk, result='return')
self.transport.write('{}\n'.format(chunk))
@inlineCallbacks
def receive_msg_any(self, new_framing):
assert self.connected
msg = yield self.recv(lambda _: True)
if new_framing:
returnValue(self._receive_11(msg))
else:
returnValue(self._receive_10(msg))
def _receive_10(self, msg):
# search for message end indicator
searchfrom = 0
eomidx = msg.find(C.DELIMITER, searchfrom)
if eomidx != -1:
log.info('received-msg', msg=msg[:eomidx])
return msg[:eomidx]
else:
log.error('no-message-end-indicators', msg=msg)
return msg
def _receive_11(self, msg):
# Message is received in the format "\n#{len}\n{msg}\n##\n"
# A message may have return characters within it
if msg:
log.info('received-msg-full', msg=msg)
msg = msg.split('\n')
if len(msg) > 2:
msg = ''.join(msg[2:(len(msg)-2)])
log.info('parsed-msg\n', msg=msg)
return msg
return None
def close_connection(self):
log.info('closing-connection')
self.exiting = True
self.transport.loseConnection()
def recv(self, predicate):
assert self.connected
return self.rx.get(predicate)
def recv_any(self, new_framing):
return self.recv(lambda _: True)
def recv_xid(self, xid):
return self.recv(lambda msg: msg.xid == xid)