Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ByteString to/from NSData conversions #384

Merged
merged 2 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bytestring/api/kotlinx-io-bytestring.klib.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Klib ABI Dump
// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm32Hfp, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Alias: apple => [iosArm64, iosSimulatorArm64, iosX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
Expand Down Expand Up @@ -82,3 +83,9 @@ final fun (kotlinx.io.bytestring/ByteStringBuilder).kotlinx.io.bytestring/append
final fun <#A: kotlin.text/Appendable> (kotlin.io.encoding/Base64).kotlinx.io.bytestring/encodeToAppendable(kotlinx.io.bytestring/ByteString, #A, kotlin/Int = ..., kotlin/Int = ...): #A // kotlinx.io.bytestring/encodeToAppendable|encodeToAppendable@kotlin.io.encoding.Base64(kotlinx.io.bytestring.ByteString;0:0;kotlin.Int;kotlin.Int){0§<kotlin.text.Appendable>}[0]
final fun kotlinx.io.bytestring/ByteString(kotlin/ByteArray...): kotlinx.io.bytestring/ByteString // kotlinx.io.bytestring/ByteString|ByteString(kotlin.ByteArray...){}[0]
final inline fun kotlinx.io.bytestring/buildByteString(kotlin/Int = ..., kotlin/Function1<kotlinx.io.bytestring/ByteStringBuilder, kotlin/Unit>): kotlinx.io.bytestring/ByteString // kotlinx.io.bytestring/buildByteString|buildByteString(kotlin.Int;kotlin.Function1<kotlinx.io.bytestring.ByteStringBuilder,kotlin.Unit>){}[0]

// Targets: [apple]
final fun (kotlinx.io.bytestring/ByteString).kotlinx.io.bytestring/toNSData(): platform.Foundation/NSData // kotlinx.io.bytestring/toNSData|toNSData@kotlinx.io.bytestring.ByteString(){}[0]

// Targets: [apple]
final fun (platform.Foundation/NSData).kotlinx.io.bytestring/toByteString(): kotlinx.io.bytestring/ByteString // kotlinx.io.bytestring/toByteString|toByteString@platform.Foundation.NSData(){}[0]
47 changes: 47 additions & 0 deletions bytestring/apple/src/ByteStringApple.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file.
*/

package kotlinx.io.bytestring

import kotlinx.cinterop.*
import kotlinx.io.bytestring.unsafe.UnsafeByteStringApi
import kotlinx.io.bytestring.unsafe.UnsafeByteStringOperations
import platform.Foundation.NSData
import platform.Foundation.create

/**
* Returns a new [NSData] instance initialized with bytes copied from [this] ByteString.
*
* @sample kotlinx.io.bytestring.samples.ByteStringSamplesApple.nsDataConversion
*/
@OptIn(UnsafeNumber::class, BetaInteropApi::class, ExperimentalForeignApi::class)
public fun ByteString.toNSData(): NSData {
if (isEmpty()) {
return NSData()
}
val data = getBackingArrayReference()
return data.usePinned {
NSData.create(bytes = it.addressOf(0), length = data.size.convert())
}
}

/**
* Returns a new [ByteString] holding data copied from [this] NSData.
*
* @sample kotlinx.io.bytestring.samples.ByteStringSamplesApple.nsDataConversion
*/
@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class, UnsafeByteStringApi::class)
public fun NSData.toByteString(): ByteString {
val l = length.toLong()
if (l == 0L) {
return ByteString.EMPTY
}
if (l > Int.MAX_VALUE) {
throw IllegalArgumentException("NSData content is to long to read as byte array: $l")
}
return UnsafeByteStringOperations.wrapUnsafe(
bytes!!.readBytes(l.toInt())
)
}
72 changes: 72 additions & 0 deletions bytestring/apple/test/ByteStringAppleTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file.
*/

package kotlinx.io.bytestring

import kotlinx.cinterop.*
import kotlinx.io.bytestring.unsafe.UnsafeByteStringApi
import kotlinx.io.bytestring.unsafe.UnsafeByteStringOperations
import platform.Foundation.NSData
import platform.Foundation.create
import platform.posix.memset
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.test.*

@OptIn(UnsafeNumber::class)
class ByteStringAppleTest {
@OptIn(ExperimentalForeignApi::class)
@Test
fun toNSData() {
val emptyData = ByteString().toNSData()
assertEquals(0u, emptyData.length)

val copy = ByteString(0, 1, 2, 3, 4, 5).toNSData()
assertContentEquals(byteArrayOf(0, 1, 2, 3, 4, 5), copy.bytes!!.readBytes(copy.length.convert()))
}

@OptIn(BetaInteropApi::class, ExperimentalEncodingApi::class)
@Test
fun fromNSData() {
assertTrue(NSData().toByteString().isEmpty())
val src = NSData.create(
base64EncodedString = Base64.Default.encode(byteArrayOf(0, 1, 2, 3, 4, 5)),
options = 0u
)!!
val copy = src.toByteString()
assertContentEquals(byteArrayOf(0, 1, 2, 3, 4, 5), copy.toByteArray())
}

@OptIn(UnsafeByteStringApi::class, ExperimentalForeignApi::class)
@Test
fun toNSDataDataIntegrity() {
val mutableArray = byteArrayOf(0, 0, 0, 0, 0, 0)
// Don't try that at home, kids!
val cursedString = UnsafeByteStringOperations.wrapUnsafe(mutableArray)
val nsData = cursedString.toNSData()

mutableArray.fill(42)
// NSData should hold a copy
assertContentEquals(ByteArray(6), nsData.bytes!!.readBytes(6))
}

@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
@Test
fun fromNSDataIntegrity() = memScoped {
val length = 6
val data = allocArray<ByteVar>(length)
memset(data, 0, length.convert())

val cursedData = NSData.create(
bytesNoCopy = data, length = length.convert(),
freeWhenDone = false
)

val byteString = cursedData.toByteString()
memset(data, 42, length.convert())

assertContentEquals(ByteArray(length), byteString.toByteArray())
}
}
30 changes: 30 additions & 0 deletions bytestring/apple/test/samples/samplesApple.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file.
*/

package kotlinx.io.bytestring.samples

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.UnsafeNumber
import kotlinx.io.bytestring.*
import platform.Foundation.*
import kotlin.test.*

class ByteStringSamplesApple {
@OptIn(UnsafeNumber::class, ExperimentalForeignApi::class, ExperimentalStdlibApi::class)
@Test
fun nsDataConversion() {
val originalByteString: ByteString = "Compress me, please!".encodeToByteString()

val compressedNSData: NSData = originalByteString.toNSData().compressedDataUsingAlgorithm(
algorithm = NSDataCompressionAlgorithmZlib,
error = null
)!!

val compressedByteString: ByteString = compressedNSData.toByteString()
assertEquals("73cecf2d284a2d2e56c84dd55128c8494d2c4e550400", compressedByteString.toHexString())
// If there's no zlib-flate on your path, you can test it using:
// zlib.decompress(binascii.unhexlify("73cecf2d284a2d2e56c84dd55128c8494d2c4e550400"), -15)
}
}