Skip to content

Commit da34f10

Browse files
authored
Support ByteString to/from NSData conversions (#384)
* Support ByteString to/from NSData conversions Closes #266
1 parent 2b77370 commit da34f10

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

bytestring/api/kotlinx-io-bytestring.klib.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Klib ABI Dump
22
// 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]
3+
// Alias: apple => [iosArm64, iosSimulatorArm64, iosX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
34
// Rendering settings:
45
// - Signature version: 2
56
// - Show manifest properties: true
@@ -84,3 +85,9 @@ final fun kotlinx.io.bytestring/ByteString(): kotlinx.io.bytestring/ByteString /
8485
final fun kotlinx.io.bytestring/ByteString(kotlin/ByteArray...): kotlinx.io.bytestring/ByteString // kotlinx.io.bytestring/ByteString|ByteString(kotlin.ByteArray...){}[0]
8586
final fun kotlinx.io.bytestring/ByteString(kotlin/UByteArray...): kotlinx.io.bytestring/ByteString // kotlinx.io.bytestring/ByteString|ByteString(kotlin.UByteArray...){}[0]
8687
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]
88+
89+
// Targets: [apple]
90+
final fun (kotlinx.io.bytestring/ByteString).kotlinx.io.bytestring/toNSData(): platform.Foundation/NSData // kotlinx.io.bytestring/toNSData|[email protected](){}[0]
91+
92+
// Targets: [apple]
93+
final fun (platform.Foundation/NSData).kotlinx.io.bytestring/toByteString(): kotlinx.io.bytestring/ByteString // kotlinx.io.bytestring/toByteString|[email protected](){}[0]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file.
4+
*/
5+
6+
package kotlinx.io.bytestring
7+
8+
import kotlinx.cinterop.*
9+
import kotlinx.io.bytestring.unsafe.UnsafeByteStringApi
10+
import kotlinx.io.bytestring.unsafe.UnsafeByteStringOperations
11+
import platform.Foundation.NSData
12+
import platform.Foundation.create
13+
14+
/**
15+
* Returns a new [NSData] instance initialized with bytes copied from [this] ByteString.
16+
*
17+
* @sample kotlinx.io.bytestring.samples.ByteStringSamplesApple.nsDataConversion
18+
*/
19+
@OptIn(UnsafeNumber::class, BetaInteropApi::class, ExperimentalForeignApi::class)
20+
public fun ByteString.toNSData(): NSData {
21+
if (isEmpty()) {
22+
return NSData()
23+
}
24+
val data = getBackingArrayReference()
25+
return data.usePinned {
26+
NSData.create(bytes = it.addressOf(0), length = data.size.convert())
27+
}
28+
}
29+
30+
/**
31+
* Returns a new [ByteString] holding data copied from [this] NSData.
32+
*
33+
* @sample kotlinx.io.bytestring.samples.ByteStringSamplesApple.nsDataConversion
34+
*/
35+
@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class, UnsafeByteStringApi::class)
36+
public fun NSData.toByteString(): ByteString {
37+
val l = length.toLong()
38+
if (l == 0L) {
39+
return ByteString.EMPTY
40+
}
41+
if (l > Int.MAX_VALUE) {
42+
throw IllegalArgumentException("NSData content is to long to read as byte array: $l")
43+
}
44+
return UnsafeByteStringOperations.wrapUnsafe(
45+
bytes!!.readBytes(l.toInt())
46+
)
47+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file.
4+
*/
5+
6+
package kotlinx.io.bytestring
7+
8+
import kotlinx.cinterop.*
9+
import kotlinx.io.bytestring.unsafe.UnsafeByteStringApi
10+
import kotlinx.io.bytestring.unsafe.UnsafeByteStringOperations
11+
import platform.Foundation.NSData
12+
import platform.Foundation.create
13+
import platform.posix.memset
14+
import kotlin.io.encoding.Base64
15+
import kotlin.io.encoding.ExperimentalEncodingApi
16+
import kotlin.test.*
17+
18+
@OptIn(UnsafeNumber::class)
19+
class ByteStringAppleTest {
20+
@OptIn(ExperimentalForeignApi::class)
21+
@Test
22+
fun toNSData() {
23+
val emptyData = ByteString().toNSData()
24+
assertEquals(0u, emptyData.length)
25+
26+
val copy = ByteString(0, 1, 2, 3, 4, 5).toNSData()
27+
assertContentEquals(byteArrayOf(0, 1, 2, 3, 4, 5), copy.bytes!!.readBytes(copy.length.convert()))
28+
}
29+
30+
@OptIn(BetaInteropApi::class, ExperimentalEncodingApi::class)
31+
@Test
32+
fun fromNSData() {
33+
assertTrue(NSData().toByteString().isEmpty())
34+
val src = NSData.create(
35+
base64EncodedString = Base64.Default.encode(byteArrayOf(0, 1, 2, 3, 4, 5)),
36+
options = 0u
37+
)!!
38+
val copy = src.toByteString()
39+
assertContentEquals(byteArrayOf(0, 1, 2, 3, 4, 5), copy.toByteArray())
40+
}
41+
42+
@OptIn(UnsafeByteStringApi::class, ExperimentalForeignApi::class)
43+
@Test
44+
fun toNSDataDataIntegrity() {
45+
val mutableArray = byteArrayOf(0, 0, 0, 0, 0, 0)
46+
// Don't try that at home, kids!
47+
val cursedString = UnsafeByteStringOperations.wrapUnsafe(mutableArray)
48+
val nsData = cursedString.toNSData()
49+
50+
mutableArray.fill(42)
51+
// NSData should hold a copy
52+
assertContentEquals(ByteArray(6), nsData.bytes!!.readBytes(6))
53+
}
54+
55+
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
56+
@Test
57+
fun fromNSDataIntegrity() = memScoped {
58+
val length = 6
59+
val data = allocArray<ByteVar>(length)
60+
memset(data, 0, length.convert())
61+
62+
val cursedData = NSData.create(
63+
bytesNoCopy = data, length = length.convert(),
64+
freeWhenDone = false
65+
)
66+
67+
val byteString = cursedData.toByteString()
68+
memset(data, 42, length.convert())
69+
70+
assertContentEquals(ByteArray(length), byteString.toByteArray())
71+
}
72+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file.
4+
*/
5+
6+
package kotlinx.io.bytestring.samples
7+
8+
import kotlinx.cinterop.ExperimentalForeignApi
9+
import kotlinx.cinterop.UnsafeNumber
10+
import kotlinx.io.bytestring.*
11+
import platform.Foundation.*
12+
import kotlin.test.*
13+
14+
class ByteStringSamplesApple {
15+
@OptIn(UnsafeNumber::class, ExperimentalForeignApi::class, ExperimentalStdlibApi::class)
16+
@Test
17+
fun nsDataConversion() {
18+
val originalByteString: ByteString = "Compress me, please!".encodeToByteString()
19+
20+
val compressedNSData: NSData = originalByteString.toNSData().compressedDataUsingAlgorithm(
21+
algorithm = NSDataCompressionAlgorithmZlib,
22+
error = null
23+
)!!
24+
25+
val compressedByteString: ByteString = compressedNSData.toByteString()
26+
assertEquals("73cecf2d284a2d2e56c84dd55128c8494d2c4e550400", compressedByteString.toHexString())
27+
// If there's no zlib-flate on your path, you can test it using:
28+
// zlib.decompress(binascii.unhexlify("73cecf2d284a2d2e56c84dd55128c8494d2c4e550400"), -15)
29+
}
30+
}

0 commit comments

Comments
 (0)