28e1361caf9a20d1af1f0dc250290a209f058bc7
[ccsdk/cds.git] /
1 /*
2  * Copyright © 2017-2018 AT&T Intellectual Property.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.onap.ccsdk.apps.blueprintsprocessor.functions.netconf.executor.utils
17
18 import org.apache.commons.lang3.StringUtils
19 import org.onap.ccsdk.apps.blueprintsprocessor.functions.netconf.executor.NetconfException
20 import org.onap.ccsdk.apps.blueprintsprocessor.functions.netconf.executor.data.NetconfAdaptorConstant
21 import org.slf4j.LoggerFactory
22 import org.xml.sax.InputSource
23 import java.io.StringReader
24 import java.nio.charset.StandardCharsets
25 import java.util.Optional
26 import java.util.regex.MatchResult
27 import java.util.regex.Pattern
28 import javax.xml.XMLConstants
29 import javax.xml.parsers.DocumentBuilderFactory
30 import kotlin.collections.ArrayList
31 import kotlin.text.Charsets.UTF_8
32
33
34 class RpcMessageUtils {
35
36     companion object {
37         val log = LoggerFactory.getLogger(RpcMessageUtils::class.java)
38         // pattern to verify whole Chunked-Message format
39         val CHUNKED_FRAMING_PATTERN = Pattern.compile("(\\n#([1-9][0-9]*)\\n(.+))+\\n##\\n", Pattern.DOTALL)
40         val CHUNKED_END_REGEX_PATTERN = "\n##\n"
41         // pattern to parse each chunk-size in ChunkedMessage chunk
42         val CHUNKED_SIZE_PATTERN = Pattern.compile("\\n#([1-9][0-9]*)\\n")
43         val CAPABILITY_REGEX_PATTERN = Pattern.compile(RpcConstants.CAPABILITY_REGEX)
44         val SESSION_ID_REGEX_PATTERN = Pattern.compile(RpcConstants.SESSION_ID_REGEX)
45         val MSGID_STRING_PATTERN = Pattern.compile("${RpcConstants.MESSAGE_ID_STRING}=\"(.*?)\"")
46         val NEW_LINE = "\n"
47
48         fun getConfig(messageId: String, configType: String, filterContent: String?): String {
49             val request = StringBuilder()
50
51             request.append("<get-config>").append(NEW_LINE)
52             request.append(RpcConstants.SOURCE_OPEN).append(NEW_LINE)
53             request.append(RpcConstants.OPEN).append(configType).append(RpcConstants.TAG_CLOSE).append(NEW_LINE)
54             request.append(RpcConstants.SOURCE_CLOSE).append(NEW_LINE)
55
56             if (filterContent != null) {
57                 request.append(RpcConstants.SUBTREE_FILTER_OPEN).append(NEW_LINE)
58                 request.append(filterContent).append(NEW_LINE)
59                 request.append(RpcConstants.SUBTREE_FILTER_CLOSE).append(NEW_LINE)
60             }
61             request.append("</get-config>").append(NEW_LINE)
62
63             return doWrappedRpc(messageId, request.toString())
64         }
65
66         fun doWrappedRpc(messageId: String, request: String): String {
67             val rpc = StringBuilder(RpcConstants.XML_HEADER).append(NEW_LINE)
68             rpc.append(RpcConstants.RPC_OPEN)
69             rpc.append(RpcConstants.MESSAGE_ID_STRING).append(RpcConstants.EQUAL)
70             rpc.append(RpcConstants.QUOTE).append(messageId).append(RpcConstants.QUOTE_SPACE)
71             rpc.append(RpcConstants.NETCONF_BASE_NAMESPACE).append(RpcConstants.CLOSE).append(NEW_LINE)
72             rpc.append(request)
73             rpc.append(RpcConstants.RPC_CLOSE)
74             // rpc.append(NEW_LINE).append(END_PATTERN);
75
76             return rpc.toString()
77         }
78
79         fun editConfig(messageId: String, configType: String, defaultOperation: String?,
80                        newConfiguration: String): String {
81
82             val request = StringBuilder()
83
84             request.append("<edit-config>").append(NEW_LINE)
85             request.append(RpcConstants.TARGET_OPEN).append(NEW_LINE)
86             request.append(RpcConstants.OPEN).append(configType).append(RpcConstants.TAG_CLOSE).append(NEW_LINE)
87             request.append(RpcConstants.TARGET_CLOSE).append(NEW_LINE)
88
89             if (defaultOperation != null) {
90                 request.append(RpcConstants.DEFAULT_OPERATION_OPEN).append(defaultOperation).append(RpcConstants.DEFAULT_OPERATION_CLOSE)
91                 request.append(NEW_LINE)
92             }
93
94             request.append(RpcConstants.CONFIG_OPEN).append(NEW_LINE)
95             request.append(newConfiguration.trim { it <= ' ' }).append(NEW_LINE)
96             request.append(RpcConstants.CONFIG_CLOSE).append(NEW_LINE)
97             request.append("</edit-config>").append(NEW_LINE)
98
99             return doWrappedRpc(messageId, request.toString())
100         }
101
102         fun validate(messageId: String, configType: String): String {
103             val request = StringBuilder()
104
105             request.append("<validate>").append(NEW_LINE)
106             request.append(RpcConstants.SOURCE_OPEN).append(NEW_LINE)
107             request.append(RpcConstants.OPEN).append(configType).append(RpcConstants.TAG_CLOSE).append(NEW_LINE)
108             request.append(RpcConstants.SOURCE_CLOSE).append(NEW_LINE)
109             request.append("</validate>").append(NEW_LINE)
110
111             return doWrappedRpc(messageId, request.toString())
112         }
113
114         fun commit(messageId: String, message: String): String {
115             val request = StringBuilder()
116
117             request.append("<commit>").append(NEW_LINE)
118             request.append("</commit>").append(NEW_LINE)
119
120             return doWrappedRpc(messageId, request.toString())
121         }
122
123
124         fun unlock(messageId: String, configType: String): String {
125             val request = StringBuilder()
126
127             request.append("<unlock>").append(NEW_LINE)
128             request.append(RpcConstants.TARGET_OPEN).append(NEW_LINE)
129             request.append(RpcConstants.OPEN).append(configType).append(RpcConstants.TAG_CLOSE).append(NEW_LINE)
130             request.append(RpcConstants.TARGET_CLOSE).append(NEW_LINE)
131             request.append("</unlock>").append(NEW_LINE)
132
133             return doWrappedRpc(messageId, request.toString())
134         }
135
136         @Throws(NetconfException::class)
137         fun deleteConfig(messageId: String, netconfTargetConfig: String): String {
138             if (netconfTargetConfig == NetconfAdaptorConstant.CONFIG_TARGET_RUNNING) {
139                 log.warn("Target configuration for delete operation can't be \"running\" {}", netconfTargetConfig)
140                 throw NetconfException("Target configuration for delete operation can't be running")
141             }
142
143             val request = StringBuilder()
144
145             request.append("<delete-config>").append(NEW_LINE)
146             request.append(RpcConstants.TARGET_OPEN).append(NEW_LINE)
147             request.append(RpcConstants.OPEN).append(netconfTargetConfig).append(RpcConstants.TAG_CLOSE).append(NEW_LINE)
148             request.append(RpcConstants.TARGET_CLOSE).append(NEW_LINE)
149             request.append("</delete-config>").append(NEW_LINE)
150
151             return doWrappedRpc(messageId, request.toString())
152         }
153
154         fun discardChanges(messageId: String): String {
155             val request = StringBuilder()
156             request.append("<discard-changes/>").append(NEW_LINE)
157             return doWrappedRpc(messageId, request.toString())
158         }
159
160         fun lock(messageId: String, configType: String): String {
161             val request = StringBuilder()
162
163             request.append("<lock>").append(NEW_LINE)
164             request.append(RpcConstants.TARGET_OPEN).append(NEW_LINE)
165             request.append(RpcConstants.OPEN).append(configType).append(RpcConstants.TAG_CLOSE).append(NEW_LINE)
166             request.append(RpcConstants.TARGET_CLOSE).append(NEW_LINE)
167             request.append("</lock>").append(NEW_LINE)
168
169             return doWrappedRpc(messageId, request.toString())
170         }
171
172         fun closeSession(messageId: String, force: Boolean): String {
173             val request = StringBuilder()
174
175             if (force) {
176                 request.append("<kill-session/>").append(NEW_LINE)
177             } else {
178                 request.append("<close-session/>").append(NEW_LINE)
179             }
180
181             return doWrappedRpc(messageId, request.toString())
182         }
183
184         fun validateRPCXML(rpcRequest: String): Boolean {
185             try {
186                 if (StringUtils.isBlank(rpcRequest)) {
187                     return false
188                 }
189                 val dbf = DocumentBuilderFactory.newInstance()
190                 dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
191                 dbf.setFeature("http://xml.org/sax/features/external-general-entities", false)
192                 dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false)
193                 dbf.newDocumentBuilder().parse(InputSource(StringReader(rpcRequest.replace(RpcConstants.END_PATTERN, ""))))
194                 return true
195             } catch (e: Exception) {
196                 return false
197             }
198
199         }
200
201         fun getMsgId(message: String): Optional<String> {
202             val matcher = MSGID_STRING_PATTERN.matcher(message)
203             if (matcher.find()) {
204                 return Optional.of(matcher.group(1))
205             }
206             return if (message.contains(RpcConstants.HELLO)) {
207                 Optional.of((-1).toString())
208             } else Optional.empty()
209         }
210
211         fun validateChunkedFraming(reply: String): Boolean {
212             val matcher = CHUNKED_FRAMING_PATTERN.matcher(reply)
213             if (!matcher.matches()) {
214                 log.debug("Error Reply: {}", reply)
215                 return false
216             }
217             var chunkM = CHUNKED_SIZE_PATTERN.matcher(reply)
218             var chunks = ArrayList<MatchResult>()
219             var chunkdataStr = ""
220             while (chunkM.find()) {
221                 chunks.add(chunkM.toMatchResult())
222                 // extract chunk-data (and later) in bytes
223                 val bytes = Integer.parseInt(chunkM.group(1))
224                 // var chunkdata = reply.substring(chunkM.end()).getBytes(StandardCharsets.UTF_8)
225                 var chunkdata = reply.substring(chunkM.end()).toByteArray(StandardCharsets.UTF_8)
226                 if (bytes > chunkdata.size) {
227                     log.debug("Error Reply - wrong chunk size {}", reply)
228                     return false
229                 }
230                 // convert (only) chunk-data part into String
231
232                 chunkdataStr = String(chunkdata, 0, bytes, StandardCharsets.UTF_8)
233                 // skip chunk-data part from next match
234                 chunkM.region(chunkM.end() + chunkdataStr.length, reply.length)
235             }
236             if (!CHUNKED_END_REGEX_PATTERN
237                             .equals(reply.substring(chunks[chunks.size - 1].end() + chunkdataStr.length))) {
238                 log.debug("Error Reply: {}", reply)
239                 return false
240             }
241             return true
242         }
243
244
245         fun createHelloString(capabilities: List<String>): String {
246             val hellobuffer = StringBuilder()
247             hellobuffer.append(RpcConstants.XML_HEADER).append(NEW_LINE)
248             hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">").append(NEW_LINE)
249             hellobuffer.append("  <capabilities>").append(NEW_LINE)
250             if (capabilities.isNotEmpty()) {
251                 capabilities.forEach { cap -> hellobuffer.append("    <capability>").append(cap).append("</capability>").append(NEW_LINE) }
252             }
253             hellobuffer.append("  </capabilities>").append(NEW_LINE)
254             hellobuffer.append("</hello>").append(NEW_LINE)
255             hellobuffer.append(RpcConstants.END_PATTERN)
256             return hellobuffer.toString()
257         }
258         fun formatRPCRequest(request: String, messageId: String, deviceCapabilities: Set<String>): String {
259             var request = request
260             request = RpcMessageUtils.formatNetconfMessage(deviceCapabilities, request)
261             request = RpcMessageUtils.formatXmlHeader(request)
262             request = RpcMessageUtils.formatRequestMessageId(request, messageId)
263
264             return request
265         }
266
267         /**
268          * Validate and format netconf message. - NC1.0 if no EOM sequence present on `message`,
269          * append. - NC1.1 chunk-encode given message unless it already is chunk encoded
270          *
271          * @param deviceCapabilities Set containing Device Capabilities
272          * @param message to format
273          * @return formated message
274          */
275         fun formatNetconfMessage(deviceCapabilities: Set<String>, message: String): String {
276             var message = message
277             if (deviceCapabilities.contains(RpcConstants.NETCONF_11_CAPABILITY)) {
278                 message = formatChunkedMessage(message)
279             } else if (!message.endsWith(RpcConstants.END_PATTERN)) {
280                 message = message + NEW_LINE + RpcConstants.END_PATTERN
281             }
282             return message
283         }
284
285         /**
286          * Validate and format message according to chunked framing mechanism.
287          *
288          * @param message to format
289          * @return formated message
290          */
291         fun formatChunkedMessage(message: String): String {
292             var message = message
293             if (message.endsWith(RpcConstants.END_PATTERN)) {
294                 // message given had Netconf 1.0 EOM pattern -> remove
295                 message = message.substring(0, message.length - RpcConstants.END_PATTERN.length)
296             }
297             if (!message.startsWith(RpcConstants.NEW_LINE + RpcConstants.HASH)) {
298                 // chunk encode message
299                 //message = (RpcConstants.NEW_LINE + RpcConstants.HASH + message.getBytes(UTF_8).size + RpcConstants.NEW_LINE + message +RpcConstants. NEW_LINE + RpcConstants.HASH + RpcConstants.HASH
300                  //       + RpcConstants.NEW_LINE)
301                 message = (RpcConstants.NEW_LINE + RpcConstants.HASH + message.toByteArray(UTF_8).size + RpcConstants.NEW_LINE + message +RpcConstants. NEW_LINE + RpcConstants.HASH + RpcConstants.HASH
302                       + RpcConstants.NEW_LINE)
303             }
304             return message
305         }
306
307         /**
308          * Ensures xml start directive/declaration appears in the `request`.
309          *
310          * @param request RPC request message
311          * @return XML RPC message
312          */
313         fun formatXmlHeader(request: String): String {
314             var request = request
315             if (!request.contains(RpcConstants.XML_HEADER)) {
316                 if (request.startsWith(RpcConstants.NEW_LINE + RpcConstants.HASH)) {
317                     request = request.split("<".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] + RpcConstants.XML_HEADER + request.substring(request.split("<".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0].length)
318                 } else {
319                     request = RpcConstants.XML_HEADER + "\n" + request
320                 }
321             }
322             return request
323         }
324
325         fun formatRequestMessageId(request: String, messageId: String): String {
326             var request = request
327             if (request.contains(RpcConstants.MESSAGE_ID_STRING)) {
328                 request = request.replaceFirst((RpcConstants.MESSAGE_ID_STRING + RpcConstants.EQUAL + RpcConstants.NUMBER_BETWEEN_QUOTES_MATCHER).toRegex(), RpcConstants.MESSAGE_ID_STRING +RpcConstants. EQUAL + RpcConstants.QUOTE + messageId + RpcConstants.QUOTE)
329             } else if (!request.contains(RpcConstants.MESSAGE_ID_STRING) && !request.contains(RpcConstants.HELLO)) {
330                 request = request.replaceFirst(RpcConstants.END_OF_RPC_OPEN_TAG.toRegex(), RpcConstants.QUOTE_SPACE + RpcConstants.MESSAGE_ID_STRING + RpcConstants.EQUAL + RpcConstants.QUOTE + messageId + RpcConstants.QUOTE + ">")
331             }
332             return updateRequestLength(request)
333         }
334
335         fun updateRequestLength(request: String): String {
336             if (request.contains(NEW_LINE + RpcConstants.HASH + RpcConstants.HASH + NEW_LINE)) {
337                 val oldLen = Integer.parseInt(request.split(RpcConstants.HASH.toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()[1].split(NEW_LINE.toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()[0])
338                 val rpcWithEnding = request.substring(request.indexOf('<'))
339                 val firstBlock = request.split(RpcConstants.MSGLEN_REGEX_PATTERN.toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()[1].split((NEW_LINE + RpcConstants.HASH +RpcConstants. HASH + NEW_LINE).toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()[0]
340                 var newLen = 0
341                 newLen = firstBlock.toByteArray(UTF_8).size
342                 if (oldLen != newLen) {
343                     return NEW_LINE + RpcConstants.HASH + newLen + NEW_LINE + rpcWithEnding
344                 }
345             }
346             return request
347         }
348
349         fun checkReply(reply: String?): Boolean {
350             return if (reply != null) {
351                 !reply.contains("<rpc-error>") || reply.contains("warning") || reply.contains("<ok/>")
352             } else false
353         }
354
355
356     }
357
358 }