diff --git a/core/src/main/java/org/bitcoinj/base/internal/Buffers.java b/core/src/main/java/org/bitcoinj/base/internal/Buffers.java index f6e79042f..dd272c819 100644 --- a/core/src/main/java/org/bitcoinj/base/internal/Buffers.java +++ b/core/src/main/java/org/bitcoinj/base/internal/Buffers.java @@ -18,6 +18,7 @@ package org.bitcoinj.base.internal; import org.bitcoinj.base.VarInt; +import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -59,6 +60,18 @@ public class Buffers { return readBytes(buf, length); } + /** + * First write the length of the byte array as a {@link VarInt}. Then write the array contents. + * + * @param buf buffer to write to + * @param bytes bytes to write + * @return the buffer + * @throws BufferOverflowException if the value doesn't fit the remaining buffer + */ + public static ByteBuffer writeLengthPrefixedBytes(ByteBuffer buf, byte[] bytes) throws BufferOverflowException { + return buf.put(VarInt.of(bytes.length).serialize()).put(bytes); + } + /** * First read a {@link VarInt} from the buffer and use it to determine the number of bytes to read. Then read * that many bytes and interpret it as an UTF-8 encoded string to be returned. This construct is frequently used @@ -72,6 +85,20 @@ public class Buffers { return new String(readLengthPrefixedBytes(buf), StandardCharsets.UTF_8); } + /** + * Encode a given string using UTF-8. Then write the lnegth of the encoded bytes as a {@link VarInt}. Then write + * the bytes themselves. + * + * @param buf buffer to write to + * @param str string to write + * @return the buffer + * @throws BufferOverflowException if the value doesn't fit the remaining buffer + */ + public static ByteBuffer writeLengthPrefixedString(ByteBuffer buf, String str) throws BufferOverflowException { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + return writeLengthPrefixedBytes(buf, bytes); + } + /** * Advance buffer position by a given number of bytes. * diff --git a/core/src/test/java/org/bitcoinj/core/BuffersTest.java b/core/src/test/java/org/bitcoinj/core/BuffersTest.java new file mode 100644 index 000000000..5c9e22528 --- /dev/null +++ b/core/src/test/java/org/bitcoinj/core/BuffersTest.java @@ -0,0 +1,58 @@ +/* + * Copyright by the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.core; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.bitcoinj.base.VarInt; +import org.bitcoinj.base.internal.Buffers; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.Random; +import java.util.stream.Stream; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; + +@RunWith(JUnitParamsRunner.class) +public class BuffersTest { + @Test + @Parameters(method = "randomBytes") + public void readAndWrite(byte[] bytes) { + ByteBuffer buf = ByteBuffer.allocate(VarInt.sizeOf(bytes.length) + bytes.length); + Buffers.writeLengthPrefixedBytes(buf, bytes); + assertFalse(buf.hasRemaining()); + ((Buffer) buf).rewind(); + byte[] copy = Buffers.readLengthPrefixedBytes(buf); + assertFalse(buf.hasRemaining()); + assertArrayEquals(bytes, copy); + } + + private Iterator randomBytes() { + Random random = new Random(); + return Stream.generate(() -> { + int length = random.nextInt(10); + byte[] bytes = new byte[length]; + random.nextBytes(bytes); + return bytes; + }).limit(10).iterator(); + } +}