diff --git a/README.md b/README.md index b5fd7569..b4b32a0d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Being an API layer, TLS Channel *delegates all cryptographic operations to SSLEn ## Rationale -The world's most used encryption protocol is TLS. Created by Netscape in 1994 as SSL (Secure Socket Layer), it experimented widespread adoption, which eventually let to its standardization. TLS works on top of the Transport Control Protocol (TCP), maintaining its core abstractions: two independent byte streams, one in each direction, with ordered at-most-once delivery. It can be argued that part of the success of TLS was due to its convenient programming interface, similar to the highly successful and familiar Berkeley Sockets. Currenty, there exist a few widely-used implementations: +The world's most used encryption protocol is TLS. Created by Netscape in 1994 as SSL (Secure Socket Layer), it experimented widespread adoption, which eventually let to its standardization. TLS works on top of the Transport Control Protocol (TCP), maintaining its core abstractions: two independent byte streams, one in each direction, with ordered at-most-once delivery. It can be argued that part of the success of TLS was due to its convenient programming interface, similar to the highly successful and familiar Berkeley Sockets. Currently, there exist a few widely-used implementations: - The most used TLS library is [OpenSSL](https://www.openssl.org/). Written in C and (along with some forks) the *de facto* standard for C and C++. Also widely used in Python, PHP, Ruby and Node.js. - The Go language has its own implementation, package [crypto/tls](https://golang.org/pkg/crypto/tls/). @@ -59,8 +59,8 @@ But no TLS support, which was only available in old-style sockets. Version 1.5 saw the advent of [SSLEngine](https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLEngine.html) as the official way of doing TLS over NIO sockets. This API has been the official option for more than a decade. However, it has severe shortcomings: -- No streaming support. SSLEngine does not do any I/O, or keep any buffers. It does all cryptographic operations on user-managed buffers (but, confusingly, at the same time keeps internal state associated with the TLS connection). This no-data but stateful API is just not what users expect or are used to, and indeed not what the rest of the industry has standarized on. -- Even considering the constrains, the API is unnecessarily convoluted, with too big a surface, and many incorrect interactions just not prevented at compile-time. It's just extremely hard to use correctly. +- No streaming support. SSLEngine does not do any I/O, or keep any buffers. It does all cryptographic operations on user-managed buffers (but, confusingly, at the same time keeps internal state associated with the TLS connection). This no-data but stateful API is just not what users expect or are used to, and indeed not what the rest of the industry has standardized on. +- Even considering the constraints, the API is unnecessarily convoluted, with too big a surface, and many incorrect interactions just not prevented at compile-time. It's just extremely hard to use correctly. - No support for server-side SNI handling. #### What to do @@ -132,7 +132,7 @@ Standard ByteChannel instances communicate the fact that operations would block Ideally, a more complex return type would suffice—not merely an `int` but some object including more information. For instance, OpenSSL uses special error codes for these conditions: `SSL_ERROR_WANT_READ` and `SSL_ERROR_WANT_WRITE`. -In the case of TLS Channel, it is in practice necessary to maintain compatibility with the existing ByteChannel interface. That's why an somewhat unorthodox approach is used: when the operation would block, special exceptions are thrown: [NeedsReadException](https://oss.sonatype.org/service/local/repositories/releases/archive/com/github/marianobarrios/tls-channel/0.1.0/tls-channel-0.1.0-javadoc.jar/!/index.html?tlschannel/NeedsReadException.html) and [NeedsWriteException](https://oss.sonatype.org/service/local/repositories/releases/archive/com/github/marianobarrios/tls-channel/0.1.0/tls-channel-0.1.0-javadoc.jar/!/index.html?tlschannel/NeedsWriteException.html), meaning that the operation should be retried when the underlying channel is ready for reading or writing, respectively. +In the case of TLS Channel, it is in practice necessary to maintain compatibility with the existing ByteChannel interface. That's why a somewhat unorthodox approach is used: when the operation would block, special exceptions are thrown: [NeedsReadException](https://oss.sonatype.org/service/local/repositories/releases/archive/com/github/marianobarrios/tls-channel/0.1.0/tls-channel-0.1.0-javadoc.jar/!/index.html?tlschannel/NeedsReadException.html) and [NeedsWriteException](https://oss.sonatype.org/service/local/repositories/releases/archive/com/github/marianobarrios/tls-channel/0.1.0/tls-channel-0.1.0-javadoc.jar/!/index.html?tlschannel/NeedsWriteException.html), meaning that the operation should be retried when the underlying channel is ready for reading or writing, respectively. Typical usage inside a selector loop looks like this: diff --git a/src/main/java/tlschannel/ClientTlsChannel.java b/src/main/java/tlschannel/ClientTlsChannel.java index d9ec08c6..24b7869a 100644 --- a/src/main/java/tlschannel/ClientTlsChannel.java +++ b/src/main/java/tlschannel/ClientTlsChannel.java @@ -56,7 +56,7 @@ private static SSLEngine defaultSSLEngineFactory(SSLContext sslContext) { } /** - * Create a new {@link Builder}, configured with a underlying {@link Channel} and a fixed {@link + * Create a new {@link Builder}, configured with an underlying {@link Channel} and a fixed {@link * SSLEngine}. * * @param underlying a reference to the underlying {@link ByteChannel} @@ -68,7 +68,7 @@ public static Builder newBuilder(ByteChannel underlying, SSLEngine sslEngine) { } /** - * Create a new {@link Builder}, configured with a underlying {@link Channel} and a {@link + * Create a new {@link Builder}, configured with an underlying {@link Channel} and a {@link * SSLContext}. * * @param underlying a reference to the underlying {@link ByteChannel} diff --git a/src/main/java/tlschannel/NeedsWriteException.java b/src/main/java/tlschannel/NeedsWriteException.java index a6ce19f4..6f5ebce7 100644 --- a/src/main/java/tlschannel/NeedsWriteException.java +++ b/src/main/java/tlschannel/NeedsWriteException.java @@ -6,7 +6,7 @@ /** * This exception signals the caller that the operation cannot continue because bytesProduced need - * to be write to the underlying {@link ByteChannel}, the channel is non-blocking and there are no + * to be written to the underlying {@link ByteChannel}, the channel is non-blocking and there are no * buffer space available. The caller should try the operation again, either with the channel in * blocking mode of after ensuring that buffer space exists. * diff --git a/src/main/java/tlschannel/ServerTlsChannel.java b/src/main/java/tlschannel/ServerTlsChannel.java index 026fc9f0..4c435835 100644 --- a/src/main/java/tlschannel/ServerTlsChannel.java +++ b/src/main/java/tlschannel/ServerTlsChannel.java @@ -132,7 +132,7 @@ public ServerTlsChannel build() { } /** - * Create a new {@link Builder}, configured with a underlying {@link Channel} and a fixed {@link + * Create a new {@link Builder}, configured with an underlying {@link Channel} and a fixed {@link * SSLContext}, which will be used to create the {@link SSLEngine}. * * @param underlying a reference to the underlying {@link ByteChannel} @@ -144,9 +144,9 @@ public static Builder newBuilder(ByteChannel underlying, SSLContext sslContext) } /** - * Create a new {@link Builder}, configured with a underlying {@link Channel} and a custom {@link + * Create a new {@link Builder}, configured with an underlying {@link Channel} and a custom {@link * SSLContext} factory, which will be used to create the context (in turn used to create the - * {@link SSLEngine}, as a function of the SNI received at the TLS connection start. + * {@link SSLEngine}), as a function of the SNI received at the TLS connection start. * *
Implementation note:
* Due to limitations of {@link SSLEngine}, configuring a {@link ServerTlsChannel} to select the
@@ -220,7 +220,7 @@ public ByteChannel getUnderlying() {
/**
* Return the used {@link SSLContext}.
*
- * @return if context if present, of null if the TLS connection as not been initializer, or the
+ * @return context if present, or null if the TLS connection as not been initializer, or the
* SNI not received yet.
*/
public SSLContext getSslContext() {
diff --git a/src/main/java/tlschannel/TlsChannel.java b/src/main/java/tlschannel/TlsChannel.java
index ec265a91..7dd3eefa 100644
--- a/src/main/java/tlschannel/TlsChannel.java
+++ b/src/main/java/tlschannel/TlsChannel.java
@@ -18,7 +18,7 @@
*
In other words, an interface that allows the programmer to have TLS using the same standard * socket API used for plaintext, just like OpenSSL does for C, only for Java. * - *
Note that this is an API adapter, not a cryptographic implementation: with the exception of a + *
Note that this is an API adapter, not a cryptographic implementation: except for a * few bytesProduced of parsing at the beginning of the connection, to look for the SNI, the whole * protocol implementation is done by the SSLEngine. Both the SSLContext and SSLEngine are supplied * by the client; these classes are the ones responsible for protocol configuration, including @@ -114,9 +114,9 @@ public interface TlsChannel extends ByteChannel, GatheringByteChannel, Scatterin * its limit will not have changed. * *
A read operation might not fill the buffer, and in fact it might not read any bytesProduced - * at all. Whether or not it does so depends upon the nature and state of the underlying channel. + * at all. Whether it does so depends upon the nature and state of the underlying channel. * It is guaranteed, however, that if a channel is in blocking mode and there is at least one byte - * remaining in the buffer then this method will block until at least one byte is read. On the + * remaining in the buffer, then this method will block until at least one byte is read. On the * other hand, if the underlying channel is in non-blocking mode then a {@link * WouldBlockException} may be thrown. Note that this also includes the possibility of a {@link * NeedsWriteException}, due to the fact that, during a TLS handshake, bytesProduced need to be @@ -252,7 +252,7 @@ public interface TlsChannel extends ByteChannel, GatheringByteChannel, Scatterin *
See {@link GatheringByteChannel#write(ByteBuffer[], int, int)} for more details of the * meaning of this signature. * - *
This method behaves slightly different than the interface specification, with respect to + *
This method behaves slightly different from the interface specification, with respect to * non-blocking responses, see {@link #write(ByteBuffer)} for more details. * * @param srcs The buffers from which bytesProduced are to be retrieved @@ -287,7 +287,7 @@ public interface TlsChannel extends ByteChannel, GatheringByteChannel, Scatterin * * * - * This method behaves slightly different than the interface specification, with respect to + * This method behaves slightly different from the interface specification, with respect to * non-blocking responses, see {@link #write(ByteBuffer)} for more details. * * @param srcs The buffers from which bytesProduced are to be retrieved @@ -310,7 +310,7 @@ public interface TlsChannel extends ByteChannel, GatheringByteChannel, Scatterin *
See {@link ScatteringByteChannel#read(ByteBuffer[], int, int)} for more details of the * meaning of this signature. * - *
This method behaves slightly different than the interface specification, with respect to + *
This method behaves slightly different from the interface specification, with respect to * non-blocking responses, see {@link #read(ByteBuffer)} for more details. * * @param dsts The buffers into which bytesProduced are to be transferred @@ -346,7 +346,7 @@ public interface TlsChannel extends ByteChannel, GatheringByteChannel, Scatterin * * * - *
This method behaves slightly different than the interface specification, with respect to + *
This method behaves slightly different from the interface specification, with respect to * non-blocking responses, see {@link #read(ByteBuffer)} for more details. * * @param dsts The buffers into which bytesProduced are to be transferred @@ -369,7 +369,7 @@ public interface TlsChannel extends ByteChannel, GatheringByteChannel, Scatterin * done. The exact behavior can be configured using the {@link * TlsChannelBuilder#withWaitForCloseConfirmation}. * - *
The default behavior mimics what happens in a normal (that is, non layered) {@link + *
The default behavior mimics what happens in a normal (that is, non-layered) {@link * javax.net.ssl.SSLSocket#close()}. * *
For finer control of the TLS close, use {@link #shutdown()} @@ -390,7 +390,7 @@ public interface TlsChannel extends ByteChannel, GatheringByteChannel, Scatterin * used for more communications, the complete shutdown procedure (bidirectional "close notify" * alerts) must be performed, so that the peers stay synchronized. * - *
This class supports both uni- and bidirectional shutdown by its 2 step behavior, using this + *
This class supports both uni- and bidirectional shutdown by its 2-step behavior, using this * method. * *
When this is the first party to send the "close notify" alert, this method will only send
@@ -400,7 +400,7 @@ public interface TlsChannel extends ByteChannel, GatheringByteChannel, Scatterin
* for the peer's "close notify" shutdown alert. On success, the second call will return
* true
.
*
- *
If the peer already sent the "close notify" alert and it was already processed implicitly + *
If the peer already sent the "close notify" alert, and it was already processed implicitly
* inside a read operation, the {@link #shutdownReceived()} flag is already set. This method will
* then send the "close notify" alert, set the {@link #shutdownSent()} flag and immediately return
* Default is to release. Releasing unused buffers is specially effective in the case case of
+ * Default is to release. Releasing unused buffers is specially effective in the case of
* idle long-lived connections, when the memory footprint can be reduced significantly. A
* potential reason for setting this value to Setting this value to The resulting sequence may be empty if the {@code hasNext} predicate
- * does not hold on the seed value. Otherwise the first element will be the
+ * does not hold on the seed value. Otherwise, the first element will be the
* supplied {@code seed} value, the next element (if present) will be the
* result of applying the {@code next} function to the {@code seed} value,
* and so on iteratively until the {@code hasNext} predicate indicates that
true
. It is therefore recommended to check the return value of this method and
diff --git a/src/main/java/tlschannel/TlsChannelBuilder.java b/src/main/java/tlschannel/TlsChannelBuilder.java
index 4edcdcc1..f888e3a7 100644
--- a/src/main/java/tlschannel/TlsChannelBuilder.java
+++ b/src/main/java/tlschannel/TlsChannelBuilder.java
@@ -39,8 +39,8 @@ public T withRunTasks(boolean runTasks) {
}
/**
- * Set the {@link BufferAllocator} to use for unencrypted data. By default a {@link
- * HeapBufferAllocator} is used, as this buffers are used to supplement user-supplied ones when
+ * Set the {@link BufferAllocator} to use for unencrypted data. By default, a {@link
+ * HeapBufferAllocator} is used, as these buffers are used to supplement user-supplied ones when
* dealing with too big a TLS record, that is, they operate entirely inside the JVM.
*
* @param bufferAllocator the buffer allocator
@@ -52,7 +52,7 @@ public T withPlainBufferAllocator(BufferAllocator bufferAllocator) {
}
/**
- * Set the {@link BufferAllocator} to use for encrypted data. By default a {@link
+ * Set the {@link BufferAllocator} to use for encrypted data. By default, a {@link
* DirectBufferAllocator} is used, as this data is usually read from or written to native sockets.
*
* @param bufferAllocator the buffer allocator
@@ -80,7 +80,7 @@ public T withSessionInitCallback(Consumerfalse
is performance, since more
* releases means more allocations, which have a cost. This is effectively a memory-time
@@ -98,7 +98,7 @@ public T withReleaseBuffers(boolean releaseBuffers) {
* Whether to wait for TLS close confirmation when executing a local {@link TlsChannel#close()} on
* the channel. If the underlying channel is blocking, setting this to true
will
* block (potentially until it times out, or indefinitely) the close operation until the
- * counterpart confirms the close on their side (sending a close_notify alert. If the underlying
+ * counterpart confirms the close on their side (sending a close_notify alert). If the underlying
* channel is non-blocking, setting this parameter to true is ineffective.
*
* true
emulates the behavior of {@link SSLSocket} when used
diff --git a/src/main/java/tlschannel/WouldBlockException.java b/src/main/java/tlschannel/WouldBlockException.java
index 15cf2d1b..adf19735 100644
--- a/src/main/java/tlschannel/WouldBlockException.java
+++ b/src/main/java/tlschannel/WouldBlockException.java
@@ -1,7 +1,7 @@
package tlschannel;
/**
- * Signals that some IO operation cannot continue because the channel is in non blocking mode and
+ * Signals that some IO operation cannot continue because the channel is in non-blocking mode and
* some blocking would otherwise happen.
*/
public class WouldBlockException extends TlsChannelFlowControlException {
diff --git a/src/main/java/tlschannel/async/AsynchronousTlsChannel.java b/src/main/java/tlschannel/async/AsynchronousTlsChannel.java
index 9d77730d..1c0e19e7 100644
--- a/src/main/java/tlschannel/async/AsynchronousTlsChannel.java
+++ b/src/main/java/tlschannel/async/AsynchronousTlsChannel.java
@@ -46,7 +46,7 @@ public boolean cancel(boolean mayInterruptIfRunning) {
/**
* Initializes a new instance of this class.
*
- * @param channelGroup group to associate new new channel to
+ * @param channelGroup group to associate new channel to
* @param tlsChannel existing TLS channel to be used asynchronously
* @param socketChannel underlying socket
* @throws ClosedChannelException if any of the underlying channels are closed.
diff --git a/src/main/java/tlschannel/async/AsynchronousTlsChannelGroup.java b/src/main/java/tlschannel/async/AsynchronousTlsChannelGroup.java
index 3202e569..b4dc2716 100644
--- a/src/main/java/tlschannel/async/AsynchronousTlsChannelGroup.java
+++ b/src/main/java/tlschannel/async/AsynchronousTlsChannelGroup.java
@@ -48,7 +48,7 @@ class RegisteredSocket {
final SocketChannel socketChannel;
/**
- * Used to wait until the channel is effectively in the selector (which happens asynchronously
+ * Used to wait until the channel is effectively in the selector (which happens asynchronously)
* to the initial registration.
*/
final CountDownLatch registered = new CountDownLatch(1);
@@ -185,7 +185,7 @@ public AsynchronousTlsChannelGroup(int nThreads) {
selectorThread.start();
}
- /** Creates an instance of this class, using as many thread as available processors. */
+ /** Creates an instance of this class, using as many threads as available processors. */
public AsynchronousTlsChannelGroup() {
this(Runtime.getRuntime().availableProcessors());
}
@@ -356,7 +356,7 @@ private void loop() {
while (shutdown == Shutdown.No
|| shutdown == Shutdown.Wait && (!pendingRegistrations.isEmpty() || !registrations.isEmpty())) {
// most state-changing operations will wake the selector up, however, asynchronous closings
- // of the channels won't, so we have to timeout to allow checking those cases
+ // of the channels won't, so we have to time out to allow checking those cases
int c = selector.select(100); // block
selectionCount.increment();
// avoid unnecessary creation of iterator object
diff --git a/src/main/java/tlschannel/impl/TlsChannelImpl.java b/src/main/java/tlschannel/impl/TlsChannelImpl.java
index d47b96c9..258bcf1c 100644
--- a/src/main/java/tlschannel/impl/TlsChannelImpl.java
+++ b/src/main/java/tlschannel/impl/TlsChannelImpl.java
@@ -466,7 +466,7 @@ public void renegotiate() throws IOException {
* Renegotiation was removed in TLS 1.3. We have to do the check at this level because SSLEngine will not
* check that, and just enter into undefined behavior.
*/
- // relying in hopefully-robust lexicographic ordering of protocol names
+ // relying on hopefully-robust lexicographic ordering of protocol names
if (engine.getSession().getProtocol().compareTo("TLSv1.3") >= 0) {
throw new SSLException("renegotiation not supported in TLS 1.3 or latter");
}
diff --git a/src/test/java/tlschannel/InteroperabilityTest.java b/src/test/java/tlschannel/InteroperabilityTest.java
index 8fda867b..a53554c8 100644
--- a/src/test/java/tlschannel/InteroperabilityTest.java
+++ b/src/test/java/tlschannel/InteroperabilityTest.java
@@ -112,23 +112,23 @@ private void fullDuplexStream(Writer serverWriter, Reader clientReader, Writer c
// "old-io -> old-io (half duplex)
@Test
public void testOldToOldHalfDuplex() throws IOException, InterruptedException {
- SocketGroups.OldOldSocketPair sockerPair = factory.oldOld(Optional.empty());
+ SocketGroups.OldOldSocketPair socketPair = factory.oldOld(Optional.empty());
halfDuplexStream(
- new SSLSocketWriter(sockerPair.server),
- new SocketReader(sockerPair.client),
- new SSLSocketWriter(sockerPair.client),
- new SocketReader(sockerPair.server));
+ new SSLSocketWriter(socketPair.server),
+ new SocketReader(socketPair.client),
+ new SSLSocketWriter(socketPair.client),
+ new SocketReader(socketPair.server));
}
// old-io -> old-io (full duplex)
@Test
public void testOldToOldFullDuplex() throws IOException, InterruptedException {
- SocketGroups.OldOldSocketPair sockerPair = factory.oldOld(Optional.empty());
+ SocketGroups.OldOldSocketPair socketPair = factory.oldOld(Optional.empty());
fullDuplexStream(
- new SSLSocketWriter(sockerPair.server),
- new SocketReader(sockerPair.client),
- new SSLSocketWriter(sockerPair.client),
- new SocketReader(sockerPair.server));
+ new SSLSocketWriter(socketPair.server),
+ new SocketReader(socketPair.client),
+ new SSLSocketWriter(socketPair.client),
+ new SocketReader(socketPair.server));
}
// NIO -> OLD IO
diff --git a/src/test/java/tlschannel/NettySslEngineTest.java b/src/test/java/tlschannel/NettySslEngineTest.java
index 8c742311..27fdd7ae 100644
--- a/src/test/java/tlschannel/NettySslEngineTest.java
+++ b/src/test/java/tlschannel/NettySslEngineTest.java
@@ -12,9 +12,6 @@
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import javax.net.ssl.*;
-import javax.net.ssl.ManagerFactoryParameters;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.TrustManager;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
diff --git a/src/test/java/tlschannel/async/AsyncTimeoutTest.java b/src/test/java/tlschannel/async/AsyncTimeoutTest.java
index 8e837972..efb31fe7 100644
--- a/src/test/java/tlschannel/async/AsyncTimeoutTest.java
+++ b/src/test/java/tlschannel/async/AsyncTimeoutTest.java
@@ -24,8 +24,8 @@
@TestInstance(Lifecycle.PER_CLASS)
public class AsyncTimeoutTest implements AsyncTestBase {
- SslContextFactory sslContextFactory = new SslContextFactory();
- SocketPairFactory factory = new SocketPairFactory(sslContextFactory.defaultContext());
+ final SslContextFactory sslContextFactory = new SslContextFactory();
+ final SocketPairFactory factory = new SocketPairFactory(sslContextFactory.defaultContext());
private static final int bufferSize = 10;
diff --git a/src/test/java/tlschannel/example/AsynchronousChannelServer.java b/src/test/java/tlschannel/example/AsynchronousChannelServer.java
index b2ba7d1c..44abb9a9 100644
--- a/src/test/java/tlschannel/example/AsynchronousChannelServer.java
+++ b/src/test/java/tlschannel/example/AsynchronousChannelServer.java
@@ -67,7 +67,7 @@ public static void main(String[] args) throws IOException, GeneralSecurityExcept
public void completed(Integer result, Object attachment) {
if (result != -1) {
res.flip();
- System.out.print(utf8.decode(res).toString());
+ System.out.print(utf8.decode(res));
res.compact();
// repeat
asyncTlsChannel.read(res, null, this);
diff --git a/src/test/java/tlschannel/example/NonBlockingClient.java b/src/test/java/tlschannel/example/NonBlockingClient.java
index e24f0962..e4bf500a 100644
--- a/src/test/java/tlschannel/example/NonBlockingClient.java
+++ b/src/test/java/tlschannel/example/NonBlockingClient.java
@@ -47,7 +47,7 @@ public static void main(String[] args) throws IOException, GeneralSecurityExcept
// instantiate TlsChannel
TlsChannel tlsChannel = builder.build();
try {
- mainloop:
+ mainLoop:
while (true) {
// loop blocks here
@@ -85,7 +85,7 @@ public static void main(String[] args) throws IOException, GeneralSecurityExcept
}
if (c < 0) {
tlsChannel.close();
- break mainloop;
+ break mainLoop;
}
}
} catch (NeedsReadException e) {
diff --git a/src/test/java/tlschannel/example/NonBlockingServerWithOffLoopTasks.java b/src/test/java/tlschannel/example/NonBlockingServerWithOffLoopTasks.java
index 1a7a7308..f300be2b 100644
--- a/src/test/java/tlschannel/example/NonBlockingServerWithOffLoopTasks.java
+++ b/src/test/java/tlschannel/example/NonBlockingServerWithOffLoopTasks.java
@@ -39,9 +39,9 @@ public class NonBlockingServerWithOffLoopTasks {
private static final Charset utf8 = StandardCharsets.UTF_8;
- private static Executor taskExecutor =
+ private static final Executor taskExecutor =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
- private static Set