diff --git a/.pubnub.yml b/.pubnub.yml
index 718a79057..cee689285 100644
--- a/.pubnub.yml
+++ b/.pubnub.yml
@@ -1,9 +1,9 @@
name: kotlin
-version: 12.0.0
+version: 12.0.1
schema: 1
scm: github.com/pubnub/kotlin
files:
- - build/libs/pubnub-kotlin-12.0.0-all.jar
+ - build/libs/pubnub-kotlin-12.0.1-all.jar
sdks:
-
type: library
@@ -23,8 +23,8 @@ sdks:
-
distribution-type: library
distribution-repository: maven
- package-name: pubnub-kotlin-12.0.0
- location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-kotlin/12.0.0/pubnub-kotlin-12.0.0.jar
+ package-name: pubnub-kotlin-12.0.1
+ location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-kotlin/12.0.1/pubnub-kotlin-12.0.1.jar
supported-platforms:
supported-operating-systems:
Android:
@@ -121,6 +121,13 @@ sdks:
license-url: https://www.apache.org/licenses/LICENSE-2.0.txt
is-required: Required
changelog:
+ - date: 2025-11-19
+ version: v12.0.1
+ changes:
+ - type: bug
+ text: "Fixed upload/download encrypted file API. When file is encrypted application/octet-stream data format is enforced regardless of original file type (image/jpeg, video/mp4, text/plain) or server's suggested Content-Type from generateUploadUrl."
+ - type: bug
+ text: "Removed redundant buffering when parsing encrypted data."
- date: 2025-11-10
version: v12.0.0
changes:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e25e0a77..c897cf272 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## v12.0.1
+November 19 2025
+
+#### Fixed
+- Fixed upload/download encrypted file API. When file is encrypted application/octet-stream data format is enforced regardless of original file type (image/jpeg, video/mp4, text/plain) or server's suggested Content-Type from generateUploadUrl.
+- Removed redundant buffering when parsing encrypted data.
+
## v12.0.0
November 10 2025
diff --git a/README.md b/README.md
index d4ffdd0d5..4fd49659d 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ You will need the publish and subscribe keys to authenticate your app. Get your
com.pubnub
pubnub-kotlin
- 12.0.0
+ 12.0.1
```
diff --git a/gradle.properties b/gradle.properties
index 5fabf03be..eeaffa5db 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,7 +18,7 @@ RELEASE_SIGNING_ENABLED=true
SONATYPE_HOST=DEFAULT
SONATYPE_AUTOMATIC_RELEASE=false
GROUP=com.pubnub
-VERSION_NAME=12.0.0
+VERSION_NAME=12.0.1
POM_PACKAGING=jar
POM_NAME=PubNub SDK
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/FilesIntegrationTest.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/FilesIntegrationTest.kt
index 117d812a5..0c332e6d3 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/FilesIntegrationTest.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/FilesIntegrationTest.kt
@@ -31,6 +31,111 @@ class FilesIntegrationTest : BaseIntegrationTest() {
uploadListDownloadDelete(false)
}
+ @Test
+ fun legacyEncryptedFileTransfer() {
+ uploadListDownloadDeleteFileWithCipher(true)
+ }
+
+ @Test
+ fun aesCbcEncryptedFileTransfer() {
+ uploadListDownloadDeleteFileWithCipher(false)
+ }
+
+ fun uploadListDownloadDeleteFileWithCipher(withLegacyCrypto: Boolean) {
+ if (withLegacyCrypto) {
+ clientConfig = {
+ cryptoModule = CryptoModule.createLegacyCryptoModule("enigma")
+ }
+ } else {
+ clientConfig = {
+ cryptoModule = CryptoModule.createAesCbcCryptoModule("enigma")
+ }
+ }
+
+ val channel: String = randomChannel()
+ val fileName = "logback.xml"
+ val message = "This is message"
+ val meta = "This is meta"
+ val customMessageType = "myCustomType"
+
+ // Read the logback.xml file from resources
+ val logbackResource = this.javaClass.classLoader.getResourceAsStream("logback.xml")
+ ?: throw IllegalStateException("logback.xml not found in resources")
+ val originalContent = logbackResource.readBytes()
+ val originalContentString = String(originalContent, StandardCharsets.UTF_8)
+
+ val connectedLatch = CountDownLatch(1)
+ val fileEventReceived = CountDownLatch(1)
+ pubnub.addListener(
+ object : SubscribeCallback() {
+ override fun status(
+ pubnub: PubNub,
+ status: PNStatus,
+ ) {
+ if (status.category == PNStatusCategory.PNConnectedCategory) {
+ connectedLatch.countDown()
+ }
+ }
+
+ override fun file(
+ pubnub: PubNub,
+ result: PNFileEventResult,
+ ) {
+ if (result.file.name == fileName && result.customMessageType == customMessageType) {
+ fileEventReceived.countDown()
+ }
+ }
+ },
+ )
+ pubnub.subscribe(channels = listOf(channel))
+ connectedLatch.await(10, TimeUnit.SECONDS)
+
+ val sendResult: PNFileUploadResult? =
+ ByteArrayInputStream(originalContent).use {
+ pubnub.sendFile(
+ channel = channel,
+ fileName = fileName,
+ inputStream = it,
+ message = message,
+ meta = meta,
+ customMessageType = customMessageType
+ ).sync()
+ }
+
+ if (sendResult == null) {
+ Assert.fail()
+ return
+ }
+ fileEventReceived.await(10, TimeUnit.SECONDS)
+
+ val (_, _, _, data) = pubnub.listFiles(channel = channel).sync()
+ val fileFoundOnList = data.find { it.id == sendResult.file.id } != null
+ Assert.assertTrue(fileFoundOnList)
+
+ val (_, byteStream) =
+ pubnub.downloadFile(
+ channel = channel,
+ fileName = fileName,
+ fileId = sendResult.file.id,
+ ).sync()
+
+ byteStream?.use {
+ val downloadedContent = it.readBytes()
+ val downloadedString = String(downloadedContent, StandardCharsets.UTF_8)
+ Assert.assertEquals(
+ "Downloaded content should match original logback.xml",
+ originalContentString,
+ downloadedString
+ )
+ }
+
+ pubnub.deleteFile(
+ channel = channel,
+ fileName = fileName,
+ fileId = sendResult.file.id,
+ ).sync()
+ }
+
@Test
fun testSendFileAndDeleteFileOnChannelEntity() {
val sendFileResultReference: AtomicReference = AtomicReference()
@@ -205,6 +310,126 @@ class FilesIntegrationTest : BaseIntegrationTest() {
).sync()
}
+ @Test
+ fun uploadLargeEncryptedFileWithLegacyCryptoModule() {
+ uploadLargeEncryptedFileWithCryptoModule(withLegacyCrypto = true)
+ }
+
+ @Test
+ fun uploadLargeEncryptedFileWithAesCbcCryptoModule() {
+ uploadLargeEncryptedFileWithCryptoModule(withLegacyCrypto = false)
+ }
+
+ fun uploadLargeEncryptedFileWithCryptoModule(withLegacyCrypto: Boolean) {
+ clientConfig = {
+ cryptoModule = CryptoModule.createLegacyCryptoModule("enigma")
+ }
+ val channel: String = randomChannel()
+ val fileName = "large_file_${System.currentTimeMillis()}.bin"
+
+ // Create a large binary file (1MB) to test encryption
+ val largeContent = ByteArray(1024 * 1024) { it.toByte() }
+
+ val sendResult: PNFileUploadResult? =
+ ByteArrayInputStream(largeContent).use {
+ pubnub.sendFile(
+ channel = channel,
+ fileName = fileName,
+ inputStream = it,
+ message = "Large encrypted file test",
+ ).sync()
+ }
+
+ if (sendResult == null) {
+ Assert.fail("Failed to upload large encrypted file")
+ return
+ }
+
+ // Download and verify
+ val (_, byteStream) =
+ pubnub.downloadFile(
+ channel = channel,
+ fileName = fileName,
+ fileId = sendResult.file.id,
+ ).sync()
+
+ byteStream?.use {
+ val downloadedContent = it.readBytes()
+ Assert.assertArrayEquals(
+ "Downloaded encrypted content should match original",
+ largeContent,
+ downloadedContent
+ )
+ }
+
+ // Cleanup
+ pubnub.deleteFile(
+ channel = channel,
+ fileName = fileName,
+ fileId = sendResult.file.id,
+ ).sync()
+ }
+
+ @Test
+ fun uploadMultipleSizesWithEncryption() {
+ clientConfig = {
+ cryptoModule = CryptoModule.createLegacyCryptoModule("enigma")
+ }
+ val channel: String = randomChannel()
+
+ val testSizes = listOf(
+ 100, // Small file
+ 1024, // 1KB
+ 10240, // 10KB
+ 102400, // 100KB
+ 524288 // 512KB
+ )
+
+ for (size in testSizes) {
+ val fileName = "test_${size}_${System.currentTimeMillis()}.bin"
+ val content = ByteArray(size) { (it % 256).toByte() }
+
+ val sendResult: PNFileUploadResult? =
+ ByteArrayInputStream(content).use {
+ pubnub.sendFile(
+ channel = channel,
+ fileName = fileName,
+ inputStream = it,
+ message = "Test file size: $size",
+ ).sync()
+ }
+
+ if (sendResult == null) {
+ Assert.fail("Failed to upload file of size $size")
+ return
+ }
+
+ // Download and verify
+ val (_, byteStream) =
+ pubnub.downloadFile(
+ channel = channel,
+ fileName = fileName,
+ fileId = sendResult.file.id,
+ ).sync()
+
+ byteStream?.use {
+ val downloadedContent = it.readBytes()
+ Assert.assertArrayEquals(
+ "Downloaded content should match original for size $size",
+ content,
+ downloadedContent
+ )
+ }
+
+ // Cleanup
+ pubnub.deleteFile(
+ channel = channel,
+ fileName = fileName,
+ fileId = sendResult.file.id,
+ ).sync()
+ }
+ }
+
private fun readToString(inputStream: InputStream): String {
Scanner(inputStream).useDelimiter("\\A").use { s ->
return if (s.hasNext()) {
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/crypto/cryptor/HeaderParser.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/crypto/cryptor/HeaderParser.kt
index d0610e8b7..6f71c67fe 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/crypto/cryptor/HeaderParser.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/crypto/cryptor/HeaderParser.kt
@@ -36,12 +36,11 @@ internal class HeaderParser(val logConfig: LogConfig?) {
)
fun parseDataWithHeader(stream: BufferedInputStream): ParseResult {
- val bufferedInputStream = stream.buffered()
- bufferedInputStream.mark(Int.MAX_VALUE) // TODO Can be calculated from spec
+ stream.mark(Int.MAX_VALUE) // TODO Can be calculated from spec
val possibleInitialHeader = ByteArray(MINIMAL_SIZE_OF_CRYPTO_HEADER)
- val initiallyRead = bufferedInputStream.read(possibleInitialHeader)
+ val initiallyRead = stream.read(possibleInitialHeader)
if (!possibleInitialHeader.sliceArray(SENTINEL_STARTING_INDEX..SENTINEL_ENDING_INDEX).contentEquals(SENTINEL)) {
- bufferedInputStream.reset()
+ stream.reset()
return ParseResult.NoHeader
}
@@ -58,17 +57,17 @@ internal class HeaderParser(val logConfig: LogConfig?) {
val cryptorData: ByteArray =
if (cryptorDataSizeFirstByte == THREE_BYTES_SIZE_CRYPTOR_DATA_INDICATOR) {
- val cryptorDataSizeBytes = readExactlyNBytez(bufferedInputStream, 2)
+ val cryptorDataSizeBytes = readExactlyNBytez(stream, 2)
val cryptorDataSize = convertTwoBytesToIntBigEndian(cryptorDataSizeBytes[0], cryptorDataSizeBytes[1])
- readExactlyNBytez(bufferedInputStream, cryptorDataSize)
+ readExactlyNBytez(stream, cryptorDataSize)
} else {
if (cryptorDataSizeFirstByte == UByte.MIN_VALUE) {
byteArrayOf()
} else {
- readExactlyNBytez(bufferedInputStream, cryptorDataSizeFirstByte.toInt())
+ readExactlyNBytez(stream, cryptorDataSizeFirstByte.toInt())
}
}
- return ParseResult.Success(cryptorId, cryptorData, bufferedInputStream)
+ return ParseResult.Success(cryptorId, cryptorData, stream)
}
private fun readExactlyNBytez(
@@ -130,9 +129,9 @@ internal class HeaderParser(val logConfig: LogConfig?) {
val finalCryptorDataSize: ByteArray =
if (cryptorDataSize < THREE_BYTES_SIZE_CRYPTOR_DATA_INDICATOR.toInt()) {
byteArrayOf(cryptorDataSize.toByte()) // cryptorDataSize will be stored on 1 byte
- } else if (cryptorDataSize < MAX_VALUE_THAT_CAN_BE_STORED_ON_TWO_BYTES) {
- // cryptorDataSize will be stored on 3 byte
- byteArrayOf(cryptorDataSize.toByte()) + writeNumberOnTwoBytes(cryptorDataSize)
+ } else if (cryptorDataSize <= MAX_VALUE_THAT_CAN_BE_STORED_ON_TWO_BYTES) {
+ // cryptorDataSize will be stored on 3 bytes: indicator (255) + 2 bytes for actual size
+ byteArrayOf(THREE_BYTES_SIZE_CRYPTOR_DATA_INDICATOR.toByte()) + writeNumberOnTwoBytes(cryptorDataSize)
} else {
throw PubNubException(
errorMessage = "Cryptor Data Size is: $cryptorDataSize whereas max cryptor data size is: $MAX_VALUE_THAT_CAN_BE_STORED_ON_TWO_BYTES",
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/files/SendFileEndpoint.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/files/SendFileEndpoint.kt
index 20949ee30..1e9af018c 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/files/SendFileEndpoint.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/files/SendFileEndpoint.kt
@@ -86,6 +86,7 @@ class SendFileEndpoint internal constructor(
): ExtendedRemoteAction {
val result = AtomicReference()
+ val isEncrypted = cryptoModule != null
val content =
cryptoModule?.encryptStream(InputStreamSeparator(inputStream))?.use {
it.readBytes()
@@ -93,7 +94,7 @@ class SendFileEndpoint internal constructor(
return ComposableRemoteAction.firstDo(generateUploadUrlFactory.create(channel, fileName)) // 1. generateUrl
.then { res ->
result.set(res)
- sendFileToS3Factory.create(fileName, content, res) // 2. upload to s3
+ sendFileToS3Factory.create(fileName, content, res, isEncrypted) // 2. upload to s3
}.checkpoint().then {
val details = result.get()
publishFileMessageFactory.create( // 3. PublishFileMessage
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/files/UploadFileEndpoint.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/files/UploadFileEndpoint.kt
index 85b894c62..d627792f3 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/files/UploadFileEndpoint.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/files/UploadFileEndpoint.kt
@@ -28,6 +28,7 @@ internal class UploadFileEndpoint(
private val key: FormField,
private val formParams: List,
private val baseUrl: String,
+ private val isEncrypted: Boolean = false,
pubNub: PubNubImpl,
) : EndpointCore(pubNub) {
private val log = LoggerManager.instance.getLogger(pubnub.logConfig, this::class.java)
@@ -65,21 +66,40 @@ internal class UploadFileEndpoint(
log.debug(
LogMessage(
message = LogMessageContent.Text(
- "Initiating S3 upload - fileName: $fileName, contentSize: ${content.size} bytes, contentType: $contentType, formFieldsCount: ${formParams.size}"
+ "Initiating S3 upload - fileName: $fileName, contentSize: ${content.size} bytes, contentType: $contentType, isEncrypted: $isEncrypted, formFieldsCount: ${formParams.size}"
)
)
)
+ // Override Content-Type for encrypted files to prevent UTF-8 corruption of binary data
+ val modifiedFormParams = if (isEncrypted) {
+ formParams.map { param ->
+ if (param.key.equals(CONTENT_TYPE_HEADER, ignoreCase = true)) {
+ FormField(param.key, "application/octet-stream")
+ } else {
+ param
+ }
+ }
+ } else {
+ formParams
+ }
+
val builder = MultipartBody.Builder().setType(MultipartBody.FORM)
- addFormParamsWithKeyFirst(key, formParams, builder)
- val mediaType = getMediaType(contentType)
+ addFormParamsWithKeyFirst(key, modifiedFormParams, builder)
+
+ // For encrypted files, always use application/octet-stream to prevent charset interpretation
+ val mediaType = if (isEncrypted) {
+ APPLICATION_OCTET_STREAM
+ } else {
+ getMediaType(modifiedFormParams.findContentType())
+ }
builder.addFormDataPart(FILE_PART_MULTIPART, fileName, content.toRequestBody(mediaType, 0, content.size))
log.debug(
LogMessage(
message = LogMessageContent.Text(
- "Multipart request built - executing upload to S3 for fileName: $fileName"
+ "Multipart request built - executing upload to S3 for fileName: $fileName with mediaType: $mediaType"
)
)
)
@@ -288,6 +308,7 @@ internal class UploadFileEndpoint(
fileName: String,
content: ByteArray,
fileUploadRequestDetails: FileUploadRequestDetails,
+ isEncrypted: Boolean = false,
): UploadFileEndpoint {
return UploadFileEndpoint(
fileName = fileName,
@@ -295,6 +316,7 @@ internal class UploadFileEndpoint(
key = fileUploadRequestDetails.keyFormField,
formParams = fileUploadRequestDetails.formFields,
baseUrl = fileUploadRequestDetails.url,
+ isEncrypted = isEncrypted,
pubNub = pubNub
)
}
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/crypto/cryptor/HeaderParserTest.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/crypto/cryptor/HeaderParserTest.kt
index 66429d912..b758b760b 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/crypto/cryptor/HeaderParserTest.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/crypto/cryptor/HeaderParserTest.kt
@@ -116,6 +116,82 @@ class HeaderParserTest {
assertEquals(PubNubError.CRYPTOR_DATA_HEADER_SIZE_TO_SMALL, exception.pubnubError)
}
+ @Test
+ fun `createCryptorHeader should use 255 indicator for size 256`() {
+ val cryptorId = byteArrayOf('A'.code.toByte(), 'C'.code.toByte(), 'R'.code.toByte(), 'V'.code.toByte())
+ val cryptorData = ByteArray(256) { it.toByte() }
+
+ val header = objectUnderTest.createCryptorHeader(cryptorId, cryptorData)
+
+ // Header structure: PNED(4) + version(1) + cryptorId(4) + sizeField(3) + data(256)
+ // Total: 268 bytes
+ assertEquals(268, header.size, "Header size should be 268 bytes")
+
+ // Byte 9 should be 255 (0xFF) - the indicator
+ assertEquals(255.toByte(), header[9], "Byte 9 should be 255 (0xFF) indicator")
+
+ // Bytes 10-11 should be 256 in big-endian (0x01, 0x00)
+ assertEquals(1, header[10].toInt() and 0xFF, "Byte 10 should be 1 (high byte of 256)")
+ assertEquals(0, header[11].toInt() and 0xFF, "Byte 11 should be 0 (low byte of 256)")
+ }
+
+ @Test
+ fun `createCryptorHeader should use 255 indicator for size 300`() {
+ val cryptorId = byteArrayOf('A'.code.toByte(), 'C'.code.toByte(), 'R'.code.toByte(), 'V'.code.toByte())
+ val cryptorData = ByteArray(300) { it.toByte() }
+
+ val header = objectUnderTest.createCryptorHeader(cryptorId, cryptorData)
+
+ assertEquals(312, header.size, "Header size should be 312 bytes") // 4+1+4+3+300
+ assertEquals(255.toByte(), header[9], "Byte 9 should be 255 (0xFF) indicator")
+ assertEquals(1, header[10].toInt() and 0xFF, "Byte 10 should be 1 (300 >> 8 = 1)")
+ assertEquals(44, header[11].toInt() and 0xFF, "Byte 11 should be 44 (300 & 0xFF = 44)")
+ }
+
+ @Test
+ fun `createCryptorHeader should use 255 indicator for size 512`() {
+ val cryptorId = byteArrayOf('A'.code.toByte(), 'C'.code.toByte(), 'R'.code.toByte(), 'V'.code.toByte())
+ val cryptorData = ByteArray(512) { it.toByte() }
+
+ val header = objectUnderTest.createCryptorHeader(cryptorId, cryptorData)
+
+ assertEquals(524, header.size, "Header size should be 524 bytes") // 4+1+4+3+512
+ assertEquals(255.toByte(), header[9], "Byte 9 should be 255 (0xFF) indicator")
+ assertEquals(2, header[10].toInt() and 0xFF, "Byte 10 should be 2 (512 >> 8 = 2)")
+ assertEquals(0, header[11].toInt() and 0xFF, "Byte 11 should be 0 (512 & 0xFF = 0)")
+ }
+
+ @Test
+ fun `createCryptorHeader should use 255 indicator for size 65535`() {
+ val cryptorId = byteArrayOf('A'.code.toByte(), 'C'.code.toByte(), 'R'.code.toByte(), 'V'.code.toByte())
+ val cryptorData = ByteArray(65535) { 0 }
+
+ val header = objectUnderTest.createCryptorHeader(cryptorId, cryptorData)
+
+ assertEquals(65547, header.size, "Header size should be 65547 bytes") // 4+1+4+3+65535
+ assertEquals(255.toByte(), header[9], "Byte 9 should be 255 (0xFF) indicator")
+ assertEquals(255.toByte(), header[10], "Byte 10 should be 255 (65535 >> 8 = 255)")
+ assertEquals(255.toByte(), header[11], "Byte 11 should be 255 (65535 & 0xFF = 255)")
+ }
+
+ @Test
+ fun `createCryptorHeader should round-trip with parseDataWithHeader for size 256`() {
+ val cryptorId = byteArrayOf('A'.code.toByte(), 'C'.code.toByte(), 'R'.code.toByte(), 'V'.code.toByte())
+ val originalData = ByteArray(256) { it.toByte() }
+ val encryptedPayload = ByteArray(100) { 0xFF.toByte() }
+
+ val header = objectUnderTest.createCryptorHeader(cryptorId, originalData)
+ val fullData = header + encryptedPayload
+
+ val result = objectUnderTest.parseDataWithHeader(fullData)
+
+ assertTrue(result is ParseResult.Success, "Should parse successfully")
+ result as ParseResult.Success
+ assertTrue(cryptorId.contentEquals(result.cryptoId), "Cryptor ID should match")
+ assertTrue(originalData.contentEquals(result.cryptorData), "Cryptor data should match")
+ assertTrue(encryptedPayload.contentEquals(result.encryptedData), "Encrypted data should match")
+ }
+
private fun createByteArrayThatHas255Elements(): ByteArray {
var byteArray: ByteArray = byteArrayOf()
for (i in 1..255) {
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/legacy/PubNubImplTest.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/legacy/PubNubImplTest.kt
index fc459cb28..f841ba3e1 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/legacy/PubNubImplTest.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/legacy/PubNubImplTest.kt
@@ -56,7 +56,7 @@ class PubNubImplTest : BaseTest() {
fun getVersionAndTimeStamp() {
val version = PubNubImpl.SDK_VERSION
val timeStamp = PubNubImpl.timestamp()
- assertEquals("12.0.0", version)
+ assertEquals("12.0.1", version)
assertTrue(timeStamp > 0)
}