blob: 10a64d461907f5d6d81d4c9ff7d4bdc9386f146c [file] [log] [blame]
/*
*
* 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.qpid.proton.engine.impl.ssl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
/**
* A simpler implementation of an SSLEngine that has predictable human-readable output, and that allows us to
* easily trigger {@link Status#BUFFER_OVERFLOW} and {@link Status#BUFFER_UNDERFLOW}.
*
* Using a true SSLEngine for this would be impractical.
*/
public class CapitalisingDummySslEngine implements ProtonSslEngine
{
static final int SHORT_ENCODED_CHUNK_SIZE = 2;
static final int MAX_ENCODED_CHUNK_SIZE = 5;
private static final char ENCODED_TEXT_BEGIN = '<';
private static final char ENCODED_TEXT_END = '>';
private static final char ENCODED_TEXT_INNER_CHAR = '-';
private static final int CLEAR_CHUNK_SIZE = 2;
private static final char CLEARTEXT_PADDING = '_';
private SSLException _nextException;
private int _applicationBufferSize = CLEAR_CHUNK_SIZE;
private int _packetBufferSize = MAX_ENCODED_CHUNK_SIZE;
private int _unwrapCount;
/**
* Converts a_ to <-A->. z_ is special and encodes as <> (to give us packets of different lengths).
* If dst is not sufficiently large ({@value #SHORT_ENCODED_CHUNK_SIZE} in our encoding), we return
* {@link Status#BUFFER_OVERFLOW}, and the src and dst ByteBuffers are unchanged.
*/
@Override
public SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst)
throws SSLException
{
int consumed = 0;
int produced = 0;
final Status resultStatus;
if (src.remaining() >= CLEAR_CHUNK_SIZE)
{
src.mark();
char uncapitalisedChar = (char) src.get();
char underscore = (char) src.get();
validateClear(uncapitalisedChar, underscore);
boolean useShortEncoding = uncapitalisedChar == 'z';
int encodingLength = useShortEncoding ? SHORT_ENCODED_CHUNK_SIZE : MAX_ENCODED_CHUNK_SIZE;
boolean overflow = dst.remaining() < encodingLength;
if (overflow)
{
src.reset();
resultStatus = Status.BUFFER_OVERFLOW;
}
else
{
consumed = CLEAR_CHUNK_SIZE;
char capitalisedChar = Character.toUpperCase(uncapitalisedChar);
dst.put((byte)ENCODED_TEXT_BEGIN);
if (!useShortEncoding)
{
dst.put((byte)ENCODED_TEXT_INNER_CHAR);
dst.put((byte)capitalisedChar);
dst.put((byte)ENCODED_TEXT_INNER_CHAR);
}
dst.put((byte)ENCODED_TEXT_END);
produced = encodingLength;
resultStatus = Status.OK;
}
}
else
{
resultStatus = Status.OK;
}
return new SSLEngineResult(resultStatus, HandshakeStatus.NOT_HANDSHAKING, consumed, produced);
}
/**
* Converts <-A-><-B-><-C-> to a_. <> is special and decodes as z_
* Input such as "<A" will causes a {@link Status#BUFFER_UNDERFLOW} result status.
*/
@Override
public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst)
throws SSLException
{
_unwrapCount++;
if(_nextException != null)
{
throw _nextException;
}
Status resultStatus;
final int consumed;
final int produced;
if (src.remaining() >= SHORT_ENCODED_CHUNK_SIZE)
{
src.mark();
char begin = (char)src.get();
char nextChar = (char)src.get(); // Could be - or >
final int readSoFar = 2;
final char capitalisedChar;
if (nextChar != ENCODED_TEXT_END)
{
int remainingBytesForMaxLengthPacket = MAX_ENCODED_CHUNK_SIZE - readSoFar;
if (src.remaining() < remainingBytesForMaxLengthPacket )
{
src.reset();
resultStatus = Status.BUFFER_UNDERFLOW;
return new SSLEngineResult(resultStatus, HandshakeStatus.NOT_HANDSHAKING, 0, 0);
}
else
{
char beginInner = nextChar;
capitalisedChar = (char)src.get();
char endInner = (char)src.get();
char end = (char)src.get();
consumed = MAX_ENCODED_CHUNK_SIZE;
validateEncoded(begin, beginInner, capitalisedChar, endInner, end);
}
}
else
{
assertEquals("Unexpected begin", Character.toString(ENCODED_TEXT_BEGIN), Character.toString(begin));
capitalisedChar = 'Z';
consumed = SHORT_ENCODED_CHUNK_SIZE;;
}
char lowerCaseChar = Character.toLowerCase(capitalisedChar);
dst.put((byte)lowerCaseChar);
dst.put((byte)CLEARTEXT_PADDING);
produced = CLEAR_CHUNK_SIZE;
resultStatus = Status.OK;
}
else
{
resultStatus = Status.BUFFER_UNDERFLOW;
consumed = 0;
produced = 0;
}
return new SSLEngineResult(resultStatus, HandshakeStatus.NOT_HANDSHAKING, consumed, produced);
}
@Override
public int getEffectiveApplicationBufferSize()
{
return getApplicationBufferSize();
}
private int getApplicationBufferSize()
{
return _applicationBufferSize;
}
@Override
public int getPacketBufferSize()
{
return _packetBufferSize;
}
public void setApplicationBufferSize(int value)
{
_applicationBufferSize = value;
}
public void setPacketBufferSize(int value)
{
_packetBufferSize = value;
}
@Override
public String getProtocol()
{
throw new UnsupportedOperationException();
}
@Override
public HandshakeStatus getHandshakeStatus()
{
throw new UnsupportedOperationException();
}
@Override
public Runnable getDelegatedTask()
{
throw new UnsupportedOperationException();
}
@Override
public String getCipherSuite()
{
throw new UnsupportedOperationException();
}
private void validateEncoded(char begin, char beginInner, char capitalisedChar, char endInner, char end)
{
assertEquals("Unexpected begin", Character.toString(ENCODED_TEXT_BEGIN), Character.toString(begin));
assertEquals("Unexpected begin inner", Character.toString(ENCODED_TEXT_INNER_CHAR), Character.toString(beginInner));
assertEquals("Unexpected end inner", Character.toString(ENCODED_TEXT_INNER_CHAR), Character.toString(endInner));
assertEquals("Unexpected end", Character.toString(ENCODED_TEXT_END), Character.toString(end));
assertTrue("Encoded character " + capitalisedChar + " must be capital", Character.isUpperCase(capitalisedChar));
}
private void validateClear(char uncapitalisedChar, char underscore)
{
assertTrue("Clear text character " + uncapitalisedChar + " must be lowercase", Character.isLowerCase(uncapitalisedChar));
assertEquals("Unexpected clear text pad", Character.toString(CLEARTEXT_PADDING), Character.toString(underscore));
}
@Override
public boolean getUseClientMode()
{
return true;
}
public void rejectNextEncodedPacket(SSLException nextException)
{
_nextException = nextException;
}
int getUnwrapCount() {
return _unwrapCount;
}
}