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

Timestamp extension type support #45

Merged
merged 5 commits into from
Sep 10, 2021
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).

## [Unreleased]
### Added
- Upgraded kotlin version to 1.5.0
- Upgraded kotlinx-serialization version to 1.2.2
- Added support for timestamp extension ([#10][i10])

### Fixed
- Bug with failing to decode extension types with variable data size

## [0.2.1] - 2020-09-07
### Added
Expand Down Expand Up @@ -36,6 +41,7 @@ All notable changes to this project will be documented in this file. This change
[0.2.1]: https://github.com/esensar/kotlinx-serialization-msgpack/compare/0.2.0...0.2.1
[i6]: https://github.com/esensar/kotlinx-serialization-msgpack/issues/6
[i9]: https://github.com/esensar/kotlinx-serialization-msgpack/issues/9
[i10]: https://github.com/esensar/kotlinx-serialization-msgpack/issues/10
[i11]: https://github.com/esensar/kotlinx-serialization-msgpack/issues/11
[i13]: https://github.com/esensar/kotlinx-serialization-msgpack/issues/13
[i14]: https://github.com/esensar/kotlinx-serialization-msgpack/issues/14
Expand Down
87 changes: 87 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ buildscript {
}

val snapshot: String? by project
val sonatypeStaging = "https://oss.sonatype.org/service/local/staging/deploy/maven2"
val sonatypeSnapshots = "https://oss.sonatype.org/content/repositories/snapshots"

val sonatypePassword: String? by project
val sonatypeUsername: String? by project

val sonatypePasswordEnv: String? = System.getenv()["SONATYPE_PASSWORD"]
val sonatypeUsernameEnv: String? = System.getenv()["SONATYPE_USERNAME"]

allprojects {
group = Config.group
Expand All @@ -26,3 +34,82 @@ allprojects {
mavenCentral()
}
}

subprojects {
afterEvaluate {
apply(plugin = "maven-publish")
apply(plugin = "signing")
apply(plugin = "org.jetbrains.dokka")

val dokkaHtml = tasks["dokkaHtml"]
tasks {
create<Jar>("javadocJar") {
dependsOn(dokkaHtml)
archiveClassifier.set("javadoc")
from(dokkaHtml)
}
}

configure<SigningExtension> {
isRequired = false
sign(extensions.getByType<PublishingExtension>().publications)
}

configure<PublishingExtension> {
publications.withType(MavenPublication::class) {
artifact(tasks["javadocJar"])
pom {
name.set("Kotlinx Serialization MsgPack")
description.set("MsgPack format support for kotlinx.serialization")
url.set("https://github.com/esensar/kotlinx-serialization-msgpack")
licenses {
license {
name.set("MIT License")
url.set("https://opensource.org/licenses/mit-license.php")
}
}
developers {
developer {
id.set("esensar")
name.set("Ensar Sarajčić")
url.set("https://ensarsarajcic.com")
email.set("[email protected]")
}
}
scm {
url.set("https://github.com/esensar/kotlinx-serialization-msgpack")
connection.set("scm:git:https://github.com/esensar/kotlinx-serialization-msgpack.git")
developerConnection.set("scm:git:[email protected]:esensar/kotlinx-serialization-msgpack.git")
}
}
}
repositories {
maven {
url = uri(sonatypeStaging)
credentials {
username = sonatypeUsername ?: sonatypeUsernameEnv ?: ""
password = sonatypePassword ?: sonatypePasswordEnv ?: ""
}
}

maven {
name = "snapshot"
url = uri(sonatypeSnapshots)
credentials {
username = sonatypeUsername ?: sonatypeUsernameEnv ?: ""
password = sonatypePassword ?: sonatypePasswordEnv ?: ""
}
}

maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/esensar/kotlinx-serialization-msgpack")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
}
}
}
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ object Dependencies {
object Versions {
const val kotlin = "1.5.30"
const val serialization = "1.2.2"
const val datetime = "0.2.1"
const val ktlintGradle = "10.2.0"
const val dokkaGradle = "1.5.0"
}
Expand Down
64 changes: 64 additions & 0 deletions serialization-msgpack-timestamp-extension/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
}

kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
}
js {
browser {
testTask {
useKarma {
useChromeHeadless()
webpackConfig.cssSupport.enabled = true
}
}
}
}
ios()
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}

fun kotlinx(name: String, version: String): String = "org.jetbrains.kotlinx:kotlinx-$name:$version"
fun kotlinxSerialization(name: String) = kotlinx("serialization-$name", Dependencies.Versions.serialization)

sourceSets {
all {
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
}

val commonMain by getting {
dependencies {
implementation(project(":serialization-msgpack"))
implementation(kotlinx("datetime", Dependencies.Versions.datetime))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test-junit"))
}
}
val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.ensarsarajcic.kotlinx.serialization.msgpack.datetime

import com.ensarsarajcic.kotlinx.serialization.msgpack.extensions.BaseMsgPackExtensionSerializer
import com.ensarsarajcic.kotlinx.serialization.msgpack.extensions.MsgPackExtension
import com.ensarsarajcic.kotlinx.serialization.msgpack.extensions.MsgPackTimestamp
import com.ensarsarajcic.kotlinx.serialization.msgpack.extensions.MsgPackTimestampExtensionSerializer
import kotlinx.datetime.Instant

sealed class BaseMsgPackDatetimeSerializer(private val outputType: Byte) : BaseMsgPackExtensionSerializer<Instant>() {
private val timestampSerializer = MsgPackTimestampExtensionSerializer()
override fun deserialize(extension: MsgPackExtension): Instant {
return when (val timestamp = timestampSerializer.deserialize(extension)) {
is MsgPackTimestamp.T32 -> Instant.fromEpochSeconds(timestamp.seconds, 0)
is MsgPackTimestamp.T64 -> Instant.fromEpochSeconds(timestamp.seconds, timestamp.nanoseconds)
is MsgPackTimestamp.T92 -> Instant.fromEpochSeconds(timestamp.seconds, timestamp.nanoseconds)
}
}

override fun serialize(extension: Instant): MsgPackExtension {
val timestamp = when (outputType) {
0.toByte() -> {
MsgPackTimestamp.T32(extension.epochSeconds)
}
1.toByte() -> {
MsgPackTimestamp.T64(extension.epochSeconds, extension.nanosecondsOfSecond)
}
2.toByte() -> {
MsgPackTimestamp.T92(extension.epochSeconds, extension.nanosecondsOfSecond.toLong())
}
else -> TODO("Needs more info")
}
return timestampSerializer.serialize(timestamp)
}

override val extTypeId: Byte = -1
}

class MsgPackTimestamp32DatetimeSerializer() : BaseMsgPackDatetimeSerializer(0)
class MsgPackTimestamp64DatetimeSerializer() : BaseMsgPackDatetimeSerializer(1)
class MsgPackTimestamp92DatetimeSerializer() : BaseMsgPackDatetimeSerializer(2)
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.ensarsarajcic.kotlinx.serialization.msgpack.datetime

import com.ensarsarajcic.kotlinx.serialization.msgpack.extensions.MsgPackExtension
import com.ensarsarajcic.kotlinx.serialization.msgpack.extensions.MsgPackTimestamp
import kotlinx.datetime.Instant
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.fail

class MsgPackDatetimeSerializerTest {
private val t32TestPairs = arrayOf(
Instant.fromEpochSeconds(0) to MsgPackExtension(MsgPackExtension.Type.FIXEXT4, -1, byteArrayOf(0x00, 0x00, 0x00, 0x00)),
Instant.fromEpochSeconds(1000) to MsgPackExtension(MsgPackExtension.Type.FIXEXT4, -1, byteArrayOf(0x00, 0x00, 0x03, 0xe8.toByte())),
Instant.fromEpochSeconds(4_294_967_295) to MsgPackExtension(
MsgPackExtension.Type.FIXEXT4, -1,
byteArrayOf(
0xff.toByte(),
0xff.toByte(),
0xff.toByte(),
0xff.toByte()
)
)
)

private val t64TestPairs = arrayOf(
Instant.fromEpochSeconds(0, 999_999_999) to MsgPackExtension(
MsgPackExtension.Type.FIXEXT8,
-1,
byteArrayOf(
0xee.toByte(), 0x6b, 0x27, 0xfc.toByte(),
0x00, 0x00, 0x00, 0x00
)
),
Instant.fromEpochSeconds(50000, 1000) to MsgPackExtension(
MsgPackExtension.Type.FIXEXT8,
-1,
byteArrayOf(
0x00, 0x00, 0x0f, 0xa0.toByte(),
0x00, 0x00, 0xc3.toByte(), 0x50
)
)
)

private val t92TestPairs = arrayOf(
Instant.fromEpochSeconds(-1000) to MsgPackExtension(
MsgPackExtension.Type.EXT8,
-1,
byteArrayOf(
0x00, 0x00, 0x00, 0x00,
0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(),
0xff.toByte(), 0xff.toByte(), 0xfc.toByte(), 0x18
)
),
Instant.fromEpochSeconds(-1000, 1000) to MsgPackExtension(
MsgPackExtension.Type.EXT8,
-1,
byteArrayOf(
0x00, 0x00, 0x03, 0xe8.toByte(),
0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(),
0xff.toByte(), 0xff.toByte(), 0xfc.toByte(), 0x18
)
),
Instant.fromEpochSeconds(Instant.DISTANT_FUTURE.epochSeconds) to MsgPackExtension(
MsgPackExtension.Type.EXT8,
-1,
byteArrayOf(
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0xd0.toByte(),
0x44, 0xa2.toByte(), 0xeb.toByte(), 0x00
)
),
Instant.fromEpochSeconds(Instant.DISTANT_PAST.epochSeconds) to MsgPackExtension(
MsgPackExtension.Type.EXT8,
-1,
byteArrayOf(
0x00, 0x00, 0x00, 0x00,
0xff.toByte(), 0xff.toByte(), 0xfd.toByte(), 0x12,
0xc8.toByte(), 0x74, 0x1c, 0xff.toByte()
)
)
)

@Test
fun testT32Encode() {
testEncodePairs<MsgPackTimestamp.T32>(t32TestPairs)
}

@Test
fun testT32Decode() {
testDecodePairs<MsgPackTimestamp.T32>(t32TestPairs)
}

@Test
fun testT64Encode() {
testEncodePairs<MsgPackTimestamp.T64>(t64TestPairs)
}

@Test
fun testT64Decode() {
testDecodePairs<MsgPackTimestamp.T64>(t64TestPairs)
}

@Test
fun testT92Encode() {
testEncodePairs<MsgPackTimestamp.T92>(t92TestPairs)
}

@Test
fun testT92Decode() {
testDecodePairs<MsgPackTimestamp.T92>(t92TestPairs)
}

private inline fun <reified T : MsgPackTimestamp> testEncodePairs(pairs: Array<Pair<Instant, MsgPackExtension>>) {
pairs.forEach { (time, expected) ->
val serializer = when (T::class) {
MsgPackTimestamp.T32::class -> MsgPackTimestamp32DatetimeSerializer()
MsgPackTimestamp.T64::class -> MsgPackTimestamp64DatetimeSerializer()
MsgPackTimestamp.T92::class -> MsgPackTimestamp92DatetimeSerializer()
else -> fail()
}
val result = serializer.serialize(time)
assertEquals(expected.type, result.type)
assertEquals(expected.extTypeId, result.extTypeId)
assertContentEquals(expected.data, result.data)
}
}

private inline fun <reified T : MsgPackTimestamp> testDecodePairs(pairs: Array<Pair<Instant, MsgPackExtension>>) {
pairs.forEach { (expected, extension) ->
val serializer = when (T::class) {
MsgPackTimestamp.T32::class -> MsgPackTimestamp32DatetimeSerializer()
MsgPackTimestamp.T64::class -> MsgPackTimestamp64DatetimeSerializer()
MsgPackTimestamp.T92::class -> MsgPackTimestamp92DatetimeSerializer()
else -> fail()
}
val result = serializer.deserialize(extension)
assertEquals(expected, result)
}
}
}
Loading