Initial source code
[oam/tr069-adapter.git] / acs / cpe / src / main / java / org / commscope / tr069adapter / acs / cpe / CPEManagementService.java
diff --git a/acs/cpe/src/main/java/org/commscope/tr069adapter/acs/cpe/CPEManagementService.java b/acs/cpe/src/main/java/org/commscope/tr069adapter/acs/cpe/CPEManagementService.java
new file mode 100644 (file)
index 0000000..6f07e36
--- /dev/null
@@ -0,0 +1,577 @@
+/*\r
+ * ============LICENSE_START========================================================================\r
+ * ONAP : tr-069-adapter\r
+ * =================================================================================================\r
+ * Copyright (C) 2020 CommScope Inc Intellectual Property.\r
+ * =================================================================================================\r
+ * This tr-069-adapter software file is distributed by CommScope Inc under the Apache License,\r
+ * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You\r
+ * may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,\r
+ * either express or implied. See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ===============LICENSE_END=======================================================================\r
+ */\r
+\r
+package org.commscope.tr069adapter.acs.cpe;\r
+\r
+import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.ACS_SESSIONID;\r
+import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.CWMP_VERSION;\r
+import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.INFORM;\r
+import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.TRANSFER_COMPLETE;\r
+\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.InputStreamReader;\r
+import java.io.OutputStream;\r
+import java.io.OutputStreamWriter;\r
+import java.io.PipedInputStream;\r
+import java.io.PipedOutputStream;\r
+import java.io.Reader;\r
+import java.io.UnsupportedEncodingException;\r
+import java.io.Writer;\r
+import java.net.URLDecoder;\r
+import java.nio.charset.StandardCharsets;\r
+import java.util.Enumeration;\r
+\r
+import javax.servlet.http.Cookie;\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
+import javax.ws.rs.core.Context;\r
+import javax.xml.soap.MessageFactory;\r
+import javax.xml.soap.MimeHeaders;\r
+import javax.xml.soap.SOAPException;\r
+import javax.xml.soap.SOAPMessage;\r
+\r
+import org.commscope.tr069adapter.acs.common.exception.TR069EventProcessingException;\r
+import org.commscope.tr069adapter.acs.common.response.DeviceInformResponse;\r
+import org.commscope.tr069adapter.acs.common.utils.ErrorCode;\r
+import org.commscope.tr069adapter.acs.cpe.handler.DeviceEventHandler;\r
+import org.commscope.tr069adapter.acs.cpe.rpc.Fault;\r
+import org.commscope.tr069adapter.acs.cpe.rpc.Inform;\r
+import org.commscope.tr069adapter.acs.cpe.rpc.InformResponse;\r
+import org.commscope.tr069adapter.acs.cpe.rpc.TransferComplete;\r
+import org.commscope.tr069adapter.acs.cpe.rpc.TransferCompleteResponse;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.web.bind.annotation.PostMapping;\r
+import org.springframework.web.bind.annotation.RequestMapping;\r
+import org.springframework.web.bind.annotation.RestController;\r
+\r
+@RestController\r
+@RequestMapping("/CPEMgmt")\r
+public class CPEManagementService {\r
+\r
+  private static final String UTF_8 = "UTF-8";\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(CPEManagementService.class);\r
+\r
+  private static final int MY_MAX_ENVELOPES = 1;\r
+\r
+  @Autowired\r
+  DeviceEventHandler deviceEventHandler;\r
+\r
+  /**\r
+   * @param request\r
+   * @param response\r
+   */\r
+  @SuppressWarnings("static-access")\r
+  @PostMapping("/acs")\r
+  public void processDeviceEvent(@Context HttpServletRequest request,\r
+      @Context HttpServletResponse response) {\r
+\r
+    logger.debug("A device event occurred");\r
+    logHeaderElements(request.getHeaderNames());\r
+\r
+    try {\r
+      Boolean isEmptyCPERequest = true;\r
+      SOAPMessage soapMsg = null;\r
+      ByteArrayOutputStream out = new ByteArrayOutputStream();\r
+\r
+      String ct = request.getContentType();\r
+      int csix = -1;\r
+      String csFrom = "ISO-8859-1";\r
+      if (ct != null) {\r
+        csix = ct.indexOf("charset=");\r
+        response.setContentType(ct);\r
+      } else {\r
+        response.setContentType("text/xml;charset=UTF-8");\r
+      }\r
+\r
+      if (csix != -1)\r
+        csFrom = ct.substring(csix + 8).replaceAll("\"", "");\r
+\r
+      Cookie[] cookies = request.getCookies();\r
+      String acsSessionID = getACSSessionCookieData(cookies);\r
+      String cwmpVersion = getCWMPVersionCookieData(cookies);\r
+\r
+      XmlFilterInputStream f =\r
+          new XmlFilterInputStream(request.getInputStream(), request.getContentLength());\r
+      MessageFactory mf = getSOAPMessageFactory();\r
+      while (f.next()) {\r
+        isEmptyCPERequest = false;\r
+        MimeHeaders hdrs = new MimeHeaders();\r
+        hdrs.setHeader("Content-Type", "text/xml; charset=UTF-8");\r
+        InputStream in = getInputStream(csFrom, f);\r
+        soapMsg = mf.createMessage(hdrs, in);\r
+\r
+        logSoapMsg(soapMsg);\r
+\r
+        TR069RPC msg = null;\r
+        msg = TR069RPC.parse(soapMsg);\r
+\r
+        String reqType = getRequestType(msg);\r
+        logger.info("Event notified by the device is of type: {}", reqType);\r
+\r
+        if (reqType != null) {\r
+          if (reqType.equals(INFORM)) {\r
+            processDeviceInform(msg, request, response, out);\r
+          } else if (reqType.equals(TRANSFER_COMPLETE)) {\r
+            processTransferComplete(msg, response, out);\r
+          } else {\r
+            processOperationResult(msg, response, reqType, acsSessionID, out);\r
+          }\r
+        }\r
+      }\r
+\r
+      if (isEmptyCPERequest.booleanValue()) {\r
+        processEmptyCPERequest(response, cwmpVersion, acsSessionID, out);\r
+      }\r
+\r
+      if (out.size() < 1) {// To delete dm_sessionId cookie\r
+        clearCookies(cookies, response);\r
+      }\r
+\r
+      response.setContentLength(out.size());\r
+      response.setHeader("SOAPAction", "");\r
+      String sout = out.toString().trim();\r
+      logger.info(sout);\r
+      response.getOutputStream().print(sout);\r
+      response.getOutputStream().flush();\r
+      logger.debug("End of processing");\r
+\r
+    } catch (Exception e) {\r
+      logger.error("An error occurred while processing device event, Exception: {}",\r
+          e.getMessage());\r
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
+    }\r
+\r
+    logger.debug("End of processing the HTTP post request");\r
+  }\r
+\r
+  private InputStream getInputStream(String csFrom, XmlFilterInputStream f) throws IOException {\r
+    InputStream in = null;\r
+    if (csFrom.equalsIgnoreCase(UTF_8)) {\r
+      in = new XmlFilterNS(f);\r
+    } else {\r
+      in = new CharsetConverterInputStream(csFrom, UTF_8, new XmlFilterNS(f));\r
+    }\r
+    return in;\r
+  }\r
+\r
+  private void processDeviceInform(TR069RPC msg, HttpServletRequest request,\r
+      HttpServletResponse response, ByteArrayOutputStream out) throws IOException {\r
+    Inform inform = (Inform) msg;\r
+    DeviceInformResponse deviceInformResponse = null;\r
+    try {\r
+      deviceInformResponse =\r
+          deviceEventHandler.processDeviceInform(inform, request.getHeader("Authorization"));\r
+      Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());\r
+      Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion());\r
+      response.addCookie(cookie);\r
+      response.addCookie(cwmpVerCookie);\r
+    } catch (TR069EventProcessingException tr069ex) {\r
+      ErrorCode errorCode = tr069ex.getErrorCode();\r
+      if (ErrorCode.OUI_OR_PC_MISMATCH.equals(errorCode)) {\r
+        sendFault(response, out, Fault.FCODE_ACS_REQUEST_DENIED, "OUIandProductClassNotValid",\r
+            inform.getId());\r
+      } else {\r
+        int httpStatusCode = deviceEventHandler.handleException(tr069ex);\r
+        response.setStatus(httpStatusCode);\r
+      }\r
+      int httpStatusCode = deviceEventHandler.handleException(tr069ex);\r
+      response.setStatus(httpStatusCode);\r
+    }\r
+\r
+    InformResponse resp = new InformResponse(inform.getId(), MY_MAX_ENVELOPES);\r
+    resp.setCWMPVersion(msg.getCWMPVersion());\r
+    resp.writeTo(out);\r
+  }\r
+\r
+  private void processTransferComplete(TR069RPC msg, HttpServletResponse response,\r
+      ByteArrayOutputStream out) {\r
+    TransferComplete tc = (TransferComplete) msg;\r
+    try {\r
+      DeviceInformResponse deviceInformResponse = deviceEventHandler.processTransferComplete(tc);\r
+      Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());\r
+      Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion() + ";");\r
+      response.addCookie(cookie);\r
+      response.addCookie(cwmpVerCookie);\r
+    } catch (Exception e) {\r
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
+      return;\r
+    }\r
+    TransferCompleteResponse tr = new TransferCompleteResponse(tc.getId());\r
+    tr.setCWMPVersion(msg.getCWMPVersion());\r
+    tr.writeTo(out);\r
+  }\r
+\r
+  private void processOperationResult(TR069RPC msg, HttpServletResponse response, String reqType,\r
+      String acsSessionID, ByteArrayOutputStream out) {\r
+    logger.debug("Received Operation Result response {}", msg);\r
+    if (null == acsSessionID) {\r
+      logger.error("Received response without session ID, response: {}", reqType);\r
+    } else {\r
+      try {\r
+        TR069RPC message = deviceEventHandler.processRPCResponse(msg, acsSessionID);\r
+        if (null != message) {\r
+          message.setCWMPVersion(msg.getCWMPVersion());\r
+          message.writeTo(out);\r
+        } else {\r
+          response.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
+        }\r
+      } catch (TR069EventProcessingException tr069ex) {\r
+        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
+      }\r
+    }\r
+  }\r
+\r
+  private void processEmptyCPERequest(HttpServletResponse response, String cwmpVersion,\r
+      String acsSessionID, ByteArrayOutputStream out) {\r
+    if (null == acsSessionID) {\r
+      logger.error("Received empty response without session ID");\r
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
+      return;\r
+    }\r
+\r
+    try {\r
+      logger.info("Received Empty Device response");\r
+      TR069RPC message = deviceEventHandler.processEmptyRequest(acsSessionID);\r
+      if (null != message) {\r
+        message.setCWMPVersion(cwmpVersion);\r
+        message.writeTo(out);\r
+      } else {\r
+        response.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
+      }\r
+    } catch (TR069EventProcessingException tr069ex) {\r
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
+    }\r
+  }\r
+\r
+  private void clearCookies(Cookie[] cookies, HttpServletResponse response) {\r
+    Cookie cookieToDelete = null;\r
+    if (null != cookies) {\r
+      logger.debug("Clearing the cookies");\r
+      for (int i = 0; i < cookies.length; i++) {\r
+        if (cookies[i].getName().equals(ACS_SESSIONID)\r
+            || cookies[i].getName().equals(CWMP_VERSION)) {\r
+          cookieToDelete = cookies[i];\r
+          cookieToDelete.setMaxAge(0);\r
+          response.addCookie(cookieToDelete);\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  private static class XmlFilterInputStream extends InputStream {\r
+\r
+    private InputStream istream;\r
+    private int lvl;\r
+    private int lastchar;\r
+    @SuppressWarnings("unused")\r
+    private int len;\r
+    private int nextchar;\r
+    private boolean intag = false;\r
+    private StringBuilder buff = new StringBuilder(16);\r
+\r
+    /** Creates a new instance of xmlFilterInputStream */\r
+    public XmlFilterInputStream(InputStream is, int l) {\r
+      len = l;\r
+      istream = is;\r
+    }\r
+\r
+    @Override\r
+    public int read() throws IOException {\r
+      if (lastchar == '>' && lvl == 0) {\r
+        return -1;\r
+      }\r
+      int l = lastchar;\r
+      if (!readLastChar())\r
+        return lastchar;\r
+\r
+      if (!intag && lastchar == '&') {\r
+        int amppos = buff.length();\r
+        updateBuffer();\r
+        String s = buff.substring(amppos);\r
+        replaceSpecialChars(s, amppos);\r
+        return read();\r
+      }\r
+\r
+      if (l == '<') {\r
+        intag = true;\r
+        if (lastchar == '/') {\r
+          lvl--;\r
+        } else {\r
+          lvl++;\r
+        }\r
+      }\r
+\r
+      len--;\r
+      return lastchar;\r
+    }\r
+\r
+    public boolean next() throws IOException {\r
+      if ((nextchar = istream.read()) == -1) {\r
+        logger.debug("Next char is {}", nextchar);\r
+        lvl = 0;\r
+        lastchar = 0;\r
+      }\r
+      return (nextchar != -1);\r
+    }\r
+\r
+    private boolean readLastChar() throws IOException {\r
+      if (nextchar != -1) {\r
+        lastchar = nextchar;\r
+        nextchar = -1;\r
+      } else {\r
+        if (buff.length() > 0) {\r
+          lastchar = buff.charAt(0);\r
+          buff.deleteCharAt(0);\r
+          return false;\r
+        } else {\r
+          lastchar = istream.read();\r
+        }\r
+      }\r
+\r
+      if (lastchar == '<') {\r
+        intag = true;\r
+      } else if (lastchar == '>') {\r
+        intag = false;\r
+      }\r
+\r
+      return true;\r
+    }\r
+\r
+    private void updateBuffer() throws IOException {\r
+      // fix up broken xml not encoding &\r
+      buff.append((char) lastchar);\r
+      for (int c = 0; c < 10; c++) {\r
+        int ch = istream.read();\r
+        boolean breakLoop = false;\r
+        if (ch == -1) {\r
+          breakLoop = true;\r
+        }\r
+        if (ch == '&') {\r
+          nextchar = ch;\r
+          breakLoop = true;\r
+        }\r
+        if (breakLoop)\r
+          break;\r
+\r
+        buff.append((char) ch);\r
+      }\r
+    }\r
+\r
+    private void replaceSpecialChars(String s, int amppos) {\r
+      if (!s.startsWith("&amp;") && !s.startsWith("&lt;") && !s.startsWith("&gt;")\r
+          && !s.startsWith("&apos;") && !s.startsWith("&quot;") && !s.startsWith("&#")) {\r
+        buff.replace(amppos, amppos + 1, "&amp;");\r
+      }\r
+    }\r
+  }\r
+\r
+  private static class XmlFilterNS extends InputStream {\r
+    // Dumb class to filter out declaration of default xmlns\r
+\r
+    private String pat = "xmlns=\"urn:dslforum-org:cwmp-1-0\"";\r
+    private String pat2 = "xmlns=\"urn:dslforum-org:cwmp-1-1\"";\r
+    private int length = 0;\r
+    private int pos = 0;\r
+    private boolean f = false;\r
+    private byte[] buff = new byte[1024];\r
+    private InputStream is;\r
+\r
+    @Override\r
+    public int read() throws IOException {\r
+      if (!f) {\r
+        length = is.read(buff);\r
+        if (length < buff.length) {\r
+          byte[] b2 = new byte[length];\r
+          System.arraycopy(buff, 0, b2, 0, length);\r
+          buff = b2;\r
+        }\r
+\r
+        String b = new String(buff, StandardCharsets.UTF_8);\r
+        b = b.replace(pat, "");\r
+        b = b.replace(pat2, "");\r
+        buff = b.getBytes(StandardCharsets.UTF_8);\r
+        length = buff.length;\r
+        f = true;\r
+      }\r
+\r
+      if (pos < length) {\r
+        return buff[pos++] & 0xFF;\r
+      }\r
+      return is.read();\r
+    }\r
+\r
+    public XmlFilterNS(InputStream is) {\r
+      this.is = is;\r
+    }\r
+  }\r
+\r
+  private static class CharsetConverterInputStream extends InputStream {\r
+\r
+    @SuppressWarnings("unused")\r
+    private InputStream in;\r
+    private PipedInputStream pipein;\r
+    private OutputStream pipeout;\r
+    private Reader r;\r
+    private Writer w;\r
+\r
+    public CharsetConverterInputStream(String csFrom, String csTo, InputStream in)\r
+        throws IOException {\r
+      this.in = in;\r
+      r = new InputStreamReader(in, csFrom);\r
+      pipein = new PipedInputStream();\r
+      pipeout = new PipedOutputStream(pipein);\r
+      w = new OutputStreamWriter(pipeout, csTo);\r
+    }\r
+\r
+    @Override\r
+    public int read() throws IOException {\r
+      if (pipein.available() > 0) {\r
+        return pipein.read();\r
+      }\r
+      int c = r.read();\r
+      if (c == -1) {\r
+        return -1;\r
+      }\r
+      w.write(c);\r
+      w.flush();\r
+      return pipein.read();\r
+    }\r
+  }\r
+\r
+  /**\r
+   * @return\r
+   * @throws Exception\r
+   */\r
+  private MessageFactory getSOAPMessageFactory() throws SOAPException {\r
+    MessageFactory mf = null;\r
+    mf = MessageFactory.newInstance();\r
+    return mf;\r
+  }\r
+\r
+  /**\r
+   * @param cookies\r
+   * @return\r
+   */\r
+  private String getACSSessionCookieData(Cookie[] cookies) {\r
+    String acsSessionID = null;\r
+    if (null != cookies) {\r
+      for (Cookie cookie : cookies) {\r
+        if (cookie.getName().equals(ACS_SESSIONID)) {\r
+          acsSessionID = cookie.getValue();\r
+          logger.debug("The session id is {}", acsSessionID);\r
+        }\r
+      }\r
+    }\r
+    return acsSessionID;\r
+  }\r
+\r
+  /**\r
+   * @param cookies\r
+   * @return\r
+   * @throws TR069EventProcessingException\r
+   */\r
+  private String getCWMPVersionCookieData(Cookie[] cookies) throws TR069EventProcessingException {\r
+    String cwmpVersion = null;\r
+    try {\r
+      if (null != cookies) {\r
+        for (Cookie cookie : cookies) {\r
+          if (cookie.getName().equals(CWMP_VERSION)) {\r
+            cwmpVersion = cookie.getValue();\r
+            if (cwmpVersion != null) {\r
+              cwmpVersion = URLDecoder.decode(cwmpVersion, StandardCharsets.UTF_8.name());\r
+            }\r
+            logger.debug("The CWMP version supported by the device is: {}", cwmpVersion);\r
+          }\r
+        }\r
+      }\r
+    } catch (UnsupportedEncodingException e) {\r
+      logger.error(e.getMessage());\r
+      TR069EventProcessingException ex = new TR069EventProcessingException(\r
+          ErrorCode.UNSUPPORTED_CHARACTER_ENCODING, StandardCharsets.UTF_8.name());\r
+      logger.error(ex.getMessage());\r
+      throw ex;\r
+    }\r
+    return cwmpVersion;\r
+  }\r
+\r
+  /**\r
+   * @param soapMsg\r
+   */\r
+  private void logSoapMsg(SOAPMessage soapMsg) {\r
+    StringBuilder buffer = new StringBuilder();\r
+    buffer.append(soapMsg.toString());\r
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
+    try {\r
+      soapMsg.writeTo(baos);\r
+    } catch (SOAPException | IOException e) {\r
+      logger.error("Error while writting soap message");\r
+    }\r
+    buffer.append(baos);\r
+    String soapMessage = buffer.toString();\r
+    logger.debug(soapMessage);\r
+  }\r
+\r
+  /**\r
+   * @param response\r
+   * @param out\r
+   * @param fcodeAcsRequestDenied\r
+   * @param faultString\r
+   * @param id\r
+   * @throws IOException\r
+   */\r
+  private void sendFault(HttpServletResponse response, ByteArrayOutputStream out,\r
+      String fcodeAcsRequestDenied, String faultString, String id) throws IOException {\r
+    Fault fault = new Fault(fcodeAcsRequestDenied, faultString, id);\r
+    fault.writeTo(out);\r
+    response.setContentLength(out.size());\r
+    String sout = out.toString(UTF_8);\r
+    sout = sout.replace('\'', '"');\r
+    response.getOutputStream().print(sout);\r
+  }\r
+\r
+  private void logHeaderElements(Enumeration<String> headerName) {\r
+    while (headerName.hasMoreElements()) {\r
+      String requestHeader = headerName.nextElement();\r
+      logger.debug("Request Headers {}", requestHeader);\r
+    }\r
+  }\r
+\r
+  private String getRequestType(TR069RPC msg) {\r
+    String requestType = msg.getName();\r
+    if (requestType == null)\r
+      requestType = "Empty Request";\r
+\r
+    return requestType;\r
+  }\r
+\r
+  /******************************************************************************************************************/\r
+\r
+  public DeviceEventHandler getDeviceEventHandler() {\r
+    return deviceEventHandler;\r
+  }\r
+\r
+  public void setDeviceEventHandler(DeviceEventHandler deviceEventHandler) {\r
+    this.deviceEventHandler = deviceEventHandler;\r
+  }\r
+\r
+}\r