This commit is contained in:
Sergey Chernov 2024-08-05 17:51:33 +02:00
parent 439e229294
commit 8a21a836e5
5 changed files with 127 additions and 20 deletions

3
.gitignore vendored
View File

@ -23,8 +23,7 @@ out/
.project .project
.settings .settings
.springBeans .springBeans
.sts4-cache .sts4-caches
bin/
!**/src/main/**/bin/ !**/src/main/**/bin/
!**/src/test/**/bin/ !**/src/test/**/bin/

View File

@ -146,6 +146,11 @@ val ns: NettyApplicationEngine = embeddedServer(Netty, port = 8080, host = "0.0.
~~~ ~~~
## See also:
- [Source documentation](https://code.sergeych.net/docs/kiloparsec/)
- [Project's WIKI](https://gitea.sergeych.net/sergeych/kiloparsec/wiki)
# Details # Details
It is not compatible with parsec family and no more based on an Universa crypto library. To better fit It is not compatible with parsec family and no more based on an Universa crypto library. To better fit

View File

@ -2,6 +2,7 @@ plugins {
kotlin("multiplatform") version "2.0.0" kotlin("multiplatform") version "2.0.0"
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0" id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0"
`maven-publish` `maven-publish`
id("org.jetbrains.dokka") version "1.9.20"
} }
group = "net.sergeych" group = "net.sergeych"
@ -135,3 +136,13 @@ kotlin {
} }
} }
} }
tasks.dokkaHtml.configure {
outputDirectory.set(buildDir.resolve("dokka"))
dokkaSourceSets {
configureEach {
// includes.from("docs/bipack.md")
}
}
}

View File

@ -13,7 +13,91 @@ import net.sergeych.mp_tools.globalLaunch
import net.sergeych.tools.AtomicCounter import net.sergeych.tools.AtomicCounter
private val instances = AtomicCounter() private val instances = AtomicCounter()
@Suppress("unused")
/**
* The Kiloparsec server.
* Server accepts incoming connections and serves them using the same [clientInterface].
*
* ## Incoming connections
*
* Server collecting incoming connections provided by [connections] `Flow`. For each incoming connection
* the Kiloparsec handshake is performed, then the session object is created, see below, and connection is
* served with [clientInterface] until closed.
*
* ## Session param [S]
*
* After the successful handshake server creates new session for each connection calling the [sessionBuilder].
* Then it creates a [KiloScope] so [KiloScope.session] holds this connection-specific instance. Then
* [KiloInterface.onConnected] is called with this scope. Session can be used to hold connection state. Session
* objects are not persistent, but could be initialized in [KiloInterface.onConnected] where the remote side
* [KiloScope.remoteIdentity] is already verified and set.
*
* ## Usage:
*
* Create a shared library between you server and clients, to specify the interface (otherwise you can
* share sources).
*
* Suppose we have session with a state:
* ```kotlin
* data class Session(
* var data: String,
* )
*```
*
* And some commands to access and change it, in the shared library too:
*
* ```kotlin
* val cmdSave by command<String, Unit>()
* val cmdLoad by command<Unit, String>()
* val cmdDrop by command<Unit, Unit>()
* val cmdException by command<Unit, Unit>()
* ```
* Then the server code (TCP/IP variant) could look like:
*
* ```kotlin
* // The server implementation (could be shared between server instances connected
* // to different protocol adapters):
*
* val cli = KiloInterface<Session>().apply {
* // Suppose we want to throw this exception at the caller site, so we need to register it:
* registerError { SomeException() }
*
* // Session initialization. If you need a sessino to depend initially on the client's identity.
* // you can do it here:
* onConnected {
* // check the remoteIdentity
* session.data = if( remoteIdentity == somePublicVerifyingKey )
* "known"
* else
* "unknown"
* }
* on(cmdSave) { session.data = it }
* on(cmdLoad) {
* session.data
* }
* on(cmdException) {
* throw TestException()
* }
* on(cmdDrop) {
* throw LocalInterface.BreakConnectionException()
* }
* }
*
* // Create the server instance that accepts incoming TCP/IP connections on all local interfaces using the
* // specified port:
*
* val server = KiloServer(cli, acceptTcpDevice(port)) {
* // This creates a new session
* Session("unknown")
* }
* ```
* @param S the type of the server session object, returned by [sessionBuilder]. See above
* @param clientInterface the interface available for remote calls
* @param connections flow of incoming connections. Server stops when the flow is fully collected (normally
* it shouldn't)
* @param serverSecretKey the [SigningKey] used to identify this server during Kiloparsec handshake.
* @param sessionBuilder callback that creates session objects for successful incoming connections
*/
class KiloServer<S>( class KiloServer<S>(
private val clientInterface: KiloInterface<S>, private val clientInterface: KiloInterface<S>,
private val connections: Flow<InetTransportDevice>, private val connections: Flow<InetTransportDevice>,
@ -29,20 +113,28 @@ class KiloServer<S>(
KiloServerConnection(clientInterface, device, sessionBuilder(), serverSecretKey) KiloServerConnection(clientInterface, device, sessionBuilder(), serverSecretKey)
.apply { debug { "server connection is ready" } } .apply { debug { "server connection is ready" } }
.run() .run()
} } catch (_: CancellationException) {
catch(_: CancellationException) { } catch (cce: LocalInterface.BreakConnectionException) {
}
catch(cce: LocalInterface.BreakConnectionException) {
info { "Closed exception caught, closing (${cce.flushSendQueue}" } info { "Closed exception caught, closing (${cce.flushSendQueue}" }
} } catch (t: Throwable) {
catch (t: Throwable) {
exception { "unexpected while creating kiloclient" to t } exception { "unexpected while creating kiloclient" to t }
} }
} }
} }
} }
/**
* Stop the server and cancel all pending sessions. Unlike finishing the flow passed
* for [KiloServer.connections], it will cancel all currently active sessions.
*/
fun close() { fun close() {
job.cancel() job.cancel()
} }
/**
* Server is closed either if [close] was called or all [KiloServer.connections] were collected and flow was
* closed.
*/
@Suppress("unused")
val isClosed: Boolean get() = job.isCompleted
} }

View File

@ -17,7 +17,7 @@ class TcpTest {
initCrypto() initCrypto()
// Log.connectConsole(Log.Level.DEBUG) // Log.connectConsole(Log.Level.DEBUG)
data class Session( data class Session(
var data: String var data: String,
) )
val port = 27170 + Random.nextInt(1, 200) val port = 27170 + Random.nextInt(1, 200)