2 * ============LICENSE_START========================================================================
3 * ONAP : tr-069-adapter
4 * =================================================================================================
5 * Copyright (C) 2020 CommScope Inc Intellectual Property.
6 * =================================================================================================
7 * This tr-069-adapter software file is distributed by CommScope Inc under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
9 * may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
14 * either express or implied. See the License for the specific language governing permissions and
15 * limitations under the License.
16 * ===============LICENSE_END=======================================================================
19 package org.commscope.tr069adapter.acs.cpe;
21 import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.ACS_SESSIONID;
22 import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.CWMP_VERSION;
23 import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.INFORM;
24 import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.TRANSFER_COMPLETE;
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.InputStreamReader;
30 import java.io.OutputStream;
31 import java.io.OutputStreamWriter;
32 import java.io.PipedInputStream;
33 import java.io.PipedOutputStream;
34 import java.io.Reader;
35 import java.io.UnsupportedEncodingException;
36 import java.io.Writer;
37 import java.net.URLDecoder;
38 import java.nio.charset.StandardCharsets;
39 import java.util.Enumeration;
41 import javax.servlet.http.Cookie;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpServletResponse;
44 import javax.ws.rs.core.Context;
45 import javax.xml.soap.MessageFactory;
46 import javax.xml.soap.MimeHeaders;
47 import javax.xml.soap.SOAPException;
48 import javax.xml.soap.SOAPMessage;
50 import org.commscope.tr069adapter.acs.common.exception.TR069EventProcessingException;
51 import org.commscope.tr069adapter.acs.common.response.DeviceInformResponse;
52 import org.commscope.tr069adapter.acs.common.utils.ErrorCode;
53 import org.commscope.tr069adapter.acs.cpe.handler.DeviceEventHandler;
54 import org.commscope.tr069adapter.acs.cpe.rpc.Fault;
55 import org.commscope.tr069adapter.acs.cpe.rpc.Inform;
56 import org.commscope.tr069adapter.acs.cpe.rpc.InformResponse;
57 import org.commscope.tr069adapter.acs.cpe.rpc.TransferComplete;
58 import org.commscope.tr069adapter.acs.cpe.rpc.TransferCompleteResponse;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.springframework.beans.factory.annotation.Autowired;
62 import org.springframework.web.bind.annotation.PostMapping;
63 import org.springframework.web.bind.annotation.RequestMapping;
64 import org.springframework.web.bind.annotation.RestController;
67 @RequestMapping("/CPEMgmt")
68 public class CPEManagementService {
70 private static final String UTF_8 = "UTF-8";
72 private static final Logger logger = LoggerFactory.getLogger(CPEManagementService.class);
74 private static final int MY_MAX_ENVELOPES = 1;
77 DeviceEventHandler deviceEventHandler;
83 @SuppressWarnings("static-access")
85 public void processDeviceEvent(@Context HttpServletRequest request,
86 @Context HttpServletResponse response) {
88 logger.debug("A device event occurred");
89 logHeaderElements(request.getHeaderNames());
92 Boolean isEmptyCPERequest = true;
93 SOAPMessage soapMsg = null;
94 ByteArrayOutputStream out = new ByteArrayOutputStream();
96 String ct = request.getContentType();
98 String csFrom = "ISO-8859-1";
100 csix = ct.indexOf("charset=");
101 response.setContentType(ct);
103 response.setContentType("text/xml;charset=UTF-8");
107 csFrom = ct.substring(csix + 8).replaceAll("\"", "");
109 Cookie[] cookies = request.getCookies();
110 String acsSessionID = getACSSessionCookieData(cookies);
111 String cwmpVersion = getCWMPVersionCookieData(cookies);
113 XmlFilterInputStream f =
114 new XmlFilterInputStream(request.getInputStream(), request.getContentLength());
115 MessageFactory mf = getSOAPMessageFactory();
117 isEmptyCPERequest = false;
118 MimeHeaders hdrs = new MimeHeaders();
119 hdrs.setHeader("Content-Type", "text/xml; charset=UTF-8");
120 InputStream in = getInputStream(csFrom, f);
121 soapMsg = mf.createMessage(hdrs, in);
126 msg = TR069RPC.parse(soapMsg);
128 String reqType = getRequestType(msg);
129 logger.info("Event notified by the device is of type: {}", reqType);
131 if (reqType != null) {
132 if (reqType.equals(INFORM)) {
133 processDeviceInform(msg, request, response, out);
134 } else if (reqType.equals(TRANSFER_COMPLETE)) {
135 processTransferComplete(msg, request, response, out);
137 processOperationResult(msg, response, reqType, acsSessionID, out);
142 if (isEmptyCPERequest.booleanValue()) {
143 processEmptyCPERequest(response, cwmpVersion, acsSessionID, out);
146 if (out.size() < 1) {// To delete dm_sessionId cookie
147 clearCookies(cookies, response);
150 response.setContentLength(out.size());
151 response.setHeader("SOAPAction", "");
152 String sout = out.toString().trim();
154 response.getOutputStream().print(sout);
155 response.getOutputStream().flush();
156 logger.debug("End of processing");
158 } catch (Exception e) {
159 logger.error("An error occurred while processing device event, Exception: {}",
161 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
164 logger.debug("End of processing the HTTP post request");
167 private InputStream getInputStream(String csFrom, XmlFilterInputStream f) throws IOException {
168 InputStream in = null;
169 if (csFrom.equalsIgnoreCase(UTF_8)) {
170 in = new XmlFilterNS(f);
172 in = new CharsetConverterInputStream(csFrom, UTF_8, new XmlFilterNS(f));
177 private void processDeviceInform(TR069RPC msg, HttpServletRequest request,
178 HttpServletResponse response, ByteArrayOutputStream out) throws IOException {
179 Inform inform = (Inform) msg;
180 DeviceInformResponse deviceInformResponse = null;
182 deviceInformResponse =
183 deviceEventHandler.processDeviceInform(inform, request.getHeader("Authorization"));
184 Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());
185 cookie.setSecure(request.isSecure());
186 cookie.setHttpOnly(true);
187 Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion());
188 cwmpVerCookie.setSecure(request.isSecure());
189 cwmpVerCookie.setHttpOnly(true);
190 response.addCookie(cookie);
191 response.addCookie(cwmpVerCookie);
192 } catch (TR069EventProcessingException tr069ex) {
193 ErrorCode errorCode = tr069ex.getErrorCode();
194 if (ErrorCode.OUI_OR_PC_MISMATCH.equals(errorCode)) {
195 sendFault(response, out, Fault.FCODE_ACS_REQUEST_DENIED, "OUIandProductClassNotValid",
198 int httpStatusCode = deviceEventHandler.handleException(tr069ex);
199 response.setStatus(httpStatusCode);
201 int httpStatusCode = deviceEventHandler.handleException(tr069ex);
202 response.setStatus(httpStatusCode);
205 InformResponse resp = new InformResponse(inform.getId(), MY_MAX_ENVELOPES);
206 resp.setCWMPVersion(msg.getCWMPVersion());
210 private void processTransferComplete(TR069RPC msg, HttpServletRequest request,
211 HttpServletResponse response, ByteArrayOutputStream out) {
212 TransferComplete tc = (TransferComplete) msg;
214 DeviceInformResponse deviceInformResponse = deviceEventHandler.processTransferComplete(tc);
215 Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());
216 cookie.setSecure(request.isSecure());
217 cookie.setHttpOnly(true);
218 Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion() + ";");
219 cwmpVerCookie.setSecure(request.isSecure());
220 cwmpVerCookie.setHttpOnly(true);
221 response.addCookie(cookie);
222 response.addCookie(cwmpVerCookie);
223 } catch (Exception e) {
224 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
227 TransferCompleteResponse tr = new TransferCompleteResponse(tc.getId());
228 tr.setCWMPVersion(msg.getCWMPVersion());
232 private void processOperationResult(TR069RPC msg, HttpServletResponse response, String reqType,
233 String acsSessionID, ByteArrayOutputStream out) {
234 logger.debug("Received Operation Result response {}", msg);
235 if (null == acsSessionID) {
236 logger.error("Received response without session ID, response: {}", reqType);
239 TR069RPC message = deviceEventHandler.processRPCResponse(msg, acsSessionID);
240 if (null != message) {
241 message.setCWMPVersion(msg.getCWMPVersion());
242 message.writeTo(out);
244 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
246 } catch (TR069EventProcessingException tr069ex) {
247 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
252 private void processEmptyCPERequest(HttpServletResponse response, String cwmpVersion,
253 String acsSessionID, ByteArrayOutputStream out) {
254 if (null == acsSessionID) {
255 logger.error("Received empty response without session ID");
256 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
261 logger.info("Received Empty Device response");
262 TR069RPC message = deviceEventHandler.processEmptyRequest(acsSessionID);
263 if (null != message) {
264 message.setCWMPVersion(cwmpVersion);
265 message.writeTo(out);
267 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
269 } catch (TR069EventProcessingException tr069ex) {
270 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
274 private void clearCookies(Cookie[] cookies, HttpServletResponse response) {
275 Cookie cookieToDelete = null;
276 if (null != cookies) {
277 logger.debug("Clearing the cookies");
278 for (int i = 0; i < cookies.length; i++) {
279 if (cookies[i].getName().equals(ACS_SESSIONID)
280 || cookies[i].getName().equals(CWMP_VERSION)) {
281 cookieToDelete = cookies[i];
282 cookieToDelete.setMaxAge(0);
283 response.addCookie(cookieToDelete);
289 private static class XmlFilterInputStream extends InputStream {
291 private InputStream istream;
293 private int lastchar;
294 @SuppressWarnings("unused")
296 private int nextchar;
297 private boolean intag = false;
298 private StringBuilder buff = new StringBuilder(16);
300 /** Creates a new instance of xmlFilterInputStream */
301 public XmlFilterInputStream(InputStream is, int l) {
308 public int read() throws IOException {
309 if (lastchar == '>' && lvl == 0) {
316 if (!intag && lastchar == '&') {
317 int amppos = buff.length();
319 String s = buff.substring(amppos);
320 replaceSpecialChars(s, amppos);
326 if (lastchar == '/') {
337 public boolean next() throws IOException {
338 if ((nextchar = istream.read()) == -1) {
339 logger.debug("Next char is {}", nextchar);
343 return (nextchar != -1);
346 private boolean readLastChar() throws IOException {
347 if (nextchar != -1) {
351 if (buff.length() > 0) {
352 lastchar = buff.charAt(0);
353 buff.deleteCharAt(0);
356 lastchar = istream.read();
360 if (lastchar == '<') {
362 } else if (lastchar == '>') {
369 private void updateBuffer() throws IOException {
370 // fix up broken xml not encoding &
371 buff.append((char) lastchar);
372 for (int c = 0; c < 10; c++) {
373 int ch = istream.read();
374 boolean breakLoop = false;
385 buff.append((char) ch);
389 private void replaceSpecialChars(String s, int amppos) {
390 if (!s.startsWith("&") && !s.startsWith("<") && !s.startsWith(">")
391 && !s.startsWith("'") && !s.startsWith(""") && !s.startsWith("&#")) {
392 buff.replace(amppos, amppos + 1, "&");
397 private static class XmlFilterNS extends InputStream {
398 // Dumb class to filter out declaration of default xmlns
400 private String pat = "xmlns=\"urn:dslforum-org:cwmp-1-0\"";
401 private String pat2 = "xmlns=\"urn:dslforum-org:cwmp-1-1\"";
402 private int length = 0;
404 private boolean f = false;
405 private byte[] buff = new byte[1024];
406 private InputStream is;
409 public int read() throws IOException {
411 length = is.read(buff);
412 if (length < buff.length) {
413 byte[] b2 = new byte[length];
414 System.arraycopy(buff, 0, b2, 0, length);
418 String b = new String(buff, StandardCharsets.UTF_8);
419 b = b.replace(pat, "");
420 b = b.replace(pat2, "");
421 buff = b.getBytes(StandardCharsets.UTF_8);
422 length = buff.length;
427 return buff[pos++] & 0xFF;
432 public XmlFilterNS(InputStream is) {
437 private static class CharsetConverterInputStream extends InputStream {
439 @SuppressWarnings("unused")
440 private InputStream in;
441 private PipedInputStream pipein;
442 private OutputStream pipeout;
446 public CharsetConverterInputStream(String csFrom, String csTo, InputStream in)
449 r = new InputStreamReader(in, csFrom);
450 pipein = new PipedInputStream();
451 pipeout = new PipedOutputStream(pipein);
452 w = new OutputStreamWriter(pipeout, csTo);
456 public int read() throws IOException {
457 if (pipein.available() > 0) {
458 return pipein.read();
466 return pipein.read();
474 private MessageFactory getSOAPMessageFactory() throws SOAPException {
475 MessageFactory mf = null;
476 mf = MessageFactory.newInstance();
484 private String getACSSessionCookieData(Cookie[] cookies) {
485 String acsSessionID = null;
486 if (null != cookies) {
487 for (Cookie cookie : cookies) {
488 if (cookie.getName().equals(ACS_SESSIONID)) {
489 acsSessionID = cookie.getValue();
490 logger.debug("The session id is {}", acsSessionID);
500 * @throws TR069EventProcessingException
502 private String getCWMPVersionCookieData(Cookie[] cookies) throws TR069EventProcessingException {
503 String cwmpVersion = null;
505 if (null != cookies) {
506 for (Cookie cookie : cookies) {
507 if (cookie.getName().equals(CWMP_VERSION)) {
508 cwmpVersion = cookie.getValue();
509 if (cwmpVersion != null) {
510 cwmpVersion = URLDecoder.decode(cwmpVersion, StandardCharsets.UTF_8.name());
512 logger.debug("The CWMP version supported by the device is: {}", cwmpVersion);
516 } catch (UnsupportedEncodingException e) {
517 logger.error(e.getMessage());
518 TR069EventProcessingException ex = new TR069EventProcessingException(
519 ErrorCode.UNSUPPORTED_CHARACTER_ENCODING, StandardCharsets.UTF_8.name());
520 logger.error(ex.getMessage());
529 private void logSoapMsg(SOAPMessage soapMsg) {
530 StringBuilder buffer = new StringBuilder();
531 buffer.append(soapMsg.toString());
532 ByteArrayOutputStream baos = new ByteArrayOutputStream();
534 soapMsg.writeTo(baos);
535 } catch (SOAPException | IOException e) {
536 logger.error("Error while writting soap message");
539 String soapMessage = buffer.toString();
540 logger.debug(soapMessage);
546 * @param fcodeAcsRequestDenied
549 * @throws IOException
551 private void sendFault(HttpServletResponse response, ByteArrayOutputStream out,
552 String fcodeAcsRequestDenied, String faultString, String id) throws IOException {
553 Fault fault = new Fault(fcodeAcsRequestDenied, faultString, id);
555 response.setContentLength(out.size());
556 String sout = out.toString(UTF_8);
557 sout = sout.replace('\'', '"');
558 response.getOutputStream().print(sout);
561 private void logHeaderElements(Enumeration<String> headerName) {
562 while (headerName.hasMoreElements()) {
563 String requestHeader = headerName.nextElement();
564 logger.debug("Request Headers {}", requestHeader);
568 private String getRequestType(TR069RPC msg) {
569 String requestType = msg.getName();
570 if (requestType == null)
571 requestType = "Empty Request";
576 /******************************************************************************************************************/
578 public DeviceEventHandler getDeviceEventHandler() {
579 return deviceEventHandler;
582 public void setDeviceEventHandler(DeviceEventHandler deviceEventHandler) {
583 this.deviceEventHandler = deviceEventHandler;