2 * ============LICENSE_START========================================================================
\r
3 * ONAP : tr-069-adapter
\r
4 * =================================================================================================
\r
5 * Copyright (C) 2020 CommScope Inc Intellectual Property.
\r
6 * =================================================================================================
\r
7 * This tr-069-adapter software file is distributed by CommScope Inc under the Apache License,
\r
8 * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
\r
9 * may obtain a copy of the License at
\r
11 * http://www.apache.org/licenses/LICENSE-2.0
\r
13 * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
\r
14 * either express or implied. See the License for the specific language governing permissions and
\r
15 * limitations under the License.
\r
16 * ===============LICENSE_END=======================================================================
\r
19 package org.commscope.tr069adapter.acs.cpe;
\r
21 import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.ACS_SESSIONID;
\r
22 import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.CWMP_VERSION;
\r
23 import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.INFORM;
\r
24 import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.TRANSFER_COMPLETE;
\r
26 import java.io.ByteArrayOutputStream;
\r
27 import java.io.IOException;
\r
28 import java.io.InputStream;
\r
29 import java.io.InputStreamReader;
\r
30 import java.io.OutputStream;
\r
31 import java.io.OutputStreamWriter;
\r
32 import java.io.PipedInputStream;
\r
33 import java.io.PipedOutputStream;
\r
34 import java.io.Reader;
\r
35 import java.io.UnsupportedEncodingException;
\r
36 import java.io.Writer;
\r
37 import java.net.URLDecoder;
\r
38 import java.nio.charset.StandardCharsets;
\r
39 import java.util.Enumeration;
\r
41 import javax.servlet.http.Cookie;
\r
42 import javax.servlet.http.HttpServletRequest;
\r
43 import javax.servlet.http.HttpServletResponse;
\r
44 import javax.ws.rs.core.Context;
\r
45 import javax.xml.soap.MessageFactory;
\r
46 import javax.xml.soap.MimeHeaders;
\r
47 import javax.xml.soap.SOAPException;
\r
48 import javax.xml.soap.SOAPMessage;
\r
50 import org.commscope.tr069adapter.acs.common.exception.TR069EventProcessingException;
\r
51 import org.commscope.tr069adapter.acs.common.response.DeviceInformResponse;
\r
52 import org.commscope.tr069adapter.acs.common.utils.ErrorCode;
\r
53 import org.commscope.tr069adapter.acs.cpe.handler.DeviceEventHandler;
\r
54 import org.commscope.tr069adapter.acs.cpe.rpc.Fault;
\r
55 import org.commscope.tr069adapter.acs.cpe.rpc.Inform;
\r
56 import org.commscope.tr069adapter.acs.cpe.rpc.InformResponse;
\r
57 import org.commscope.tr069adapter.acs.cpe.rpc.TransferComplete;
\r
58 import org.commscope.tr069adapter.acs.cpe.rpc.TransferCompleteResponse;
\r
59 import org.slf4j.Logger;
\r
60 import org.slf4j.LoggerFactory;
\r
61 import org.springframework.beans.factory.annotation.Autowired;
\r
62 import org.springframework.web.bind.annotation.PostMapping;
\r
63 import org.springframework.web.bind.annotation.RequestMapping;
\r
64 import org.springframework.web.bind.annotation.RestController;
\r
67 @RequestMapping("/CPEMgmt")
\r
68 public class CPEManagementService {
\r
70 private static final String UTF_8 = "UTF-8";
\r
72 private static final Logger logger = LoggerFactory.getLogger(CPEManagementService.class);
\r
74 private static final int MY_MAX_ENVELOPES = 1;
\r
77 DeviceEventHandler deviceEventHandler;
\r
83 @SuppressWarnings("static-access")
\r
84 @PostMapping("/acs")
\r
85 public void processDeviceEvent(@Context HttpServletRequest request,
\r
86 @Context HttpServletResponse response) {
\r
88 logger.debug("A device event occurred");
\r
89 logHeaderElements(request.getHeaderNames());
\r
92 Boolean isEmptyCPERequest = true;
\r
93 SOAPMessage soapMsg = null;
\r
94 ByteArrayOutputStream out = new ByteArrayOutputStream();
\r
96 String ct = request.getContentType();
\r
98 String csFrom = "ISO-8859-1";
\r
100 csix = ct.indexOf("charset=");
\r
101 response.setContentType(ct);
\r
103 response.setContentType("text/xml;charset=UTF-8");
\r
107 csFrom = ct.substring(csix + 8).replaceAll("\"", "");
\r
109 Cookie[] cookies = request.getCookies();
\r
110 String acsSessionID = getACSSessionCookieData(cookies);
\r
111 String cwmpVersion = getCWMPVersionCookieData(cookies);
\r
113 XmlFilterInputStream f =
\r
114 new XmlFilterInputStream(request.getInputStream(), request.getContentLength());
\r
115 MessageFactory mf = getSOAPMessageFactory();
\r
117 isEmptyCPERequest = false;
\r
118 MimeHeaders hdrs = new MimeHeaders();
\r
119 hdrs.setHeader("Content-Type", "text/xml; charset=UTF-8");
\r
120 InputStream in = getInputStream(csFrom, f);
\r
121 soapMsg = mf.createMessage(hdrs, in);
\r
123 logSoapMsg(soapMsg);
\r
125 TR069RPC msg = null;
\r
126 msg = TR069RPC.parse(soapMsg);
\r
128 String reqType = getRequestType(msg);
\r
129 logger.info("Event notified by the device is of type: {}", reqType);
\r
131 if (reqType != null) {
\r
132 if (reqType.equals(INFORM)) {
\r
133 processDeviceInform(msg, request, response, out);
\r
134 } else if (reqType.equals(TRANSFER_COMPLETE)) {
\r
135 processTransferComplete(msg, response, out);
\r
137 processOperationResult(msg, response, reqType, acsSessionID, out);
\r
142 if (isEmptyCPERequest.booleanValue()) {
\r
143 processEmptyCPERequest(response, cwmpVersion, acsSessionID, out);
\r
146 if (out.size() < 1) {// To delete dm_sessionId cookie
\r
147 clearCookies(cookies, response);
\r
150 response.setContentLength(out.size());
\r
151 response.setHeader("SOAPAction", "");
\r
152 String sout = out.toString().trim();
\r
154 response.getOutputStream().print(sout);
\r
155 response.getOutputStream().flush();
\r
156 logger.debug("End of processing");
\r
158 } catch (Exception e) {
\r
159 logger.error("An error occurred while processing device event, Exception: {}",
\r
161 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
\r
164 logger.debug("End of processing the HTTP post request");
\r
167 private InputStream getInputStream(String csFrom, XmlFilterInputStream f) throws IOException {
\r
168 InputStream in = null;
\r
169 if (csFrom.equalsIgnoreCase(UTF_8)) {
\r
170 in = new XmlFilterNS(f);
\r
172 in = new CharsetConverterInputStream(csFrom, UTF_8, new XmlFilterNS(f));
\r
177 private void processDeviceInform(TR069RPC msg, HttpServletRequest request,
\r
178 HttpServletResponse response, ByteArrayOutputStream out) throws IOException {
\r
179 Inform inform = (Inform) msg;
\r
180 DeviceInformResponse deviceInformResponse = null;
\r
182 deviceInformResponse =
\r
183 deviceEventHandler.processDeviceInform(inform, request.getHeader("Authorization"));
\r
184 Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());
\r
185 Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion());
\r
186 response.addCookie(cookie);
\r
187 response.addCookie(cwmpVerCookie);
\r
188 } catch (TR069EventProcessingException tr069ex) {
\r
189 ErrorCode errorCode = tr069ex.getErrorCode();
\r
190 if (ErrorCode.OUI_OR_PC_MISMATCH.equals(errorCode)) {
\r
191 sendFault(response, out, Fault.FCODE_ACS_REQUEST_DENIED, "OUIandProductClassNotValid",
\r
194 int httpStatusCode = deviceEventHandler.handleException(tr069ex);
\r
195 response.setStatus(httpStatusCode);
\r
197 int httpStatusCode = deviceEventHandler.handleException(tr069ex);
\r
198 response.setStatus(httpStatusCode);
\r
201 InformResponse resp = new InformResponse(inform.getId(), MY_MAX_ENVELOPES);
\r
202 resp.setCWMPVersion(msg.getCWMPVersion());
\r
206 private void processTransferComplete(TR069RPC msg, HttpServletResponse response,
\r
207 ByteArrayOutputStream out) {
\r
208 TransferComplete tc = (TransferComplete) msg;
\r
210 DeviceInformResponse deviceInformResponse = deviceEventHandler.processTransferComplete(tc);
\r
211 Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());
\r
212 Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion() + ";");
\r
213 response.addCookie(cookie);
\r
214 response.addCookie(cwmpVerCookie);
\r
215 } catch (Exception e) {
\r
216 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
\r
219 TransferCompleteResponse tr = new TransferCompleteResponse(tc.getId());
\r
220 tr.setCWMPVersion(msg.getCWMPVersion());
\r
224 private void processOperationResult(TR069RPC msg, HttpServletResponse response, String reqType,
\r
225 String acsSessionID, ByteArrayOutputStream out) {
\r
226 logger.debug("Received Operation Result response {}", msg);
\r
227 if (null == acsSessionID) {
\r
228 logger.error("Received response without session ID, response: {}", reqType);
\r
231 TR069RPC message = deviceEventHandler.processRPCResponse(msg, acsSessionID);
\r
232 if (null != message) {
\r
233 message.setCWMPVersion(msg.getCWMPVersion());
\r
234 message.writeTo(out);
\r
236 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
\r
238 } catch (TR069EventProcessingException tr069ex) {
\r
239 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
\r
244 private void processEmptyCPERequest(HttpServletResponse response, String cwmpVersion,
\r
245 String acsSessionID, ByteArrayOutputStream out) {
\r
246 if (null == acsSessionID) {
\r
247 logger.error("Received empty response without session ID");
\r
248 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
\r
253 logger.info("Received Empty Device response");
\r
254 TR069RPC message = deviceEventHandler.processEmptyRequest(acsSessionID);
\r
255 if (null != message) {
\r
256 message.setCWMPVersion(cwmpVersion);
\r
257 message.writeTo(out);
\r
259 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
\r
261 } catch (TR069EventProcessingException tr069ex) {
\r
262 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
\r
266 private void clearCookies(Cookie[] cookies, HttpServletResponse response) {
\r
267 Cookie cookieToDelete = null;
\r
268 if (null != cookies) {
\r
269 logger.debug("Clearing the cookies");
\r
270 for (int i = 0; i < cookies.length; i++) {
\r
271 if (cookies[i].getName().equals(ACS_SESSIONID)
\r
272 || cookies[i].getName().equals(CWMP_VERSION)) {
\r
273 cookieToDelete = cookies[i];
\r
274 cookieToDelete.setMaxAge(0);
\r
275 response.addCookie(cookieToDelete);
\r
281 private static class XmlFilterInputStream extends InputStream {
\r
283 private InputStream istream;
\r
285 private int lastchar;
\r
286 @SuppressWarnings("unused")
\r
288 private int nextchar;
\r
289 private boolean intag = false;
\r
290 private StringBuilder buff = new StringBuilder(16);
\r
292 /** Creates a new instance of xmlFilterInputStream */
\r
293 public XmlFilterInputStream(InputStream is, int l) {
\r
299 public int read() throws IOException {
\r
300 if (lastchar == '>' && lvl == 0) {
\r
304 if (!readLastChar())
\r
307 if (!intag && lastchar == '&') {
\r
308 int amppos = buff.length();
\r
310 String s = buff.substring(amppos);
\r
311 replaceSpecialChars(s, amppos);
\r
317 if (lastchar == '/') {
\r
328 public boolean next() throws IOException {
\r
329 if ((nextchar = istream.read()) == -1) {
\r
330 logger.debug("Next char is {}", nextchar);
\r
334 return (nextchar != -1);
\r
337 private boolean readLastChar() throws IOException {
\r
338 if (nextchar != -1) {
\r
339 lastchar = nextchar;
\r
342 if (buff.length() > 0) {
\r
343 lastchar = buff.charAt(0);
\r
344 buff.deleteCharAt(0);
\r
347 lastchar = istream.read();
\r
351 if (lastchar == '<') {
\r
353 } else if (lastchar == '>') {
\r
360 private void updateBuffer() throws IOException {
\r
361 // fix up broken xml not encoding &
\r
362 buff.append((char) lastchar);
\r
363 for (int c = 0; c < 10; c++) {
\r
364 int ch = istream.read();
\r
365 boolean breakLoop = false;
\r
376 buff.append((char) ch);
\r
380 private void replaceSpecialChars(String s, int amppos) {
\r
381 if (!s.startsWith("&") && !s.startsWith("<") && !s.startsWith(">")
\r
382 && !s.startsWith("'") && !s.startsWith(""") && !s.startsWith("&#")) {
\r
383 buff.replace(amppos, amppos + 1, "&");
\r
388 private static class XmlFilterNS extends InputStream {
\r
389 // Dumb class to filter out declaration of default xmlns
\r
391 private String pat = "xmlns=\"urn:dslforum-org:cwmp-1-0\"";
\r
392 private String pat2 = "xmlns=\"urn:dslforum-org:cwmp-1-1\"";
\r
393 private int length = 0;
\r
394 private int pos = 0;
\r
395 private boolean f = false;
\r
396 private byte[] buff = new byte[1024];
\r
397 private InputStream is;
\r
400 public int read() throws IOException {
\r
402 length = is.read(buff);
\r
403 if (length < buff.length) {
\r
404 byte[] b2 = new byte[length];
\r
405 System.arraycopy(buff, 0, b2, 0, length);
\r
409 String b = new String(buff, StandardCharsets.UTF_8);
\r
410 b = b.replace(pat, "");
\r
411 b = b.replace(pat2, "");
\r
412 buff = b.getBytes(StandardCharsets.UTF_8);
\r
413 length = buff.length;
\r
417 if (pos < length) {
\r
418 return buff[pos++] & 0xFF;
\r
423 public XmlFilterNS(InputStream is) {
\r
428 private static class CharsetConverterInputStream extends InputStream {
\r
430 @SuppressWarnings("unused")
\r
431 private InputStream in;
\r
432 private PipedInputStream pipein;
\r
433 private OutputStream pipeout;
\r
437 public CharsetConverterInputStream(String csFrom, String csTo, InputStream in)
\r
438 throws IOException {
\r
440 r = new InputStreamReader(in, csFrom);
\r
441 pipein = new PipedInputStream();
\r
442 pipeout = new PipedOutputStream(pipein);
\r
443 w = new OutputStreamWriter(pipeout, csTo);
\r
447 public int read() throws IOException {
\r
448 if (pipein.available() > 0) {
\r
449 return pipein.read();
\r
457 return pipein.read();
\r
463 * @throws Exception
\r
465 private MessageFactory getSOAPMessageFactory() throws SOAPException {
\r
466 MessageFactory mf = null;
\r
467 mf = MessageFactory.newInstance();
\r
475 private String getACSSessionCookieData(Cookie[] cookies) {
\r
476 String acsSessionID = null;
\r
477 if (null != cookies) {
\r
478 for (Cookie cookie : cookies) {
\r
479 if (cookie.getName().equals(ACS_SESSIONID)) {
\r
480 acsSessionID = cookie.getValue();
\r
481 logger.debug("The session id is {}", acsSessionID);
\r
485 return acsSessionID;
\r
491 * @throws TR069EventProcessingException
\r
493 private String getCWMPVersionCookieData(Cookie[] cookies) throws TR069EventProcessingException {
\r
494 String cwmpVersion = null;
\r
496 if (null != cookies) {
\r
497 for (Cookie cookie : cookies) {
\r
498 if (cookie.getName().equals(CWMP_VERSION)) {
\r
499 cwmpVersion = cookie.getValue();
\r
500 if (cwmpVersion != null) {
\r
501 cwmpVersion = URLDecoder.decode(cwmpVersion, StandardCharsets.UTF_8.name());
\r
503 logger.debug("The CWMP version supported by the device is: {}", cwmpVersion);
\r
507 } catch (UnsupportedEncodingException e) {
\r
508 logger.error(e.getMessage());
\r
509 TR069EventProcessingException ex = new TR069EventProcessingException(
\r
510 ErrorCode.UNSUPPORTED_CHARACTER_ENCODING, StandardCharsets.UTF_8.name());
\r
511 logger.error(ex.getMessage());
\r
514 return cwmpVersion;
\r
520 private void logSoapMsg(SOAPMessage soapMsg) {
\r
521 StringBuilder buffer = new StringBuilder();
\r
522 buffer.append(soapMsg.toString());
\r
523 ByteArrayOutputStream baos = new ByteArrayOutputStream();
\r
525 soapMsg.writeTo(baos);
\r
526 } catch (SOAPException | IOException e) {
\r
527 logger.error("Error while writting soap message");
\r
529 buffer.append(baos);
\r
530 String soapMessage = buffer.toString();
\r
531 logger.debug(soapMessage);
\r
537 * @param fcodeAcsRequestDenied
\r
538 * @param faultString
\r
540 * @throws IOException
\r
542 private void sendFault(HttpServletResponse response, ByteArrayOutputStream out,
\r
543 String fcodeAcsRequestDenied, String faultString, String id) throws IOException {
\r
544 Fault fault = new Fault(fcodeAcsRequestDenied, faultString, id);
\r
545 fault.writeTo(out);
\r
546 response.setContentLength(out.size());
\r
547 String sout = out.toString(UTF_8);
\r
548 sout = sout.replace('\'', '"');
\r
549 response.getOutputStream().print(sout);
\r
552 private void logHeaderElements(Enumeration<String> headerName) {
\r
553 while (headerName.hasMoreElements()) {
\r
554 String requestHeader = headerName.nextElement();
\r
555 logger.debug("Request Headers {}", requestHeader);
\r
559 private String getRequestType(TR069RPC msg) {
\r
560 String requestType = msg.getName();
\r
561 if (requestType == null)
\r
562 requestType = "Empty Request";
\r
564 return requestType;
\r
567 /******************************************************************************************************************/
\r
569 public DeviceEventHandler getDeviceEventHandler() {
\r
570 return deviceEventHandler;
\r
573 public void setDeviceEventHandler(DeviceEventHandler deviceEventHandler) {
\r
574 this.deviceEventHandler = deviceEventHandler;
\r