[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] 01/03: Time types handling.
From: |
gnunet |
Subject: |
[libeufin] 01/03: Time types handling. |
Date: |
Fri, 29 Sep 2023 09:55:12 +0200 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository libeufin.
commit a6350237b5b9905437e4dd9d763b4794f834c926
Author: MS <ms@taler.net>
AuthorDate: Thu Sep 28 13:58:26 2023 +0200
Time types handling.
Completing serializers for duration and timestamp types
based on java.time. The serialization includes the values
"never" and "forever".
---
.../main/kotlin/tech/libeufin/bank/BankMessages.kt | 19 ++--
.../tech/libeufin/bank/CorebankApiHandlers.kt | 43 ++++++---
.../src/main/kotlin/tech/libeufin/bank/Database.kt | 1 -
bank/src/main/kotlin/tech/libeufin/bank/Main.kt | 105 +++++++++++++++++----
.../tech/libeufin/bank/WireGatewayApiHandlers.kt | 1 -
bank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 4 +
bank/src/test/kotlin/DatabaseTest.kt | 3 +-
bank/src/test/kotlin/JsonTest.kt | 44 ++++-----
bank/src/test/kotlin/LibeuFinApiTest.kt | 26 ++++-
util/src/main/kotlin/Config.kt | 2 +
util/src/main/kotlin/DB.kt | 2 -
util/src/main/kotlin/Ebics.kt | 2 -
util/src/main/kotlin/time.kt | 36 ++++++-
13 files changed, 216 insertions(+), 72 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
index d0807f95..9911c933 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
@@ -21,10 +21,11 @@ package tech.libeufin.bank
import io.ktor.http.*
import io.ktor.server.application.*
-import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
+import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
import java.util.*
-import kotlin.reflect.jvm.internal.impl.types.AbstractStubType
/**
* Allowed lengths for fractional digits in amounts.
@@ -38,13 +39,15 @@ enum class FracDigits(howMany: Int) {
/**
* Timestamp containing the number of seconds since epoch.
*/
-@Serializable
+@Serializable(with = TalerProtocolTimestampSerializer::class)
data class TalerProtocolTimestamp(
- val t_s: Long, // FIXME (?): not supporting "never" at the moment.
+ val t_s: Instant,
) {
companion object {
fun fromMicroseconds(uSec: Long): TalerProtocolTimestamp {
- return TalerProtocolTimestamp(uSec / 1000000)
+ return TalerProtocolTimestamp(
+ Instant.EPOCH.plus(uSec, ChronoUnit.MICROS)
+ )
}
}
}
@@ -101,8 +104,9 @@ data class RegisterAccountRequest(
* Internal representation of relative times. The
* "forever" case is represented with Long.MAX_VALUE.
*/
+@Serializable(with = RelativeTimeSerializer::class)
data class RelativeTime(
- val d_us: Long
+ val d_us: Duration
)
/**
@@ -112,7 +116,6 @@ data class RelativeTime(
@Serializable
data class TokenRequest(
val scope: TokenScope,
- @Contextual
val duration: RelativeTime? = null,
val refreshable: Boolean = false
)
@@ -169,6 +172,7 @@ data class Customer(
* maybeCurrency is typically null when the TalerAmount object gets
* defined by the Database class.
*/
+@Serializable(with = TalerAmountSerializer::class)
class TalerAmount(
val value: Long,
val frac: Int,
@@ -592,7 +596,6 @@ data class IncomingReserveTransaction(
@Serializable
data class TransferRequest(
val request_uid: String,
- @Contextual
val amount: TalerAmount,
val exchange_base_url: String,
val wtid: String,
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
index a523b291..797d1195 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -10,6 +10,9 @@ import net.taler.wallet.crypto.Base32Crockford
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import tech.libeufin.util.*
+import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
import java.util.*
private val logger: Logger =
LoggerFactory.getLogger("tech.libeufin.bank.accountsMgmtHandlers")
@@ -50,29 +53,38 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
val tokenBytes = ByteArray(32).apply {
Random().nextBytes(this)
}
- val tokenDurationUs = req.duration?.d_us ?: TOKEN_DEFAULT_DURATION_US
+ val tokenDuration: Duration = req.duration?.d_us ?:
TOKEN_DEFAULT_DURATION
+
+ val creationTime = Instant.now()
+ val expirationTimestamp = if (tokenDuration ==
ChronoUnit.FOREVER.duration) {
+ Instant.MAX
+ } else {
+ try {
+ creationTime.plus(tokenDuration)
+ } catch (e: Exception) {
+ logger.error("Could not add token duration to current time:
${e.message}")
+ throw badRequest("Bad token duration: ${e.message}")
+ }
+ }
val customerDbRow = customer.dbRowId ?: throw internalServerError(
"Could not get customer '${customer.login}' database row ID"
)
- val creationTime = getNowUs()
- val expirationTimestampUs: Long = creationTime + tokenDurationUs
- if (expirationTimestampUs < tokenDurationUs) throw badRequest(
- "Token duration caused arithmetic overflow", // FIXME: need
dedicate EC (?)
- talerErrorCode = TalerErrorCode.TALER_EC_END
- )
val token = BearerToken(
bankCustomer = customerDbRow,
content = tokenBytes,
- creationTime = creationTime,
- expirationTime = expirationTimestampUs,
+ creationTime = creationTime.toDbMicros()
+ ?: throw internalServerError("Could not get micros out of
token creationTime Instant."),
+ expirationTime = expirationTimestamp.toDbMicros()
+ ?: throw internalServerError("Could not get micros out of
token expirationTime Instant."),
scope = req.scope,
isRefreshable = req.refreshable
)
- if (!db.bearerTokenCreate(token)) throw internalServerError("Failed at
inserting new token in the database")
+ if (!db.bearerTokenCreate(token))
+ throw internalServerError("Failed at inserting new token in the
database")
call.respond(
TokenSuccessResponse(
access_token = Base32Crockford.encode(tokenBytes), expiration
= TalerProtocolTimestamp(
- t_s = expirationTimestampUs / 1000000L
+ t_s = expirationTimestamp
)
)
)
@@ -295,10 +307,11 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
// Note: 'when' helps not to omit more result codes, should more
// be added.
when (db.talerWithdrawalConfirm(op.withdrawalUuid, getNowUs())) {
- WithdrawalConfirmationResult.BALANCE_INSUFFICIENT -> throw
conflict(
- "Insufficient funds", TalerErrorCode.TALER_EC_END // FIXME:
define EC for this.
- )
-
+ WithdrawalConfirmationResult.BALANCE_INSUFFICIENT ->
+ throw conflict(
+ "Insufficient funds",
+ TalerErrorCode.TALER_EC_END // FIXME: define EC for this.
+ )
WithdrawalConfirmationResult.OP_NOT_FOUND ->
/**
* Despite previous checks, the database _still_ did not
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index 110f3a1e..6ee99a05 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -318,7 +318,6 @@ class Database(private val dbConfig: String, private val
bankCurrency: String) {
FROM bearer_tokens
WHERE content=?;
""")
-
stmt.setBytes(1, token)
stmt.executeQuery().use {
if (!it.next()) return null
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index c556b0f2..115264c8 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -51,21 +51,23 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.encoding.encodeStructure
import kotlinx.serialization.json.*
-import kotlinx.serialization.modules.SerializersModule
import net.taler.common.errorcodes.TalerErrorCode
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
import tech.libeufin.util.*
import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
import java.util.zip.InflaterInputStream
import kotlin.system.exitProcess
// GLOBALS
private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.Main")
const val GENERIC_UNDEFINED = -1 // Filler for ECs that don't exist yet.
-val TOKEN_DEFAULT_DURATION_US = Duration.ofDays(1L).seconds * 1000000
+val TOKEN_DEFAULT_DURATION: java.time.Duration = Duration.ofDays(1L)
/**
@@ -115,6 +117,59 @@ data class BankApplicationContext(
val spaCaptchaURL: String?,
)
+
+/**
+ * This custom (de)serializer interprets the Timestamp JSON
+ * type of the Taler common API. In particular, it is responsible
+ * for _serializing_ timestamps, as this datatype is so far
+ * only used to respond to clients.
+ */
+object TalerProtocolTimestampSerializer : KSerializer<TalerProtocolTimestamp> {
+ override fun serialize(encoder: Encoder, value: TalerProtocolTimestamp) {
+ // Thanks:
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#hand-written-composite-serializer
+ encoder.encodeStructure(descriptor) {
+ if (value.t_s == Instant.MAX) {
+ encodeStringElement(descriptor, 0, "never")
+ return@encodeStructure
+ }
+ val ts = value.t_s.toDbMicros() ?: throw
internalServerError("Could not serialize timestamp")
+ encodeLongElement(descriptor, 0, ts)
+ }
+ }
+
+ override fun deserialize(decoder: Decoder): TalerProtocolTimestamp {
+ val jsonInput = decoder as? JsonDecoder ?: throw
internalServerError("TalerProtocolTimestamp had no JsonDecoder")
+ val json = try {
+ jsonInput.decodeJsonElement().jsonObject
+ } catch (e: Exception) {
+ throw badRequest(
+ "Did not find a JSON object for TalerProtocolTimestamp:
${e.message}",
+ TalerErrorCode.TALER_EC_GENERIC_JSON_INVALID
+ )
+ }
+ val maybeTs = json["t_s"]?.jsonPrimitive ?: throw badRequest("Taler
timestamp invalid: t_s field not found")
+ if (maybeTs.isString) {
+ if (maybeTs.content != "never") throw badRequest("Only 'never'
allowed for t_s as string, but '${maybeTs.content}' was found")
+ return TalerProtocolTimestamp(t_s = Instant.MAX)
+ }
+ val ts: Long = maybeTs.longOrNull
+ ?: throw badRequest("Could not convert t_s '${maybeTs.content}' to
a number")
+ val instant = try {
+ Instant.ofEpochSecond(ts)
+ } catch (e: Exception) {
+ logger.error("Could not get Instant from t_s: $ts: ${e.message}")
+ // Bank's fault. API doesn't allow clients to pass this datatype.
+ throw internalServerError("Could not serialize this t_s: ${ts}")
+ }
+ return TalerProtocolTimestamp(instant)
+ }
+
+ override val descriptor: SerialDescriptor =
+ buildClassSerialDescriptor("TalerProtocolTimestamp") {
+ element<JsonElement>("t_s")
+ }
+}
+
/**
* This custom (de)serializer interprets the RelativeTime JSON
* type. In particular, it is responsible for converting the
@@ -122,10 +177,30 @@ data class BankApplicationContext(
* is passed as is.
*/
object RelativeTimeSerializer : KSerializer<RelativeTime> {
+ /**
+ * Internal representation to JSON.
+ */
override fun serialize(encoder: Encoder, value: RelativeTime) {
- throw internalServerError("Encoding of RelativeTime not implemented.")
// API doesn't require this.
+ // Thanks:
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#hand-written-composite-serializer
+ encoder.encodeStructure(descriptor) {
+ if (value.d_us == ChronoUnit.FOREVER.duration) {
+ encodeStringElement(descriptor, 0, "forever")
+ return@encodeStructure
+ }
+ val dUs = try {
+ value.d_us.toNanos()
+ } catch (e: Exception) {
+ logger.error(e.message)
+ // Bank's fault, as each numeric value should be checked
before entering the system.
+ throw internalServerError("Could not convert
java.time.Duration to JSON")
+ }
+ encodeLongElement(descriptor, 0, dUs / 1000L)
+ }
}
+ /**
+ * JSON to internal representation.
+ */
override fun deserialize(decoder: Decoder): RelativeTime {
val jsonInput = decoder as? JsonDecoder ?: throw
internalServerError("RelativeTime had no JsonDecoder")
val json = try {
@@ -139,15 +214,22 @@ object RelativeTimeSerializer : KSerializer<RelativeTime>
{
val maybeDUs = json["d_us"]?.jsonPrimitive ?: throw
badRequest("Relative time invalid: d_us field not found")
if (maybeDUs.isString) {
if (maybeDUs.content != "forever") throw badRequest("Only
'forever' allowed for d_us as string, but '${maybeDUs.content}' was found")
- return RelativeTime(d_us = Long.MAX_VALUE)
+ return RelativeTime(d_us = ChronoUnit.FOREVER.duration)
+ }
+ val dUs: Long = maybeDUs.longOrNull
+ ?: throw badRequest("Could not convert d_us: '${maybeDUs.content}'
to a number")
+ val duration = try {
+ Duration.ofNanos(dUs * 1000L)
+ } catch (e: Exception) {
+ logger.error("Could not get Duration out of d_us content: ${dUs}.
${e.message}")
+ throw badRequest("Could not get Duration out of d_us content:
${dUs}")
}
- val dUs: Long =
- maybeDUs.longOrNull ?: throw badRequest("Could not convert d_us:
'${maybeDUs.content}' to a number")
- return RelativeTime(d_us = dUs)
+ return RelativeTime(d_us = duration)
}
override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("RelativeTime") {
+ // JsonElement helps to obtain "union" type Long|String
element<JsonElement>("d_us")
}
}
@@ -232,15 +314,6 @@ fun Application.corebankWebApp(db: Database, ctx:
BankApplicationContext) {
encodeDefaults = true
prettyPrint = true
ignoreUnknownKeys = true
- // Registering custom parser for RelativeTime
- serializersModule = SerializersModule {
- contextual(RelativeTime::class) {
- RelativeTimeSerializer
- }
- contextual(TalerAmount::class) {
- TalerAmountSerializer
- }
- }
})
}
install(RequestValidation)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
index 9c5cd9af..5ec4b672 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
@@ -27,7 +27,6 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import net.taler.common.errorcodes.TalerErrorCode
-import tech.libeufin.util.getNowUs
fun Routing.talerWireGatewayHandlers(db: Database, ctx:
BankApplicationContext) {
get("/taler-wire-gateway/config") {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index 34a966d8..2a79fde6 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -30,6 +30,7 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory
import tech.libeufin.util.*
import java.net.URL
+import java.time.Instant
import java.util.*
const val FRACTION_BASE = 100000000
@@ -438,3 +439,6 @@ fun maybeCreateAdminAccount(db: Database, ctx:
BankApplicationContext): Boolean
}
return true
}
+
+fun getNowUs(): Long = Instant.now().toDbMicros()
+ ?: throw internalServerError("Could not get micros out of Instant.now()")
\ No newline at end of file
diff --git a/bank/src/test/kotlin/DatabaseTest.kt
b/bank/src/test/kotlin/DatabaseTest.kt
index 45466947..80ade7c4 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -20,7 +20,6 @@
import org.junit.Test
import tech.libeufin.bank.*
import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.getNowUs
import java.util.Random
import java.util.UUID
@@ -136,7 +135,7 @@ class DatabaseTest {
val token = BearerToken(
bankCustomer = 1L,
content = tokenBytes,
- creationTime = getNowUs(), // make .toMicro()? implicit?
+ creationTime = getNowUs(),
expirationTime = getNowUs(),
scope = TokenScope.readonly
)
diff --git a/bank/src/test/kotlin/JsonTest.kt b/bank/src/test/kotlin/JsonTest.kt
index 63eff6f9..263ed613 100644
--- a/bank/src/test/kotlin/JsonTest.kt
+++ b/bank/src/test/kotlin/JsonTest.kt
@@ -1,12 +1,11 @@
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
-import kotlinx.serialization.modules.SerializersModule
import org.junit.Test
-import tech.libeufin.bank.RelativeTime
-import tech.libeufin.bank.RelativeTimeSerializer
-import tech.libeufin.bank.TokenRequest
-import tech.libeufin.bank.TokenScope
+import tech.libeufin.bank.*
+import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
@Serializable
data class MyJsonType(
@@ -27,23 +26,24 @@ class JsonTest {
""".trimIndent()
Json.decodeFromString<MyJsonType>(serialized)
}
+
+ /**
+ * Testing the custom absolute and relative time serializers.
+ */
@Test
- fun unionTypeTest() {
- val jsonCfg = Json {
- serializersModule = SerializersModule {
- contextual(RelativeTime::class) {
- RelativeTimeSerializer
- }
- }
- }
- assert(jsonCfg.decodeFromString<RelativeTime>("{\"d_us\": 3}").d_us ==
3L)
- assert(jsonCfg.decodeFromString<RelativeTime>("{\"d_us\":
\"forever\"}").d_us == Long.MAX_VALUE)
- val tokenReq = jsonCfg.decodeFromString<TokenRequest>("""
- {
- "scope": "readonly",
- "duration": {"d_us": 30}
- }
- """.trimIndent())
- assert(tokenReq.scope == TokenScope.readonly &&
tokenReq.duration?.d_us == 30L)
+ fun timeSerializers() {
+ // from JSON to time types
+ assert(Json.decodeFromString<RelativeTime>("{\"d_us\":
3}").d_us.toNanos() == 3000L)
+ assert(Json.decodeFromString<RelativeTime>("{\"d_us\":
\"forever\"}").d_us == ChronoUnit.FOREVER.duration)
+ assert(Json.decodeFromString<TalerProtocolTimestamp>("{\"t_s\":
3}").t_s == Instant.ofEpochSecond(3))
+ assert(Json.decodeFromString<TalerProtocolTimestamp>("{\"t_s\":
\"never\"}").t_s == Instant.MAX)
+
+ // from time types to JSON
+ val oneDay = RelativeTime(d_us = Duration.of(1, ChronoUnit.DAYS))
+ val oneDaySerial = Json.encodeToString(oneDay)
+ assert(Json.decodeFromString<RelativeTime>(oneDaySerial).d_us ==
oneDay.d_us)
+ val forever = RelativeTime(d_us = ChronoUnit.FOREVER.duration)
+ val foreverSerial = Json.encodeToString(forever)
+ assert(Json.decodeFromString<RelativeTime>(foreverSerial).d_us ==
forever.d_us)
}
}
\ No newline at end of file
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index 4cd2323f..77ced5d5 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -8,8 +8,8 @@ import net.taler.wallet.crypto.Base32Crockford
import org.junit.Test
import tech.libeufin.bank.*
import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.getNowUs
import java.time.Duration
+import java.time.Instant
import kotlin.random.Random
class LibeuFinApiTest {
@@ -131,6 +131,30 @@ class LibeuFinApiTest {
}
}
+ // Creating token with "forever" duration.
+ @Test
+ fun tokenForeverTest() {
+ val db = initDb()
+ val ctx = getTestContext()
+ assert(db.customerCreate(customerFoo) != null)
+ testApplication {
+ application {
+ corebankWebApp(db, ctx)
+ }
+ val newTok = client.post("/accounts/foo/token") {
+ expectSuccess = true
+ contentType(ContentType.Application.Json)
+ basicAuth("foo", "pw")
+ setBody(
+ """
+ {"duration": {"d_us": "forever"}, "scope": "readonly"}
+ """.trimIndent()
+ )
+ }
+ val newTokObj =
Json.decodeFromString<TokenSuccessResponse>(newTok.bodyAsText())
+ assert(newTokObj.expiration.t_s == Instant.MAX)
+ }
+ }
// Checking the POST /token handling.
@Test
fun tokenTest() {
diff --git a/util/src/main/kotlin/Config.kt b/util/src/main/kotlin/Config.kt
index 62a302a5..c54dc4ed 100644
--- a/util/src/main/kotlin/Config.kt
+++ b/util/src/main/kotlin/Config.kt
@@ -4,8 +4,10 @@ import ch.qos.logback.classic.Level
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.core.util.Loader
import io.ktor.util.*
+import org.slf4j.Logger
import org.slf4j.LoggerFactory
+val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
/**
* Putting those values into the 'attributes' container because they
* are needed by the util routines that do NOT have Sandbox and Nexus
diff --git a/util/src/main/kotlin/DB.kt b/util/src/main/kotlin/DB.kt
index 7f518ebd..af26d3d3 100644
--- a/util/src/main/kotlin/DB.kt
+++ b/util/src/main/kotlin/DB.kt
@@ -33,8 +33,6 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.net.URI
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util.DB")
-
fun getCurrentUser(): String = System.getProperty("user.name")
fun isPostgres(): Boolean {
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
index 8b78932d..837f49ba 100644
--- a/util/src/main/kotlin/Ebics.kt
+++ b/util/src/main/kotlin/Ebics.kt
@@ -45,8 +45,6 @@ import javax.xml.bind.JAXBElement
import javax.xml.datatype.DatatypeFactory
import javax.xml.datatype.XMLGregorianCalendar
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
-
data class EbicsProtocolError(
val httpStatusCode: HttpStatusCode,
val reason: String,
diff --git a/util/src/main/kotlin/time.kt b/util/src/main/kotlin/time.kt
index 687a97f7..ff86c4bf 100644
--- a/util/src/main/kotlin/time.kt
+++ b/util/src/main/kotlin/time.kt
@@ -20,7 +20,39 @@
package tech.libeufin.util
import java.time.*
-import java.time.temporal.ChronoUnit
+import java.util.concurrent.TimeUnit
+/**
+ * Converts the 'this' Instant to the number of nanoseconds
+ * since the Epoch. It returns the result as Long, or null
+ * if one arithmetic overflow occurred.
+ */
+private fun Instant.toNanos(): Long? {
+ val oneSecNanos = TimeUnit.SECONDS.toNanos(1)
+ val nanoBase: Long = this.epochSecond * oneSecNanos
+ if (nanoBase != 0L && nanoBase / this.epochSecond != oneSecNanos)
+ return null
+ val res = nanoBase + this.nano
+ if (res < nanoBase)
+ return null
+ return res
+}
-fun getNowUs(): Long = ChronoUnit.MICROS.between(Instant.EPOCH, Instant.now())
\ No newline at end of file
+/**
+ * This function converts an Instant input to the
+ * number of microseconds since the Epoch, except that
+ * it yields Long.MAX if the Input is Instant.MAX.
+ *
+ * Takes the name after the way timestamps are designed
+ * in the database: micros since Epoch, or Long.MAX for
+ * "never".
+ *
+ * Returns the Long representation of 'this' or null
+ * if that would overflow.
+ */
+fun Instant.toDbMicros(): Long? {
+ if (this == Instant.MAX)
+ return Long.MAX_VALUE
+ val nanos = this.toNanos() ?: return null
+ return nanos / 1000L
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.