From eb1c71a478930702f549a5256df237507b544338 Mon Sep 17 00:00:00 2001 From: jansupol Date: Wed, 10 Jan 2024 17:07:04 +0100 Subject: [PATCH] Add URL of KeyStore and TrustStore to SslConfigurator Put back some Nio Paths usages reverted in 5392 Signed-off-by: jansupol --- .../internal/SslFilterTLS13UrlStoresTest.java | 36 ++++ .../jdk/connector/internal/SslFilterTest.java | 16 +- .../org/glassfish/jersey/SslConfigurator.java | 203 +++++++++--------- .../JarZipSchemeResourceFinderFactory.java | 6 +- .../mvc/spi/AbstractTemplateProcessor.java | 6 +- 5 files changed, 157 insertions(+), 110 deletions(-) create mode 100644 connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS13UrlStoresTest.java diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS13UrlStoresTest.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS13UrlStoresTest.java new file mode 100644 index 0000000000..b8e96c08f3 --- /dev/null +++ b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTLS13UrlStoresTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jdk.connector.internal; + +import org.glassfish.jersey.SslConfigurator; + +public class SslFilterTLS13UrlStoresTest extends SslFilterTest { + + public SslFilterTLS13UrlStoresTest() { + System.setProperty("jdk.tls.server.protocols", "TLSv1.3"); + System.setProperty("jdk.tls.client.protocols", "TLSv1.3"); + } + + @Override + protected SslConfigurator getSslConfigurator() { + return SslConfigurator.newInstance() + .trustStoreUrl(this.getClass().getResource("/truststore_client")) + .trustStorePassword("asdfgh") + .keyStoreUrl(this.getClass().getResource("/keystore_client")) + .keyStorePassword("asdfgh"); + } +} diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTest.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTest.java index ef13997809..7f3b00ace7 100644 --- a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTest.java +++ b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/SslFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -388,11 +388,7 @@ private Filter openClientSocket( final CountDownLatch completionLatch, HostnameVerifier customHostnameVerifier) throws Throwable { - SslConfigurator sslConfig = SslConfigurator.newInstance() - .trustStoreFile(this.getClass().getResource("/truststore_client").getPath()) - .trustStorePassword("asdfgh") - .keyStoreFile(this.getClass().getResource("/keystore_client").getPath()) - .keyStorePassword("asdfgh"); + SslConfigurator sslConfig = getSslConfigurator(); TransportFilter transportFilter = new TransportFilter(17_000, ThreadPoolConfig.defaultConfig(), 100_000); final SSLParamConfigurator sslParamConfigurator = SSLParamConfigurator.builder() @@ -481,6 +477,14 @@ void close() { return clientSocket; } + protected SslConfigurator getSslConfigurator() { + return SslConfigurator.newInstance() + .trustStoreFile(this.getClass().getResource("/truststore_client").getPath()) + .trustStorePassword("asdfgh") + .keyStoreFile(this.getClass().getResource("/keystore_client").getPath()) + .keyStorePassword("asdfgh"); + } + /** * SSL echo server. It expects a message to be terminated with \n. */ diff --git a/core-common/src/main/java/org/glassfish/jersey/SslConfigurator.java b/core-common/src/main/java/org/glassfish/jersey/SslConfigurator.java index b3befea473..2bef140be4 100644 --- a/core-common/src/main/java/org/glassfish/jersey/SslConfigurator.java +++ b/core-common/src/main/java/org/glassfish/jersey/SslConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,6 +21,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.nio.file.Files; import java.security.AccessController; import java.security.KeyManagementException; @@ -31,6 +32,7 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Arrays; +import java.util.Objects; import java.util.Properties; import java.util.logging.Logger; @@ -241,6 +243,9 @@ public final class SslConfigurator { private String trustStoreFile; private String keyStoreFile; + private URL trustStoreUrl; + private URL keyStoreUrl; + private byte[] trustStoreBytes; private byte[] keyStoreBytes; @@ -321,6 +326,8 @@ private SslConfigurator(SslConfigurator that) { this.keyPass = that.keyPass; this.trustStoreFile = that.trustStoreFile; this.keyStoreFile = that.keyStoreFile; + this.keyStoreUrl = that.keyStoreUrl; + this.trustStoreUrl = that.trustStoreUrl; this.trustStoreBytes = that.trustStoreBytes; this.keyStoreBytes = that.keyStoreBytes; this.trustManagerFactoryAlgorithm = that.trustManagerFactoryAlgorithm; @@ -441,8 +448,9 @@ public SslConfigurator keyPassword(char[] password) { /** * Set the trust store file name. *

- * Setting a trust store instance resets any {@link #trustStore(java.security.KeyStore) trust store instance} - * or {@link #trustStoreBytes(byte[]) trust store payload} value previously set. + * Setting a trust store instance resets any {@link #trustStore(java.security.KeyStore) trust store instance}, + * {@link #trustStoreBytes(byte[]) trust store payload} or {@link #trustStoreUrl(URL) trust store url} + * value previously set. *

* * @param fileName {@link java.io.File file} name of the trust store. @@ -451,6 +459,26 @@ public SslConfigurator keyPassword(char[] password) { public SslConfigurator trustStoreFile(String fileName) { this.trustStoreFile = fileName; this.trustStoreBytes = null; + this.trustStoreUrl = null; + this.trustStore = null; + return this; + } + + /** + * Set the trust store file url. + *

+ * Setting a trust store instance resets any {@link #trustStore(java.security.KeyStore) trust store instance}, + * {@link #trustStoreBytes(byte[]) trust store payload} or {@link #trustStoreUrl(URL) trust store url} + * value previously set. + *

+ * + * @param url {@link java.net.URL url} link of the trust store. + * @return updated SSL configurator instance. + */ + public SslConfigurator trustStoreUrl(URL url) { + this.trustStoreFile = null; + this.trustStoreBytes = null; + this.trustStoreUrl = url; this.trustStore = null; return this; } @@ -458,8 +486,9 @@ public SslConfigurator trustStoreFile(String fileName) { /** * Set the trust store payload as byte array. *

- * Setting a trust store instance resets any {@link #trustStoreFile(String) trust store file} - * or {@link #trustStore(java.security.KeyStore) trust store instance} value previously set. + * Setting a trust store instance resets any {@link #trustStoreFile(String) trust store file}, + * {@link #trustStore(java.security.KeyStore) trust store instance} or {@link #trustStoreUrl(URL) trust store url} + * value previously set. *

* * @param payload trust store payload. @@ -468,6 +497,7 @@ public SslConfigurator trustStoreFile(String fileName) { public SslConfigurator trustStoreBytes(byte[] payload) { this.trustStoreBytes = payload.clone(); this.trustStoreFile = null; + this.trustStoreUrl = null; this.trustStore = null; return this; } @@ -475,8 +505,8 @@ public SslConfigurator trustStoreBytes(byte[] payload) { /** * Set the key store file name. *

- * Setting a key store instance resets any {@link #keyStore(java.security.KeyStore) key store instance} - * or {@link #keyStoreBytes(byte[]) key store payload} value previously set. + * Setting a key store instance resets any {@link #keyStore(java.security.KeyStore) key store instance}, + * {@link #keyStoreBytes(byte[]) key store payload} or {@link #keyStoreUrl(URL) key store url} value previously set. *

* * @param fileName {@link java.io.File file} name of the key store. @@ -484,6 +514,26 @@ public SslConfigurator trustStoreBytes(byte[] payload) { */ public SslConfigurator keyStoreFile(String fileName) { this.keyStoreFile = fileName; + this.keyStoreUrl = null; + this.keyStoreBytes = null; + this.keyStore = null; + return this; + } + + /** + * Set the key store url. + *

+ * Setting a key store instance resets any {@link #keyStore(java.security.KeyStore) key store instance}, + * {@link #keyStoreBytes(byte[]) key store payload} or {@link #keyStoreFile(String) key store file} + * value previously set. + *

+ * + * @param url {@link java.net.URL url} of the key store. + * @return updated SSL configurator instance. + */ + public SslConfigurator keyStoreUrl(URL url) { + this.keyStoreFile = null; + this.keyStoreUrl = url; this.keyStoreBytes = null; this.keyStore = null; return this; @@ -492,8 +542,9 @@ public SslConfigurator keyStoreFile(String fileName) { /** * Set the key store payload as byte array. *

- * Setting a key store instance resets any {@link #keyStoreFile(String) key store file} - * or {@link #keyStore(java.security.KeyStore) key store instance} value previously set. + * Setting a key store instance resets any {@link #keyStoreFile(String) key store file}, + * {@link #keyStore(java.security.KeyStore) key store instance} or {@link #keyStoreUrl(URL) key store url} + * value previously set. *

* * @param payload key store payload. @@ -501,6 +552,7 @@ public SslConfigurator keyStoreFile(String fileName) { */ public SslConfigurator keyStoreBytes(byte[] payload) { this.keyStoreBytes = payload.clone(); + this.keyStoreUrl = null; this.keyStoreFile = null; this.keyStore = null; return this; @@ -573,8 +625,8 @@ KeyStore getKeyStore() { /** * Set the key store instance. *

- * Setting a key store instance resets any {@link #keyStoreFile(String) key store file} - * or {@link #keyStoreBytes(byte[]) key store payload} value previously set. + * Setting a key store instance resets any {@link #keyStoreFile(String) key store file}, + * {@link #keyStoreBytes(byte[]) key store payload} or {@link #keyStoreUrl(URL) key store url} value previously set. *

* * @param keyStore key store instance. @@ -584,15 +636,12 @@ public SslConfigurator keyStore(KeyStore keyStore) { this.keyStore = keyStore; this.keyStoreFile = null; this.keyStoreBytes = null; + this.keyStoreUrl = null; return this; } /** * Get the trust store instance. - *

- * Setting a trust store instance resets any {@link #trustStoreFile(String) trust store file} - * or {@link #trustStoreBytes(byte[]) trust store payload} value previously set. - *

* * @return trust store instance or {@code null} if not explicitly set. */ @@ -602,12 +651,16 @@ KeyStore getTrustStore() { /** * Set the trust store instance. - * + *

+ * Setting a trust store instance resets any {@link #trustStoreFile(String) trust store file}, + * {@link #trustStoreBytes(byte[]) trust store payload} or {@link #trustStoreUrl(URL) trust store url} value previously set. + *

* @param trustStore trust store instance. * @return updated SSL configurator instance. */ public SslConfigurator trustStore(KeyStore trustStore) { this.trustStore = trustStore; + this.trustStoreUrl = null; this.trustStoreFile = null; this.trustStoreBytes = null; return this; @@ -623,7 +676,7 @@ public SSLContext createSSLContext() { KeyManagerFactory keyManagerFactory = null; KeyStore _keyStore = keyStore; - if (_keyStore == null && (keyStoreBytes != null || keyStoreFile != null)) { + if (_keyStore == null && (keyStoreBytes != null || keyStoreFile != null || keyStoreUrl != null)) { try { if (keyStoreProvider != null) { _keyStore = KeyStore.getInstance( @@ -635,6 +688,8 @@ public SSLContext createSSLContext() { try { if (keyStoreBytes != null) { keyStoreInputStream = new ByteArrayInputStream(keyStoreBytes); + } else if (keyStoreUrl != null) { + keyStoreInputStream = keyStoreUrl.openStream(); } else if (!keyStoreFile.equals("NONE")) { keyStoreInputStream = Files.newInputStream(new File(keyStoreFile).toPath()); } @@ -697,7 +752,7 @@ public SSLContext createSSLContext() { } KeyStore _trustStore = trustStore; - if (_trustStore == null && (trustStoreBytes != null || trustStoreFile != null)) { + if (_trustStore == null && (trustStoreBytes != null || trustStoreFile != null || trustStoreUrl != null)) { try { if (trustStoreProvider != null) { _trustStore = KeyStore.getInstance( @@ -710,6 +765,8 @@ public SSLContext createSSLContext() { try { if (trustStoreBytes != null) { trustStoreInputStream = new ByteArrayInputStream(trustStoreBytes); + } else if (trustStoreUrl != null) { + trustStoreInputStream = trustStoreUrl.openStream(); } else if (!trustStoreFile.equals("NONE")) { trustStoreInputStream = Files.newInputStream(new File(trustStoreFile).toPath()); } @@ -808,6 +865,9 @@ public SslConfigurator retrieve(Properties props) { trustStoreFile = props.getProperty(TRUST_STORE_FILE); keyStoreFile = props.getProperty(KEY_STORE_FILE); + keyStoreUrl = null; + trustStoreUrl = null; + trustStoreBytes = null; keyStoreBytes = null; @@ -857,6 +917,9 @@ public SslConfigurator retrieve() { trustStoreFile = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(TRUST_STORE_FILE)); keyStoreFile = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(KEY_STORE_FILE)); + trustStoreUrl = null; + keyStoreUrl = null; + trustStoreBytes = null; keyStoreBytes = null; @@ -876,91 +939,35 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - SslConfigurator that = (SslConfigurator) o; - - if (keyManagerFactoryAlgorithm != null - ? !keyManagerFactoryAlgorithm.equals(that.keyManagerFactoryAlgorithm) : that.keyManagerFactoryAlgorithm != null) { - return false; - } - if (keyManagerFactoryProvider != null - ? !keyManagerFactoryProvider.equals(that.keyManagerFactoryProvider) : that.keyManagerFactoryProvider != null) { - return false; - } - if (!Arrays.equals(keyPass, that.keyPass)) { - return false; - } - if (keyStore != null ? !keyStore.equals(that.keyStore) : that.keyStore != null) { - return false; - } - if (!Arrays.equals(keyStoreBytes, that.keyStoreBytes)) { - return false; - } - if (keyStoreFile != null ? !keyStoreFile.equals(that.keyStoreFile) : that.keyStoreFile != null) { - return false; - } - if (!Arrays.equals(keyStorePass, that.keyStorePass)) { - return false; - } - if (keyStoreProvider != null ? !keyStoreProvider.equals(that.keyStoreProvider) : that.keyStoreProvider != null) { - return false; - } - if (keyStoreType != null ? !keyStoreType.equals(that.keyStoreType) : that.keyStoreType != null) { - return false; - } - if (securityProtocol != null ? !securityProtocol.equals(that.securityProtocol) : that.securityProtocol != null) { - return false; - } - if (trustManagerFactoryAlgorithm != null ? !trustManagerFactoryAlgorithm.equals(that.trustManagerFactoryAlgorithm) - : that.trustManagerFactoryAlgorithm != null) { - return false; - } - if (trustManagerFactoryProvider != null ? !trustManagerFactoryProvider.equals(that.trustManagerFactoryProvider) - : that.trustManagerFactoryProvider != null) { - return false; - } - if (trustStore != null ? !trustStore.equals(that.trustStore) : that.trustStore != null) { - return false; - } - if (!Arrays.equals(trustStoreBytes, that.trustStoreBytes)) { - return false; - } - if (trustStoreFile != null ? !trustStoreFile.equals(that.trustStoreFile) : that.trustStoreFile != null) { - return false; - } - if (!Arrays.equals(trustStorePass, that.trustStorePass)) { - return false; - } - if (trustStoreProvider != null ? !trustStoreProvider.equals(that.trustStoreProvider) : that.trustStoreProvider != null) { - return false; - } - if (trustStoreType != null ? !trustStoreType.equals(that.trustStoreType) : that.trustStoreType != null) { - return false; - } - - return true; + return Objects.equals(keyStore, that.keyStore) + && Objects.equals(trustStore, that.trustStore) + && Objects.equals(trustStoreProvider, that.trustStoreProvider) + && Objects.equals(keyStoreProvider, that.keyStoreProvider) + && Objects.equals(trustStoreType, that.trustStoreType) + && Objects.equals(keyStoreType, that.keyStoreType) + && Arrays.equals(trustStorePass, that.trustStorePass) + && Arrays.equals(keyStorePass, that.keyStorePass) + && Arrays.equals(keyPass, that.keyPass) + && Objects.equals(trustStoreFile, that.trustStoreFile) + && Objects.equals(keyStoreFile, that.keyStoreFile) + && Objects.equals(trustStoreUrl, that.trustStoreUrl) + && Objects.equals(keyStoreUrl, that.keyStoreUrl) + && Arrays.equals(trustStoreBytes, that.trustStoreBytes) + && Arrays.equals(keyStoreBytes, that.keyStoreBytes) + && Objects.equals(trustManagerFactoryAlgorithm, that.trustManagerFactoryAlgorithm) + && Objects.equals(keyManagerFactoryAlgorithm, that.keyManagerFactoryAlgorithm) + && Objects.equals(trustManagerFactoryProvider, that.trustManagerFactoryProvider) + && Objects.equals(keyManagerFactoryProvider, that.keyManagerFactoryProvider) + && Objects.equals(securityProtocol, that.securityProtocol); } @Override public int hashCode() { - int result = keyStore != null ? keyStore.hashCode() : 0; - result = 31 * result + (trustStore != null ? trustStore.hashCode() : 0); - result = 31 * result + (trustStoreProvider != null ? trustStoreProvider.hashCode() : 0); - result = 31 * result + (keyStoreProvider != null ? keyStoreProvider.hashCode() : 0); - result = 31 * result + (trustStoreType != null ? trustStoreType.hashCode() : 0); - result = 31 * result + (keyStoreType != null ? keyStoreType.hashCode() : 0); - result = 31 * result + (trustStorePass != null ? Arrays.hashCode(trustStorePass) : 0); - result = 31 * result + (keyStorePass != null ? Arrays.hashCode(keyStorePass) : 0); - result = 31 * result + (keyPass != null ? Arrays.hashCode(keyPass) : 0); - result = 31 * result + (trustStoreFile != null ? trustStoreFile.hashCode() : 0); - result = 31 * result + (keyStoreFile != null ? keyStoreFile.hashCode() : 0); - result = 31 * result + (trustStoreBytes != null ? Arrays.hashCode(trustStoreBytes) : 0); - result = 31 * result + (keyStoreBytes != null ? Arrays.hashCode(keyStoreBytes) : 0); - result = 31 * result + (trustManagerFactoryAlgorithm != null ? trustManagerFactoryAlgorithm.hashCode() : 0); - result = 31 * result + (keyManagerFactoryAlgorithm != null ? keyManagerFactoryAlgorithm.hashCode() : 0); - result = 31 * result + (trustManagerFactoryProvider != null ? trustManagerFactoryProvider.hashCode() : 0); - result = 31 * result + (keyManagerFactoryProvider != null ? keyManagerFactoryProvider.hashCode() : 0); - result = 31 * result + (securityProtocol != null ? securityProtocol.hashCode() : 0); + int result = Objects.hash(keyStore, trustStore, trustStoreProvider, keyStoreProvider, trustStoreType, keyStoreType, + trustStoreFile, keyStoreFile, trustStoreUrl, keyStoreUrl, trustManagerFactoryAlgorithm, + keyManagerFactoryAlgorithm, trustManagerFactoryProvider, keyManagerFactoryProvider, securityProtocol, + trustStorePass, keyStorePass, keyPass, trustStoreBytes, keyStoreBytes); return result; } } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarZipSchemeResourceFinderFactory.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarZipSchemeResourceFinderFactory.java index aa2874eb5d..f08cfb1fca 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarZipSchemeResourceFinderFactory.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarZipSchemeResourceFinderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,13 +16,13 @@ package org.glassfish.jersey.server.internal.scanning; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -155,7 +155,7 @@ private InputStream getInputStream(final String jarUrlString) throws IOException return new URL(jarUrlString).openStream(); } catch (final MalformedURLException e) { return Files.newInputStream( - new File(UriComponent.decode(jarUrlString, UriComponent.Type.PATH)).toPath()); + Paths.get(UriComponent.decode(jarUrlString, UriComponent.Type.PATH))); } } } diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java index f843ae82a1..933b7faa3c 100644 --- a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java +++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,13 +16,13 @@ package org.glassfish.jersey.server.mvc.spi; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -182,7 +182,7 @@ private T resolve(final String name) { // File-system path. if (reader == null) { try { - reader = new InputStreamReader(Files.newInputStream(new File(template).toPath()), encoding); + reader = new InputStreamReader(Files.newInputStream(Paths.get(template)), encoding); } catch (final IOException ioe) { // NOOP. }