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