diff --git a/okio/build.gradle.kts b/okio/build.gradle.kts index 88bb4646c0..bfee8356cf 100644 --- a/okio/build.gradle.kts +++ b/okio/build.gradle.kts @@ -88,6 +88,10 @@ kotlin { dependsOn(commonMain) } + val nonAppleMain by creating { + dependsOn(hashFunctions) + } + val nonJvmMain by creating { dependsOn(hashFunctions) dependsOn(commonMain) @@ -112,6 +116,7 @@ kotlin { if (kmpJsEnabled) { val jsMain by getting { dependsOn(nonJvmMain) + dependsOn(nonAppleMain) } val jsTest by getting { dependsOn(nonJvmTest) @@ -121,10 +126,14 @@ kotlin { if (kmpNativeEnabled) { createSourceSet("nativeMain", parent = nonJvmMain) .also { nativeMain -> - createSourceSet("mingwMain", parent = nativeMain, children = mingwTargets) + createSourceSet("mingwMain", parent = nativeMain, children = mingwTargets).also { mingwMain -> + mingwMain.dependsOn(nonAppleMain) + } createSourceSet("unixMain", parent = nativeMain) .also { unixMain -> - createSourceSet("linuxMain", parent = unixMain, children = linuxTargets) + createSourceSet("linuxMain", parent = unixMain, children = linuxTargets).also { linuxMain -> + linuxMain.dependsOn(nonAppleMain) + } createSourceSet("appleMain", parent = unixMain, children = appleTargets) } } diff --git a/okio/src/appleMain/kotlin/okio/ByteString.kt b/okio/src/appleMain/kotlin/okio/ByteString.kt index f335582983..262f9a47aa 100644 --- a/okio/src/appleMain/kotlin/okio/ByteString.kt +++ b/okio/src/appleMain/kotlin/okio/ByteString.kt @@ -1,11 +1,11 @@ /* - * Copyright (C) 2020 Square, Inc. + * Copyright (C) 2018 Square, Inc. * * 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 + * 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, @@ -13,27 +13,209 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package okio import kotlinx.cinterop.UnsafeNumber import kotlinx.cinterop.addressOf import kotlinx.cinterop.usePinned +import okio.internal.HashFunction +import okio.internal.Hmac +import okio.internal.Md5 +import okio.internal.Sha1 +import okio.internal.Sha256 +import okio.internal.Sha512 +import okio.internal.commonBase64 +import okio.internal.commonBase64Url +import okio.internal.commonCompareTo +import okio.internal.commonCopyInto +import okio.internal.commonDecodeBase64 +import okio.internal.commonDecodeHex +import okio.internal.commonEncodeUtf8 +import okio.internal.commonEndsWith +import okio.internal.commonEquals +import okio.internal.commonGetByte +import okio.internal.commonGetSize +import okio.internal.commonHashCode +import okio.internal.commonHex +import okio.internal.commonIndexOf +import okio.internal.commonInternalArray +import okio.internal.commonLastIndexOf +import okio.internal.commonOf +import okio.internal.commonRangeEquals +import okio.internal.commonStartsWith +import okio.internal.commonSubstring +import okio.internal.commonToAsciiLowercase +import okio.internal.commonToAsciiUppercase +import okio.internal.commonToByteArray +import okio.internal.commonToByteString +import okio.internal.commonToString +import okio.internal.commonUtf8 +import okio.internal.commonWrite import platform.Foundation.NSData import platform.posix.memcpy -@OptIn(UnsafeNumber::class) -fun NSData.toByteString(): ByteString { - val data = this - val size = data.length.toInt() - return if (size != 0) { - ByteString( - ByteArray(size).apply { - usePinned { pinned -> - memcpy(pinned.addressOf(0), data.bytes, data.length) - } +actual open class ByteString +internal actual constructor( + internal actual val data: ByteArray +) : Comparable { + @Suppress("SetterBackingFieldAssignment") + internal actual var hashCode: Int = 0 // 0 if unknown. + set(value) { + // Do nothing to avoid IllegalImmutabilityException. + } + @Suppress("SetterBackingFieldAssignment") + internal actual var utf8: String? = null + set(value) { + // Do nothing to avoid IllegalImmutabilityException. + } + + actual open fun utf8(): String = commonUtf8() + + actual open fun base64(): String = commonBase64() + + actual open fun base64Url(): String = commonBase64Url() + + actual open fun hex(): String = commonHex() + + actual fun md5() = digest(Md5()) + + actual fun sha1() = digest(Sha1()) + + actual fun sha256() = digest(Sha256()) + + actual fun sha512() = digest(Sha512()) + + /** Returns the 160-bit SHA-1 HMAC of this byte string. */ + actual fun hmacSha1(key: ByteString) = digest(Hmac.sha1(key)) + + /** Returns the 256-bit SHA-256 HMAC of this byte string. */ + actual fun hmacSha256(key: ByteString) = digest(Hmac.sha256(key)) + + /** Returns the 512-bit SHA-512 HMAC of this byte string. */ + actual fun hmacSha512(key: ByteString) = digest(Hmac.sha512(key)) + + internal open fun digest(hashFunction: HashFunction): ByteString { + hashFunction.update(data, 0, size) + val digestBytes = hashFunction.digest() + return ByteString(digestBytes) + } + + actual open fun toAsciiLowercase(): ByteString = commonToAsciiLowercase() + + actual open fun toAsciiUppercase(): ByteString = commonToAsciiUppercase() + + actual open fun substring(beginIndex: Int, endIndex: Int): ByteString = + commonSubstring(beginIndex, endIndex) + + internal actual open fun internalGet(pos: Int): Byte { + if (pos >= size || pos < 0) throw ArrayIndexOutOfBoundsException("size=$size pos=$pos") + return commonGetByte(pos) + } + + actual operator fun get(index: Int): Byte = internalGet(index) + + actual val size + get() = getSize() + + internal actual open fun getSize() = commonGetSize() + + actual open fun toByteArray() = commonToByteArray() + + internal actual open fun internalArray() = commonInternalArray() + + internal actual open fun write(buffer: Buffer, offset: Int, byteCount: Int) = + commonWrite(buffer, offset, byteCount) + + actual open fun rangeEquals( + offset: Int, + other: ByteString, + otherOffset: Int, + byteCount: Int + ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) + + actual open fun rangeEquals( + offset: Int, + other: ByteArray, + otherOffset: Int, + byteCount: Int + ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) + + actual open fun copyInto( + offset: Int, + target: ByteArray, + targetOffset: Int, + byteCount: Int + ) = commonCopyInto(offset, target, targetOffset, byteCount) + + actual fun startsWith(prefix: ByteString) = commonStartsWith(prefix) + + actual fun startsWith(prefix: ByteArray) = commonStartsWith(prefix) + + actual fun endsWith(suffix: ByteString) = commonEndsWith(suffix) + + actual fun endsWith(suffix: ByteArray) = commonEndsWith(suffix) + + actual fun indexOf(other: ByteString, fromIndex: Int) = indexOf(other.internalArray(), fromIndex) + + actual open fun indexOf(other: ByteArray, fromIndex: Int) = commonIndexOf(other, fromIndex) + + actual fun lastIndexOf(other: ByteString, fromIndex: Int) = commonLastIndexOf(other, fromIndex) + + actual open fun lastIndexOf(other: ByteArray, fromIndex: Int) = commonLastIndexOf(other, fromIndex) + + actual override fun equals(other: Any?) = commonEquals(other) + + actual override fun hashCode() = commonHashCode() + + actual override fun compareTo(other: ByteString) = commonCompareTo(other) + + /** + * Returns a human-readable string that describes the contents of this byte string. Typically this + * is a string like `[text=Hello]` or `[hex=0000ffff]`. + */ + actual override fun toString() = commonToString() + + actual companion object { + actual val EMPTY: ByteString = ByteString(byteArrayOf()) + + actual fun of(vararg data: Byte) = commonOf(data) + + actual fun ByteArray.toByteString(offset: Int, byteCount: Int): ByteString = + commonToByteString(offset, byteCount) + + actual fun String.encodeUtf8(): ByteString = commonEncodeUtf8() + + actual fun String.decodeBase64(): ByteString? = commonDecodeBase64() + + actual fun String.decodeHex() = commonDecodeHex() + + @OptIn(UnsafeNumber::class) + @CName("of") + fun NSData.toByteString(): ByteString { + val data = this + val size = data.length.toInt() + return if (size != 0) { + ByteString( + ByteArray(size).apply { + usePinned { pinned -> + memcpy(pinned.addressOf(0), data.bytes, data.length) + } + } + ) + } else { + EMPTY } - ) - } else { - ByteString.EMPTY + } + } +} + +@Deprecated( + message = "Moved to ByteString companion object", + replaceWith = ReplaceWith("this.toByteString()", "okio.ByteString.Companion.toByteString"), +) +fun NSData.toByteString(): ByteString { + with(ByteString) { + return toByteString() } } diff --git a/okio/src/nonJvmMain/kotlin/okio/SegmentedByteString.kt b/okio/src/appleMain/kotlin/okio/SegmentedByteString.kt similarity index 100% rename from okio/src/nonJvmMain/kotlin/okio/SegmentedByteString.kt rename to okio/src/appleMain/kotlin/okio/SegmentedByteString.kt diff --git a/okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt b/okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt index 15acf0b4ad..0ca4f36169 100644 --- a/okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt +++ b/okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt @@ -25,13 +25,23 @@ import kotlin.test.assertEquals class AppleByteStringTest { @Test fun nsDataToByteString() { val data = ("Hello" as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData - val byteString = data.toByteString() + + @Suppress("DEPRECATION") // Ensure deprecated function continues to work. + val byteStringDeprecated = data.toByteString() + assertEquals("Hello", byteStringDeprecated.utf8()) + + val byteString = with(ByteString) { data.toByteString() } assertEquals("Hello", byteString.utf8()) } @Test fun emptyNsDataToByteString() { val data = ("" as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData - val byteString = data.toByteString() + + @Suppress("DEPRECATION") // Ensure deprecated function continues to work. + val byteStringDeprecated = data.toByteString() + assertEquals(ByteString.EMPTY, byteStringDeprecated) + + val byteString = with(ByteString) { data.toByteString() } assertEquals(ByteString.EMPTY, byteString) } } diff --git a/okio/src/nonJvmMain/kotlin/okio/ByteString.kt b/okio/src/nonAppleMain/kotlin/okio/ByteString.kt similarity index 100% rename from okio/src/nonJvmMain/kotlin/okio/ByteString.kt rename to okio/src/nonAppleMain/kotlin/okio/ByteString.kt diff --git a/okio/src/nonAppleMain/kotlin/okio/SegmentedByteString.kt b/okio/src/nonAppleMain/kotlin/okio/SegmentedByteString.kt new file mode 100644 index 0000000000..b7225b8d17 --- /dev/null +++ b/okio/src/nonAppleMain/kotlin/okio/SegmentedByteString.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 Square, Inc. + * + * 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 okio + +import okio.internal.HashFunction +import okio.internal.commonCopyInto +import okio.internal.commonEquals +import okio.internal.commonGetSize +import okio.internal.commonHashCode +import okio.internal.commonInternalGet +import okio.internal.commonRangeEquals +import okio.internal.commonSubstring +import okio.internal.commonToByteArray +import okio.internal.commonWrite +import okio.internal.forEachSegment + +internal actual class SegmentedByteString internal actual constructor( + internal actual val segments: Array, + internal actual val directory: IntArray +) : ByteString(EMPTY.data) { + + override fun base64() = toByteString().base64() + + override fun hex() = toByteString().hex() + + override fun toAsciiLowercase() = toByteString().toAsciiLowercase() + + override fun toAsciiUppercase() = toByteString().toAsciiUppercase() + + override fun base64Url() = toByteString().base64Url() + + override fun substring(beginIndex: Int, endIndex: Int): ByteString = + commonSubstring(beginIndex, endIndex) + + override fun internalGet(pos: Int): Byte = commonInternalGet(pos) + + override fun getSize() = commonGetSize() + + override fun toByteArray(): ByteArray = commonToByteArray() + + override fun write(buffer: Buffer, offset: Int, byteCount: Int): Unit = + commonWrite(buffer, offset, byteCount) + + override fun rangeEquals( + offset: Int, + other: ByteString, + otherOffset: Int, + byteCount: Int + ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) + + override fun rangeEquals( + offset: Int, + other: ByteArray, + otherOffset: Int, + byteCount: Int + ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) + + override fun copyInto( + offset: Int, + target: ByteArray, + targetOffset: Int, + byteCount: Int + ) = commonCopyInto(offset, target, targetOffset, byteCount) + + override fun indexOf(other: ByteArray, fromIndex: Int) = toByteString().indexOf(other, fromIndex) + + override fun lastIndexOf(other: ByteArray, fromIndex: Int) = toByteString().lastIndexOf( + other, + fromIndex + ) + + override fun digest(hashFunction: HashFunction): ByteString { + forEachSegment { data, offset, byteCount -> + hashFunction.update(data, offset, byteCount) + } + val digestBytes = hashFunction.digest() + return ByteString(digestBytes) + } + + /** Returns a copy as a non-segmented byte string. */ + private fun toByteString() = ByteString(toByteArray()) + + override fun internalArray() = toByteArray() + + override fun equals(other: Any?): Boolean = commonEquals(other) + + override fun hashCode(): Int = commonHashCode() + + override fun toString() = toByteString().toString() +}