validation that generates less garbage. patch by gdusbabek, reviewed by jbellis. CASSANDRA-1814

git-svn-id: https://svn.apache.org/repos/asf/cassandra/branches/cassandra-0.7@1043985 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gary Dusbabek 2010-12-09 14:38:50 +00:00
parent 55d8d53876
commit a4ebfe322c
10 changed files with 187 additions and 4 deletions

View File

@ -48,10 +48,7 @@ public abstract class AbstractType implements Comparator<ByteBuffer>
}
/* validate that the byte array is a valid sequence for the type we are supposed to be comparing */
public void validate(ByteBuffer bytes)
{
getString(bytes);
}
public abstract void validate(ByteBuffer bytes) throws MarshalException;
public Comparator<ByteBuffer> getReverseComparator()
{

View File

@ -43,4 +43,15 @@ public class AsciiType extends BytesType
{
return ByteBuffer.wrap(source.getBytes(Charsets.US_ASCII));
}
public void validate(ByteBuffer bytes) throws MarshalException
{
// 0-127
for (int i = 0; i < bytes.remaining(); i++)
{
byte b = bytes.array()[bytes.arrayOffset() + bytes.position() + i];
if (b < 0 || b > 127)
throw new MarshalException("Invalid byte for ascii: " + Byte.toString(b));
}
}
}

View File

@ -51,4 +51,9 @@ public class BytesType extends AbstractType
{
return ByteBuffer.wrap(source.getBytes());
}
public void validate(ByteBuffer bytes) throws MarshalException
{
// all bytes are legal.
}
}

View File

@ -139,4 +139,9 @@ public final class IntegerType extends AbstractType
return ByteBuffer.wrap(integerType.toByteArray());
}
public void validate(ByteBuffer bytes) throws MarshalException
{
// no invalid integers.
}
}

View File

@ -63,4 +63,11 @@ public class LexicalUUIDType extends AbstractType
{
return ByteBuffer.wrap(UUIDGen.decompose(UUID.fromString(source)));
}
public void validate(ByteBuffer bytes) throws MarshalException
{
if (bytes.remaining() != 16 && bytes.remaining() != 0)
throw new MarshalException(String.format("LexicalUUID should be 16 or 0 bytes (%d)", bytes.remaining()));
// not sure what the version should be for this.
}
}

View File

@ -51,4 +51,9 @@ public class LocalByPartionerType<T extends Token> extends AbstractType
{
return partitioner.decorateKey(o1).compareTo(partitioner.decorateKey(o2));
}
public void validate(ByteBuffer bytes) throws MarshalException
{
throw new IllegalStateException("You shouldn't be validating this.");
}
}

View File

@ -81,4 +81,10 @@ public class LongType extends AbstractType
return FBUtilities.toByteBuffer(longType);
}
public void validate(ByteBuffer bytes) throws MarshalException
{
if (bytes.remaining() != 8 && bytes.remaining() != 0)
throw new MarshalException(String.format("Expected 8 or 0 byte long (%d)", bytes.remaining()));
}
}

View File

@ -98,4 +98,18 @@ public class TimeUUIDType extends AbstractType
return ByteBuffer.wrap(UUIDGen.decompose(uuid));
}
public void validate(ByteBuffer bytes) throws MarshalException
{
if (bytes.remaining() != 16 && bytes.remaining() != 0)
throw new MarshalException(String.format("TimeUUID should be 16 or 0 bytes (%d)", bytes.remaining()));
ByteBuffer slice = bytes.slice();
// version is bits 4-7 of byte 6.
if (bytes.remaining() > 0)
{
slice.position(6);
if ((slice.get() & 0x0f) != 1)
throw new MarshalException("Invalid version for TimeUUID type.");
}
}
}

View File

@ -49,4 +49,127 @@ public class UTF8Type extends BytesType
{
return ByteBuffer.wrap(source.getBytes(Charsets.UTF_8));
}
public void validate(ByteBuffer bytes) throws MarshalException
{
if (!UTF8Validator.validate(bytes.slice()))
throw new MarshalException("String didn't validate.");
}
static class UTF8Validator
{
enum State {
START,
TWO,
TWO_80,
THREE_a0bf,
THREE_80bf_1,
THREE_80bf_2,
FOUR_90bf,
FOUR_80bf_3,
};
// since we're not converting to java strings, we don't need to worry about converting to surrogates.
// buf has already been sliced/duplicated.
static boolean validate(ByteBuffer buf)
{
int b = 0;
State state = State.START;
while (buf.remaining() > 0)
{
b = buf.get();
switch (state)
{
case START:
if (b >= 0)
{
// ascii, state stays start.
if (b > 127)
return false;
}
else if ((b >> 5) == -2)
{
// validate first byte of 2-byte char, 0xc2-0xdf
if (b == (byte) 0xc0)
// speical case: modified utf8 null is 0xc080.
state = State.TWO_80;
else if ((b & 0x1e) == 0)
return false;
state = State.TWO;
}
else if ((b >> 4) == -2)
{
// 3 bytes. first byte will be 0xe0 or 0xe1-0xef. handling of second byte will differ.
// so 0xe0,0xa0-0xbf,0x80-0xbf or 0xe1-0xef,0x80-0xbf,0x80-0xbf.
if (b == (byte)0xe0)
state = State.THREE_a0bf;
else
state = State.THREE_80bf_2;
break;
}
else if ((b >> 3) == -2)
{
// 4 bytes. this is where the fun starts.
if (b == (byte)0xf0)
// 0xf0, 0x90-0xbf, 0x80-0xbf, 0x80-0xbf
state = State.FOUR_90bf;
else if (b == (byte)0xf4)
// 0xf4, 0x80-0xbf, 0x80-0xbf, 0x80-0xbf
state = State.FOUR_80bf_3;
else
// 0xf1-0xf3, 0x80-0xbf, 0x80-0xbf, 0x80-0xbf
state = State.FOUR_80bf_3;
break;
}
else
return false; // malformed.
break;
case TWO:
// validate second byte of 2-byte char, 0x80-0xbf
if ((b & 0xc0) != 0x80)
return false;
state = State.START;
break;
case TWO_80:
if (b != (byte)0x80)
return false;
state = State.START;
break;
case THREE_a0bf:
if ((b & 0xe0) == 0x80)
return false;
state = State.THREE_80bf_1;
break;
case THREE_80bf_1:
// expecting 0x80-0xbf
if ((b & 0xc0) != 0x80)
return false;
state = State.START;
break;
case THREE_80bf_2:
// expecting 0x80-bf and then another of the same.
if ((b & 0xc0) != 0x80)
return false;
state = State.THREE_80bf_1;
break;
case FOUR_90bf:
// expecting 0x90-bf. 2nd byte of 4byte sequence. after that it should degrade to 80-bf,80-bf (like 3byte seq).
if ((b & 0x30) == 0)
return false;
state = State.THREE_80bf_2;
break;
case FOUR_80bf_3:
// expecting 0x80-bf 3 times. degenerates to THREE_80bf_2.
if ((b & 0xc0) != 0x80)
return false;
state = State.THREE_80bf_2;
break;
default:
return false; // invalid state.
}
}
// if state != start, we've got underflow. that's an error.
return state == State.START;
}
}
}

View File

@ -40,6 +40,8 @@ public class TimeUUIDTypeTest
UUID a = generator.generateTimeBasedUUID();
UUID b = new UUID(a.asByteArray());
timeUUIDType.validate(ByteBuffer.wrap(a.asByteArray()));
timeUUIDType.validate(ByteBuffer.wrap(b.asByteArray()));
assertEquals(0, timeUUIDType.compare(ByteBuffer.wrap(a.asByteArray()), ByteBuffer.wrap(b.asByteArray())));
}
@ -50,6 +52,10 @@ public class TimeUUIDTypeTest
UUID b = generator.generateTimeBasedUUID();
UUID c = generator.generateTimeBasedUUID();
timeUUIDType.validate(ByteBuffer.wrap(a.asByteArray()));
timeUUIDType.validate(ByteBuffer.wrap(b.asByteArray()));
timeUUIDType.validate(ByteBuffer.wrap(c.asByteArray()));
assert timeUUIDType.compare(ByteBuffer.wrap(a.asByteArray()), ByteBuffer.wrap(b.asByteArray())) < 0;
assert timeUUIDType.compare(ByteBuffer.wrap(b.asByteArray()), ByteBuffer.wrap(c.asByteArray())) < 0;
assert timeUUIDType.compare(ByteBuffer.wrap(a.asByteArray()), ByteBuffer.wrap(c.asByteArray())) < 0;
@ -61,6 +67,10 @@ public class TimeUUIDTypeTest
UUID a = generator.generateTimeBasedUUID();
UUID b = generator.generateTimeBasedUUID();
UUID c = generator.generateTimeBasedUUID();
timeUUIDType.validate(ByteBuffer.wrap(a.asByteArray()));
timeUUIDType.validate(ByteBuffer.wrap(b.asByteArray()));
timeUUIDType.validate(ByteBuffer.wrap(c.asByteArray()));
assert timeUUIDType.compare(ByteBuffer.wrap(c.asByteArray()), ByteBuffer.wrap(b.asByteArray())) > 0;
assert timeUUIDType.compare(ByteBuffer.wrap(b.asByteArray()), ByteBuffer.wrap(a.asByteArray())) > 0;