diff --git a/src/main/java/net/spy/memcached/v2/vo/BKey.java b/src/main/java/net/spy/memcached/v2/vo/BKey.java index 09290849e..add5226a4 100644 --- a/src/main/java/net/spy/memcached/v2/vo/BKey.java +++ b/src/main/java/net/spy/memcached/v2/vo/BKey.java @@ -8,6 +8,15 @@ public final class BKey implements Comparable { + public static final BKey MIN_BYTE_ARRAY_BKEY = new BKey(new byte[]{(byte) 0x00}); + public static final BKey MAX_BYTE_ARRAY_BKEY; + + static { + byte[] bytes = new byte[31]; + Arrays.fill(bytes, (byte) 0xFF); + MAX_BYTE_ARRAY_BKEY = new BKey(bytes); + } + public enum BKeyType { BYTE_ARRAY, LONG diff --git a/src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java b/src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java index cd0bd28fe..7adcde08a 100644 --- a/src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java +++ b/src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java @@ -276,6 +276,28 @@ void bopGetWithDeleteAndDropIfEmpty() throws Exception { .get(300, TimeUnit.MILLISECONDS); } + @Test + void bopGetRangeWithByteArrayBKey() throws Exception { + // given + String key = keys.get(0); + BKey bKey = BKey.of(new byte[]{1, 2, 3}); + String value = "value1"; + + // when + async.bopInsert(key, new BTreeElement<>(bKey, value, null), CreateAttributes.DEFAULT) + .thenCompose(result -> async.bopGet(key, + BKey.MIN_BYTE_ARRAY_BKEY, BKey.MAX_BYTE_ARRAY_BKEY, BopRangeGetArgs.DEFAULT)) + // then + .thenAccept(elements -> { + assertNotNull(elements); + assertEquals(1, elements.getElements().size()); + assertEquals(bKey, elements.getElements().get(0).getBKey()); + assertEquals(value, elements.getElements().get(0).getValue()); + }) + .toCompletableFuture() + .get(300, TimeUnit.MILLISECONDS); + } + @Test void bopGetRangeNotExistKey() { // given diff --git a/src/test/java/net/spy/memcached/v2/vo/BKeyTest.java b/src/test/java/net/spy/memcached/v2/vo/BKeyTest.java new file mode 100644 index 000000000..0b1391a5f --- /dev/null +++ b/src/test/java/net/spy/memcached/v2/vo/BKeyTest.java @@ -0,0 +1,77 @@ +package net.spy.memcached.v2.vo; + +import java.util.Arrays; + +import net.spy.memcached.collection.BKeyObject; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BKeyTest { + + private static final int MAX_LENGTH = 31; + private static final String MAX_BKEY_HEX = + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + + @Test + void minByteArrayBKeyIsSingleZeroByte() { + assertEquals(BKey.BKeyType.BYTE_ARRAY, BKey.MIN_BYTE_ARRAY_BKEY.getType()); + assertArrayEquals(new byte[]{(byte) 0x00}, (byte[]) BKey.MIN_BYTE_ARRAY_BKEY.getData()); + assertEquals("0x00", BKey.MIN_BYTE_ARRAY_BKEY.toString()); + } + + @Test + void maxByteArrayBKeyIsMaxLengthOfFilledBytes() { + byte[] data = (byte[]) BKey.MAX_BYTE_ARRAY_BKEY.getData(); + + assertEquals(BKey.BKeyType.BYTE_ARRAY, BKey.MAX_BYTE_ARRAY_BKEY.getType()); + assertEquals(MAX_LENGTH, data.length); + for (byte b : data) { + assertEquals((byte) 0xFF, b); + } + assertEquals(MAX_BKEY_HEX, BKey.MAX_BYTE_ARRAY_BKEY.toString()); + } + + @Test + void minIsLessThanMax() { + assertTrue(BKey.MIN_BYTE_ARRAY_BKEY.compareTo(BKey.MAX_BYTE_ARRAY_BKEY) < 0); + } + + @Test + void rejectsByteArrayShorterThanMinLength() { + assertThrows(IllegalArgumentException.class, () -> BKey.of(new byte[0])); + assertThrows(IllegalArgumentException.class, () -> BKey.of("0x")); + } + + @Test + void rejectsByteArrayLongerThanMaxLength() { + assertThrows(IllegalArgumentException.class, () -> BKey.of(new byte[MAX_LENGTH + 1])); + } + + @Test + void toBKeyObjectDoesNotExposeSharedConstantData() { + BKeyObject leaked = BKey.MAX_BYTE_ARRAY_BKEY.toBKeyObject(); + Arrays.fill(leaked.getByteArrayBKeyRaw(), (byte) 0x00); + + for (byte b : (byte[]) BKey.MAX_BYTE_ARRAY_BKEY.getData()) { + assertEquals((byte) 0xFF, b); + } + } + + @Test + void getDataDoesNotExposeSharedConstantData() { + Arrays.fill((byte[]) BKey.MIN_BYTE_ARRAY_BKEY.getData(), (byte) 0xFF); + + assertArrayEquals(new byte[]{(byte) 0x00}, (byte[]) BKey.MIN_BYTE_ARRAY_BKEY.getData()); + } + + @Test + void boundsSurviveBKeyObjectRoundTrip() { + assertEquals(BKey.MIN_BYTE_ARRAY_BKEY, BKey.of(BKey.MIN_BYTE_ARRAY_BKEY.toBKeyObject())); + assertEquals(BKey.MAX_BYTE_ARRAY_BKEY, BKey.of(BKey.MAX_BYTE_ARRAY_BKEY.toBKeyObject())); + } +}