Skip to content

Commit

Permalink
Merge pull request #1 from bright/support-variable-string-size
Browse files Browse the repository at this point in the history
Add support of strings with variable size
  • Loading branch information
miensol authored Aug 1, 2023
2 parents 6c4b423 + 94155ae commit 45f15e0
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ build/
.idea/misc.xml
.idea/vcs.xml
.idea/gradle.xml
.idea/GitLink.xml
*.iws
*.iml
*.ipr
Expand Down
15 changes: 12 additions & 3 deletions src/main/kotlin/dev/bright/vb6serializer/BinaryDecoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,11 @@ internal open class BinaryDecoder(
}

override fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String {
val length = descriptor.requireSizeOnElement(index)
return decodeStringWithLength(length.length)
val length = descriptor.findSizeOnElement(index)?.length
return length?.let { decodeStringWithFixedByteSize(it) } ?: decodeStringWithVariableLength()
}

internal fun decodeStringWithLength(byteLength: Int): String {
internal fun decodeStringWithFixedByteSize(byteLength: Int): String {
val contents = ByteArray(byteLength)
input.readFully(contents)
val nonPaddedLength = contents.indexOf(0)
Expand All @@ -165,6 +165,15 @@ internal open class BinaryDecoder(
)
}

private fun decodeStringWithVariableLength(): String {
val stringSize = input.readShort().toInt()
val contents = ByteArray(stringSize)
input.readFully(contents)
return String(
contents, 0, stringSize, defaultSerializingCharset
)
}

override fun <T> decodeSerializableElement(
descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>, previousValue: T?
): T {
Expand Down
26 changes: 22 additions & 4 deletions src/main/kotlin/dev/bright/vb6serializer/BinaryEncoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ internal class BinaryEncoder(
}

override fun encodeString(value: String) {
encodeString(value, value.length)
encodeStringWithFixedByteSize(value, value.length)
}

override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) {
Expand Down Expand Up @@ -116,7 +116,21 @@ internal class BinaryEncoder(
}

override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) {
val maxLength = descriptor.requireSizeOnElement(index).length
val maxLength = descriptor.findSizeOnElement(index)?.length
if (maxLength != null) {
checkLengthMaxValue(value, maxLength, descriptor, index)
encodeStringWithFixedByteSize(value, maxLength)
} else {
encodeStringWithVariableLength(value)
}
}

private fun checkLengthMaxValue(
value: String,
maxLength: Int,
descriptor: SerialDescriptor,
index: Int
) {
if (value.length > maxLength) {
throw IllegalArgumentException(
"Value $value exceeds max length $maxLength for ${descriptor.getElementName(index)}: ${
Expand All @@ -126,10 +140,9 @@ internal class BinaryEncoder(
}"
)
}
encodeString(value, maxLength)
}

private fun encodeString(value: String, maxLength: Int) {
private fun encodeStringWithFixedByteSize(value: String, maxLength: Int) {
if (value.isNotEmpty()) {
val bytes = value.toByteArray(defaultSerializingCharset)

Expand All @@ -147,6 +160,11 @@ internal class BinaryEncoder(
}
}

private fun encodeStringWithVariableLength(value: String) {
output.writeShort(value.length)
output.write(value.toByteArray(defaultSerializingCharset))
}

override fun endStructure(descriptor: SerialDescriptor) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ open class ConstByteSizeStringSerializer(
override fun deserialize(decoder: Decoder): String {
val binaryDecoder = decoder.requireBinaryDecoderBase()
return binaryDecoder.input.skipBytesUpToAfterReading(byteSize) {
binaryDecoder.decodeStringWithLength(byteSize)
binaryDecoder.decodeStringWithFixedByteSize(byteSize)
}
}

Expand Down
91 changes: 90 additions & 1 deletion src/test/kotlin/dev/bright/vb6serializer/VB6BinaryTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,52 @@ class VB6BinaryTests {
output.shouldBe(HasName("Ala".padEnd(10)))
}

@Test
fun `can serialize object with variable size name`() {
// given
val input = HasVariableSizeName("Ala")
// when
val output = serde(input)

// then
output.shouldBe(input)
}

@OptIn(ExperimentalSerializationApi::class)
@Test
fun `should serialize variable size string with a short in front that indicates its length`() {
// given
val input = HasVariableSizeName("abc")
// when
val output = VB6Binary.encodeToByteArray(input)

// then
output shouldBe byteArrayOf(0x03, 0x00, 0x61, 0x62, 0x63)
}

@OptIn(ExperimentalSerializationApi::class)
@Test
fun `should serialize variable size string with only a short 00 if it's empty`() {
// given
val input = HasVariableSizeName("")
// when
val output = VB6Binary.encodeToByteArray(input)

// then
output.shouldBe(byteArrayOf(0x00, 0x00))
}

@Test
fun `can serialize object with empty name`() {
// given
val input = HasVariableSizeName("")
// when
val output = serde(input)

// then
output.shouldBe(input)
}

@Test
fun `can serialize object with name and age`() {
// given
Expand All @@ -46,6 +92,18 @@ class VB6BinaryTests {
output.shouldBe(HasNameAndAge("Ala".padEnd(10), 12))
}

@Test
fun `can serialize object with variable size name and age`() {
// given
val input = HasVariableSizeNameAndAge("Ala", 12)

// when
val output = serde(input)

// then
output.shouldBe(input)
}

@Test
fun `can serialize object with name and nested object`() {
// given
Expand All @@ -58,6 +116,29 @@ class VB6BinaryTests {
output.shouldBe(HasNameAndNestedObject("Ala".padEnd(10), HasNameAndAge("Ala".padEnd(10), 12)))
}

@Test
fun `can serialize object with variable size name and nested object`() {
// given
val input = HasVariableSizeNameAndNestedObject("Ala", HasVariableSizeNameAndAge("Ala", 12))

// when
val output = serde(input)

// then
output.shouldBe(input)
}

@Test
fun `can serialize object with empty name and nested object`() {
// given
val input = HasVariableSizeNameAndNestedObject("", HasVariableSizeNameAndAge("Ala", 12))

// when
val output = serde(input)

// then
output.shouldBe(input)
}

@Test
fun `can serialize object with int array`() {
Expand Down Expand Up @@ -111,7 +192,6 @@ class VB6BinaryTests {
// @Size(2) String "" --serialise--> [0,0] --deserialize--> (String) null
}


@Test
fun `can serialize empty object with list of strings`() {
// given
Expand Down Expand Up @@ -192,6 +272,15 @@ data class HasNameAndAge(@Size(10) val name: String, val age: Int)
@Serializable
data class HasNameAndNestedObject(@Size(10) val name: String, val info: HasNameAndAge)

@Serializable
data class HasVariableSizeName(val name: String)

@Serializable
data class HasVariableSizeNameAndAge(val name: String, val age: Int)

@Serializable
data class HasVariableSizeNameAndNestedObject(val name: String, val info: HasVariableSizeNameAndAge)

@Serializable
data class HasIntArray(
@Size(5)
Expand Down

0 comments on commit 45f15e0

Please sign in to comment.