1) Handles chuncked xml requests from Netconf client, 2) Handles nested xml parameters from Netconf client, 3) Handles all currently exposed rpcs

Change-Id: Id478f355ab53afc876c4f5fd9ad8f1a048a50748
diff --git a/netconf/constants.py b/netconf/constants.py
index b45363f..704e1f8 100644
--- a/netconf/constants.py
+++ b/netconf/constants.py
@@ -107,6 +107,7 @@
     MESSAGE_ID = "message-id"
     XMLNS = "xmlns"
     DELIMITER = "]]>]]>"
+    DELIMITER_1_1 ="##"
 
     NS_MAP = {
         'nc': 'urn:ietf:params:xml:ns:netconf:base:1.0',
diff --git a/netconf/grpc_client/grpc_client.py b/netconf/grpc_client/grpc_client.py
index cc52a27..bcfb972 100644
--- a/netconf/grpc_client/grpc_client.py
+++ b/netconf/grpc_client/grpc_client.py
@@ -306,15 +306,13 @@
             # Get the XML tag to use in the response
             xml_tag = mapper.get_xml_tag(service, method)
 
-            # Get the XML list item name used in the response
-            list_item_name = mapper.get_list_items_name(service, method)
+            # # Get the XML list item name used in the response
+            # list_item_name = mapper.get_list_items_name(service, method)
 
             # Get the YANG defined fields (and their order) for that service
             # and method
             fields = mapper.get_fields_from_yang_defs(service, method)
 
-            # TODO: This needs to be investigated further since the Netconf
-            # Client shows a formatting error in the code below is uncommented.
             # Check if this represents a List and whether the field name is
             # items.  In the response (a dictionary), if a list named 'items'
             # is returned then 'items' can either:
@@ -324,10 +322,11 @@
             # 2) represent the actual field name as defined in the proto
             # definitions.  If this is the case then we need to preserve the
             # name
-            # list_item_name = ''
-            # if len(fields) == 1:
-            #     if fields[0]['name'] == 'items':
-            #         list_item_name = 'items'
+            list_item_name = ''
+            if fields: # if the return type is empty then fields will be None
+                if len(fields) == 1:
+                    if fields[0]['name'] == 'items':
+                        list_item_name = 'items'
 
             # Rearrange the dictionary response as specified by the YANG
             # definitions
diff --git a/netconf/nc_rpc/ext/voltha_rpc.py b/netconf/nc_rpc/ext/voltha_rpc.py
index 835f900..9d4bf33 100644
--- a/netconf/nc_rpc/ext/voltha_rpc.py
+++ b/netconf/nc_rpc/ext/voltha_rpc.py
@@ -83,17 +83,29 @@
                     self.request_xml)
                 return
 
+    # Helper function to parse a complex xml into a dict
+    def _parse_params(self, node):
+        params = {}
+        if node is not None:
+            for r in node:
+                children = r.getchildren()
+                if children:
+                    params[ r.tag.replace(qmap(C.VOLTHA), "")] = \
+                        self._parse_params(children)
+                else:
+                    params[
+                        r.tag.replace(qmap(C.VOLTHA), "")] = r.text
+        return params
+
     def _extract_parameters(self):
         try:
             rpc_node = self.request_xml.find(''.join(
                 [qmap(C.VOLTHA),
                  self.request['command']])
             )
-            self.request['params'] = {}
-            if rpc_node is not None:
-                for r in rpc_node:
-                    self.request['params'][
-                        r.tag.replace(qmap(C.VOLTHA), "")] = r.text
+
+            # Parse rpc the parameters
+            self.request['params'] = self._parse_params(rpc_node)
 
             # Remove the subclass element in the request if it is present as
             # it is not required for rpc calls
diff --git a/netconf/nc_rpc/rpc_response.py b/netconf/nc_rpc/rpc_response.py
index 19e01e2..aced3f9 100644
--- a/netconf/nc_rpc/rpc_response.py
+++ b/netconf/nc_rpc/rpc_response.py
@@ -131,11 +131,7 @@
         if (attrib == 'list'):
             if list(elem) is None:
                 return self.copy_basic_element(elem)
-                # if elem.tag == 'items':
-                # new_elem = etree.Element('items')
             new_elem = etree.Element('ignore')
-            # else:
-            # new_elem = etree.Element('ignore')
             for elm in list(elem):
                 elm.tag = elem.tag
                 if elm.get('type') in ['list', 'dict']:
@@ -171,25 +167,31 @@
         elms = list(from_xml)
         xml_tag = yang_options[0]
         list_items_name = yang_options[1]
-        # special case the xml contain a list type
+        # Handle the special case when the the xml contains 1 element which
+        # is a list type
         if len(elms) == 1:
             item = elms[0]
             if item.get('type') == 'list':
-                if list_items_name == 'items':
-                    # Create a new parent element
-                    new_elem = etree.Element(xml_tag)
-                    self.add_node(self.process_element(item), new_elem)
-                    top.append(new_elem)
-                else:
-                    if xml_tag and custom_rpc:
+                if custom_rpc:  # custom rpc request
+                    if list_items_name == 'items': # no pre-processing required
+                        self.add_node(self.process_element(item), top)
+                        return top
+                    if xml_tag:
                         item.tag = xml_tag
-                    elif request.has_key('subclass'):
+                    else:
+                        item.tag = 'ignore'
+                else:
+                    # Default netconf operations - may end up needing
+                    # specific parsing per request type
+                    if request.has_key('subclass'):
                         item.tag = request['subclass']
                         # remove the subclass element in request to avoid duplicate tag
                         del request['subclass']
+                    elif list_items_name == 'items':
+                        item.tag = xml_tag
                     else:
                         item.tag = 'ignore'
-                    self.add_node(self.process_element(item), top)
+                self.add_node(self.process_element(item), top)
                 return top
 
         # Process normally for all other cases
diff --git a/netconf/session/nc_connection.py b/netconf/session/nc_connection.py
index 9043584..b0272a0 100644
--- a/netconf/session/nc_connection.py
+++ b/netconf/session/nc_connection.py
@@ -20,11 +20,13 @@
 from twisted.internet.defer import inlineCallbacks, returnValue
 from common.utils.message_queue import MessageQueue
 from netconf.constants import Constants as C
+import re
 
 log = structlog.get_logger()
 
 MAXSSHBUF = C.MAXSSHBUF
 
+
 class NetconfConnection(protocol.Protocol):
     def __init__(self, data=None, avatar=None, max_chunk=MAXSSHBUF):
         self.avatar = avatar
@@ -47,7 +49,7 @@
 
     def dataReceived(self, data):
         log.debug('data-received', len=len(data),
-                 received=hexdump(data, result='return'))
+                  received=hexdump(data, result='return'))
         assert len(data)
         self.rx.put(data)
 
@@ -85,32 +87,52 @@
         assert self.connected
         msg = yield self.recv(lambda _: True)
         if new_framing:
-            returnValue(self._receive_11(msg))
+            response = yield self._receive_11(msg)
         else:
-            returnValue(self._receive_10(msg))
+            response = yield self._receive_10(msg)
+        returnValue(response)
 
+    @inlineCallbacks
     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
+        partial_msgs = []
+        while 1:
+            eomidx = msg.find(C.DELIMITER, searchfrom)
+            if eomidx != -1:
+                partial_msgs.append(msg[:eomidx])
+                full_msg = ''.join(partial_msgs)
+                log.info('full-msg-received', msg=full_msg)
+                returnValue(full_msg)
+            else:
+                partial_msgs.append(msg)
+                log.debug('partial-msg-received', msg=msg)
+                msg = yield self.recv(lambda _: True)
 
+    @inlineCallbacks
     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)
+        # A message can be received in chunks where each chunk is formatted as:
+        # /\n#[len]\n
+        # \n##\n
+        # msg\n
+        # msg
+        # \n
+        # \nmsg\n
+        partial_msgs = []
+        while 1:
+            log.info('received-msg', msg=msg)
+            # Remove all reference to length if any, i.e any '#len'
+            msg = re.sub(r'#[0-9]+', "", 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
+            if C.DELIMITER_1_1 in msg:  # The '##' is the second last ref
+                partial_msgs.append(''.join(msg[0:(len(msg) - 2)]))
+                full_msg = ''.join(partial_msgs)
+                log.debug('full-msg-received', msg=full_msg)
+                returnValue(full_msg)
+            else:
+                partial_msgs.append(''.join(msg))
+                log.debug('partial-msg-received', msg=msg)
+            msg = yield self.recv(lambda _: True)
 
     def close_connection(self):
         log.info('closing-connection')