diff --git a/at_client/README.md b/at_client/README.md index 44b277e7..f4c93bff 100644 --- a/at_client/README.md +++ b/at_client/README.md @@ -59,6 +59,7 @@ The latest snapshot version can be added as a maven dependency like this... The SDK currently depends on the following * Bouncycastle (encryption, decryption, and cryptography utilities) +* Netty (networking) * Jackson (support for JSON and YAML encoding and decoding) * Pico CLI (lightweight command line interface framework) * Slf4j api (Simple logging facade that can be bound a variety of logging diff --git a/at_client/src/main/java/org/atsign/client/api/Metadata.java b/at_client/src/main/java/org/atsign/client/api/Metadata.java index 48f66d8f..117336b8 100644 --- a/at_client/src/main/java/org/atsign/client/api/Metadata.java +++ b/at_client/src/main/java/org/atsign/client/api/Metadata.java @@ -356,4 +356,8 @@ public static boolean setIvNonceIfNotNull(MetadataBuilder builder, String value) return false; } } + + public static boolean isBinary(Metadata metadata) { + return metadata.isBinary() != null && metadata.isBinary(); + } } diff --git a/at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java b/at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java index 4bb1696c..f97eb263 100644 --- a/at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java +++ b/at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java @@ -11,13 +11,10 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; -import org.atsign.client.api.AtClient; -import org.atsign.client.api.AtCommandExecutor; +import org.atsign.client.api.*; import org.atsign.client.api.AtEvents.AtEventBus; import org.atsign.client.api.AtEvents.AtEventListener; import org.atsign.client.api.AtEvents.AtEventType; -import org.atsign.client.api.AtKeys; -import org.atsign.client.api.AtSign; import org.atsign.client.api.Keys.AtKey; import org.atsign.client.api.Keys.PublicKey; import org.atsign.client.api.Keys.SelfKey; @@ -28,6 +25,7 @@ import lombok.Builder; import lombok.extern.slf4j.Slf4j; +import org.atsign.client.impl.util.Base2e15Utils; /** * Implementation of an {@link AtClient} which uses a {@link AtCommandExecutor} and @@ -144,7 +142,7 @@ public CompletableFuture get(SharedKey sharedKey) { @Override public CompletableFuture getBinary(SharedKey sharedKey) { - throw new UnsupportedOperationException("to be implemented"); + return wrapAsync(() -> Base2e15Utils.decode(SharedKeyCommands.get(executor, atSign, keys, sharedKey, true))); } @Override @@ -164,7 +162,7 @@ public CompletableFuture get(SelfKey selfKey) { @Override public CompletableFuture getBinary(SelfKey selfKey) { - throw new UnsupportedOperationException("to be implemented"); + return wrapAsync(() -> Base2e15Utils.decode(SelfKeyCommands.get(executor, atSign, keys, selfKey, true))); } @Override @@ -189,12 +187,12 @@ public CompletableFuture get(PublicKey publicKey, GetRequestOptions opti @Override public CompletableFuture getBinary(PublicKey publicKey) { - throw new UnsupportedOperationException("to be implemented"); + return wrapAsync(() -> Base2e15Utils.decode(PublicKeyCommands.get(executor, atSign, publicKey, true, null))); } @Override public CompletableFuture getBinary(PublicKey publicKey, GetRequestOptions options) { - throw new UnsupportedOperationException("to be implemented"); + return wrapAsync(() -> Base2e15Utils.decode(PublicKeyCommands.get(executor, atSign, publicKey, true, options))); } @Override @@ -209,17 +207,17 @@ public CompletableFuture delete(PublicKey publicKey) { @Override public CompletableFuture put(SharedKey sharedKey, byte[] value) { - throw new UnsupportedOperationException("to be implemented"); + return put(setIsBinary(sharedKey), Base2e15Utils.encode(value)); } @Override public CompletableFuture put(SelfKey selfKey, byte[] value) { - throw new UnsupportedOperationException("to be implemented"); + return put(setIsBinary(selfKey), Base2e15Utils.encode(value)); } @Override public CompletableFuture put(PublicKey publicKey, byte[] value) { - throw new UnsupportedOperationException("to be implemented"); + return put(setIsBinary(publicKey), Base2e15Utils.encode(value)); } @Override @@ -284,7 +282,7 @@ private void onUpdateNotification(Map eventData) throws AtExcept } /** - * A runnable command which returns a value but can throw {@link AtException}s or execution + * A runnable command which returns a String value but can throw {@link AtException}s or execution * exceptions */ public interface AtCommandThatReturnsString { @@ -301,6 +299,25 @@ private static CompletableFuture wrapAsync(AtCommandThatReturnsString co }); } + /** + * A runnable command which returns a byte array value but can throw {@link AtException}s or + * execution + * exceptions + */ + public interface AtCommandThatReturnsByteArray { + byte[] run() throws AtException, ExecutionException, InterruptedException; + } + + private static CompletableFuture wrapAsync(AtCommandThatReturnsByteArray command) { + return CompletableFuture.supplyAsync(() -> { + try { + return command.run(); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + /** * A runnable command which does NOT return a value but can throw {@link AtException}s or execution * exceptions @@ -319,4 +336,12 @@ private static CompletableFuture wrapAsync(AtCommandThatReturnsVoid comman } }); } + + private static T setIsBinary(T key) { + if (!Metadata.isBinary(key.metadata())) { + key.overwriteMetadata(key.metadata().toBuilder().isBinary(true).build()); + } + return key; + } + } diff --git a/at_client/src/main/java/org/atsign/client/impl/commands/PublicKeyCommands.java b/at_client/src/main/java/org/atsign/client/impl/commands/PublicKeyCommands.java index 45905494..6ce4df93 100644 --- a/at_client/src/main/java/org/atsign/client/impl/commands/PublicKeyCommands.java +++ b/at_client/src/main/java/org/atsign/client/impl/commands/PublicKeyCommands.java @@ -4,6 +4,7 @@ import static org.atsign.client.impl.commands.DataResponses.matchDataInt; import static org.atsign.client.impl.commands.DataResponses.matchLookupResponse; import static org.atsign.client.impl.commands.ErrorResponses.throwExceptionIfError; +import static org.atsign.client.impl.common.Preconditions.checkTrue; import static org.atsign.client.impl.util.EncryptionUtils.signSHA256RSA; import java.util.concurrent.ExecutionException; @@ -34,10 +35,29 @@ public class PublicKeyCommands { */ public static String get(AtCommandExecutor executor, AtSign atSign, PublicKey key, GetRequestOptions options) throws AtException { + return get(executor, atSign, key, false, options); + } + + /** + * Get the String value associated with a public key. + * + * @param executor The {@link AtCommandExecutor} to use. + * @param atSign The AtSign that corresponds to the executor. + * @param key The {@link PublicKey} + * @param options If set then can be used to bypass caches. + * @return The associated value. + * @throws AtException If any of the commands fail or the key does not exist. + */ + public static String get(AtCommandExecutor executor, + AtSign atSign, + PublicKey key, + boolean expectBinary, + GetRequestOptions options) + throws AtException { if (atSign.equals(key.sharedBy())) { - return getSharedByMe(executor, key); + return getSharedByMe(executor, key, expectBinary); } else { - return getSharedByOther(executor, key, options); + return getSharedByOther(executor, key, expectBinary, options); } } @@ -47,10 +67,14 @@ public static String get(AtCommandExecutor executor, AtSign atSign, PublicKey ke * * @param executor The {@link AtCommandExecutor} to use. * @param key The {@link PublicKey} + * @param expectBinary If true then metadata will be checked * @return The associated value. * @throws AtException If any of the commands fail or the key does not exist. */ - public static String getSharedByMe(AtCommandExecutor executor, PublicKey key) throws AtException { + public static String getSharedByMe(AtCommandExecutor executor, + PublicKey key, + boolean expectBinary) + throws AtException { try { // send a local lookup command and decode the response @@ -61,6 +85,10 @@ public static String getSharedByMe(AtCommandExecutor executor, PublicKey key) th String llookupResponse = executor.sendSync(llookupCommand); LookupResponse response = matchLookupResponse(throwExceptionIfError(llookupResponse)); + if (expectBinary) { + checkTrue(Metadata.isBinary(response.metaData), "metadata.isBinary not set to true"); + } + // set isCached in metadata if (response.key.contains("cached:")) { Metadata metadata = response.metaData.toBuilder().isCached(true).build(); @@ -82,10 +110,15 @@ public static String getSharedByMe(AtCommandExecutor executor, PublicKey key) th * * @param executor The {@link AtCommandExecutor} to use. * @param key The {@link PublicKey} + * @param expectBinary If true then metadata will be checked + * @param options If set then can be used to bypass caches. * @return The associated value. * @throws AtException If any of the commands fail or the key does not exist. */ - public static String getSharedByOther(AtCommandExecutor executor, PublicKey key, GetRequestOptions options) + public static String getSharedByOther(AtCommandExecutor executor, + PublicKey key, + boolean expectBinary, + GetRequestOptions options) throws AtException { try { @@ -98,6 +131,10 @@ public static String getSharedByOther(AtCommandExecutor executor, PublicKey key, String plookupResponse = executor.sendSync(plookupCommand); LookupResponse response = matchLookupResponse(throwExceptionIfError(plookupResponse)); + if (expectBinary) { + checkTrue(Metadata.isBinary(response.metaData), "isBinary not set to true"); + } + // set isCached in metadata if (response.key.contains("cached:")) { Metadata metadata = response.metaData.toBuilder().isCached(true).build(); diff --git a/at_client/src/main/java/org/atsign/client/impl/commands/SelfKeyCommands.java b/at_client/src/main/java/org/atsign/client/impl/commands/SelfKeyCommands.java index 7e4c8688..bf967a27 100644 --- a/at_client/src/main/java/org/atsign/client/impl/commands/SelfKeyCommands.java +++ b/at_client/src/main/java/org/atsign/client/impl/commands/SelfKeyCommands.java @@ -5,6 +5,7 @@ import static org.atsign.client.impl.commands.DataResponses.matchLookupResponse; import static org.atsign.client.impl.commands.ErrorResponses.throwExceptionIfError; import static org.atsign.client.impl.common.Preconditions.checkNotNull; +import static org.atsign.client.impl.common.Preconditions.checkTrue; import static org.atsign.client.impl.util.EncryptionUtils.*; import java.util.concurrent.ExecutionException; @@ -30,6 +31,26 @@ public class SelfKeyCommands { * @throws AtException If any of the commands fail or the key does not exist. */ public static String get(AtCommandExecutor executor, AtSign atSign, AtKeys keys, SelfKey key) throws AtException { + return get(executor, atSign, keys, key, false); + } + + /** + * Get the String value associated with a self key. The value will be decrypted with the AtSign's + * Self Encryption Key. + * + * @param executor The {@link AtCommandExecutor} to use. + * @param atSign The AtSign that corresponds to the executor. + * @param key The {@link Keys.SelfKey} + * @param expectBinary If true then metadata will be checked + * @return The associated value. + * @throws AtException If any of the commands fail or the key does not exist. + */ + public static String get(AtCommandExecutor executor, + AtSign atSign, + AtKeys keys, + SelfKey key, + boolean expectBinary) + throws AtException { checkAtSignCanGet(atSign, key); try { @@ -38,6 +59,10 @@ public static String get(AtCommandExecutor executor, AtSign atSign, AtKeys keys, String llookupResponse = executor.sendSync(llookupCommand); LookupResponse response = matchLookupResponse(throwExceptionIfError(llookupResponse)); + if (expectBinary) { + checkTrue(Metadata.isBinary(response.metaData), "metadata.isBinary not set to true"); + } + // decrypt with my self encrypt key String selfEncryptionKey = keys.getSelfEncryptKey(); String iv = checkNotNull(response.metaData.ivNonce(), "ivNonce is null"); diff --git a/at_client/src/main/java/org/atsign/client/impl/commands/SharedKeyCommands.java b/at_client/src/main/java/org/atsign/client/impl/commands/SharedKeyCommands.java index d84251f7..a70a9590 100644 --- a/at_client/src/main/java/org/atsign/client/impl/commands/SharedKeyCommands.java +++ b/at_client/src/main/java/org/atsign/client/impl/commands/SharedKeyCommands.java @@ -1,10 +1,11 @@ package org.atsign.client.impl.commands; import static org.atsign.client.api.AtKeyNames.toSharedByMeKeyName; +import static org.atsign.client.impl.commands.CommandBuilders.LookupOperation.all; import static org.atsign.client.impl.commands.DataResponses.*; import static org.atsign.client.impl.commands.ErrorResponses.throwExceptionIfError; -import static org.atsign.client.impl.commands.CommandBuilders.LookupOperation.all; import static org.atsign.client.impl.common.Preconditions.checkNotNull; +import static org.atsign.client.impl.common.Preconditions.checkTrue; import static org.atsign.client.impl.util.EncryptionUtils.*; import java.util.concurrent.ExecutionException; @@ -34,13 +35,30 @@ public class SharedKeyCommands { * @throws AtException If any of the commands fail or the key does not exist. */ - public static String get(AtCommandExecutor executor, AtSign atSign, AtKeys keys, SharedKey key) + public static String get(AtCommandExecutor executor, AtSign atSign, AtKeys keys, SharedKey key) throws AtException { + return get(executor, atSign, keys, key, false); + } + + /** + * Get the String value associated with a shared key. The value will be encrypted with a specific + * key for the sharedBy-sharedWith relationship. + * + * @param executor The {@link AtCommandExecutor} to use. + * @param atSign The AtSign that corresponds to the executor. + * @param key The {@link Keys.SharedKey} + * @param expectedBinary If true then lookup metadata will be checked + * @return The associated value. + * @throws AtException If any of the commands fail or the key does not exist. + */ + + public static String get(AtCommandExecutor executor, AtSign atSign, AtKeys keys, SharedKey key, + boolean expectedBinary) throws AtException { checkAtSignCanGet(atSign, key); if (key.sharedBy().equals(atSign)) { - return getSharedByMe(executor, keys, key); + return getSharedByMe(executor, keys, key, expectedBinary); } else if (key.sharedWith().equals(atSign)) { - return getSharedByOther(executor, keys, key); + return getSharedByOther(executor, keys, key, expectedBinary); } else { throw new IllegalArgumentException("the client atsign is neither the sharedBy or sharedWith"); } @@ -85,13 +103,18 @@ public static void put(AtCommandExecutor executor, AtSign atSign, AtKeys keys, S } } - private static String getSharedByMe(AtCommandExecutor executor, AtKeys keys, SharedKey key) throws AtException { + private static String getSharedByMe(AtCommandExecutor executor, AtKeys keys, SharedKey key, boolean expectBinary) + throws AtException { try { // send local lookup command and decode String llookupCommand = CommandBuilders.llookupCommandBuilder().key(key).operation(all).build(); LookupResponse llookupResponse = matchLookupResponse(throwExceptionIfError(executor.sendSync(llookupCommand))); + if (expectBinary) { + checkTrue(Metadata.isBinary(llookupResponse.metaData), "metadata.isBinary not set to true"); + } + // get my encrypt key for sharedBy sharedWith String aesKey = checkNotNull(getEncryptKeySharedByMe(executor, keys, key), key + " not found"); @@ -103,13 +126,18 @@ private static String getSharedByMe(AtCommandExecutor executor, AtKeys keys, Sha } } - private static String getSharedByOther(AtCommandExecutor executor, AtKeys keys, SharedKey key) throws AtException { + private static String getSharedByOther(AtCommandExecutor executor, AtKeys keys, SharedKey key, boolean expectBinary) + throws AtException { try { // send lookup command and decode String lookupCommand = CommandBuilders.lookupCommandBuilder().key(key).operation(all).build(); LookupResponse lookupResponse = matchLookupResponse(throwExceptionIfError(executor.sendSync(lookupCommand))); + if (expectBinary) { + checkTrue(Metadata.isBinary(lookupResponse.metaData), "isBinary not set to true"); + } + // get my encrypt key for sharedBy sharedWith String shareEncryptionKey = getEncryptKeySharedByOther(executor, keys, key); diff --git a/at_client/src/main/java/org/atsign/client/impl/util/Base2e15Utils.java b/at_client/src/main/java/org/atsign/client/impl/util/Base2e15Utils.java new file mode 100644 index 00000000..6631b79e --- /dev/null +++ b/at_client/src/main/java/org/atsign/client/impl/util/Base2e15Utils.java @@ -0,0 +1,239 @@ +package org.atsign.client.impl.util; + +import static java.lang.String.format; +import static org.atsign.client.impl.common.Preconditions.checkNotNull; + +import lombok.Builder; +import lombok.Value; + + +/** + * Base2e15 - Binary-to-text encoding using Unicode characters. + * Each character encodes 15 bits of data, except the last character which + * encodes either 7 bits (partial byte) or 15 bits (full).
+ * 15-bit values are encoded into the following Unicode code point ranges
+ * {@code 0x0000 - 0x1935 -> U+3480 - U+4DB5} (CJK Unified Ideographs Extension A)
+ * {@code 0x1936 - 0x545B -> U+4E00 - U+8925} (CJK Unified Ideographs)
+ * {@code 0x545C - 0x7FFF -> U+AC00 - U+D7A3} (Hangul Syllables)
+ * 7-bit values are encoded into the following Unicode codepoint range
+ * {@code 0x00 - 0x7F -> U+3400 - U+347F} (CJK Unified Ideographs Extension A) + * + */ +public class Base2e15Utils { + + private static final int MASK_15_BITS = 0x7FFF; + + private static final int MASK_7_BITS = 0x7F; + + private static final BitsToCodePointMapper MAPPER15 = new CompositeRange( + MASK_15_BITS, + Range.builder() + .bitsStart(0x0000).bitsEnd(0x1935) + .codePointsStart(0x3480).codePointsEnd(0x4DB5) + .build(), + Range.builder() + .bitsStart(0x1936).bitsEnd(0x545B) + .codePointsStart(0x4E00).codePointsEnd(0x8925) + .build(), + Range.builder() + .bitsStart(0x545C).bitsEnd(0x7FFF) + .codePointsStart(0xAC00).codePointsEnd(0xD7A3) + .build()); + + private static final BitsToCodePointMapper MAPPER7 = + Range.builder() + .mask(MASK_7_BITS) + .bitsStart(0x00).bitsEnd(0x7F) + .codePointsStart(0x3400).codePointsEnd(0x347F) + .build(); + + private static final int BYTE_LENGTH = 8; + + private static final int MASK_BYTE = 0xFF; + + private static final byte[] EMPTY_BYTES = new byte[0]; + private static final String EMPTY_STRING = ""; + + public static String encode(byte[] bytes) { + if (checkNotNull(bytes).length == 0) { + return EMPTY_STRING; + } + + int bytesLength = bytes.length; + int buffer = 0; + int bitCount = 0; + int[] codePoints = new int[(bytesLength * BYTE_LENGTH + 14) / 15]; + + int codePointsIndex = 0; + for (int i = 0; i < bytesLength; i++) { + int b = bytes[i] & MASK_BYTE; + buffer = (buffer << BYTE_LENGTH) | b; + bitCount = bitCount + BYTE_LENGTH; + if (bitCount >= 15) { + bitCount = bitCount - 15; + codePoints[codePointsIndex++] = MAPPER15.toCodePoint(buffer >> bitCount); + } + } + + if (bitCount > 0) { + if (bitCount <= 7) { + codePoints[codePointsIndex++] = MAPPER7.toCodePoint(buffer << (7 - bitCount)); + } else { + codePoints[codePointsIndex++] = MAPPER15.toCodePoint(buffer << (15 - bitCount)); + } + } + + return new String(codePoints, 0, codePointsIndex); + } + + public static byte[] decode(String encoded) { + if (checkNotNull(encoded).isEmpty()) { + return EMPTY_BYTES; + } + int[] codePoints = encoded.codePoints().toArray(); + int codePointsLength = codePoints.length; + int totalBits = + (codePointsLength - 1) * 15 + (MAPPER7.isCodePointInRange(codePoints[codePointsLength - 1]) ? 7 : 15); + byte[] bytes = new byte[totalBits / BYTE_LENGTH]; + int bitCount = 0; + int buffer = 0; + int lastIndex = codePointsLength - 1; + int codePoint; + int codePointBits; + + int bytesIndex = 0; + for (int i = 0; i < codePointsLength; i++) { + int cp = codePoints[i]; + if (i == lastIndex && (codePoint = MAPPER7.toBits(cp)) >= 0) { + codePointBits = 7; + } else if ((codePoint = MAPPER15.toBits(cp)) >= 0) { + codePointBits = 15; + } else { + throw new IllegalArgumentException(format("U+%04X at index %d is not within 15 or 7 bit range", cp, i)); + } + buffer = (buffer << codePointBits) | codePoint; + bitCount = bitCount + codePointBits; + + while (bitCount >= BYTE_LENGTH) { + bitCount = bitCount - BYTE_LENGTH; + bytes[bytesIndex++] = (byte) ((buffer >> bitCount) & MASK_BYTE); + } + } + checkRemainingBufferIsEmpty(buffer, bitCount); + return bytes; + } + + private static void checkRemainingBufferIsEmpty(int buffer, int bitCount) { + if (bitCount > 0) { + int remaining = buffer & ((1 << bitCount) - 1); + if (remaining != 0) { + throw new IllegalStateException("residual buffer is not empty"); + } + } + } + + private interface BitsToCodePointMapper { + int toCodePoint(int bits); + + boolean isCodePointInRange(int codePoint); + + int toBits(int codePoint); + } + + @Value + private static class Range implements BitsToCodePointMapper { + + int mask; + int bitsStart; + int bitsEnd; + int codePointsStart; + int codePointsEnd; + int offset; + + @Builder + Range(int mask, int bitsStart, int bitsEnd, int codePointsStart, int codePointsEnd) { + this.mask = mask; + this.bitsStart = bitsStart; + this.bitsEnd = bitsEnd; + this.codePointsStart = codePointsStart; + this.codePointsEnd = codePointsEnd; + this.offset = codePointsStart - bitsStart; + } + + public boolean isBitsInRange(int bits) { + return bits >= bitsStart && bits <= bitsEnd; + } + + public int toCodePoint(int bits) { + bits = bits & mask; + if (isBitsInRange(bits)) { + return _toCodePoint(bits); + } else { + throw new IllegalArgumentException(bits + " are not withing this mapping range"); + } + } + + private int _toCodePoint(int bits) { + return offset + bits; + } + + public boolean isCodePointInRange(int codePoint) { + return codePoint >= codePointsStart && codePoint <= codePointsEnd; + } + + public int toBits(int codePoint) { + if (isCodePointInRange(codePoint)) { + return _toBits(codePoint); + } else { + return -1; + } + } + + private int _toBits(int codePoint) { + return codePoint - offset; + } + } + + private static class CompositeRange implements BitsToCodePointMapper { + + private final int mask; + private final Range[] ranges; + + CompositeRange(int mask, Range... ranges) { + this.mask = mask; + this.ranges = ranges; + } + + @Override + public int toCodePoint(int bits) { + bits = bits & mask; + for (int i = 0; i < ranges.length; i++) { + if (ranges[i].isBitsInRange(bits)) { + return ranges[i]._toCodePoint(bits); + } + } + throw new IllegalArgumentException(bits + " are not withing this mapping range"); + } + + @Override + public boolean isCodePointInRange(int codePoint) { + for (int i = 0; i < ranges.length; i++) { + if (ranges[i].isCodePointInRange(codePoint)) { + return true; + } + } + return false; + } + + @Override + public int toBits(int codePoint) { + for (int i = 0; i < ranges.length; i++) { + if (ranges[i].isCodePointInRange(codePoint)) { + return ranges[i]._toBits(codePoint); + } + } + return -1; + } + } + +} diff --git a/at_client/src/main/java/org/atsign/client/impl/util/EncryptionUtils.java b/at_client/src/main/java/org/atsign/client/impl/util/EncryptionUtils.java index f464fb56..2dcca30c 100644 --- a/at_client/src/main/java/org/atsign/client/impl/util/EncryptionUtils.java +++ b/at_client/src/main/java/org/atsign/client/impl/util/EncryptionUtils.java @@ -1,6 +1,5 @@ package org.atsign.client.impl.util; -import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; @@ -16,6 +15,8 @@ import org.atsign.client.impl.exceptions.AtEncryptionException; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * Utility class which registers bouncycastle as a security {@link Provider} and provides * static methods for the various encryption functions required by Atsign client APIs @@ -49,7 +50,7 @@ public static String aesEncryptToBase64(String input, String key, String iv) throws AtEncryptionException { try { Cipher cipher = createAesCipher(Cipher.ENCRYPT_MODE, key, iv); - byte[] encrypted = cipher.doFinal(input.getBytes()); + byte[] encrypted = cipher.doFinal(input.getBytes(UTF_8)); return Base64.getEncoder().encodeToString(encrypted); } catch (NoSuchAlgorithmException | NoSuchProviderException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { @@ -70,7 +71,7 @@ public static String aesDecryptFromBase64(String input, String key, String iv) t try { Cipher cipher = createAesCipher(Cipher.DECRYPT_MODE, key, iv); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(input)); - return new String(decrypted); + return new String(decrypted, UTF_8); } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { throw new AtDecryptionException("AES decryption failed", e); @@ -123,9 +124,9 @@ public static String rsaDecryptFromBase64(String input, String key) throws AtDec PrivateKey privateKey = toPrivateKey(key); Cipher decryptCipher = Cipher.getInstance("RSA"); decryptCipher.init(Cipher.DECRYPT_MODE, privateKey); - byte[] decoded = Base64.getDecoder().decode(input.getBytes(StandardCharsets.UTF_8)); + byte[] decoded = Base64.getDecoder().decode(input.getBytes(UTF_8)); byte[] decryptedMessageBytes = decryptCipher.doFinal(decoded); - return new String(decryptedMessageBytes, StandardCharsets.UTF_8); + return new String(decryptedMessageBytes, UTF_8); } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { throw new AtDecryptionException("RSA decryption failed", e); @@ -145,7 +146,7 @@ public static String rsaEncryptToBase64(String input, String key) throws AtEncry PublicKey publicKey = toPublicKey(key); Cipher encryptCipher = Cipher.getInstance("RSA"); encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey); - byte[] clearTextBytes = input.getBytes(StandardCharsets.UTF_8); + byte[] clearTextBytes = input.getBytes(UTF_8); byte[] encryptedMessageBytes = encryptCipher.doFinal(clearTextBytes); return Base64.getEncoder().encodeToString(encryptedMessageBytes); } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException @@ -167,7 +168,7 @@ public static String signSHA256RSA(String input, String key) throws AtEncryption PrivateKey pk = toPrivateKey(key); Signature privateSignature = Signature.getInstance("SHA256withRSA"); privateSignature.initSign(pk); - privateSignature.update(input.getBytes(StandardCharsets.UTF_8)); + privateSignature.update(input.getBytes(UTF_8)); byte[] signedBytes = privateSignature.sign(); return Base64.getEncoder().encodeToString(signedBytes); } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) { @@ -176,14 +177,14 @@ public static String signSHA256RSA(String input, String key) throws AtEncryption } private static PublicKey toPublicKey(String s) throws NoSuchAlgorithmException, InvalidKeySpecException { - byte[] keyBytes = Base64.getDecoder().decode(s.getBytes(StandardCharsets.UTF_8)); + byte[] keyBytes = Base64.getDecoder().decode(s.getBytes(UTF_8)); EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA"); return rsaKeyFactory.generatePublic(keySpec); } private static PrivateKey toPrivateKey(String s) throws NoSuchAlgorithmException, InvalidKeySpecException { - byte[] keyBytes = Base64.getDecoder().decode(s.getBytes(StandardCharsets.UTF_8)); + byte[] keyBytes = Base64.getDecoder().decode(s.getBytes(UTF_8)); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA"); return rsaKeyFactory.generatePrivate(keySpec); diff --git a/at_client/src/test/java/org/atsign/client/impl/AtClientImplTest.java b/at_client/src/test/java/org/atsign/client/impl/AtClientImplTest.java new file mode 100644 index 00000000..8512fdc6 --- /dev/null +++ b/at_client/src/test/java/org/atsign/client/impl/AtClientImplTest.java @@ -0,0 +1,560 @@ +package org.atsign.client.impl; + +import static org.atsign.client.api.AtEvents.AtEventType.statsNotification; +import static org.atsign.client.api.AtSign.createAtSign; +import static org.atsign.client.impl.util.EncryptionUtils.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import java.util.*; +import java.util.function.Consumer; + +import org.atsign.client.api.*; +import org.atsign.client.impl.commands.TestExecutorBuilder; +import org.atsign.client.impl.util.Base2e15Utils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + +class AtClientImplTest { + + private AtKeys keys; + private AtEvents.AtEventBus bus; + private AtCommandExecutor executor; + private AtClientImpl client; + private AtSign atSign; + + @BeforeEach + void setUp() throws Exception { + bus = Mockito.mock(AtEvents.AtEventBus.class); + executor = TestExecutorBuilder.builder().build(); + atSign = AtSign.createAtSign("test"); + keys = AtKeys.builder() + .apkamKeyPair(generateRSAKeyPair()) + .encryptKeyPair(generateRSAKeyPair()) + .selfEncryptKey(generateAESKeyBase64()) + .build(); + client = AtClientImpl.builder() + .atSign(atSign) + .keys(keys) + .executor(executor) + .eventBus(bus) + .build(); + } + + @AfterEach + void tearDown() {} + + @Test + void testSetAtSignReturnsConstructorArg() { + AtClientImpl client = AtClientImpl.builder() + .atSign(createAtSign("test")) + .keys(keys) + .executor(executor) + .eventBus(bus) + .build(); + assertThat(client.getAtSign(), equalTo(createAtSign("test"))); + } + + @Test + void testGetCommandExecutorReturnsConstructorArg() { + AtClientImpl client = AtClientImpl.builder() + .atSign(createAtSign("test")) + .keys(keys) + .executor(executor) + .eventBus(bus) + .build(); + assertThat(client.getCommandExecutor(), sameInstance(executor)); + } + + @Test + void testCloseInvokesCloseOnExecutor() throws Exception { + client.close(); + + verify(executor).close(); + } + + @Test + void testStartMonitor() throws Exception { + + // stub the executor so that onReady consumer successfully authenticates + executor = TestExecutorBuilder.builder() + .stub("from:@alice", "data:challenge") + .stub("pkam:[^{].+", "data:success") + .build(); + + AtClientImpl client = AtClientImpl.builder() + .atSign(createAtSign("alice")) + .keys(keys) + .executor(executor) + .eventBus(bus) + .build(); + + assertThat(client.isMonitorRunning(), is(false)); + + client.startMonitor(); + + // verify onReady is called + ArgumentCaptor> onReadyCaptor = ArgumentCaptor.forClass(Consumer.class); + verify(executor).onReady(onReadyCaptor.capture()); + + // explicitly invoke the onReady consumer + onReadyCaptor.getValue().accept(executor); + + // verify monitor is called + ArgumentCaptor> monitorCaptor = ArgumentCaptor.forClass(Consumer.class); + verify(executor).sendSync(eq("monitor"), monitorCaptor.capture()); + + // invoke the consumer and verify that eventBus is invoked + monitorCaptor.getValue().accept("notification:{\"id\":\"-1\",\"from\":\"@gary\"}"); + + // verify eventBus publish event was invoked + Map expected = new HashMap<>(); + expected.put("id", "-1"); + expected.put("from", "@gary"); + verify(bus).publishEvent(eq(statsNotification), eq(expected)); + + assertThat(client.isMonitorRunning(), is(true)); + } + + @Test + void testStopMonitor() throws Exception { + // stub the executor so that onReady consumer successfully authenticates + executor = TestExecutorBuilder.builder() + .stub("from:@alice", "data:challenge") + .stub("pkam:[^{].+", "data:success") + .build(); + + AtClientImpl client = AtClientImpl.builder() + .atSign(createAtSign("alice")) + .keys(keys) + .executor(executor) + .eventBus(bus) + .build(); + + assertThat(client.isMonitorRunning(), is(false)); + + client.startMonitor(); + client.stopMonitor(); + + // verify onReady has been called twice + ArgumentCaptor> onReadyCaptor = ArgumentCaptor.forClass(Consumer.class); + verify(executor, times(2)).onReady(onReadyCaptor.capture()); + + // explicitly invoke the onReady consumer + onReadyCaptor.getAllValues().get(1).accept(executor); + + // verify monitor command is NOT sent + verify(executor, never()).sendSync(eq("monitor"), ArgumentMatchers.any(Consumer.class)); + + assertThat(client.isMonitorRunning(), is(false)); + } + + @Test + void testAddEventListenerDelegatesToEventBus() { + AtEvents.AtEventListener listener = mock(AtEvents.AtEventListener.class); + Set eventTypes = Set.of(AtEvents.AtEventType.values()); + + client.addEventListener(listener, eventTypes); + + verify(bus).addEventListener(listener, eventTypes); + } + + @Test + void testRemoveEventListenerDelegatesToEventBus() { + AtEvents.AtEventListener listener = mock(AtEvents.AtEventListener.class); + + client.removeEventListener(listener); + + verify(bus).removeEventListener(listener); + } + + @Test + void testPublishEventDelegatesToEventBus() { + Map eventData = Collections.singletonMap("id", "-1"); + client.publishEvent(statsNotification, eventData); + + verify(bus).publishEvent(statsNotification, eventData); + } + + @Test + void testGetSelfKey() throws Exception { + String iv = generateRandomIvBase64(16); + String encrypted = aesEncryptToBase64("hello me", keys.getSelfEncryptKey(), iv); + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("llookup:all:key1@test", "key1@test", encrypted, "ivNonce", iv) + .stub("llookup:all:key2@test", "error:AT0001:deliberate") + .stubExecutionException("llookup:all:key3@test") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + + Keys.SelfKey key1 = Keys.selfKeyBuilder().sharedBy(atSign).name("key1").build(); + assertThat(client.get(key1).get(), equalTo("hello me")); + + Keys.SelfKey key2 = Keys.selfKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.get(key2).get()); + + Keys.SelfKey key3 = Keys.selfKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.get(key3).get()); + } + + @Test + void testGetSelfKeyBinary() throws Exception { + byte[] bytes = new byte[15]; + Arrays.fill(bytes, (byte) Integer.parseInt("10101010", 2)); + + String iv = generateRandomIvBase64(16); + String encrypted = aesEncryptToBase64(Base2e15Utils.encode(bytes), keys.getSelfEncryptKey(), iv); + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("llookup:all:key1@test", "key1@test", encrypted, "ivNonce", iv, "isBinary", true) + .stubLookupResponse("llookup:all:key4@test", "key4@test", encrypted, "ivNonce", iv) + .stub("llookup:all:key2@test", "error:AT0001:deliberate") + .stubExecutionException("llookup:all:key3@test") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + + Keys.SelfKey key1 = Keys.selfKeyBuilder().sharedBy(atSign).name("key1").build(); + assertThat(client.getBinary(key1).get(), equalTo(bytes)); + + Keys.SelfKey key2 = Keys.selfKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.getBinary(key2).get()); + + Keys.SelfKey key3 = Keys.selfKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.getBinary(key3).get()); + + Keys.SelfKey key4 = Keys.selfKeyBuilder().sharedBy(atSign).name("key4").build(); + Exception ex = assertThrows(Exception.class, () -> client.getBinary(key4).get()); + assertThat(ex.getMessage(), containsString("metadata.isBinary not set to true")); + } + + @Test + void testPutSelfKey() throws Exception { + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("update:dataSignature:.+:isEncrypted:true:ivNonce:.+:key1@test .+", "data:123") + .stub("update:dataSignature:.+:key2@test", "error:AT0001:deliberate") + .stubExecutionException("update:dataSignature:.+:key3@test .+") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + Keys.SelfKey key1 = Keys.selfKeyBuilder().sharedBy(atSign).name("key1").build(); + client.put(key1, "hello world").get(); + + Keys.SelfKey key2 = Keys.selfKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.put(key2, "hello world").get()); + + Keys.SelfKey key3 = Keys.selfKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.put(key3, "hello world").get()); + } + + @Test + void testPutSelfKeyBytes() throws Exception { + byte[] bytes = new byte[15]; + Arrays.fill(bytes, (byte) Integer.parseInt("10101010", 2)); + + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("update:dataSignature:.+:isBinary:true:isEncrypted:true:ivNonce:.+:key1@test .+", "data:123") + .stub("update:.+:key2@test .+", "error:AT0001:deliberate") + .stubExecutionException("update:.+:key3@test .+") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + Keys.SelfKey key1 = Keys.selfKeyBuilder().sharedBy(atSign).name("key1").build(); + client.put(key1, bytes).get(); + + Keys.SelfKey key2 = Keys.selfKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.put(key2, bytes).get()); + + Keys.SelfKey key3 = Keys.selfKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.put(key3, bytes).get()); + } + + @Test + void testDeleteSelfKey() throws Exception { + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("delete:key1@test", "data:123") + .stub("delete:key2@test", "error:AT0001:deliberate") + .stubExecutionException("delete:key3@test") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + Keys.SelfKey key1 = Keys.selfKeyBuilder().sharedBy(atSign).name("key1").build(); + client.delete(key1).get(); + + Keys.SelfKey key2 = Keys.selfKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.delete(key2).get()); + + Keys.SelfKey key3 = Keys.selfKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.delete(key3).get()); + } + + @Test + void testGetPublicKey() throws Exception { + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("llookup:all:public:key1@test", "key1@test", "hello world") + .stub("llookup:all:public:key2@test", "error:AT0001:deliberate") + .stubExecutionException("llookup:all:public:key3@test") + .stubLookupResponse("plookup:all:key4@another", "key4@test", "greetings from another world") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + + Keys.PublicKey key1 = Keys.publicKeyBuilder().sharedBy(atSign).name("key1").build(); + assertThat(client.get(key1).get(), equalTo("hello world")); + + Keys.PublicKey key2 = Keys.publicKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.get(key2).get()); + + Keys.PublicKey key3 = Keys.publicKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.get(key3).get()); + + Keys.PublicKey key4 = Keys.publicKeyBuilder().sharedBy(createAtSign("another")).name("key4").build(); + assertThat(client.get(key4).get(), equalTo("greetings from another world")); + } + + @Test + void testGetPublicKeyBinary() throws Exception { + byte[] bytes1 = new byte[15]; + Arrays.fill(bytes1, (byte) Integer.parseInt("10101010", 2)); + byte[] bytes2 = new byte[15]; + Arrays.fill(bytes2, (byte) Integer.parseInt("11110000", 2)); + + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("llookup:all:public:key1@test", "key1@test", Base2e15Utils.encode(bytes1), "isBinary", true) + .stub("llookup:all:public:key2@test", "error:AT0001:deliberate") + .stubExecutionException("llookup:all:public:key3@test") + .stubLookupResponse("plookup:all:key4@another", "key4@test", Base2e15Utils.encode(bytes2), "isBinary", true) + .stubLookupResponse("llookup:all:public:key5@test", "key5@test", Base2e15Utils.encode(bytes1)) + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + + Keys.PublicKey key1 = Keys.publicKeyBuilder().sharedBy(atSign).name("key1").build(); + assertThat(client.getBinary(key1).get(), equalTo(bytes1)); + + Keys.PublicKey key2 = Keys.publicKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.getBinary(key2).get()); + + Keys.PublicKey key3 = Keys.publicKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.getBinary(key3).get()); + + Keys.PublicKey key4 = Keys.publicKeyBuilder().sharedBy(createAtSign("another")).name("key4").build(); + assertThat(client.getBinary(key4).get(), equalTo(bytes2)); + + Keys.PublicKey key5 = Keys.publicKeyBuilder().sharedBy(atSign).name("key5").build(); + assertThrows(Exception.class, () -> client.getBinary(key5).get()); + } + + @Test + void testPutPublicKey() throws Exception { + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("update:dataSignature:.+:isEncrypted:false:public:key1@test hello world", "data:123") + .stub("update:dataSignature:.+:key2@test", "error:AT0001:deliberate") + .stubExecutionException("update:dataSignature:.+:key3@test .+") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + Keys.PublicKey key1 = Keys.publicKeyBuilder().sharedBy(atSign).name("key1").build(); + client.put(key1, "hello world").get(); + + Keys.PublicKey key2 = Keys.publicKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.put(key2, "hello world").get()); + + Keys.PublicKey key3 = Keys.publicKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.put(key3, "hello world").get()); + } + + @Test + void testPutPublicKeyBytes() throws Exception { + byte[] bytes = new byte[15]; + Arrays.fill(bytes, (byte) Integer.parseInt("10101010", 2)); + + String encoded = Base2e15Utils.encode(bytes); + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("update:dataSignature:.+:isEncrypted:false:public:key1@test " + encoded, "data:123") + .stub("update:dataSignature:.+:key2@test", "error:AT0001:deliberate") + .stubExecutionException("update:dataSignature:.+:key3@test .+") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + Keys.PublicKey key1 = Keys.publicKeyBuilder().sharedBy(atSign).name("key1").build(); + client.put(key1, bytes).get(); + + Keys.PublicKey key2 = Keys.publicKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.put(key2, bytes).get()); + + Keys.PublicKey key3 = Keys.publicKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.put(key3, bytes).get()); + } + + @Test + void testDeletePublicKey() throws Exception { + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("delete:public:key1@test", "data:123") + .stub("delete:public:key2@test", "error:AT0001:deliberate") + .stubExecutionException("delete:public:key3@test") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + Keys.PublicKey key1 = Keys.publicKeyBuilder().sharedBy(atSign).name("key1").build(); + client.delete(key1).get(); + + Keys.PublicKey key2 = Keys.publicKeyBuilder().sharedBy(atSign).name("key2").build(); + assertThrows(Exception.class, () -> client.delete(key2).get()); + + Keys.PublicKey key3 = Keys.publicKeyBuilder().sharedBy(atSign).name("key3").build(); + assertThrows(Exception.class, () -> client.delete(key3).get()); + } + + + @Test + void testGetSharedKey() throws Exception { + String encryptKey = generateAESKeyBase64(); + String iv = generateRandomIvBase64(16); + String encrypted1 = aesEncryptToBase64("hello from me", encryptKey, iv); + String encrypted2 = aesEncryptToBase64("greetings from another world", encryptKey, iv); + + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("llookup:shared_key.another@test", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) + .stubLookupResponse("llookup:all:@another:key1@test", "key1@test", encrypted1, "ivNonce", iv) + .stub("llookup:all:public:key2@test", "error:AT0001:deliberate") + .stubExecutionException("llookup:all:public:key3@test") + .stub("lookup:shared_key@another", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) + .stubLookupResponse("lookup:all:key4@another", "key4@test", encrypted2, "ivNonce", iv) + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + AtSign atSign2 = createAtSign("another"); + + Keys.SharedKey key1 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key1").build(); + assertThat(client.get(key1).get(), equalTo("hello from me")); + + Keys.SharedKey key2 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key2").build(); + assertThrows(Exception.class, () -> client.get(key2).get()); + + Keys.SharedKey key3 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key3").build(); + assertThrows(Exception.class, () -> client.get(key3).get()); + + Keys.SharedKey key4 = Keys.sharedKeyBuilder().sharedBy(atSign2).sharedWith(atSign).name("key4").build(); + assertThat(client.get(key4).get(), equalTo("greetings from another world")); + } + + @Test + void testGetSharedKeyBinary() throws Exception { + byte[] bytes1 = new byte[15]; + Arrays.fill(bytes1, (byte) Integer.parseInt("10101010", 2)); + byte[] bytes2 = new byte[15]; + Arrays.fill(bytes2, (byte) Integer.parseInt("11110000", 2)); + + String encryptKey = generateAESKeyBase64(); + String iv = generateRandomIvBase64(16); + String encrypted1 = aesEncryptToBase64(Base2e15Utils.encode(bytes1), encryptKey, iv); + String encrypted2 = aesEncryptToBase64(Base2e15Utils.encode(bytes2), encryptKey, iv); + + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("llookup:shared_key.another@test", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) + .stub("lookup:shared_key@another", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) + .stubLookupResponse("llookup:all:@another:key1@test", "key1@test", encrypted1, "ivNonce", iv, "isBinary", true) + .stub("llookup:all:public:key2@test", "error:AT0001:deliberate") + .stubExecutionException("llookup:all:public:key3@test") + .stubLookupResponse("lookup:all:key4@another", "key4@test", encrypted2, "ivNonce", iv, "isBinary", true) + .stubLookupResponse("llookup:all:@another:key5@test", "key5@test", encrypted1, "ivNonce", iv) + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + AtSign atSign2 = createAtSign("another"); + + Keys.SharedKey key1 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key1").build(); + assertThat(client.getBinary(key1).get(), equalTo(bytes1)); + + Keys.SharedKey key2 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key2").build(); + assertThrows(Exception.class, () -> client.getBinary(key2).get()); + + Keys.SharedKey key3 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key3").build(); + assertThrows(Exception.class, () -> client.getBinary(key3).get()); + + Keys.SharedKey key4 = Keys.sharedKeyBuilder().sharedBy(atSign2).sharedWith(atSign).name("key4").build(); + assertThat(client.getBinary(key4).get(), equalTo(bytes2)); + + Keys.SharedKey key5 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key5").build(); + assertThrows(Exception.class, () -> client.getBinary(key5).get()); + } + + @Test + void testPutSharedKey() throws Exception { + String encryptKey = generateAESKeyBase64(); + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("llookup:shared_key.another@test", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) + .stub("update:isEncrypted:true:.+@another:key1@test .+", "data:123") + .stub("update:.+:key2@test", "error:AT0001:deliberate") + .stubExecutionException("update:.+:key3@test .+") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + AtSign atSign2 = createAtSign("another"); + + Keys.SharedKey key1 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key1").build(); + client.put(key1, "hello world").get(); + + Keys.SharedKey key2 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key2").build(); + assertThrows(Exception.class, () -> client.put(key2, "hello world").get()); + + Keys.SharedKey key3 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key3").build(); + assertThrows(Exception.class, () -> client.put(key3, "hello world").get()); + } + + @Test + void testPutSharedKeyBytes() throws Exception { + byte[] bytes = new byte[15]; + Arrays.fill(bytes, (byte) Integer.parseInt("10101010", 2)); + + String encryptKey = generateAESKeyBase64(); + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("llookup:shared_key.another@test", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) + .stub("update:isBinary:true:isEncrypted:true:.+@another:key1@test .+", "data:123") + .stub("update:.+:key2@test", "error:AT0001:deliberate") + .stubExecutionException("update:.+:key3@test .+") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + AtSign atSign2 = createAtSign("another"); + + Keys.SharedKey key1 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key1").build(); + client.put(key1, bytes).get(); + + Keys.SharedKey key2 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key2").build(); + assertThrows(Exception.class, () -> client.put(key2, bytes).get()); + + Keys.SharedKey key3 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key3").build(); + assertThrows(Exception.class, () -> client.put(key3, bytes).get()); + } + + @Test + void testDeleteSharedKey() throws Exception { + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stub("delete:@another:key1@test", "data:123") + .stub("delete:@another:key2@test", "error:AT0001:deliberate") + .stubExecutionException("delete:@another:key3@test") + .build(); + + AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build(); + AtSign atSign2 = createAtSign("another"); + + Keys.SharedKey key1 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key1").build(); + client.delete(key1).get(); + + Keys.SharedKey key2 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key2").build(); + assertThrows(Exception.class, () -> client.delete(key2).get()); + + Keys.SharedKey key3 = Keys.sharedKeyBuilder().sharedBy(atSign).sharedWith(atSign2).name("key3").build(); + assertThrows(Exception.class, () -> client.delete(key3).get()); + } + +} diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/AuthenticationCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/AuthenticationCommandsTest.java index 84821f14..9b7519e1 100644 --- a/at_client/src/test/java/org/atsign/client/impl/commands/AuthenticationCommandsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/commands/AuthenticationCommandsTest.java @@ -17,7 +17,7 @@ public class AuthenticationCommandsTest { @Test public void testAuthenticateWithCramDoesNotThrowException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("from:@alice", "data:challenge") .stub("cram:7e91508d5.+", "data:success") .build(); @@ -27,7 +27,7 @@ public void testAuthenticateWithCramDoesNotThrowException() throws Exception { @Test public void testAuthenticateWithCramFailThrowsExpectedException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("from:@alice", "data:challenge") .stub("cram:.+", "error:AT0401:deliberate") .build(); @@ -41,7 +41,7 @@ public void testAuthenticateWithCramFailThrowsExpectedException() throws Excepti @Test public void testAuthenticateWithPkamDoesNotThrowException() throws Exception { AtKeys keys = AtKeys.builder().apkamKeyPair(generateRSAKeyPair()).build(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("from:@alice", "data:challenge") .stub("pkam:[^{].+", "data:success") .build(); @@ -52,7 +52,7 @@ public void testAuthenticateWithPkamDoesNotThrowException() throws Exception { @Test public void testAuthenticateWithApkamWithEnrollmentId() throws Exception { AtKeys keys = AtKeys.builder().apkamKeyPair(generateRSAKeyPair()).enrollmentId(createEnrollmentId("12345")).build(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("from:@alice", "data:challenge") .stub("pkam:signingAlgo:rsa2048:hashingAlgo:sha256:enrollmentId:12345:.+", "data:success") .build(); @@ -63,7 +63,7 @@ public void testAuthenticateWithApkamWithEnrollmentId() throws Exception { @Test public void testAuthenticateWithApkamFailThrowsExpectedException() throws Exception { AtKeys keys = AtKeys.builder().apkamKeyPair(generateRSAKeyPair()).enrollmentId(createEnrollmentId("12345")).build(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("from:@alice", "data:challenge") .stub("pkam:.+", "error:AT0401:deliberate") .build(); @@ -77,7 +77,7 @@ public void testAuthenticateWithApkamFailThrowsExpectedException() throws Except @Test public void testPkamAuthenticatorThrowsOnReadyException() throws Exception { AtKeys keys = AtKeys.builder().apkamKeyPair(generateRSAKeyPair()).enrollmentId(createEnrollmentId("12345")).build(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("from:@alice", "data:challenge") .stub("pkam:.+", "error:AT0401:deliberate") .build(); diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/EnrollCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/EnrollCommandsTest.java index 739d2093..fe07ee02 100644 --- a/at_client/src/test/java/org/atsign/client/impl/commands/EnrollCommandsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/commands/EnrollCommandsTest.java @@ -34,7 +34,7 @@ public class EnrollCommandsTest { @Test public void testDeleteCramSecretDoesNotThrowException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("delete:privatekey:at_secret", "data:1") .build(); @@ -43,7 +43,7 @@ public void testDeleteCramSecretDoesNotThrowException() throws Exception { @Test public void testDeleteCramSecretThrowsException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("delete:privatekey:at_secret", "error:AT0001:deliberate") .build(); @@ -58,7 +58,7 @@ public void testOnboardThrowsExceptionIfSigningPublicKeyIsMissing() throws Excep .encryptKeyPair(generateRSAKeyPair()) .build(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("scan", "data:[\"signing_publickey@gary\"]") .build(); @@ -69,7 +69,7 @@ public void testOnboardThrowsExceptionIfSigningPublicKeyIsMissing() throws Excep @Test void testOtpSendsExpectedCommandAndMatchesExpectedResponse() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("otp:get", "data:ABC123") .build(); @@ -78,7 +78,7 @@ void testOtpSendsExpectedCommandAndMatchesExpectedResponse() throws Exception { @Test void testOtpThrowsExceptionOnError() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("otp:get", "error:AT0013:deliberate") .build(); @@ -93,7 +93,7 @@ public void testOnboard() throws Exception { .encryptKeyPair(generateRSAKeyPair()) .build(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("scan", "data:[\"signing_publickey@alice\"]") .stub("from:@alice", "data:challenge") .stub("cram:7e91508d5.+", "data:success") @@ -116,7 +116,7 @@ public void testEnroll() throws Exception { .apkamSymmetricKey(generateAESKeyBase64()) .build(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("lookup:publickey@alice", "data:" + RSA_KEY) .stub("enroll:request\\{.+}", "data:{\"enrollmentId\":\"759acb09\",\"status\":\"pending\"}") .build(); @@ -143,7 +143,7 @@ public void testComplete() throws Exception { String privateEncryptKeysGetResponse = format("data:{\"value\":\"%s\",\"iv\":\"%s\"}", aesEncryptToBase64(RSA_KEY, keys.getApkamSymmetricKey(), IV), IV); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("from:@alice", "data:challenge") .stub("pkam:[^{].+", "data:success") .stub("keys:get:keyName:12345.default_self_enc_key.__manage@alice", selfEncryptKeysGetResponse) @@ -168,7 +168,7 @@ public void testApprove() throws Exception { "\"namespace\":{\"ns\":\"rw\"},\"encryptedAPKAMSymmetricKey\":\"%s\",\"status\":\"pending\"}", rsaEncryptToBase64(AES_KEY, keys.getEncryptPublicKey())); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("enroll:fetch\\{\"enrollmentId\":\"12345\"}", fetchResponse) .stub("enroll:approve\\{\"enrollmentId\":\"12345\".+", "data:{\"status\":\"approved\",\"enrollmentId\":\"12345\"}") @@ -179,7 +179,7 @@ public void testApprove() throws Exception { @Test public void testDeny() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("enroll:deny\\{\"enrollmentId\":\"12345\".+", "data:{\"status\":\"denied\",\"enrollmentId\":\"12345\"}") .build(); @@ -189,7 +189,7 @@ public void testDeny() throws Exception { @Test public void testRevoke() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("enroll:revoke\\{\"enrollmentId\":\"12345\".+", "data:{\"status\":\"revoked\",\"enrollmentId\":\"12345\"}") .build(); @@ -199,7 +199,7 @@ public void testRevoke() throws Exception { @Test public void testUnrevoke() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("enroll:unrevoke\\{\"enrollmentId\":\"12345\".+", "data:{\"status\":\"approved\",\"enrollmentId\":\"12345\"}") .build(); @@ -209,7 +209,7 @@ public void testUnrevoke() throws Exception { @Test public void testDelete() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("enroll:delete\\{\"enrollmentId\":\"12345\".+", "data:{\"status\":\"deleted\",\"enrollmentId\":\"12345\"}") .build(); @@ -239,7 +239,7 @@ public void testList() throws Exception { " \"fredns\": \"rw\"" + " }}}"; - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("enroll:list\\{\"enrollmentStatusFilter\":\\[\"pending\"]}", response) .build(); diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/KeyCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/KeyCommandsTest.java index 4d31480d..7198e1b7 100644 --- a/at_client/src/test/java/org/atsign/client/impl/commands/KeyCommandsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/commands/KeyCommandsTest.java @@ -16,7 +16,7 @@ class KeyCommandsTest { @Test void testDeleteKeyRawKey() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("delete:selfkey1@alice", "data:1") .build(); @@ -25,7 +25,7 @@ void testDeleteKeyRawKey() throws Exception { @Test void testDeleteKeyRawKeyThrowsExceptionIfResponseIsError() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("delete:selfkey1@alice", "error:AT0001:an error message") .build(); @@ -34,7 +34,7 @@ void testDeleteKeyRawKeyThrowsExceptionIfResponseIsError() throws Exception { @Test void testDeleteKey() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("delete:keyname@colin", "data:1") .build(); @@ -50,7 +50,7 @@ void testDeleteKey() throws Exception { @Test void getKeysWithMetaDataReturnsExpectedResults() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("scan:showHidden:true .+", "data:[\"public:publickey@gary\",\"public:signing_publickey@gary\"]") .stub("llookup:meta:public:publickey@gary", "data:{\"ttl\":1000}") .stub("llookup:meta:public:signing_publickey@gary", "data:{\"ttl\":2000}") @@ -73,7 +73,7 @@ void getKeysWithMetaDataReturnsExpectedResults() throws Exception { @Test void getKeysWithoutMetaDataReturnsExpectedResults() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("scan:showHidden:true .+", "data:[\"public:publickey@gary\",\"public:signing_publickey@gary\"]") .build(); diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/PublicKeyCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/PublicKeyCommandsTest.java index 6f743bc5..f7c4bd95 100644 --- a/at_client/src/test/java/org/atsign/client/impl/commands/PublicKeyCommandsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/commands/PublicKeyCommandsTest.java @@ -39,8 +39,8 @@ public void setup() throws Exception { @Test void testGetSharedByMe() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() - .stub("llookup:all:public:test@gary", createMockLookupResponse("public:test@gary", "hello world")) + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("llookup:all:public:test@gary", "public:test@gary", "hello world") .build(); String actual = PublicKeyCommands.get(executor, createAtSign("gary"), key, null); @@ -50,8 +50,8 @@ void testGetSharedByMe() throws Exception { @Test void testGetCachedSharedByMe() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() - .stub("llookup:all:public:test@gary", createMockLookupResponse("cached:public:test@gary", "hello world")) + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("llookup:all:public:test@gary", "cached:public:test@gary", "hello world") .build(); PublicKeyCommands.get(executor, createAtSign("gary"), key, null); @@ -61,7 +61,7 @@ void testGetCachedSharedByMe() throws Exception { @Test void testGetSharedByMeNoSuchKey() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("llookup:all:public:test@gary", "error:AT0015:deliberate") .build(); @@ -70,7 +70,7 @@ void testGetSharedByMeNoSuchKey() throws Exception { @Test void testGetSharedByMeExecutionException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("llookup:all:public:test@gary", new ExecutionException("deliberate", null)) .build(); @@ -79,8 +79,8 @@ void testGetSharedByMeExecutionException() throws Exception { @Test void testGetSharedByOther() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() - .stub("plookup:all:test@gary", createMockLookupResponse("public:test@gary", "hello world")) + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("plookup:all:test@gary", "public:test@gary", "hello world") .build(); String actual = PublicKeyCommands.get(executor, createAtSign("colin"), key, null); @@ -90,8 +90,8 @@ void testGetSharedByOther() throws Exception { @Test void testGetCachedSharedByOther() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() - .stub("plookup:all:test@gary", createMockLookupResponse("cached:public:test@gary", "hello world")) + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("plookup:all:test@gary", "cached:public:test@gary", "hello world") .build(); PublicKeyCommands.get(executor, createAtSign("colin"), key, null); @@ -101,8 +101,8 @@ void testGetCachedSharedByOther() throws Exception { @Test void testGetSharedByOtherBypassCache() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() - .stub("plookup:bypassCache:true:all:test@gary", createMockLookupResponse("public:test@gary", "hello world")) + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("plookup:bypassCache:true:all:test@gary", "public:test@gary", "hello world") .build(); GetRequestOptions options = GetRequestOptions.builder().bypassCache(true).build(); @@ -115,7 +115,7 @@ void testGetSharedByOtherBypassCache() throws Exception { @Test void testGetSharedByOtherNoSuchKey() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("plookup:all:test@gary", "error:AT0015:deliberate") .build(); @@ -124,7 +124,7 @@ void testGetSharedByOtherNoSuchKey() throws Exception { @Test void testGetSharedByOtherExecutionException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stubExecutionException("plookup:all:test@gary") .build(); @@ -133,7 +133,7 @@ void testGetSharedByOtherExecutionException() throws Exception { @Test void testPutSendsExpectedCommands() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("update:dataSignature:.+:isEncrypted:false:public:test@gary hello world", "data:123") .build(); @@ -142,7 +142,7 @@ void testPutSendsExpectedCommands() throws Exception { @Test void testPutThrowExceptionIfCommandFails() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("update:dataSignature:.+:isEncrypted:false:public:test@gary hello world", "error:AT0001:deliberate") .build(); @@ -156,7 +156,7 @@ void testPutThrowExceptionIfCommandFails() throws Exception { @Test void testPutExecutionException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stubExecutionException("update:dataSignature:.+:isEncrypted:false:public:test@gary hello world") .build(); @@ -167,12 +167,4 @@ void testPutExecutionException() throws Exception { assertThrows(RuntimeException.class, () -> PublicKeyCommands.put(executor, createAtSign("gary"), keys, key, "hello world")); } - - private static String createMockLookupResponse(String key, String value) { - return String.format("data:{" + - "\"key\": \"%s\"," + - "\"data\": \"%s\"," + - "\"metaData\": {\"ttl\": 86400000, \"isBinary\": false, \"isEncrypted\": false, \"isPublic\": true}" + - "}", key, value); - } } diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/ScanCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/ScanCommandsTest.java index 68a58d7b..7f64dee6 100644 --- a/at_client/src/test/java/org/atsign/client/impl/commands/ScanCommandsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/commands/ScanCommandsTest.java @@ -14,20 +14,20 @@ class ScanCommandsTest { @Test void testScanReturnsExpectedResults() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("scan:showHidden:true .*", "data:[]") .build(); assertThat(ScanCommands.scan(executor, true, ".*"), equalTo(List.of())); - executor = TestConnectionBuilder.builder() + executor = TestExecutorBuilder.builder() .stub("scan:showHidden:true .+", "data:[\"public:publickey@gary\",\"public:signing_publickey@gary\"]") .build(); assertThat(ScanCommands.scan(executor, true, ".+"), equalTo(List.of("public:publickey@gary", "public:signing_publickey@gary"))); - executor = TestConnectionBuilder.builder() + executor = TestExecutorBuilder.builder() .stub("scan .*", "data:[\"public:publickey@gary\"]") .build(); assertThat(ScanCommands.scan(executor, false, ".*"), @@ -36,7 +36,7 @@ void testScanReturnsExpectedResults() throws Exception { @Test void testScanThrowsServerException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("scan:showHidden:true .*", "error:AT0001:deliberate") .build(); assertThrows(AtServerRuntimeException.class, () -> ScanCommands.scan(executor, true, ".*")); @@ -45,7 +45,7 @@ void testScanThrowsServerException() throws Exception { @Test void testScanThrowsExecutionException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stubExecutionException("scan:showHidden:true .*") .build(); assertThrows(RuntimeException.class, () -> ScanCommands.scan(executor, true, ".*")); diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/SelfKeyCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/SelfKeyCommandsTest.java index 2a74780f..7d9fb61c 100644 --- a/at_client/src/test/java/org/atsign/client/impl/commands/SelfKeyCommandsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/commands/SelfKeyCommandsTest.java @@ -41,8 +41,8 @@ void testGet() throws Exception { String iv = generateRandomIvBase64(16); String encrypted = aesEncryptToBase64("hello me", keys.getSelfEncryptKey(), iv); - AtCommandExecutor executor = TestConnectionBuilder.builder() - .stub("llookup:all:test@gary", createMockLookupResponse("test@gary", encrypted, iv)) + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("llookup:all:test@gary", "test@gary", encrypted, "ivNonce", iv) .build(); String actual = SelfKeyCommands.get(executor, atSign, keys, key); @@ -52,7 +52,7 @@ void testGet() throws Exception { @Test void testGetException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("llookup:all:test@gary", "error:AT0001:deliberate") .build(); @@ -61,7 +61,7 @@ void testGetException() throws Exception { @Test void testGetExecutionException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stubExecutionException("llookup:all:test@gary") .build(); @@ -70,7 +70,7 @@ void testGetExecutionException() throws Exception { @Test void testPut() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("update:dataSignature:.+:isEncrypted:true:ivNonce:.+:test@gary .+", "data:123") .build(); @@ -80,7 +80,7 @@ void testPut() throws Exception { @Test void testPutAtException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("update:dataSignature:.+:isEncrypted:true:ivNonce:.+:test@gary .+", "error:AT0001:deliberate") .build(); @@ -89,20 +89,11 @@ void testPutAtException() throws Exception { @Test void testPutExecutionException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stubExecutionException("update:dataSignature:.+:isEncrypted:true:ivNonce:.+:test@gary .+") .build(); assertThrows(RuntimeException.class, () -> SelfKeyCommands.put(executor, atSign, keys, key, "hello world")); } - private static String createMockLookupResponse(String key, String encrypted, String iv) { - - return String.format("data:{" + - "\"key\": \"%s\"," + - "\"data\": \"%s\"," + - "\"metaData\": {\"ttl\": 86400000, \"ivNonce\": \"%s\", \"isEncrypted\": true, \"isPublic\": false}" + - "}", key, encrypted, iv); - } - } diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/SharedKeyCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/SharedKeyCommandsTest.java index f7e627f5..c694bfd2 100644 --- a/at_client/src/test/java/org/atsign/client/impl/commands/SharedKeyCommandsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/commands/SharedKeyCommandsTest.java @@ -40,9 +40,9 @@ void testGetSharedByMe() throws Exception { String encryptKey = generateAESKeyBase64(); String iv = generateRandomIvBase64(16); String encrypted = aesEncryptToBase64("hello colin", encryptKey, iv); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("llookup:shared_key.colin@gary", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) - .stub("llookup:all:@colin:test@gary", createMockLookupResponse("@colin:test@gary", encrypted, iv)) + .stubLookupResponse("llookup:all:@colin:test@gary", "@colin:test@gary", encrypted, "ivNonce", iv) .build(); String actual = SharedKeyCommands.get(executor, createAtSign("gary"), keys, key); @@ -52,7 +52,7 @@ void testGetSharedByMe() throws Exception { @Test void testGetNotSharedByMeOrWithMeThrowsException() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .build(); RuntimeException ex = @@ -66,8 +66,8 @@ void testGetSharedByMeCachedKey() throws Exception { String iv = generateRandomIvBase64(16); String encrypted = aesEncryptToBase64("hello colin", encryptKey, iv); keys.put("shared_key.colin", encryptKey); - AtCommandExecutor executor = TestConnectionBuilder.builder() - .stub("llookup:all:@colin:test@gary", createMockLookupResponse("@colin:test@gary", encrypted, iv)) + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("llookup:all:@colin:test@gary", "@colin:test@gary", encrypted, "ivNonce", iv) .build(); String actual = SharedKeyCommands.get(executor, createAtSign("gary"), keys, key); @@ -78,7 +78,7 @@ void testGetSharedByMeCachedKey() throws Exception { @Test void testGetSharedByMeServerException() throws Exception { String encryptKey = generateAESKeyBase64(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("llookup:shared_key.colin@gary", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) .stub("llookup:all:@colin:test@gary", "error:AT0001:deliberate") .build(); @@ -90,7 +90,7 @@ void testGetSharedByMeServerException() throws Exception { @Test void testGetSharedByMeExecutionException() throws Exception { String encryptKey = generateAESKeyBase64(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("llookup:shared_key.colin@gary", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) .stubExecutionException("llookup:all:@colin:test@gary") .build(); @@ -103,8 +103,8 @@ void getGetSharedByOther() throws Exception { String encryptKey = generateAESKeyBase64(); String iv = generateRandomIvBase64(16); String encrypted = aesEncryptToBase64("hello colin", encryptKey, iv); - AtCommandExecutor executor = TestConnectionBuilder.builder() - .stub("lookup:all:test@gary", createMockLookupResponse("@colin:test@gary", encrypted, iv)) + AtCommandExecutor executor = TestExecutorBuilder.builder() + .stubLookupResponse("lookup:all:test@gary", "@colin:test@gary", encrypted, "ivNonce", iv) .stub("lookup:shared_key@gary", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) .build(); @@ -116,7 +116,7 @@ void getGetSharedByOther() throws Exception { @Test void testPutWhenSharedKeyAlreadyExists() throws Exception { String encryptKey = generateAESKeyBase64(); - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("llookup:shared_key.colin@gary", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey())) .stub("update:isEncrypted:true:ivNonce:.+:@colin:test@gary .+", "data:123") .build(); @@ -127,7 +127,7 @@ void testPutWhenSharedKeyAlreadyExists() throws Exception { @Test void testPutWhenSharedKeDoesNotAlreadyExists() throws Exception { - AtCommandExecutor executor = TestConnectionBuilder.builder() + AtCommandExecutor executor = TestExecutorBuilder.builder() .stub("llookup:shared_key.colin@gary", "error:AT0015:deliberate") .stub("plookup:publickey@colin", "data:" + keys.getEncryptPublicKey()) .stub("update:shared_key.colin@gary .+", "data:1") @@ -137,22 +137,4 @@ void testPutWhenSharedKeDoesNotAlreadyExists() throws Exception { SharedKeyCommands.put(executor, createAtSign("gary"), keys, key, "hello colin"); } - - private static String createMockLookupResponse(String key, String value) { - return String.format("data:{" + - "\"key\": \"%s\"," + - "\"data\": \"%s\"," + - "\"metaData\": {\"ttl\": 86400000, \"isEncrypted\": false, \"isPublic\": true}" + - "}", key, value); - } - - - private static String createMockLookupResponse(String key, String encrypted, String iv) { - return String.format("data:{" + - "\"key\": \"%s\"," + - "\"data\": \"%s\"," + - "\"metaData\": {\"ttl\": 86400000, \"ivNonce\": \"%s\", \"isEncrypted\": true, \"isPublic\": false}" + - "}", key, encrypted, iv); - } - } diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/TestConnectionBuilder.java b/at_client/src/test/java/org/atsign/client/impl/commands/TestExecutorBuilder.java similarity index 55% rename from at_client/src/test/java/org/atsign/client/impl/commands/TestConnectionBuilder.java rename to at_client/src/test/java/org/atsign/client/impl/commands/TestExecutorBuilder.java index 15c32fa7..0f224e69 100644 --- a/at_client/src/test/java/org/atsign/client/impl/commands/TestConnectionBuilder.java +++ b/at_client/src/test/java/org/atsign/client/impl/commands/TestExecutorBuilder.java @@ -1,8 +1,10 @@ package org.atsign.client.impl.commands; +import static org.atsign.client.impl.common.Preconditions.checkTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -11,44 +13,49 @@ import java.util.regex.Pattern; import org.atsign.client.api.AtCommandExecutor; +import org.atsign.client.impl.util.JsonUtils; import org.mockito.Mockito; import org.mockito.stubbing.Answer; -public class TestConnectionBuilder { +public class TestExecutorBuilder { private Map mapping = new LinkedHashMap<>(); - public static TestConnectionBuilder builder() { - return new TestConnectionBuilder(); + public static TestExecutorBuilder builder() { + return new TestExecutorBuilder(); } - public TestConnectionBuilder stub(String command, String response) { + public TestExecutorBuilder stub(String command, String response) { return stub(Pattern.compile(command), response); } - public TestConnectionBuilder stub(Pattern command, String response) { + public TestExecutorBuilder stub(Pattern command, String response) { mapping.put(command, response); return this; } - public TestConnectionBuilder stub(String command, Function fn) { + public TestExecutorBuilder stub(String command, Function fn) { return stub(Pattern.compile(command), fn); } - public TestConnectionBuilder stub(Pattern command, Function fn) { + public TestExecutorBuilder stub(Pattern command, Function fn) { mapping.put(command, fn); return this; } - public TestConnectionBuilder stub(String command, Exception ex) { + public TestExecutorBuilder stub(String command, Exception ex) { return stub(Pattern.compile(command), ex); } - public TestConnectionBuilder stubExecutionException(String command) { + public TestExecutorBuilder stubExecutionException(String command) { return stub(Pattern.compile(command), new ExecutionException("deliberate", null)); } - public TestConnectionBuilder stub(Pattern command, Exception ex) { + public TestExecutorBuilder stubLookupResponse(String command, String key, String value, Object... metadata) { + return stub(Pattern.compile(command), createLookupResponse(key, value, metadata)); + } + + public TestExecutorBuilder stub(Pattern command, Exception ex) { mapping.put(command, ex); return this; } @@ -73,4 +80,15 @@ public AtCommandExecutor build() throws ExecutionException, InterruptedException }); return mock; } + + private static String createLookupResponse(String key, String value, Object... metadata) { + checkTrue((metadata.length % 2) == 0, "expected name value pairs"); + Map map = new HashMap<>(); + for (int i = 0; i < metadata.length; i++) { + map.put((String) metadata[i], metadata[++i]); + } + return String.format("data:{\"key\": \"%s\",\"data\": \"%s\",\"metaData\": %s}", + key, value, JsonUtils.writeValueAsString(map)); + } + } diff --git a/at_client/src/test/java/org/atsign/client/impl/util/Base2e15UtilsTest.java b/at_client/src/test/java/org/atsign/client/impl/util/Base2e15UtilsTest.java new file mode 100644 index 00000000..f2ade038 --- /dev/null +++ b/at_client/src/test/java/org/atsign/client/impl/util/Base2e15UtilsTest.java @@ -0,0 +1,153 @@ +package org.atsign.client.impl.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Random; + +import org.junit.jupiter.api.Test; + +class Base2e15UtilsTest { + + // these mirror the values used in the tests for the dart implementation + public static final byte[] DART_TEST_BYTES = "Base2e15 is awesome!".getBytes(StandardCharsets.UTF_8); + public static final String DART_TEST_ENCODED = "嗺둽嬖蟝巍媖疌켉溁닽壪"; + + @Test + void testEncodeMatchesDartTestResult() { + assertThat(Base2e15Utils.encode(DART_TEST_BYTES), equalTo(DART_TEST_ENCODED)); + } + + @Test + void testDecodeMatchesDartTestResult() { + assertThat(Base2e15Utils.decode(DART_TEST_ENCODED), equalTo(DART_TEST_BYTES)); + } + + @Test + void testDecodeOutsideUnicodeRangeThrowsException() { + Exception ex = assertThrows(IllegalArgumentException.class, () -> Base2e15Utils.decode("嗺둽嬖蟝巍x疌켉溁닽壪")); + assertThat(ex.getMessage(), containsString("U+0078 at index 5 is not within 15 or 7 bit range")); + } + + @Test + void testEncodeRepeatingSequenceOf15BytesGeneratesTheExpectedResult() { + byte[] bytes = new byte[15]; + Arrays.fill(bytes, (byte) Integer.parseInt("10101010", 2)); + String encoded = Base2e15Utils.encode(bytes); + + assertThat(encoded.length(), equalTo(8)); + + // first sequence of bits will fall in the 3rd base2e15 range + int bits1 = Integer.parseInt("101010101010101", 2) & 0x7FFF; + assertThat(bits1, equalTo(21845)); + int codePoint1 = bits1 + 0xAC00 - 0x545C; + String unicode1 = new String(Character.toChars(codePoint1)); + + // second sequence of bits will fall in the 2nd base2e15 range + int bits2 = Integer.parseInt("010101010101010", 2) & 0x7FFF; + assertThat(bits2, equalTo(10922)); + int codePoint2 = bits2 + 0x4E00 - 0x1936; + String unicode2 = new String(Character.toChars(codePoint2)); + + String expected = unicode1 + unicode2 + + unicode1 + unicode2 + + unicode1 + unicode2 + + unicode1 + unicode2; + + assertThat(encoded, equalTo(expected)); + + assertThat(Base2e15Utils.decode(encoded), equalTo(bytes)); + } + + @Test + void testEncodeRepeatingSequenceOf16BytesGeneratesTheExpectedResult() { + byte[] bytes = new byte[16]; + Arrays.fill(bytes, (byte) Integer.parseInt("10101010", 2)); + String encoded = Base2e15Utils.encode(bytes); + + assertThat(encoded.length(), equalTo(9)); + + // first sequence of bits will fall in the 3rd 15 bit range + int bits1 = Integer.parseInt("101010101010101", 2) & 0x7FFF; + assertThat(bits1, equalTo(21845)); + int codePoint1 = bits1 + 0xAC00 - 0x545C; + String unicode1 = new String(Character.toChars(codePoint1)); + + // second sequence of bits will fall in the 2nd 15 bit range + int bits2 = Integer.parseInt("010101010101010", 2) & 0x7FFF; + assertThat(bits2, equalTo(10922)); + int codePoint2 = bits2 + 0x4E00 - 0x1936; + String unicode2 = new String(Character.toChars(codePoint2)); + + // third sequence of bits will be padded to 15 bits and fall in the 3rd 15 bit range + int bits3 = (Integer.parseInt("10101010", 2) << 7) & 0x7FFF; + assertThat(bits3, equalTo(21760)); + int codePoint3 = bits3 + 0xAC00 - 0x545C; + String unicode3 = new String(Character.toChars(codePoint3)); + + String expected = unicode1 + unicode2 + + unicode1 + unicode2 + + unicode1 + unicode2 + + unicode1 + unicode2 + + unicode3; + + assertThat(encoded, equalTo(expected)); + + assertThat(Base2e15Utils.decode(encoded), equalTo(bytes)); + } + + @Test + void testEncodeRepeatingSequenceOf14BytesGeneratesTheExpectedResult() { + byte[] bytes = new byte[14]; + Arrays.fill(bytes, (byte) Integer.parseInt("10101010", 2)); + String encoded = Base2e15Utils.encode(bytes); + + assertThat(encoded.length(), equalTo(8)); + + // first sequence of bits will fall in the 3rd 15 bit range + int bits1 = Integer.parseInt("101010101010101", 2) & 0x7FFF; + assertThat(bits1, equalTo(21845)); + int codePoint1 = bits1 + 0xAC00 - 0x545C; + String unicode1 = new String(Character.toChars(codePoint1)); + + // second sequence of bits will fall in the 2nd 15 bit range + int bits2 = Integer.parseInt("010101010101010", 2) & 0x7FFF; + assertThat(bits2, equalTo(10922)); + int codePoint2 = bits2 + 0x4E00 - 0x1936; + String unicode2 = new String(Character.toChars(codePoint2)); + + // third sequence of bits will NOT be padded to 15 bits and fall in the 7 bit range + int bits3 = Integer.parseInt("10101010", 2) & 0x7F; + assertThat(bits3, equalTo(42)); + int codePoint3 = bits3 + 0x3400; + String unicode3 = new String(Character.toChars(codePoint3)); + + String expected = unicode1 + unicode2 + + unicode1 + unicode2 + + unicode1 + unicode2 + + unicode1 + unicode3; + + assertThat(encoded, equalTo(expected)); + + assertThat(Base2e15Utils.decode(encoded), equalTo(bytes)); + } + + @Test + void testEncodeDecodeRandomBytesResultsInOriginalBytes() { + Random random = new Random(42); + + for (int len = 0; len < 1000; len++) { + byte[] input = new byte[len]; + random.nextBytes(input); + + String encoded = Base2e15Utils.encode(input); + byte[] decoded = Base2e15Utils.decode(encoded); + + assertThat(decoded, equalTo(input)); + } + } +} diff --git a/at_client/src/test/java/org/atsign/cucumber/steps/ParameterTypes.java b/at_client/src/test/java/org/atsign/cucumber/steps/ParameterTypes.java index 76f4814a..422bbf13 100644 --- a/at_client/src/test/java/org/atsign/cucumber/steps/ParameterTypes.java +++ b/at_client/src/test/java/org/atsign/cucumber/steps/ParameterTypes.java @@ -1,10 +1,14 @@ package org.atsign.cucumber.steps; import java.io.File; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import io.cucumber.datatable.DataTable; import org.atsign.client.impl.exceptions.AtException; import org.atsign.client.api.AtSign; @@ -47,4 +51,31 @@ public TimeUnit timeunit(String unit) { return TimeUnit.valueOf(s); } + public static byte[] toBytes(DataTable table) { + List cells = table.asLists().stream() + .flatMap(List::stream) + .filter(s -> s != null && !s.isEmpty()) + .collect(Collectors.toList()); + Function transformer = getByteTransformer(cells.get(0)); + byte[] bytes = new byte[cells.size() - 1]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = transformer.apply(cells.get(i + 1)); + } + return bytes; + } + + private static Function getByteTransformer(String base) { + Matcher matcher = Pattern.compile("base\\s*(\\d+)", Pattern.CASE_INSENSITIVE).matcher(base); + if (matcher.matches()) { + return s -> (byte) Integer.parseInt(s, Integer.parseInt(matcher.group(1))); + } else if (base.equalsIgnoreCase("binary")) { + return s -> (byte) Integer.parseInt(s, 2); + } else if (base.toLowerCase().startsWith("hex")) { + return s -> (byte) Integer.parseInt(s, 2); + } else { + throw new IllegalArgumentException("expected leading cell to be base e.g. Binary, Hex, Base 2, etc..."); + } + } + + } diff --git a/at_client/src/test/java/org/atsign/cucumber/steps/PublicAtKeySteps.java b/at_client/src/test/java/org/atsign/cucumber/steps/PublicAtKeySteps.java index 3ca48c2a..51366925 100644 --- a/at_client/src/test/java/org/atsign/cucumber/steps/PublicAtKeySteps.java +++ b/at_client/src/test/java/org/atsign/cucumber/steps/PublicAtKeySteps.java @@ -1,11 +1,13 @@ package org.atsign.cucumber.steps; +import static org.atsign.cucumber.steps.ParameterTypes.toBytes; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; +import io.cucumber.datatable.DataTable; import org.atsign.client.api.AtClient; import org.atsign.client.api.AtSign; import org.atsign.client.api.Keys; @@ -29,6 +31,12 @@ public void put(Integer ordinal, AtSign clientAtSign, String name, String value) putKeyValue(atClient, clientAtSign, name, value); } + @When("{ordinal} {atsign} AtClient.put for PublicKey {word} and bytes") + public void put(Integer ordinal, AtSign clientAtSign, String name, DataTable table) throws Exception { + AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); + putKeyValue(atClient, clientAtSign, name, toBytes(table)); + } + @When("{ordinal} {atsign} AtClient.put fails for PublicKey {word} and value {string}") public void putFails(Integer ordinal, AtSign clientAtSign, String name, String value) throws Exception { AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); @@ -41,6 +49,12 @@ public void put(AtSign clientAtSign, String name, String value) throws Exception putKeyValue(atClient, clientAtSign, name, value); } + @When("{atsign} AtClient.put for PublicKey {word} and bytes") + public void put(AtSign clientAtSign, String name, DataTable table) throws Exception { + AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); + putKeyValue(atClient, clientAtSign, name, toBytes(table)); + } + @When("{atsign} AtClient.put fails for PublicKey {word} and value {string}") public void putFails(AtSign clientAtSign, String name, String value) throws Exception { AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); @@ -54,6 +68,13 @@ public void put(String name, String value) throws Exception { putKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name, value); } + @When("AtClient.put for PublicKey {word} and bytes") + public void put(String name, DataTable table) throws Exception { + QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); + AtClient atClient = context.lookupAtClient(currentQualifiedAtSign); + putKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name, toBytes(table)); + } + @When("AtClient.put fails for PublicKey {word} and value {string}") public void putFails(String name, String value) throws Exception { QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); @@ -110,6 +131,13 @@ public void getAsOwner(Integer ordinal, AtSign clientAtSign, String name, String assertThat(keyValue, equalTo(expected)); } + @Then("{ordinal} {atsign} AtClient.getBinary for PublicKey {word} returns bytes that matches") + public void getAsOwner(Integer ordinal, AtSign clientAtSign, String name, DataTable expected) throws Exception { + AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); + byte[] keyValue = getBinaryKeyValue(atClient, clientAtSign, name); + assertThat(keyValue, equalTo(toBytes(expected))); + } + @Then("{ordinal} {atsign} AtClient.get fails for PublicKey {word}") public void getAsOwnerFails(Integer ordinal, AtSign clientAtSign, String name) throws Exception { AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); @@ -123,6 +151,13 @@ public void getAsOwner(AtSign clientAtSign, String name, String expected) throws assertThat(keyValue, equalTo(expected)); } + @Then("{atsign} AtClient.getBinary for PublicKey {word} returns bytes that matches") + public void getAsOwner(AtSign clientAtSign, String name, DataTable table) throws Exception { + AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, clientAtSign, name); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{atsign} AtClient.get fails for PublicKey {word}") public void getAsOwnerFails(AtSign clientAtSign, String name) throws Exception { AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); @@ -137,6 +172,14 @@ public void getAsOwner(String name, String expected) throws Exception { assertThat(keyValue, equalTo(expected)); } + @Then("AtClient.getBinary for PublicKey {word} returns bytes that matches") + public void getAsOwner(String name, DataTable expected) throws Exception { + QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); + AtClient atClient = context.lookupAtClient(currentQualifiedAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name); + assertThat(keyValue, equalTo(toBytes(expected))); + } + @Then("AtClient.get fails for PublicKey {word}") public void getAsOwnerFails(String name) throws Exception { QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); @@ -152,6 +195,14 @@ public void getAsNonOwner(Integer ordinal, AtSign clientAtSign, String name, AtS assertThat(keyValue, equalTo(expected)); } + @Then("{ordinal} {atsign} AtClient.getBinary for PublicKey {word} shared by {atsign} returns bytes that matches") + public void getAsNonOwner(Integer ordinal, AtSign clientAtSign, String name, AtSign sharedBy, DataTable table) + throws Exception { + AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); + byte[] keyValue = getBinaryKeyValue(atClient, sharedBy, name); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{ordinal} {atsign} AtClient.get fails for PublicKey shared by {atsign}") public void getAsNonOwnerFails(Integer ordinal, AtSign clientAtSign, String name, AtSign sharedBy) throws Exception { AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); @@ -165,6 +216,13 @@ public void getAsNonOwner(AtSign clientAtSign, String name, AtSign sharedBy, Str assertThat(keyValue, equalTo(expected)); } + @Then("{atsign} AtClient.getBinary for PublicKey {word} shared by {atsign} returns bytes that matches") + public void getAsNonOwner(AtSign clientAtSign, String name, AtSign sharedBy, DataTable table) throws Exception { + AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, sharedBy, name); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{atsign} AtClient.get fails for PublicKey {word} shared by {atsign}") public void getAsNonOwnerFails(AtSign clientAtSign, String name, AtSign sharedBy) throws Exception { AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); @@ -179,6 +237,14 @@ public void getAsNonOwner(String name, AtSign sharedBy, String expected) throws assertThat(keyValue, equalTo(expected)); } + @Then("AtClient.getBinary for PublicKey {word} shared by {atsign} returns bytes that matches") + public void getAsNonOwner(String name, AtSign sharedBy, DataTable table) throws Exception { + QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); + AtClient atClient = context.lookupAtClient(currentQualifiedAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, sharedBy, name); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("AtClient.get fails for PublicKey {word} shared by {atsign}") public void getAsNonOwnerFails(String name, AtSign sharedBy) throws Exception { QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); @@ -192,11 +258,22 @@ private void putKeyValue(AtClient atClient, AtSign sharedBy, String name, String atClient.put(key, value).get(); } + private void putKeyValue(AtClient atClient, AtSign sharedBy, String name, byte[] value) + throws InterruptedException, ExecutionException { + Keys.PublicKey key = toKey(sharedBy, name); + atClient.put(key, value).get(); + } + private String getKeyValue(AtClient atClient, AtSign sharedBy, String name) throws Exception { Keys.PublicKey key = toKey(sharedBy, name); return atClient.get(key).get(); } + private byte[] getBinaryKeyValue(AtClient atClient, AtSign sharedBy, String name) throws Exception { + Keys.PublicKey key = toKey(sharedBy, name); + return atClient.getBinary(key).get(); + } + private void deleteKeyValue(AtClient atClient, AtSign owner, String name) throws Exception { Keys.PublicKey key = toKey(owner, name); atClient.delete(key).get(); diff --git a/at_client/src/test/java/org/atsign/cucumber/steps/SelfAtKeySteps.java b/at_client/src/test/java/org/atsign/cucumber/steps/SelfAtKeySteps.java index adb7e001..a0ce8ce3 100644 --- a/at_client/src/test/java/org/atsign/cucumber/steps/SelfAtKeySteps.java +++ b/at_client/src/test/java/org/atsign/cucumber/steps/SelfAtKeySteps.java @@ -1,11 +1,13 @@ package org.atsign.cucumber.steps; +import static org.atsign.cucumber.steps.ParameterTypes.toBytes; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; +import io.cucumber.datatable.DataTable; import org.atsign.client.api.AtClient; import org.atsign.client.api.AtSign; import org.atsign.client.api.Keys; @@ -29,6 +31,12 @@ public void put(Integer ordinal, AtSign clientAtSign, String name, String value) putKeyValue(atClient, clientAtSign, name, value); } + @When("{ordinal} {atsign} AtClient.put for SelfKey {word} and bytes") + public void put(Integer ordinal, AtSign clientAtSign, String name, DataTable table) throws Exception { + AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); + putKeyValue(atClient, clientAtSign, name, toBytes(table)); + } + @When("{ordinal} {atsign} AtClient.put fails for SelfKey {word} and value {string}") public void putFails(Integer ordinal, AtSign clientAtSign, String name, String value) throws Exception { AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); @@ -41,6 +49,12 @@ public void put(AtSign clientAtSign, String name, String value) throws Exception putKeyValue(atClient, clientAtSign, name, value); } + @When("{atsign} AtClient.put for SelfKey {word} and bytes") + public void put(AtSign clientAtSign, String name, DataTable table) throws Exception { + AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); + putKeyValue(atClient, clientAtSign, name, toBytes(table)); + } + @When("AtClient.put for SelfKey {word} and value {string}") public void put(String name, String value) throws Exception { QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); @@ -48,6 +62,13 @@ public void put(String name, String value) throws Exception { putKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name, value); } + @When("AtClient.put for SelfKey {word} and bytes") + public void put(String name, DataTable table) throws Exception { + QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); + AtClient atClient = context.lookupAtClient(currentQualifiedAtSign); + putKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name, toBytes(table)); + } + // delete @When("{ordinal} {atsign} AtClient.delete for SelfKey {word}") @@ -97,6 +118,13 @@ public void assertGet(Integer ordinal, AtSign clientAtSign, String name, String assertThat(keyValue, equalTo(expected)); } + @Then("{ordinal} {atsign} AtClient.getBinary for SelfKey {word} returns bytes that matches") + public void assertGet(Integer ordinal, AtSign clientAtSign, String name, DataTable table) throws Exception { + AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); + byte[] keyValue = getBinaryKeyValue(atClient, clientAtSign, name); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{ordinal} {atsign} AtClient.get fails for SelfKey {word}") public void assertGetFails(Integer ordinal, AtSign clientAtSign, String name) throws Exception { AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); @@ -110,6 +138,13 @@ public void assertGet(AtSign clientAtSign, String name, String expected) throws assertThat(keyValue, equalTo(expected)); } + @Then("{atsign} AtClient.getBinary for SelfKey {word} returns bytes that matches") + public void assertGet(AtSign clientAtSign, String name, DataTable table) throws Exception { + AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, clientAtSign, name); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{atsign} AtClient.get fails for SelfKey {word}") public void assertGetFails(AtSign clientAtSign, String name) throws Exception { AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); @@ -124,6 +159,14 @@ public void assertGet(String name, String expected) throws Exception { assertThat(keyValue, equalTo(expected)); } + @Then("AtClient.getBinary for SelfKey {word} returns bytes that matches") + public void assertGet(String name, DataTable table) throws Exception { + QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); + AtClient atClient = context.lookupAtClient(currentQualifiedAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("AtClient.get fails for SelfKey {word}") public void assertGetFails(String name) throws Exception { QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); @@ -137,11 +180,22 @@ private void putKeyValue(AtClient atClient, AtSign atSign, String name, String v atClient.put(key, value).get(); } + private void putKeyValue(AtClient atClient, AtSign atSign, String name, byte[] value) + throws InterruptedException, ExecutionException { + Keys.SelfKey key = createKey(atSign, name); + atClient.put(key, value).get(); + } + private String getKeyValue(AtClient atClient, AtSign atSign, String name) throws Exception { Keys.SelfKey key = createKey(atSign, name); return atClient.get(key).get(); } + private byte[] getBinaryKeyValue(AtClient atClient, AtSign atSign, String name) throws Exception { + Keys.SelfKey key = createKey(atSign, name); + return atClient.getBinary(key).get(); + } + private void deleteKeyValue(AtClient atClient, AtSign atSign, String name) throws Exception { Keys.SelfKey key = createKey(atSign, name); atClient.delete(key).get(); diff --git a/at_client/src/test/java/org/atsign/cucumber/steps/SharedAtKeySteps.java b/at_client/src/test/java/org/atsign/cucumber/steps/SharedAtKeySteps.java index 22a44eee..fa60fae2 100644 --- a/at_client/src/test/java/org/atsign/cucumber/steps/SharedAtKeySteps.java +++ b/at_client/src/test/java/org/atsign/cucumber/steps/SharedAtKeySteps.java @@ -1,5 +1,6 @@ package org.atsign.cucumber.steps; +import static org.atsign.cucumber.steps.ParameterTypes.toBytes; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -9,6 +10,7 @@ import org.atsign.client.api.AtSign; import org.atsign.client.api.Keys; +import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -29,6 +31,13 @@ public void putAsOwner(Integer ordinal, AtSign clientAtSign, String name, AtSign putKeyValue(atClient, clientAtSign, name, sharedWith, value); } + @When("{ordinal} {atsign} AtClient.put for SharedKey {word} shared with {atsign} and bytes") + public void putAsOwner(Integer ordinal, AtSign clientAtSign, String name, AtSign sharedWith, DataTable table) + throws Exception { + AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); + putKeyValue(atClient, clientAtSign, name, sharedWith, toBytes(table)); + } + @Then("{ordinal} {atsign} AtClient.put fails for SharedKey {word} shared with {atsign} and value {string}") public void putAsOwnerFails(Integer ordinal, AtSign clientAtSign, String name, AtSign sharedWith, String value) throws Exception { @@ -42,6 +51,12 @@ public void putAsOwner(AtSign clientAtSign, String name, AtSign sharedWith, Stri putKeyValue(atClient, clientAtSign, name, sharedWith, value); } + @When("{atsign} AtClient.put for SharedKey {word} shared with {atsign} and bytes") + public void putAsOwner(AtSign clientAtSign, String name, AtSign sharedWith, DataTable table) throws Exception { + AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); + putKeyValue(atClient, clientAtSign, name, sharedWith, toBytes(table)); + } + @Then("{atsign} AtClient.put fails for SharedKey {word} shared with {atsign} and value {string}") public void putAsOwnerFails(AtSign clientAtSign, String name, AtSign sharedWith, String value) throws Exception { AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); @@ -55,6 +70,13 @@ public void putAsOwner(String name, AtSign sharedWith, String value) throws Exce putKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name, sharedWith, value); } + @When("AtClient.put for SharedKey {word} shared with {atsign} and bytes") + public void putAsOwner(String name, AtSign sharedWith, DataTable table) throws Exception { + QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); + AtClient atClient = context.lookupAtClient(currentQualifiedAtSign); + putKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name, sharedWith, toBytes(table)); + } + @Then("AtClient.put for fails SharedKey {word} shared with {atsign} and value {string}") public void putAsOwnerFails(String name, AtSign sharedWith, String value) throws Exception { QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); @@ -106,6 +128,14 @@ public void assertGetAsOwner(Integer ordinal, AtSign clientAtSign, String name, assertThat(keyValue, equalTo(expected)); } + @Then("{ordinal} {atsign} AtClient.getBinary for SharedKey {word} shared with {atsign} returns bytes that matches") + public void assertGetAsOwner(Integer ordinal, AtSign clientAtSign, String name, AtSign sharedWith, DataTable table) + throws Exception { + AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); + byte[] keyValue = getBinaryKeyValue(atClient, clientAtSign, name, sharedWith); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{ordinal} {atsign} AtClient.get fails for SharedKey {word} shared with {atsign}") public void assertGetExceptionAsOwner(Integer ordinal, AtSign clientAtSign, String name, AtSign sharedWith) { AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); @@ -119,6 +149,13 @@ public void assertGetAsOwner(AtSign clientAtSign, String name, AtSign sharedWith assertThat(keyValue, equalTo(expected)); } + @Then("{atsign} AtClient.getBinary for SharedKey {word} shared with {atsign} returns bytes that matches") + public void assertGetAsOwner(AtSign clientAtSign, String name, AtSign sharedWith, DataTable table) throws Exception { + AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, clientAtSign, name, sharedWith); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{atsign} AtClient.get fails for SharedKey {word} shared with {atsign}") public void assertGetExceptionAsOwner(AtSign clientAtSign, String name, AtSign sharedWith) throws Exception { AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); @@ -133,6 +170,14 @@ public void assertGetAsOwner(String name, AtSign sharedWith, String expected) th assertThat(keyValue, equalTo(expected)); } + @Then("AtClient.getBinary for SharedKey {word} shared with {atsign} returns bytes that matches") + public void assertGetAsOwner(String name, AtSign sharedWith, DataTable table) throws Exception { + QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); + AtClient atClient = context.lookupAtClient(currentQualifiedAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, currentQualifiedAtSign.getAtSign(), name, sharedWith); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("AtClient.get fails for SharedKey {word} shared with {atsign}") public void assertGetExceptionAsOwner(String name, AtSign sharedWith) { QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); @@ -150,6 +195,14 @@ public void assertGetAsRecipient(Integer ordinal, AtSign clientAtSign, String na assertThat(keyValue, equalTo(expected)); } + @Then("{ordinal} {atsign} AtClient.getBinary for SharedKey {word} shared by {atsign} returns bytes that matches") + public void assertGetAsRecipient(Integer ordinal, AtSign clientAtSign, String name, AtSign sharedBy, DataTable table) + throws Exception { + AtClient atClient = context.lookupAtClient(clientAtSign, ordinal); + byte[] keyValue = getBinaryKeyValue(atClient, sharedBy, name, clientAtSign); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{ordinal} {atsign} AtClient.get fails for SharedKey {word} shared by {atsign}}") public void assertGetSharedKeyResultForRecipientFails(Integer ordinal, AtSign clientAtSign, String name, AtSign sharedBy) @@ -166,6 +219,14 @@ public void assertGetAsRecipient(AtSign clientAtSign, String name, AtSign shared assertThat(keyValue, equalTo(expected)); } + @Then("{atsign} AtClient.getBinary for SharedKey {word} shared by {atsign} returns bytes that matches") + public void assertGetAsRecipient(AtSign clientAtSign, String name, AtSign sharedBy, DataTable table) + throws Exception { + AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, sharedBy, name, clientAtSign); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("{atsign} AtClient.get fails for SharedKey {word} shared by {atsign}") public void assertGetAsRecipientFails(AtSign clientAtSign, String name, AtSign sharedBy) throws Exception { AtClient atClient = context.lookupOrCreateAtClient(clientAtSign); @@ -180,6 +241,14 @@ public void assertGetAsRecipient(String name, AtSign sharedBy, String expected) assertThat(keyValue, equalTo(expected)); } + @Then("AtClient.getBinary for SharedKey {word} shared by {atsign} returns bytes that matches") + public void assertGetAsRecipient(String name, AtSign sharedBy, DataTable table) throws Exception { + QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); + AtClient atClient = context.lookupAtClient(currentQualifiedAtSign); + byte[] keyValue = getBinaryKeyValue(atClient, sharedBy, name, currentQualifiedAtSign.getAtSign()); + assertThat(keyValue, equalTo(toBytes(table))); + } + @Then("AtClient.get fails for SharedKey {word} shared by {atsign}") public void assertGetAsRecipientFails(String name, AtSign sharedBy, String expected) throws Exception { QualifiedAtSign currentQualifiedAtSign = context.getCurrentQualifiedAtSign(); @@ -193,11 +262,23 @@ private void putKeyValue(AtClient atClient, AtSign sharedBy, String name, AtSign atClient.put(key, value).get(); } + private void putKeyValue(AtClient atClient, AtSign sharedBy, String name, AtSign sharedWith, byte[] value) + throws Exception { + Keys.SharedKey key = createKey(sharedBy, name, sharedWith); + atClient.put(key, value).get(); + } + private String getKeyValue(AtClient atClient, AtSign sharedBy, String name, AtSign sharedWith) throws Exception { Keys.SharedKey key = createKey(sharedBy, name, sharedWith); return atClient.get(key).get(); } + private byte[] getBinaryKeyValue(AtClient atClient, AtSign sharedBy, String name, AtSign sharedWith) + throws Exception { + Keys.SharedKey key = createKey(sharedBy, name, sharedWith); + return atClient.getBinary(key).get(); + } + private void deleteKeyValue(AtClient atClient, AtSign sharedBy, String name, AtSign sharedWith) throws Exception { Keys.SharedKey key = createKey(sharedBy, name, sharedWith); atClient.delete(key).get(); diff --git a/at_client/src/test/resources/features/PublicKey.feature b/at_client/src/test/resources/features/PublicKey.feature index 7d8809e6..6a7d5f6f 100644 --- a/at_client/src/test/resources/features/PublicKey.feature +++ b/at_client/src/test/resources/features/PublicKey.feature @@ -44,3 +44,25 @@ Feature: AtClient API test for PublicKeys And AtClient.getAtKeys for "test.+" matches | Key | Name | Namespace | Shared By | Shared With | | public:test@gary | test | | gary | | + + Scenario: PublicKey put bytes encodes as Base15e2 + When AtClient.put for PublicKey test and bytes + | Binary | | | | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | | + Then AtClient.get for PublicKey test returns value that matches "곹彴곹彴곹彴곹彴" + But AtClient.getBinary for PublicKey test returns bytes that matches + | Binary | | | | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | | + And @colin AtClient.getBinary for PublicKey test shared by @gary returns bytes that matches + | Binary | | | | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | | + diff --git a/at_client/src/test/resources/features/SelfKey.feature b/at_client/src/test/resources/features/SelfKey.feature index d12e3d33..2f713879 100644 --- a/at_client/src/test/resources/features/SelfKey.feature +++ b/at_client/src/test/resources/features/SelfKey.feature @@ -35,3 +35,18 @@ Feature: AtClient API tests for SelfKeys Then @colin AtClient.getAtKeys for ".+" does NOT contain | test@gary | + Scenario: SelfKey put bytes encodes as Base15e2 + When AtClient.put for SelfKey test and bytes + | Binary | | | | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | | + Then AtClient.get for SelfKey test returns value that matches "곹彴곹彴곹彴곹彴" + But AtClient.getBinary for SelfKey test returns bytes that matches + | Binary | | | | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | | + diff --git a/at_client/src/test/resources/features/SharedKey.feature b/at_client/src/test/resources/features/SharedKey.feature index bae5448b..28bf9585 100644 --- a/at_client/src/test/resources/features/SharedKey.feature +++ b/at_client/src/test/resources/features/SharedKey.feature @@ -48,13 +48,34 @@ Feature: AtClient API test for SharedKeys | public:test@gary | Scenario: Namespace qualified - When @gary AtClient.put for SharedKey message.ns shared with @colin and value "hi colin it's gary" - And @colin AtClient.put for SharedKey message.ns shared with @gary and value "hi gary it's colin" - Then @colin AtClient.get for SharedKey message.ns shared by @gary returns value that matches "hi colin it's gary" - And @gary AtClient.get for SharedKey message.ns shared by @colin returns value that matches "hi gary it's colin" + When @gary AtClient.put for SharedKey message.ns shared with @colin and value "hi colin it's gary" + And @colin AtClient.put for SharedKey message.ns shared with @gary and value "hi gary it's colin" + Then @colin AtClient.get for SharedKey message.ns shared by @gary returns value that matches "hi colin it's gary" + And @gary AtClient.get for SharedKey message.ns shared by @colin returns value that matches "hi gary it's colin" Scenario: SharedKey get returns expected value for "Shared With" atsign after change When AtClient.put for SharedKey test shared with @colin and value "hello world" And @colin AtClient.get for SharedKey test shared by @gary returns value that matches "hello world" And AtClient.put for SharedKey test shared with @colin and value "goodbye cruel world" Then @colin AtClient.get for SharedKey test shared by @gary returns value that matches "goodbye cruel world" + + Scenario: SharedKey put bytes encodes as Base15e2 + When AtClient.put for SharedKey test shared with @colin and bytes + | Binary | | | | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | | + Then AtClient.get for SharedKey test shared with @colin returns value that matches "곹彴곹彴곹彴곹彴" + But AtClient.getBinary for SharedKey test shared with @colin returns bytes that matches + | Binary | | | | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | | + And @colin AtClient.getBinary for SharedKey test shared by @gary returns bytes that matches + | Binary | | | | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | 10101010 | + | 10101010 | 10101010 | 10101010 | | diff --git a/at_client/src/test/resources/org/atsign/virtualenv/docker-compose.yml b/at_client/src/test/resources/org/atsign/virtualenv/docker-compose.yml index ef4e9bb3..51a2aa14 100644 --- a/at_client/src/test/resources/org/atsign/virtualenv/docker-compose.yml +++ b/at_client/src/test/resources/org/atsign/virtualenv/docker-compose.yml @@ -9,6 +9,8 @@ services: - '25000-25999:25000-25999' extra_hosts: - "vip.ve.atsign.zone:127.0.0.1" + environment: + DART_VM_OPTIONS: "--use_compactor,--force_evacuation,--idle_duration_micros=0,--compactor_tasks=5,--marker_tasks=5,--scavenger_tasks=5,--old_gen_growth_time_ratio=50,--old_gen_growth_space_ratio=10" command: > sh -c ' supervisord -n & PID=$! &&