diff --git a/bytestring/api/kotlinx-io-bytestring.api b/bytestring/api/kotlinx-io-bytestring.api index 8c054de27..b51c6fc9c 100644 --- a/bytestring/api/kotlinx-io-bytestring.api +++ b/bytestring/api/kotlinx-io-bytestring.api @@ -1,3 +1,24 @@ +public final class kotlinx/io/bytestring/Base64Kt { + public static final fun decode (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;II)[B + public static synthetic fun decode$default (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;IIILjava/lang/Object;)[B + public static final fun decodeIntoByteArray (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;[BIII)I + public static synthetic fun decodeIntoByteArray$default (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;[BIIIILjava/lang/Object;)I + public static final fun decodeToByteString (Lkotlin/io/encoding/Base64;Ljava/lang/CharSequence;II)Lkotlinx/io/bytestring/ByteString; + public static final fun decodeToByteString (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;II)Lkotlinx/io/bytestring/ByteString; + public static final fun decodeToByteString (Lkotlin/io/encoding/Base64;[BII)Lkotlinx/io/bytestring/ByteString; + public static synthetic fun decodeToByteString$default (Lkotlin/io/encoding/Base64;Ljava/lang/CharSequence;IIILjava/lang/Object;)Lkotlinx/io/bytestring/ByteString; + public static synthetic fun decodeToByteString$default (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;IIILjava/lang/Object;)Lkotlinx/io/bytestring/ByteString; + public static synthetic fun decodeToByteString$default (Lkotlin/io/encoding/Base64;[BIIILjava/lang/Object;)Lkotlinx/io/bytestring/ByteString; + public static final fun encode (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;II)Ljava/lang/String; + public static synthetic fun encode$default (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;IIILjava/lang/Object;)Ljava/lang/String; + public static final fun encodeIntoByteArray (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;[BIII)I + public static synthetic fun encodeIntoByteArray$default (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;[BIIIILjava/lang/Object;)I + public static final fun encodeToAppendable (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;Ljava/lang/Appendable;II)Ljava/lang/Appendable; + public static synthetic fun encodeToAppendable$default (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;Ljava/lang/Appendable;IIILjava/lang/Object;)Ljava/lang/Appendable; + public static final fun encodeToByteArray (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;II)[B + public static synthetic fun encodeToByteArray$default (Lkotlin/io/encoding/Base64;Lkotlinx/io/bytestring/ByteString;IIILjava/lang/Object;)[B +} + public final class kotlinx/io/bytestring/ByteString : java/lang/Comparable { public static final field Companion Lkotlinx/io/bytestring/ByteString$Companion; public fun ([BII)V @@ -73,6 +94,15 @@ public final class kotlinx/io/bytestring/ByteStringKt { public static final fun startsWith (Lkotlinx/io/bytestring/ByteString;[B)Z } +public final class kotlinx/io/bytestring/HexKt { + public static final fun hexToByteString (Ljava/lang/String;Lkotlin/text/HexFormat;)Lkotlinx/io/bytestring/ByteString; + public static synthetic fun hexToByteString$default (Ljava/lang/String;Lkotlin/text/HexFormat;ILjava/lang/Object;)Lkotlinx/io/bytestring/ByteString; + public static final fun toHexString (Lkotlinx/io/bytestring/ByteString;IILkotlin/text/HexFormat;)Ljava/lang/String; + public static final fun toHexString (Lkotlinx/io/bytestring/ByteString;Lkotlin/text/HexFormat;)Ljava/lang/String; + public static synthetic fun toHexString$default (Lkotlinx/io/bytestring/ByteString;IILkotlin/text/HexFormat;ILjava/lang/Object;)Ljava/lang/String; + public static synthetic fun toHexString$default (Lkotlinx/io/bytestring/ByteString;Lkotlin/text/HexFormat;ILjava/lang/Object;)Ljava/lang/String; +} + public abstract interface annotation class kotlinx/io/bytestring/unsafe/UnsafeByteStringApi : java/lang/annotation/Annotation { } diff --git a/bytestring/common/src/Base64.kt b/bytestring/common/src/Base64.kt new file mode 100644 index 000000000..f21ebc4e1 --- /dev/null +++ b/bytestring/common/src/Base64.kt @@ -0,0 +1,247 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package kotlinx.io.bytestring + +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.Base64.Default.encode +import kotlin.io.encoding.Base64.Default.encodeToByteArray +import kotlin.io.encoding.ExperimentalEncodingApi + +/** + * Encodes bytes from the specified [source] byte string or its subrange. + * Returns a [ByteArray] containing the resulting symbols. + * + * If the size of the [source] byte string or its subrange is not an integral multiple of 3, + * the result is padded with `'='` to an integral multiple of 4 symbols. + * + * Each resulting symbol occupies one byte in the returned byte array. + * + * Use [encode] to get the output in string form. + * + * @param source the byte string to encode bytes from. + * @param startIndex the beginning (inclusive) of the subrange to encode, 0 by default. + * @param endIndex the end (exclusive) of the subrange to encode, size of the [source] byte string by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * + * @return a [ByteArray] with the resulting symbols. + */ +@ExperimentalEncodingApi +public fun Base64.encodeToByteArray(source: ByteString, startIndex: Int = 0, endIndex: Int = source.size): ByteArray { + return encodeToByteArray(source.getBackingArrayReference(), startIndex, endIndex) +} + +/** + * Encodes bytes from the specified [source] byte string or its subrange and writes resulting symbols into the [destination] array. + * Returns the number of symbols written. + * + * If the size of the [source] byte string or its subrange is not an integral multiple of 3, + * the result is padded with `'='` to an integral multiple of 4 symbols. + * + * @param source the byte string to encode bytes from. + * @param destination the array to write symbols into. + * @param destinationOffset the starting index in the [destination] array to write symbols to, 0 by default. + * @param startIndex the beginning (inclusive) of the subrange to encode, 0 by default. + * @param endIndex the end (exclusive) of the subrange to encode, size of the [source] byte string by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the resulting symbols don't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + * + * @return the number of symbols written into [destination] array. + */ +@ExperimentalEncodingApi +public fun Base64.encodeIntoByteArray( + source: ByteString, + destination: ByteArray, + destinationOffset: Int = 0, + startIndex: Int = 0, + endIndex: Int = source.size +): Int { + return encodeIntoByteArray(source.getBackingArrayReference(), destination, destinationOffset, startIndex, endIndex) +} + +/** + * Encodes bytes from the specified [source] byte string or its subrange. + * Returns a string with the resulting symbols. + * + * If the size of the [source] byte string or its subrange is not an integral multiple of 3, + * the result is padded with `'='` to an integral multiple of 4 symbols. + * + * Use [encodeToByteArray] to get the output in [ByteArray] form. + * + * @param source the byte string to encode bytes from. + * @param startIndex the beginning (inclusive) of the subrange to encode, 0 by default. + * @param endIndex the end (exclusive) of the subrange to encode, size of the [source] byte string by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * + * @return a string with the resulting symbols. + */ +@ExperimentalEncodingApi +public fun Base64.encode( + source: ByteString, + startIndex: Int = 0, + endIndex: Int = source.size +): String { + return encode(source.getBackingArrayReference(), startIndex, endIndex) +} + +/** + * Encodes bytes from the specified [source] byte string or its subrange and appends resulting symbols to the [destination] appendable. + * Returns the destination appendable. + * + * If the size of the [source] byte string or its subrange is not an integral multiple of 3, + * the result is padded with `'='` to an integral multiple of 4 symbols. + * + * @param source the byte string to encode bytes from. + * @param destination the appendable to append symbols to. + * @param startIndex the beginning (inclusive) of the subrange to encode, 0 by default. + * @param endIndex the end (exclusive) of the subrange to encode, size of the [source] byte string by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * + * @return the destination appendable. + */ +@ExperimentalEncodingApi +public fun Base64.encodeToAppendable( + source: ByteString, + destination: A, + startIndex: Int = 0, + endIndex: Int = source.size +): A { + return encodeToAppendable(source.getBackingArrayReference(), destination, startIndex, endIndex) +} + + +/** + * Decodes symbols from the specified [source] byte string or its subrange. + * Returns a [ByteArray] containing the resulting bytes. + * + * The symbols for decoding are not required to be padded. + * However, if there is a padding character present, the correct amount of padding character(s) must be present. + * The padding character `'='` is interpreted as the end of the encoded byte data. Subsequent symbols are prohibited. + * + * @param source the byte string to decode symbols from. + * @param startIndex the beginning (inclusive) of the subrange to decode, 0 by default. + * @param endIndex the end (exclusive) of the subrange to decode, size of the [source] byte string by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * @throws IllegalArgumentException when the symbols for decoding are padded incorrectly or there are extra symbols after the padding. + * + * @return a [ByteArray] with the resulting bytes. + */ +@ExperimentalEncodingApi +public fun Base64.decode(source: ByteString, startIndex: Int = 0, endIndex: Int = source.size): ByteArray { + return decode(source.getBackingArrayReference(), startIndex, endIndex) +} + +/** + * Decodes symbols from the specified [source] char sequence or its substring. + * Returns a [ByteString] containing the resulting bytes. + * + * The symbols for decoding are not required to be padded. + * However, if there is a padding character present, the correct amount of padding character(s) must be present. + * The padding character `'='` is interpreted as the end of the encoded byte data. Subsequent symbols are prohibited. + * + * @param source the char sequence to decode symbols from. + * @param startIndex the beginning (inclusive) of the substring to decode, 0 by default. + * @param endIndex the end (exclusive) of the substring to decode, length of the [source] by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * @throws IllegalArgumentException when the symbols for decoding are padded incorrectly or there are extra symbols after the padding. + * + * @return a [ByteArray] with the resulting bytes. + */ +@ExperimentalEncodingApi +public fun Base64.decodeToByteString(source: CharSequence, startIndex: Int = 0, endIndex: Int = source.length): ByteString { + return ByteString.wrap(decode(source, startIndex, endIndex)) +} + +/** + * Decodes symbols from the specified [source] byte string or its subrange and writes resulting bytes into the [destination] array. + * Returns the number of bytes written. + * + * The symbols for decoding are not required to be padded. + * However, if there is a padding character present, the correct amount of padding character(s) must be present. + * The padding character `'='` is interpreted as the end of the encoded byte data. Subsequent symbols are prohibited. + * + * @param source the byte string to decode symbols from. + * @param destination the array to write bytes into. + * @param destinationOffset the starting index in the [destination] array to write bytes to, 0 by default. + * @param startIndex the beginning (inclusive) of the subrange to decode, 0 by default. + * @param endIndex the end (exclusive) of the subrange to decode, size of the [source] byte string by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the resulting bytes don't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + * @throws IllegalArgumentException when the symbols for decoding are padded incorrectly or there are extra symbols after the padding. + * + * @return the number of bytes written into [destination] array. + */ +@ExperimentalEncodingApi +public fun Base64.decodeIntoByteArray( + source: ByteString, + destination: ByteArray, + destinationOffset: Int = 0, + startIndex: Int = 0, + endIndex: Int = source.size +): Int { + return decodeIntoByteArray(source.getBackingArrayReference(), destination, destinationOffset, startIndex, endIndex) +} + +/** + * Decodes symbols from the specified [source] byte string or its subrange. + * Returns a [ByteString] containing the resulting bytes. + * + * The symbols for decoding are not required to be padded. + * However, if there is a padding character present, the correct amount of padding character(s) must be present. + * The padding character `'='` is interpreted as the end of the encoded byte data. Subsequent symbols are prohibited. + * + * @param source the byte string to decode symbols from. + * @param startIndex the beginning (inclusive) of the subrange to decode, 0 by default. + * @param endIndex the end (exclusive) of the subrange to decode, size of the [source] byte string by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * @throws IllegalArgumentException when the symbols for decoding are padded incorrectly or there are extra symbols after the padding. + * + * @return a [ByteString] with the resulting bytes. + */ +@ExperimentalEncodingApi +public fun Base64.decodeToByteString(source: ByteArray, startIndex: Int = 0, endIndex: Int = source.size): ByteString { + return ByteString.wrap(decode(source, startIndex, endIndex)) +} + +/** + * Decodes symbols from the specified [source] byte string or its subrange. + * Returns a [ByteString] containing the resulting bytes. + * + * The symbols for decoding are not required to be padded. + * However, if there is a padding character present, the correct amount of padding character(s) must be present. + * The padding character `'='` is interpreted as the end of the encoded byte data. Subsequent symbols are prohibited. + * + * @param source the byte string to decode symbols from. + * @param startIndex the beginning (inclusive) of the subrange to decode, 0 by default. + * @param endIndex the end (exclusive) of the subrange to decode, size of the [source] byte string by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * @throws IllegalArgumentException when the symbols for decoding are padded incorrectly or there are extra symbols after the padding. + * + * @return a [ByteString] with the resulting bytes. + */ +@ExperimentalEncodingApi +public fun Base64.decodeToByteString(source: ByteString, startIndex: Int = 0, endIndex: Int = source.size): ByteString { + return ByteString.wrap(decode(source.getBackingArrayReference(), startIndex, endIndex)) +} diff --git a/bytestring/common/src/Hex.kt b/bytestring/common/src/Hex.kt new file mode 100644 index 000000000..4ecb46b6e --- /dev/null +++ b/bytestring/common/src/Hex.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package kotlinx.io.bytestring + +/** + * Formats bytes in this byte string using the specified [format]. + * + * Note that only [HexFormat.upperCase] and [HexFormat.BytesHexFormat] affect formatting. + * + * @param format the [HexFormat] to use for formatting, [HexFormat.Default] by default. + * + * @throws IllegalArgumentException if the result length is more than [String] maximum capacity. + */ +@ExperimentalStdlibApi +public fun ByteString.toHexString(format: HexFormat = HexFormat.Default): String { + return getBackingArrayReference().toHexString(0, getBackingArrayReference().size, format) +} + +/** + * Formats bytes in this byte string using the specified [HexFormat]. + * + * Note that only [HexFormat.upperCase] and [HexFormat.BytesHexFormat] affect formatting. + * + * @param startIndex the beginning (inclusive) of the subrange to format, 0 by default. + * @param endIndex the end (exclusive) of the subrange to format, size of this byte string by default. + * @param format the [HexFormat] to use for formatting, [HexFormat.Default] by default. + * + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of this byte string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. + * @throws IllegalArgumentException if the result length is more than [String] maximum capacity. + */ +@ExperimentalStdlibApi +public fun ByteString.toHexString( + startIndex: Int = 0, + endIndex: Int = size, + format: HexFormat = HexFormat.Default +): String { + return getBackingArrayReference().toHexString(startIndex, endIndex, format) +} + +/** + * Parses bytes from this string using the specified [HexFormat]. + * + * Note that only [HexFormat.BytesHexFormat] affects parsing, + * and parsing is performed in case-insensitive manner. + * Also, any of the char sequences CRLF, LF and CR is considered a valid line separator. + * + * @param format the [HexFormat] to use for parsing, [HexFormat.Default] by default. + * + * @throws IllegalArgumentException if this string does not comply with the specified [format]. + */ +@ExperimentalStdlibApi +public fun String.hexToByteString(format: HexFormat = HexFormat.Default): ByteString { + return ByteString.wrap(hexToByteArray(format)) +} diff --git a/bytestring/common/test/ByteStringBase64Test.kt b/bytestring/common/test/ByteStringBase64Test.kt new file mode 100644 index 000000000..482029096 --- /dev/null +++ b/bytestring/common/test/ByteStringBase64Test.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package kotlinx.io.bytestring + +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi +import kotlin.test.* + +class ByteStringBase64Test { + private fun bytes(vararg values: Int): ByteArray { + return ByteArray(values.size) { values[it].toByte() } + } + + + private val byteArray: ByteArray = bytes(0b0000_0100, 0b0010_0000, 0b1100_0100, 0b0001_0100, 0b0110_0001, 0b1100_1000) + private val byteString = ByteString.wrap(byteArray) + + private val encodedSymbols = "BCDEFGHI" + private val encodedBytes = encodedSymbols.encodeToByteArray() + private val encodedByteString = ByteString.wrap(encodedBytes) + + + @Test + fun testEncodeToByteArray() { + assertFailsWith { Base64.encodeToByteArray(byteString, startIndex = -1) } + assertFailsWith { Base64.encodeToByteArray(byteString, endIndex = byteString.size + 1) } + assertFailsWith { Base64.encodeToByteArray(byteString, startIndex = byteString.size + 1) } + assertFailsWith { Base64.encodeToByteArray(byteString, startIndex = 3, endIndex = 0) } + + assertTrue(Base64.encodeToByteArray(ByteString.EMPTY).isEmpty()) + assertContentEquals(encodedBytes, Base64.encodeToByteArray(byteString)) + + assertContentEquals(encodedSymbols.encodeToByteArray(0, 4), Base64.encodeToByteArray(byteString, endIndex = 3)) + assertContentEquals(encodedSymbols.encodeToByteArray(4), Base64.encodeToByteArray(byteString, startIndex = 3)) + } + + @Test + fun testEncodeIntoByteArray() { + val destination = ByteArray(encodedBytes.size) + + assertFailsWith { Base64.encodeIntoByteArray(byteString, destination, destinationOffset = -1) } + assertFailsWith { Base64.encodeIntoByteArray(byteString, destination, destinationOffset = destination.size + 1) } + assertFailsWith { Base64.encodeIntoByteArray(byteString, destination, destinationOffset = 1) } + + assertEquals(0, Base64.encodeIntoByteArray(ByteString.EMPTY, destination)) + assertEquals(encodedBytes.size, Base64.encodeIntoByteArray(byteString, destination)) + assertContentEquals(encodedBytes, destination.copyOf(encodedBytes.size)) + + var length = Base64.encodeIntoByteArray(byteString, destination, endIndex = 3) + assertContentEquals(encodedSymbols.encodeToByteArray(0, 4), destination.copyOf(length)) + length += Base64.encodeIntoByteArray(byteString, destination, destinationOffset = length, startIndex = 3) + assertContentEquals(encodedSymbols.encodeToByteArray(), destination) + } + + @Test + fun testEncode() { + assertFailsWith { Base64.encode(byteString, startIndex = -1) } + assertFailsWith { Base64.encode(byteString, endIndex = byteString.size + 1) } + assertFailsWith { Base64.encode(byteString, startIndex = byteString.size + 1) } + assertFailsWith { Base64.encode(byteString, startIndex = 3, endIndex = 0) } + + assertTrue(Base64.encode(ByteArray(0)).isEmpty()) + assertEquals(encodedSymbols, Base64.encode(byteString)) + assertEquals(encodedSymbols.substring(0, 4), Base64.encode(byteString, endIndex = 3)) + assertEquals(encodedSymbols.substring(4), Base64.encode(byteString, startIndex = 3)) + + val destination = StringBuilder() + Base64.encodeToAppendable(byteString, destination, endIndex = 3) + assertEquals(encodedSymbols.substring(0, 4), destination.toString()) + Base64.encodeToAppendable(byteString, destination, startIndex = 3) + assertEquals(encodedSymbols, destination.toString()) + } + + @Test + fun testDecode() { + assertFailsWith { Base64.decode(encodedByteString, startIndex = -1) } + assertFailsWith { Base64.decode(encodedByteString, endIndex = encodedByteString.size + 1) } + assertFailsWith { Base64.decode(encodedByteString, startIndex = encodedByteString.size + 1) } + assertFailsWith { Base64.decode(encodedByteString, startIndex = 4, endIndex = 0) } + + assertEquals(0, Base64.decode(ByteString.EMPTY).size) + assertContentEquals(byteArray, Base64.decode(encodedByteString)) + assertContentEquals(byteArray.copyOfRange(0, 3), Base64.decode(encodedByteString, endIndex = 4)) + assertContentEquals(byteArray.copyOfRange(3, byteString.size), Base64.decode(encodedByteString, startIndex = 4)) + } + + @Test + fun testDecodeIntoByteArray() { + val destination = ByteArray(6) + assertFailsWith { Base64.decodeIntoByteArray(encodedByteString, destination, destinationOffset = -1) } + assertFailsWith { Base64.decodeIntoByteArray(encodedByteString, destination, destinationOffset = destination.size + 1) } + assertFailsWith { Base64.decodeIntoByteArray(encodedByteString, destination, destinationOffset = 1) } + + assertTrue(destination.all { it == 0.toByte() }) + + assertEquals(0, Base64.decodeIntoByteArray(ByteString.EMPTY, destination)) + + var length = Base64.decodeIntoByteArray(encodedByteString, destination, endIndex = 4) + assertContentEquals(byteArray.copyOfRange(0, 3), destination.copyOf(length)) + length += Base64.decodeIntoByteArray(encodedByteString, destination, destinationOffset = length, startIndex = 4) + assertContentEquals(byteArray, destination) + } + + @Test + fun testDecodeToByteString() { + assertFailsWith { Base64.decodeToByteString(encodedSymbols, startIndex = -1) } + assertFailsWith { Base64.decodeToByteString(encodedSymbols, endIndex = encodedByteString.size + 1) } + assertFailsWith { Base64.decodeToByteString(encodedSymbols, startIndex = encodedByteString.size + 1) } + assertFailsWith { Base64.decodeToByteString(encodedSymbols, startIndex = 4, endIndex = 0) } + + assertEquals(0, Base64.decodeToByteString(ByteArray(0)).size) + assertEquals(byteString, Base64.decodeToByteString(encodedSymbols)) + assertEquals(ByteString.wrap(byteArray.copyOfRange(0, 3)), Base64.decodeToByteString(encodedSymbols, endIndex = 4)) + assertEquals(ByteString.wrap(byteArray.copyOfRange(3, byteString.size)), Base64.decodeToByteString(encodedSymbols, startIndex = 4)) + } + + @Test + fun testByteArrayDecodeToByteString() { + assertFailsWith { Base64.decodeToByteString(encodedBytes, startIndex = -1) } + assertFailsWith { Base64.decodeToByteString(encodedBytes, endIndex = encodedByteString.size + 1) } + assertFailsWith { Base64.decodeToByteString(encodedBytes, startIndex = encodedByteString.size + 1) } + assertFailsWith { Base64.decodeToByteString(encodedBytes, startIndex = 4, endIndex = 0) } + + assertEquals(0, Base64.decodeToByteString(ByteArray(0)).size) + assertEquals(byteString, Base64.decodeToByteString(encodedBytes)) + assertEquals(ByteString.wrap(byteArray.copyOfRange(0, 3)), Base64.decodeToByteString(encodedBytes, endIndex = 4)) + assertEquals(ByteString.wrap(byteArray.copyOfRange(3, byteString.size)), Base64.decodeToByteString(encodedBytes, startIndex = 4)) + } + + @Test + fun testByteStringDecodeToByteString() { + assertFailsWith { Base64.decodeToByteString(encodedByteString, startIndex = -1) } + assertFailsWith { Base64.decodeToByteString(encodedByteString, endIndex = encodedByteString.size + 1) } + assertFailsWith { Base64.decodeToByteString(encodedByteString, startIndex = encodedByteString.size + 1) } + assertFailsWith { Base64.decodeToByteString(encodedByteString, startIndex = 4, endIndex = 0) } + + assertEquals(0, Base64.decodeToByteString(ByteString.EMPTY).size) + assertEquals(byteString, Base64.decodeToByteString(encodedByteString)) + assertEquals(ByteString.wrap(byteArray.copyOfRange(0, 3)), Base64.decodeToByteString(encodedByteString, endIndex = 4)) + assertEquals(ByteString.wrap(byteArray.copyOfRange(3, byteString.size)), Base64.decodeToByteString(encodedByteString, startIndex = 4)) + } +} diff --git a/bytestring/common/test/ByteStringHexTest.kt b/bytestring/common/test/ByteStringHexTest.kt new file mode 100644 index 000000000..cd249a050 --- /dev/null +++ b/bytestring/common/test/ByteStringHexTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +@file:OptIn(ExperimentalStdlibApi::class) + +package kotlinx.io.bytestring + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails +import kotlin.test.assertTrue + +class ByteStringHexTest { + private val byteString = ByteString.wrap(byteArrayOf(10, 11, 12, 13, 14, 15)) + + @Test + fun testEmpty() { + assertTrue(ByteString.EMPTY.toHexString().isEmpty()) + assertTrue("".hexToByteString().isEmpty()) + } + + @Test + fun testIndexes() { + assertEquals("0b0c", byteString.toHexString(1, 3)) + assertEquals("0c0d0e0f", byteString.toHexString(2)) + assertEquals("0a0b0c0d", byteString.toHexString(endIndex = 4)) + + assertFails { byteString.toHexString(-1) } + assertFails { byteString.toHexString(endIndex = -1) } + assertFails { byteString.toHexString(3, 2) } + assertFails { byteString.toHexString(10) } + assertFails { byteString.toHexString(endIndex = 11) } + assertFails { byteString.toHexString(10, 11) } + } + + @Test + fun testFormats() { + val format = HexFormat { + bytes { + byteSeparator = "|" + } + } + assertEquals("0a|0b|0c|0d|0e|0f", byteString.toHexString(format)) + assertEquals("0b|0c|0d", byteString.toHexString(1, 4, format)) + + assertEquals(byteString, "0a|0b|0c|0d|0e|0f".hexToByteString(format)) + assertFails { "0a0b0c0d0e0f".hexToByteString(format) } + } + + @Test + fun testDefault() { + assertEquals("0a0b0c0d0e0f", byteString.toHexString()) + assertEquals("0b0c0d", byteString.toHexString(1, 4)) + + assertEquals(byteString, "0a0b0c0d0e0f".hexToByteString()) + assertFails { "0a|0b|0c|0d|0e|0f".hexToByteString() } + } + +}