| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the License. You may obtain a |
| * copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package org.apache.geode.internal.net; |
| |
| import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; |
| import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_TASK; |
| import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP; |
| import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_WRAP; |
| import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; |
| import static javax.net.ssl.SSLEngineResult.Status.BUFFER_OVERFLOW; |
| import static javax.net.ssl.SSLEngineResult.Status.BUFFER_UNDERFLOW; |
| import static javax.net.ssl.SSLEngineResult.Status.CLOSED; |
| import static javax.net.ssl.SSLEngineResult.Status.OK; |
| import static org.assertj.core.api.Assertions.assertThat; |
| import static org.assertj.core.api.Assertions.assertThatThrownBy; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.isA; |
| import static org.mockito.Mockito.atLeast; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import java.net.Socket; |
| import java.net.SocketException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.ClosedChannelException; |
| import java.nio.channels.SocketChannel; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import javax.net.ssl.SSLEngine; |
| import javax.net.ssl.SSLEngineResult; |
| import javax.net.ssl.SSLException; |
| import javax.net.ssl.SSLHandshakeException; |
| import javax.net.ssl.SSLSession; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.mockito.invocation.InvocationOnMock; |
| import org.mockito.stubbing.Answer; |
| |
| import org.apache.geode.GemFireIOException; |
| import org.apache.geode.distributed.internal.DMStats; |
| import org.apache.geode.test.junit.categories.MembershipTest; |
| |
| @Category({MembershipTest.class}) |
| public class NioSslEngineTest { |
| private static final int netBufferSize = 10000; |
| private static final int appBufferSize = 20000; |
| |
| private SSLEngine mockEngine; |
| private DMStats mockStats; |
| private NioSslEngine nioSslEngine; |
| private NioSslEngine spyNioSslEngine; |
| |
| @Before |
| public void setUp() throws Exception { |
| mockEngine = mock(SSLEngine.class); |
| |
| SSLSession mockSession = mock(SSLSession.class); |
| when(mockEngine.getSession()).thenReturn(mockSession); |
| when(mockSession.getPacketBufferSize()).thenReturn(netBufferSize); |
| when(mockSession.getApplicationBufferSize()).thenReturn(appBufferSize); |
| |
| mockStats = mock(DMStats.class); |
| |
| nioSslEngine = new NioSslEngine(mockEngine, new BufferPool(mockStats)); |
| spyNioSslEngine = spy(nioSslEngine); |
| } |
| |
| @Test |
| public void handshake() throws Exception { |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| when(mockChannel.read(any(ByteBuffer.class))).thenReturn(100, 100, 100, 0); |
| Socket mockSocket = mock(Socket.class); |
| when(mockChannel.socket()).thenReturn(mockSocket); |
| when(mockSocket.isClosed()).thenReturn(false); |
| |
| // initial read of handshake status followed by read of handshake status after task execution |
| when(mockEngine.getHandshakeStatus()).thenReturn(NEED_UNWRAP, NEED_WRAP); |
| |
| // interleaved wraps/unwraps/task-execution |
| when(mockEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(OK, NEED_WRAP, 100, 100), |
| new SSLEngineResult(BUFFER_OVERFLOW, NEED_UNWRAP, 0, 0), |
| new SSLEngineResult(OK, NEED_TASK, 100, 0)); |
| |
| when(mockEngine.getDelegatedTask()).thenReturn(() -> { |
| }, (Runnable) null); |
| |
| when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(OK, NEED_UNWRAP, 100, 100), |
| new SSLEngineResult(BUFFER_OVERFLOW, NEED_WRAP, 0, 0), |
| new SSLEngineResult(CLOSED, FINISHED, 100, 0)); |
| |
| spyNioSslEngine.handshake(mockChannel, 10000, ByteBuffer.allocate(netBufferSize / 2)); |
| verify(mockEngine, atLeast(2)).getHandshakeStatus(); |
| verify(mockEngine, times(3)).wrap(any(ByteBuffer.class), any(ByteBuffer.class)); |
| verify(mockEngine, times(3)).unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); |
| verify(spyNioSslEngine, times(2)).expandWriteBuffer(any(BufferPool.BufferType.class), |
| any(ByteBuffer.class), any(Integer.class)); |
| verify(spyNioSslEngine, times(1)).handleBlockingTasks(); |
| verify(mockChannel, times(3)).read(any(ByteBuffer.class)); |
| } |
| |
| @Test |
| public void handshakeUsesBufferParameter() throws Exception { |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| when(mockChannel.read(any(ByteBuffer.class))).thenReturn(100, 100, 100, 0); |
| Socket mockSocket = mock(Socket.class); |
| when(mockChannel.socket()).thenReturn(mockSocket); |
| when(mockSocket.isClosed()).thenReturn(false); |
| |
| // initial read of handshake status followed by read of handshake status after task execution |
| when(mockEngine.getHandshakeStatus()).thenReturn(NEED_UNWRAP, NEED_WRAP); |
| |
| // interleaved wraps/unwraps/task-execution |
| when(mockEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(OK, NEED_WRAP, 100, 100), |
| new SSLEngineResult(BUFFER_OVERFLOW, NEED_UNWRAP, 0, 0), |
| new SSLEngineResult(OK, NEED_TASK, 100, 0)); |
| |
| when(mockEngine.getDelegatedTask()).thenReturn(() -> { |
| }, (Runnable) null); |
| |
| when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(OK, NEED_UNWRAP, 100, 100), |
| new SSLEngineResult(BUFFER_OVERFLOW, NEED_WRAP, 0, 0), |
| new SSLEngineResult(CLOSED, FINISHED, 100, 0)); |
| |
| ByteBuffer byteBuffer = ByteBuffer.allocate(netBufferSize); |
| |
| spyNioSslEngine.handshake(mockChannel, 10000, byteBuffer); |
| |
| assertThat(spyNioSslEngine.handshakeBuffer).isSameAs(byteBuffer); |
| } |
| |
| |
| @Test |
| public void handshakeDetectsClosedSocket() throws Exception { |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| when(mockChannel.read(any(ByteBuffer.class))).thenReturn(100, 100, 100, 0); |
| Socket mockSocket = mock(Socket.class); |
| when(mockChannel.socket()).thenReturn(mockSocket); |
| when(mockSocket.isClosed()).thenReturn(true); |
| |
| // initial read of handshake status followed by read of handshake status after task execution |
| when(mockEngine.getHandshakeStatus()).thenReturn(NEED_UNWRAP); |
| |
| ByteBuffer byteBuffer = ByteBuffer.allocate(netBufferSize); |
| |
| assertThatThrownBy(() -> spyNioSslEngine.handshake(mockChannel, 10000, byteBuffer)) |
| .isInstanceOf( |
| SocketException.class) |
| .hasMessageContaining("handshake terminated"); |
| } |
| |
| @Test |
| public void handshakeDoesNotTerminateWithFinished() throws Exception { |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| when(mockChannel.read(any(ByteBuffer.class))).thenReturn(100, 100, 100, 0); |
| Socket mockSocket = mock(Socket.class); |
| when(mockChannel.socket()).thenReturn(mockSocket); |
| when(mockSocket.isClosed()).thenReturn(false); |
| |
| // initial read of handshake status followed by read of handshake status after task execution |
| when(mockEngine.getHandshakeStatus()).thenReturn(NEED_UNWRAP); |
| |
| // interleaved wraps/unwraps/task-execution |
| when(mockEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(OK, NEED_WRAP, 100, 100)); |
| |
| when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 100, 0)); |
| |
| ByteBuffer byteBuffer = ByteBuffer.allocate(netBufferSize); |
| |
| assertThatThrownBy(() -> spyNioSslEngine.handshake(mockChannel, 10000, byteBuffer)) |
| .isInstanceOf( |
| SSLHandshakeException.class) |
| .hasMessageContaining("SSL Handshake terminated with status"); |
| } |
| |
| |
| @Test |
| public void checkClosed() { |
| nioSslEngine.checkClosed(); |
| } |
| |
| @Test(expected = IllegalStateException.class) |
| public void checkClosedThrows() throws Exception { |
| when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(CLOSED, FINISHED, 0, 100)); |
| nioSslEngine.close(mock(SocketChannel.class)); |
| nioSslEngine.checkClosed(); |
| } |
| |
| @Test |
| public void wrap() throws Exception { |
| // make the application data too big to fit into the engine's encryption buffer |
| ByteBuffer appData = ByteBuffer.allocate(nioSslEngine.myNetData.capacity() + 100); |
| byte[] appBytes = new byte[appData.capacity()]; |
| Arrays.fill(appBytes, (byte) 0x1F); |
| appData.put(appBytes); |
| appData.flip(); |
| |
| // create an engine that will transfer bytes from the application buffer to the encrypted buffer |
| TestSSLEngine testEngine = new TestSSLEngine(); |
| testEngine.addReturnResult( |
| new SSLEngineResult(OK, NEED_TASK, appData.remaining(), appData.remaining())); |
| spyNioSslEngine.engine = testEngine; |
| |
| ByteBuffer wrappedBuffer = spyNioSslEngine.wrap(appData); |
| |
| verify(spyNioSslEngine, times(1)).expandWriteBuffer(any(BufferPool.BufferType.class), |
| any(ByteBuffer.class), any(Integer.class)); |
| appData.flip(); |
| assertThat(wrappedBuffer).isEqualTo(appData); |
| verify(spyNioSslEngine, times(1)).handleBlockingTasks(); |
| } |
| |
| @Test |
| public void wrapFails() { |
| // make the application data too big to fit into the engine's encryption buffer |
| ByteBuffer appData = ByteBuffer.allocate(nioSslEngine.myNetData.capacity() + 100); |
| byte[] appBytes = new byte[appData.capacity()]; |
| Arrays.fill(appBytes, (byte) 0x1F); |
| appData.put(appBytes); |
| appData.flip(); |
| |
| // create an engine that will transfer bytes from the application buffer to the encrypted buffer |
| TestSSLEngine testEngine = new TestSSLEngine(); |
| testEngine.addReturnResult( |
| new SSLEngineResult(CLOSED, NEED_TASK, appData.remaining(), appData.remaining())); |
| spyNioSslEngine.engine = testEngine; |
| |
| assertThatThrownBy(() -> spyNioSslEngine.wrap(appData)).isInstanceOf(SSLException.class) |
| .hasMessageContaining("Error encrypting data"); |
| } |
| |
| @Test |
| public void unwrapWithBufferOverflow() throws Exception { |
| // make the application data too big to fit into the engine's encryption buffer |
| ByteBuffer wrappedData = ByteBuffer.allocate(nioSslEngine.peerAppData.capacity() + 100); |
| byte[] netBytes = new byte[wrappedData.capacity()]; |
| Arrays.fill(netBytes, (byte) 0x1F); |
| wrappedData.put(netBytes); |
| wrappedData.flip(); |
| |
| // create an engine that will transfer bytes from the application buffer to the encrypted buffer |
| TestSSLEngine testEngine = new TestSSLEngine(); |
| spyNioSslEngine.engine = testEngine; |
| |
| testEngine.addReturnResult( |
| new SSLEngineResult(BUFFER_OVERFLOW, NEED_UNWRAP, netBytes.length, netBytes.length), |
| new SSLEngineResult(OK, FINISHED, netBytes.length, netBytes.length)); |
| |
| ByteBuffer unwrappedBuffer = spyNioSslEngine.unwrap(wrappedData); |
| unwrappedBuffer.flip(); |
| |
| verify(spyNioSslEngine, times(2)).expandPeerAppData(any(ByteBuffer.class)); |
| assertThat(unwrappedBuffer).isEqualTo(ByteBuffer.wrap(netBytes)); |
| } |
| |
| @Test |
| public void unwrapWithBufferUnderflow() throws Exception { |
| ByteBuffer wrappedData = ByteBuffer.allocate(nioSslEngine.peerAppData.capacity()); |
| byte[] netBytes = new byte[wrappedData.capacity() / 2]; |
| Arrays.fill(netBytes, (byte) 0x1F); |
| wrappedData.put(netBytes); |
| wrappedData.flip(); |
| |
| // create an engine that will transfer bytes from the application buffer to the encrypted buffer |
| TestSSLEngine testEngine = new TestSSLEngine(); |
| testEngine.addReturnResult(new SSLEngineResult(BUFFER_UNDERFLOW, NEED_TASK, 0, 0)); |
| spyNioSslEngine.engine = testEngine; |
| |
| ByteBuffer unwrappedBuffer = spyNioSslEngine.unwrap(wrappedData); |
| unwrappedBuffer.flip(); |
| assertThat(unwrappedBuffer.remaining()).isEqualTo(0); |
| assertThat(wrappedData.position()).isEqualTo(netBytes.length); |
| } |
| |
| @Test |
| public void unwrapWithDecryptionError() { |
| // make the application data too big to fit into the engine's encryption buffer |
| ByteBuffer wrappedData = ByteBuffer.allocate(nioSslEngine.peerAppData.capacity()); |
| byte[] netBytes = new byte[wrappedData.capacity() / 2]; |
| Arrays.fill(netBytes, (byte) 0x1F); |
| wrappedData.put(netBytes); |
| wrappedData.flip(); |
| |
| // create an engine that will transfer bytes from the application buffer to the encrypted buffer |
| TestSSLEngine testEngine = new TestSSLEngine(); |
| testEngine.addReturnResult(new SSLEngineResult(CLOSED, FINISHED, 0, 0)); |
| spyNioSslEngine.engine = testEngine; |
| |
| assertThatThrownBy(() -> spyNioSslEngine.unwrap(wrappedData)).isInstanceOf(SSLException.class) |
| .hasMessageContaining("Error decrypting data"); |
| } |
| |
| @Test |
| public void ensureUnwrappedCapacity() { |
| ByteBuffer wrappedBuffer = ByteBuffer.allocate(netBufferSize); |
| int requestedCapacity = nioSslEngine.getUnwrappedBuffer(wrappedBuffer).capacity() * 2; |
| ByteBuffer unwrappedBuffer = nioSslEngine.ensureUnwrappedCapacity(requestedCapacity); |
| assertThat(unwrappedBuffer.capacity()).isGreaterThanOrEqualTo(requestedCapacity); |
| } |
| |
| @Test |
| public void close() throws Exception { |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| Socket mockSocket = mock(Socket.class); |
| when(mockChannel.socket()).thenReturn(mockSocket); |
| when(mockSocket.isClosed()).thenReturn(false); |
| |
| when(mockEngine.isOutboundDone()).thenReturn(Boolean.FALSE); |
| when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(CLOSED, FINISHED, 0, 0)); |
| nioSslEngine.close(mockChannel); |
| assertThatThrownBy(() -> nioSslEngine.checkClosed()).isInstanceOf(IllegalStateException.class); |
| nioSslEngine.close(mockChannel); |
| } |
| |
| @Test |
| public void closeWhenUnwrapError() throws Exception { |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| Socket mockSocket = mock(Socket.class); |
| when(mockChannel.socket()).thenReturn(mockSocket); |
| when(mockSocket.isClosed()).thenReturn(true); |
| |
| when(mockEngine.isOutboundDone()).thenReturn(Boolean.FALSE); |
| when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenReturn( |
| new SSLEngineResult(BUFFER_OVERFLOW, FINISHED, 0, 0)); |
| assertThatThrownBy(() -> nioSslEngine.close(mockChannel)).isInstanceOf(GemFireIOException.class) |
| .hasMessageContaining("exception closing SSL session") |
| .hasCauseInstanceOf(SSLException.class); |
| } |
| |
| @Test |
| public void closeWhenSocketWriteError() throws Exception { |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| Socket mockSocket = mock(Socket.class); |
| when(mockChannel.socket()).thenReturn(mockSocket); |
| when(mockSocket.isClosed()).thenReturn(true); |
| |
| when(mockEngine.isOutboundDone()).thenReturn(Boolean.FALSE); |
| when(mockEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))).thenAnswer((x) -> { |
| // give the NioSslEngine something to write on its socket channel, simulating a TLS close |
| // message |
| nioSslEngine.myNetData.put("Goodbye cruel world".getBytes()); |
| return new SSLEngineResult(CLOSED, FINISHED, 0, 0); |
| }); |
| when(mockChannel.write(any(ByteBuffer.class))).thenThrow(new ClosedChannelException()); |
| nioSslEngine.close(mockChannel); |
| verify(mockChannel, times(1)).write(any(ByteBuffer.class)); |
| } |
| |
| @Test |
| public void ensureWrappedCapacityOfSmallMessage() { |
| ByteBuffer buffer = ByteBuffer.allocate(netBufferSize); |
| assertThat( |
| nioSslEngine.ensureWrappedCapacity(10, buffer, BufferPool.BufferType.UNTRACKED)) |
| .isEqualTo(buffer); |
| } |
| |
| @Test |
| public void ensureWrappedCapacityWithNoBuffer() { |
| assertThat( |
| nioSslEngine.ensureWrappedCapacity(10, null, BufferPool.BufferType.UNTRACKED) |
| .capacity()) |
| .isEqualTo(netBufferSize); |
| } |
| |
| @Test |
| public void readAtLeast() throws Exception { |
| final int amountToRead = 150; |
| final int individualRead = 60; |
| final int preexistingBytes = 10; |
| ByteBuffer wrappedBuffer = ByteBuffer.allocate(1000); |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| |
| // force a compaction by making the decoded buffer appear near to being full |
| ByteBuffer unwrappedBuffer = nioSslEngine.peerAppData; |
| unwrappedBuffer.position(unwrappedBuffer.capacity() - individualRead); |
| unwrappedBuffer.limit(unwrappedBuffer.position() + preexistingBytes); |
| |
| // simulate some socket reads |
| when(mockChannel.read(any(ByteBuffer.class))).thenAnswer(new Answer<Integer>() { |
| @Override |
| public Integer answer(InvocationOnMock invocation) throws Throwable { |
| ByteBuffer buffer = invocation.getArgument(0); |
| buffer.position(buffer.position() + individualRead); |
| return individualRead; |
| } |
| }); |
| |
| TestSSLEngine testSSLEngine = new TestSSLEngine(); |
| testSSLEngine.addReturnResult(new SSLEngineResult(OK, NEED_UNWRAP, 0, 0)); |
| nioSslEngine.engine = testSSLEngine; |
| |
| ByteBuffer data = nioSslEngine.readAtLeast(mockChannel, amountToRead, wrappedBuffer); |
| verify(mockChannel, times(3)).read(isA(ByteBuffer.class)); |
| assertThat(data.position()).isEqualTo(0); |
| assertThat(data.limit()).isEqualTo(individualRead * 3 + preexistingBytes); |
| } |
| |
| |
| /** |
| * This tests the case where a message header has been read and part of a message has been |
| * read, but the decoded buffer is too small to hold all of the message. In this case |
| * the readAtLeast method will have to expand the capacity of the decoded buffer and return |
| * the new, expanded, buffer as the method result. |
| */ |
| @Test |
| public void readAtLeastUsingSmallAppBuffer() throws Exception { |
| final int amountToRead = 150; |
| final int individualRead = 60; |
| final int preexistingBytes = 10; |
| ByteBuffer wrappedBuffer = ByteBuffer.allocate(1000); |
| SocketChannel mockChannel = mock(SocketChannel.class); |
| |
| // force buffer expansion by making a small decoded buffer appear near to being full |
| ByteBuffer unwrappedBuffer = ByteBuffer.allocate(100); |
| unwrappedBuffer.position(7).limit(preexistingBytes + 7); // 7 bytes of message header - ignored |
| nioSslEngine.peerAppData = unwrappedBuffer; |
| |
| // simulate some socket reads |
| when(mockChannel.read(any(ByteBuffer.class))).thenAnswer(new Answer<Integer>() { |
| @Override |
| public Integer answer(InvocationOnMock invocation) throws Throwable { |
| ByteBuffer buffer = invocation.getArgument(0); |
| buffer.position(buffer.position() + individualRead); |
| return individualRead; |
| } |
| }); |
| |
| TestSSLEngine testSSLEngine = new TestSSLEngine(); |
| testSSLEngine.addReturnResult( |
| new SSLEngineResult(OK, NEED_UNWRAP, 0, 0), // 10 + 60 bytes = 70 |
| new SSLEngineResult(OK, NEED_UNWRAP, 0, 0), // 70 + 60 bytes = 130 |
| new SSLEngineResult(BUFFER_OVERFLOW, NEED_UNWRAP, 0, 0), // need 190 bytes capacity |
| new SSLEngineResult(OK, NEED_UNWRAP, 0, 0)); // 130 + 60 bytes = 190 |
| nioSslEngine.engine = testSSLEngine; |
| |
| ByteBuffer data = nioSslEngine.readAtLeast(mockChannel, amountToRead, wrappedBuffer); |
| verify(mockChannel, times(3)).read(isA(ByteBuffer.class)); |
| assertThat(data.position()).isEqualTo(0); |
| assertThat(data.limit()).isEqualTo(individualRead * 3 + preexistingBytes); |
| } |
| |
| |
| // TestSSLEngine holds a stack of SSLEngineResults and always copies the |
| // input buffer to the output buffer byte-for-byte in wrap() and unwrap() operations. |
| // We use it in some tests where we need the byte-copying behavior because it's |
| // pretty difficult & cumbersome to implement with Mockito. |
| static class TestSSLEngine extends SSLEngine { |
| |
| private List<SSLEngineResult> returnResults = new ArrayList<>(); |
| |
| private SSLEngineResult nextResult() { |
| SSLEngineResult result = returnResults.remove(0); |
| if (returnResults.isEmpty()) { |
| returnResults.add(result); |
| } |
| return result; |
| } |
| |
| @Override |
| public SSLEngineResult wrap(ByteBuffer[] sources, int i, int i1, ByteBuffer destination) { |
| for (ByteBuffer source : sources) { |
| destination.put(source); |
| } |
| return nextResult(); |
| } |
| |
| @Override |
| public SSLEngineResult unwrap(ByteBuffer source, ByteBuffer[] destinations, int i, int i1) { |
| SSLEngineResult sslEngineResult = nextResult(); |
| if (sslEngineResult.getStatus() != BUFFER_UNDERFLOW |
| && sslEngineResult.getStatus() != BUFFER_OVERFLOW) { |
| destinations[0].put(source); |
| } |
| return sslEngineResult; |
| } |
| |
| @Override |
| public Runnable getDelegatedTask() { |
| return null; |
| } |
| |
| @Override |
| public void closeInbound() {} |
| |
| @Override |
| public boolean isInboundDone() { |
| return false; |
| } |
| |
| @Override |
| public void closeOutbound() {} |
| |
| @Override |
| public boolean isOutboundDone() { |
| return false; |
| } |
| |
| @Override |
| public String[] getSupportedCipherSuites() { |
| return new String[0]; |
| } |
| |
| @Override |
| public String[] getEnabledCipherSuites() { |
| return new String[0]; |
| } |
| |
| @Override |
| public void setEnabledCipherSuites(String[] strings) {} |
| |
| @Override |
| public String[] getSupportedProtocols() { |
| return new String[0]; |
| } |
| |
| @Override |
| public String[] getEnabledProtocols() { |
| return new String[0]; |
| } |
| |
| @Override |
| public void setEnabledProtocols(String[] strings) {} |
| |
| @Override |
| public SSLSession getSession() { |
| return null; |
| } |
| |
| @Override |
| public void beginHandshake() {} |
| |
| @Override |
| public SSLEngineResult.HandshakeStatus getHandshakeStatus() { |
| return null; |
| } |
| |
| @Override |
| public void setUseClientMode(boolean b) {} |
| |
| @Override |
| public boolean getUseClientMode() { |
| return false; |
| } |
| |
| @Override |
| public void setNeedClientAuth(boolean b) {} |
| |
| @Override |
| public boolean getNeedClientAuth() { |
| return false; |
| } |
| |
| @Override |
| public void setWantClientAuth(boolean b) {} |
| |
| @Override |
| public boolean getWantClientAuth() { |
| return false; |
| } |
| |
| @Override |
| public void setEnableSessionCreation(boolean b) {} |
| |
| @Override |
| public boolean getEnableSessionCreation() { |
| return false; |
| } |
| |
| /** |
| * add an engine operation result to be returned by wrap or unwrap. |
| * Like Mockito's thenReturn(), the last return result will repeat forever |
| */ |
| void addReturnResult(SSLEngineResult... sslEngineResult) { |
| for (SSLEngineResult result : sslEngineResult) { |
| returnResults.add(result); |
| } |
| } |
| } |
| } |