Init commit for standalone enodebd
Change-Id: I88eeef5135dd7ba8551ddd9fb6a0695f5325337b
diff --git a/tr069/spyne_mods.py b/tr069/spyne_mods.py
new file mode 100644
index 0000000..0f0e918
--- /dev/null
+++ b/tr069/spyne_mods.py
@@ -0,0 +1,155 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+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.
+
+This file contains modifications of the core spyne functionality. This is done
+using child classes and function override to avoid modifying spyne code itself.
+Each function below is a modified version of the parent function. These
+modifications are required because:
+1) Spyne is not fully python3-compliant
+2) Not all parts of the TR-069 spec are possible through spyne APIs (e.g RPC
+ calls from server to client in HTTP responses)
+3) Minor enhancements for debug-ability
+"""
+
+from lxml import etree
+from logger import EnodebdLogger as logger
+from spyne.application import Application
+from spyne.interface._base import Interface
+from spyne.protocol.soap import Soap11
+from spyne.protocol.xml import XmlDocument
+
+
+class Tr069Interface(Interface):
+ """ Modified base interface class. """
+
+ def reset_interface(self):
+ super(Tr069Interface, self).reset_interface()
+ # Replace default namespace prefix (may not strictly be
+ # required, but makes it easier to debug)
+ del self.nsmap['tns']
+ self.nsmap['cwmp'] = self.get_tns()
+ self.prefmap[self.get_tns()] = 'cwmp'
+ # To validate against the xsd:<types>, the namespace
+ # prefix is expected to be the same
+ del self.nsmap['xs']
+ self.nsmap['xsd'] = 'http://www.w3.org/2001/XMLSchema'
+ self.prefmap['http://www.w3.org/2001/XMLSchema'] = 'xsd'
+
+
+class Tr069Application(Application):
+ """ Modified spyne application. """
+
+ def __init__(
+ self, services, tns, name=None, in_protocol=None,
+ out_protocol=None, config=None,
+ ):
+ super(Tr069Application, self).__init__(
+ services, tns, name, in_protocol, out_protocol, config,
+ )
+ # Use modified interface class
+ self.interface = Tr069Interface(self)
+
+
+class Tr069Soap11(Soap11):
+ """ Modified SOAP protocol. """
+
+ def __init__(self, *args, **kwargs):
+ super(Tr069Soap11, self).__init__(*args, **kwargs)
+ # Disabling type resolution as a workaround for
+ # https://github.com/arskom/spyne/issues/567
+ self.parse_xsi_type = False
+ # Bug in spyne is cleaning up the default XSD namespace
+ # and causes validation issues on TR-069 clients
+ self.cleanup_namespaces = False
+
+ def create_in_document(self, ctx, charset=None):
+ """
+ In TR-069, the ACS (e.g Magma) is an HTTP server, but acts as a client
+ for SOAP messages. This is done by the CPE (e.g ENodeB) sending an
+ empty HTTP request, and the ACS responding with a SOAP request in the
+ HTTP response. This code replaces an empty HTTP request with a string
+ that gets decoded to a call to the 'EmptyHttp' RPC .
+ """
+
+ # Try cp437 as default to ensure that we dont get any decoding errors,
+ # since it uses 1-byte encoding and has a 'full' char map
+ if not charset:
+ charset = 'cp437'
+
+ # Convert from generator to bytes before doing comparison
+ # Re-encode to chosen charset to remove invalid characters
+ in_string = b''.join(ctx.in_string).decode(charset, 'ignore')
+ ctx.in_string = [in_string.encode(charset, 'ignore')]
+ if ctx.in_string == [b'']:
+ ctx.in_string = [
+ b'<soap11env:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/">/n'
+ b' <soap11env:Body>/n'
+ b' <cwmp:EmptyHttp/>/n'
+ b' </soap11env:Body>/n'
+ b'</soap11env:Envelope>',
+ ]
+
+ super(Tr069Soap11, self).create_in_document(ctx, charset)
+
+ def decompose_incoming_envelope(self, ctx, message=XmlDocument.REQUEST):
+ """
+ For TR-069, the SOAP fault message (CPE->ACS) contains useful
+ information, and should not result in another fault response (ACS->CPE).
+ Strip the outer SOAP fault structure, so that the CWMP fault structure
+ is treated as a normal RPC call (to the 'Fault' function).
+ """
+ super(Tr069Soap11, self).decompose_incoming_envelope(ctx, message)
+
+ if ctx.in_body_doc.tag == '{%s}Fault' % self.ns_soap_env:
+ faultstring = ctx.in_body_doc.findtext('faultstring')
+ if not faultstring or 'CWMP fault' not in faultstring:
+ # Not a CWMP fault
+ return
+
+ # Strip SOAP fault structure, leaving inner CWMP fault structure
+ detail_elem = ctx.in_body_doc.find('detail')
+ if detail_elem is not None:
+ detail_children = list(detail_elem)
+ if len(detail_children):
+ if len(detail_children) > 1:
+ logger.warning(
+ "Multiple detail elements found in SOAP"
+ " fault - using first one",
+ )
+ ctx.in_body_doc = detail_children[0]
+ ctx.method_request_string = ctx.in_body_doc.tag
+ self.validate_body(ctx, message)
+
+ def get_call_handles(self, ctx):
+ """
+ Modified function to fix bug in receiving SOAP fault. In this case,
+ ctx.method_request_string is None, so 'startswith' errors out.
+ """
+ if ctx.method_request_string is None:
+ return []
+
+ return super(Tr069Soap11, self).get_call_handles(ctx)
+
+ def serialize(self, ctx, message):
+ # Workaround for issue https://github.com/magma/magma/issues/7869
+ # Updates to ctx.descriptor.out_message.Attributes.sub_name are taking
+ # effect on the descriptor. But when puled from _attrcache dictionary,
+ # it still has a stale value.
+ # Force repopulation of dictionary by deleting entry
+ # TODO Remove this code once we have a better fix
+ if (ctx.descriptor and ctx.descriptor.out_message in self._attrcache):
+ del self._attrcache[ctx.descriptor.out_message] # noqa: WPS529
+
+ super(Tr069Soap11, self).serialize(ctx, message)
+
+ # Keep XSD namespace
+ etree.cleanup_namespaces(ctx.out_document, keep_ns_prefixes=['xsd'])