From 167ed77da1c890ceec18da50786362fc73ef00c4 Mon Sep 17 00:00:00 2001 From: Andrei Koval Date: Mon, 13 May 2024 22:11:50 +0200 Subject: [PATCH] Fix custom MIME type serialization incompatibility (#261) Fixes #260 --- .../io/rsocket/kotlin/frame/io/mimeType.kt | 16 ++++++-- .../rsocket/kotlin/frame/io/MimeTypeTest.kt | 41 +++++++++++++++++++ .../kotlin/io/rsocket/kotlin/frame/io/Util.kt | 24 +++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 rsocket-core/src/commonTest/kotlin/io/rsocket/kotlin/frame/io/MimeTypeTest.kt create mode 100644 rsocket-core/src/commonTest/kotlin/io/rsocket/kotlin/frame/io/Util.kt diff --git a/rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/frame/io/mimeType.kt b/rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/frame/io/mimeType.kt index 571ad91d..ce322ac1 100644 --- a/rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/frame/io/mimeType.kt +++ b/rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/frame/io/mimeType.kt @@ -47,8 +47,13 @@ internal fun ByteReadPacket.readAuthType(): AuthType = readType( private fun BytePacketBuilder.writeTextWithLength(text: String) { val typeBytes = text.encodeToByteArray() - writeByte(typeBytes.size.toByte()) //write length - writeFully(typeBytes) //write mime type + // The first byte indicates MIME type encoding: + // - Values 0x00 to 0x7F represent predefined "well-known" MIME types, directly using the byte as the type ID. + // - Values 0x80 to 0xFF denote custom MIME types, where the byte represents the length of the MIME type name that follows. + // For custom types, the length stored (byte value minus 0x80) is adjusted by -1 when writing, and adjusted by +1 when reading, + // mapping to an effective range of 1 to 128 characters for the MIME type name length. + writeByte((typeBytes.size - 1).toByte()) + writeFully(typeBytes) } private const val KnownTypeFlag: Byte = Byte.MIN_VALUE @@ -66,7 +71,12 @@ private inline fun ByteReadPacket.readType( val identifier = byte xor KnownTypeFlag fromIdentifier(identifier) } else { - val stringType = readTextExactBytes(byte.toInt()) + // The first byte indicates MIME type encoding: + // - Values 0x00 to 0x7F represent predefined "well-known" MIME types, directly using the byte as the type ID. + // - Values 0x80 to 0xFF denote custom MIME types, where the byte represents the length of the MIME type name that follows. + // For custom types, the length stored (byte value minus 0x80) is adjusted by -1 when writing, and adjusted by +1 when reading, + // mapping to an effective range of 1 to 128 characters for the MIME type name length. + val stringType = readTextExactBytes(byte.toInt() + 1) fromText(stringType) } } diff --git a/rsocket-core/src/commonTest/kotlin/io/rsocket/kotlin/frame/io/MimeTypeTest.kt b/rsocket-core/src/commonTest/kotlin/io/rsocket/kotlin/frame/io/MimeTypeTest.kt new file mode 100644 index 00000000..03a769c0 --- /dev/null +++ b/rsocket-core/src/commonTest/kotlin/io/rsocket/kotlin/frame/io/MimeTypeTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * 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 io.rsocket.kotlin.frame.io + +import io.rsocket.kotlin.core.* +import io.rsocket.kotlin.test.* +import kotlin.test.* + +class MimeTypeTest : TestWithLeakCheck { + + @Test + fun customMimeTypeSerialization() { + testCustomMimeType("message/x.foo") + testCustomMimeType(randomAsciiString(1)) + testCustomMimeType(randomAsciiString(127)) + testCustomMimeType(randomAsciiString(128)) + } + + private fun testCustomMimeType(name: String) { + val mimeType = CustomMimeType(name) + val packet = packet { + writeMimeType(mimeType) + } + assertEquals(name.length - 1, packet.tryPeek()) + assertEquals(mimeType, packet.readMimeType()) + } +} diff --git a/rsocket-core/src/commonTest/kotlin/io/rsocket/kotlin/frame/io/Util.kt b/rsocket-core/src/commonTest/kotlin/io/rsocket/kotlin/frame/io/Util.kt new file mode 100644 index 00000000..33864f33 --- /dev/null +++ b/rsocket-core/src/commonTest/kotlin/io/rsocket/kotlin/frame/io/Util.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * 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 io.rsocket.kotlin.frame.io + + +private val asciiChars = '!'..'~' + +internal fun randomAsciiString(length: Int): String { + return (1..length).joinToString(separator = "") { asciiChars.random().toString() } +}