2 * Copyright © 2017-2018 AT&T Intellectual Property.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.onap.ccsdk.apps.blueprintsprocessor.functions.netconf.executor.utils
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
34 class RpcMessageUtils {
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}=\"(.*?)\"")
48 fun getConfig(messageId: String, configType: String, filterContent: String?): String {
49 val request = StringBuilder()
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)
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)
61 request.append("</get-config>").append(NEW_LINE)
63 return doWrappedRpc(messageId, request.toString())
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)
73 rpc.append(RpcConstants.RPC_CLOSE)
74 // rpc.append(NEW_LINE).append(END_PATTERN);
79 fun editConfig(messageId: String, configType: String, defaultOperation: String?,
80 newConfiguration: String): String {
82 val request = StringBuilder()
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)
89 if (defaultOperation != null) {
90 request.append(RpcConstants.DEFAULT_OPERATION_OPEN).append(defaultOperation).append(RpcConstants.DEFAULT_OPERATION_CLOSE)
91 request.append(NEW_LINE)
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)
99 return doWrappedRpc(messageId, request.toString())
102 fun validate(messageId: String, configType: String): String {
103 val request = StringBuilder()
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)
111 return doWrappedRpc(messageId, request.toString())
114 fun commit(messageId: String, message: String): String {
115 val request = StringBuilder()
117 request.append("<commit>").append(NEW_LINE)
118 request.append("</commit>").append(NEW_LINE)
120 return doWrappedRpc(messageId, request.toString())
124 fun unlock(messageId: String, configType: String): String {
125 val request = StringBuilder()
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)
133 return doWrappedRpc(messageId, request.toString())
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")
143 val request = StringBuilder()
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)
151 return doWrappedRpc(messageId, request.toString())
154 fun discardChanges(messageId: String): String {
155 val request = StringBuilder()
156 request.append("<discard-changes/>").append(NEW_LINE)
157 return doWrappedRpc(messageId, request.toString())
160 fun lock(messageId: String, configType: String): String {
161 val request = StringBuilder()
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)
169 return doWrappedRpc(messageId, request.toString())
172 fun closeSession(messageId: String, force: Boolean): String {
173 val request = StringBuilder()
176 request.append("<kill-session/>").append(NEW_LINE)
178 request.append("<close-session/>").append(NEW_LINE)
181 return doWrappedRpc(messageId, request.toString())
184 fun validateRPCXML(rpcRequest: String): Boolean {
186 if (StringUtils.isBlank(rpcRequest)) {
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, ""))))
195 } catch (e: Exception) {
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))
206 return if (message.contains(RpcConstants.HELLO)) {
207 Optional.of((-1).toString())
208 } else Optional.empty()
211 fun validateChunkedFraming(reply: String): Boolean {
212 val matcher = CHUNKED_FRAMING_PATTERN.matcher(reply)
213 if (!matcher.matches()) {
214 log.debug("Error Reply: {}", reply)
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)
230 // convert (only) chunk-data part into String
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)
236 if (!CHUNKED_END_REGEX_PATTERN
237 .equals(reply.substring(chunks[chunks.size - 1].end() + chunkdataStr.length))) {
238 log.debug("Error Reply: {}", reply)
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) }
253 hellobuffer.append(" </capabilities>").append(NEW_LINE)
254 hellobuffer.append("</hello>").append(NEW_LINE)
255 hellobuffer.append(RpcConstants.END_PATTERN)
256 return hellobuffer.toString()
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)
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
271 * @param deviceCapabilities Set containing Device Capabilities
272 * @param message to format
273 * @return formated message
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
286 * Validate and format message according to chunked framing mechanism.
288 * @param message to format
289 * @return formated message
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)
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)
308 * Ensures xml start directive/declaration appears in the `request`.
310 * @param request RPC request message
311 * @return XML RPC message
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)
319 request = RpcConstants.XML_HEADER + "\n" + request
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 + ">")
332 return updateRequestLength(request)
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]
341 newLen = firstBlock.toByteArray(UTF_8).size
342 if (oldLen != newLen) {
343 return NEW_LINE + RpcConstants.HASH + newLen + NEW_LINE + rpcWithEnding
349 fun checkReply(reply: String?): Boolean {
350 return if (reply != null) {
351 !reply.contains("<rpc-error>") || reply.contains("warning") || reply.contains("<ok/>")