Merge pull request #27 from Happy0/slice_bug

Fix bug with scuttlebutt boxstream segments not being formed properly
diff --git a/scuttlebutt-handshake/src/main/java/org/apache/tuweni/scuttlebutt/handshake/SecureScuttlebuttStream.java b/scuttlebutt-handshake/src/main/java/org/apache/tuweni/scuttlebutt/handshake/SecureScuttlebuttStream.java
index e9ebbb3..923dd96 100644
--- a/scuttlebutt-handshake/src/main/java/org/apache/tuweni/scuttlebutt/handshake/SecureScuttlebuttStream.java
+++ b/scuttlebutt-handshake/src/main/java/org/apache/tuweni/scuttlebutt/handshake/SecureScuttlebuttStream.java
@@ -18,7 +18,9 @@
 import org.apache.tuweni.crypto.sodium.SecretBox;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 final class SecureScuttlebuttStream implements SecureScuttlebuttStreamClient, SecureScuttlebuttStreamServer {
 
@@ -135,18 +137,36 @@
 
   private Bytes encrypt(Bytes message, SecretBox.Key clientToServerKey, MutableBytes clientToServerNonce) {
     int messages = (int) Math.ceil((double) message.size() / 4096d);
-    Bytes[] encryptedMessages = new Bytes[messages];
-    for (int i = 0; i < messages; i++) {
-      Bytes encryptedMessage = encryptMessage(
-          message.slice(i * 4096, Math.min((i + 1) * 4096, message.size() - i * 4096)),
-          clientToServerKey,
-          clientToServerNonce);
-      encryptedMessages[i] = encryptedMessage;
-    }
-    return Bytes.concatenate(encryptedMessages);
+
+    ArrayList<Bytes> bytes = breakIntoParts(message);
+
+    List<Bytes> segments =
+        bytes.stream().map(slice -> encryptMessage(slice, clientToServerKey, clientToServerNonce)).collect(
+            Collectors.toList());
+
+    return Bytes.concatenate(segments.toArray(new Bytes[] {}));
   }
 
+  private ArrayList<Bytes> breakIntoParts(Bytes message) {
+
+    byte[] original = message.toArray();
+
+    int chunk = 4096;
+
+    ArrayList<Bytes> result = new ArrayList<>();
+    for (int i = 0; i < original.length; i += chunk) {
+      byte[] bytes = Arrays.copyOfRange(original, i, Math.min(original.length, i + chunk));
+
+      Bytes wrap = Bytes.wrap(bytes);
+      result.add(wrap);
+    }
+
+    return result;
+  }
+
+
   private Bytes encryptMessage(Bytes message, SecretBox.Key key, MutableBytes nonce) {
+
     SecretBox.Nonce headerNonce = null;
     SecretBox.Nonce bodyNonce = null;
     try {
diff --git a/scuttlebutt-rpc/src/test/java/org/apache/tuweni/scuttlebutt/rpc/mux/PatchworkIntegrationTest.java b/scuttlebutt-rpc/src/test/java/org/apache/tuweni/scuttlebutt/rpc/mux/PatchworkIntegrationTest.java
index df9a620..7f2cce1 100644
--- a/scuttlebutt-rpc/src/test/java/org/apache/tuweni/scuttlebutt/rpc/mux/PatchworkIntegrationTest.java
+++ b/scuttlebutt-rpc/src/test/java/org/apache/tuweni/scuttlebutt/rpc/mux/PatchworkIntegrationTest.java
@@ -11,7 +11,6 @@
  * specific language governing permissions and limitations under the License.
  */
 package org.apache.tuweni.scuttlebutt.rpc.mux;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -159,6 +158,38 @@
     rpcMessages.forEach(msg -> System.out.println(msg.asString()));
   }
 
+  @Test
+  @Disabled
+  /**
+   * We expect this to complete the AsyncResult with an exception.
+   */
+  public void postMessageThatIsTooLong(@VertxInstance Vertx vertx) throws Exception {
+
+    RPCHandler rpcHandler = makeRPCHandler(vertx);
+
+    List<AsyncResult<RPCResponse>> results = new ArrayList<>();
+
+    String longString = new String(new char[40000]).replace("\0", "a");
+
+    for (int i = 0; i < 20; i++) {
+      // Note: in a real use case, this would more likely be a Java class with these fields
+      HashMap<String, String> params = new HashMap<>();
+      params.put("type", "post");
+      params.put("text", longString);
+
+      RPCAsyncRequest asyncRequest = new RPCAsyncRequest(new RPCFunction("publish"), Arrays.asList(params));
+
+      AsyncResult<RPCResponse> rpcMessageAsyncResult = rpcHandler.makeAsyncRequest(asyncRequest);
+
+      results.add(rpcMessageAsyncResult);
+
+    }
+
+    List<RPCResponse> rpcMessages = AsyncResult.combine(results).get();
+
+    rpcMessages.forEach(msg -> System.out.println(msg.asString()));
+  }
+
   private RPCHandler makeRPCHandler(Vertx vertx) throws Exception {
     Signature.KeyPair keyPair = getLocalKeys();
     String networkKeyBase64 = "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=";