gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]